From 9a1e42f22809b4a12b59803f2ac2ed5a5d0c8574 Mon Sep 17 00:00:00 2001 From: Jeff Young Date: Thu, 20 Mar 2025 16:58:47 +0000 Subject: [PATCH] Autocomplete for Execute Command job. Fixes https://gitlab.com/kicad/code/kicad/-/issues/19735 --- common/env_vars.cpp | 69 +++++++++++-------- common/scintilla_tricks.cpp | 4 +- include/env_vars.h | 10 ++- .../dialog_executecommand_job_settings.cpp | 30 +++++++- .../dialog_executecommand_job_settings.h | 8 ++- ...ialog_executecommand_job_settings_base.cpp | 38 ++++++++-- ...ialog_executecommand_job_settings_base.fbp | 29 ++++---- .../dialog_executecommand_job_settings_base.h | 3 +- kicad/dialogs/panel_jobset.cpp | 15 ++-- 9 files changed, 140 insertions(+), 66 deletions(-) diff --git a/common/env_vars.cpp b/common/env_vars.cpp index 06f4d67578..1971acc677 100644 --- a/common/env_vars.cpp +++ b/common/env_vars.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include @@ -27,15 +28,13 @@ #include #include -using STRING_MAP = std::map; - /** * List of pre-defined environment variables. * * @todo Instead of defining these values here, extract them from elsewhere in the program * (where they are originally defined). */ -static const ENV_VAR::ENV_VAR_LIST predefinedEnvVars = { +static const std::vector predefinedEnvVars = { wxS( "KIPRJMOD" ), ENV_VAR::GetVersionedEnvVarName( wxS( "SYMBOL_DIR" ) ), ENV_VAR::GetVersionedEnvVarName( wxS( "3DMODEL_DIR" ) ), @@ -65,12 +64,22 @@ bool ENV_VAR::IsEnvVarImmutable( const wxString& aEnvVar ) } -const ENV_VAR::ENV_VAR_LIST& ENV_VAR::GetPredefinedEnvVars() +const std::vector& ENV_VAR::GetPredefinedEnvVars() { return predefinedEnvVars; } +void ENV_VAR::GetEnvVarAutocompleteTokens( wxArrayString* aVars ) +{ + for( const wxString& var : ENV_VAR::GetPredefinedEnvVars() ) + { + if( !alg::contains( *aVars, var ) ) + aVars->push_back( var ); + } +} + + wxString ENV_VAR::GetVersionedEnvVarName( const wxString& aBaseName ) { int version = 0; @@ -100,43 +109,46 @@ std::optional ENV_VAR::GetVersionedEnvVarValue( const ENV_VAR_MAP& aMa } -static void initialiseEnvVarHelp( STRING_MAP& aMap ) +static void initialiseEnvVarHelp( std::map& aMap ) { // Set up dynamically, as we want to be able to use _() translations, // which can't be done statically aMap[ENV_VAR::GetVersionedEnvVarName( wxS( "FOOTPRINT_DIR" ) )] = - _( "The base path of locally installed system " - "footprint libraries (.pretty folders)."); + _( "The base path of locally installed system footprint libraries (.pretty folders)." ); + aMap[ENV_VAR::GetVersionedEnvVarName( wxS( "3DMODEL_DIR" ) )] = - _( "The base path of system footprint 3D shapes (.3Dshapes folders)."); + _( "The base path of system footprint 3D shapes (.3Dshapes folders)." ); + aMap[ENV_VAR::GetVersionedEnvVarName( wxS( "SYMBOL_DIR" ) )] = - _( "The base path of the locally installed symbol libraries."); + _( "The base path of the locally installed symbol libraries." ); + aMap[ENV_VAR::GetVersionedEnvVarName( wxS( "TEMPLATE_DIR" ) )] = - _( "A directory containing project templates installed with KiCad."); + _( "A directory containing project templates installed with KiCad." ); + aMap[wxS( "KICAD_USER_TEMPLATE_DIR" )] = - _( "Optional. Can be defined if you want to create your own project " - "templates folder."); + _( "Optional. Can be defined if you want to create your own project templates folder." ); + aMap[ENV_VAR::GetVersionedEnvVarName( wxS( "3RD_PARTY" ) )] = - _( "A directory containing 3rd party plugins, libraries and other " - "downloadable content."); + _( "A directory containing 3rd party plugins, libraries and other downloadable content." ); + aMap[wxS( "KIPRJMOD" )] = - _("Internally defined by KiCad (cannot be edited) and is set " - "to the absolute path of the currently loaded project file. This environment " - "variable can be used to define files and paths relative to the currently loaded " - "project. For instance, ${KIPRJMOD}/libs/footprints.pretty can be defined as a " - "folder containing a project specific footprint library named footprints.pretty." ); + _( "Internally defined by KiCad (cannot be edited) and is set to the absolute path of the currently " + "loaded project file. This environment variable can be used to define files and paths relative " + "to the currently loaded project. For instance, ${KIPRJMOD}/libs/footprints.pretty can be " + "defined as a folder containing a project specific footprint library named footprints.pretty." ); + aMap[ENV_VAR::GetVersionedEnvVarName( wxS( "SCRIPTING_DIR" ) )] = - _( "A directory containing system-wide scripts installed with KiCad" ); + _( "A directory containing system-wide scripts installed with KiCad." ); + aMap[ENV_VAR::GetVersionedEnvVarName( wxS( "USER_SCRIPTING_DIR" ) )] = - _( "A directory containing user-specific scripts installed with KiCad" ); + _( "A directory containing user-specific scripts installed with KiCad." ); // Deprecated vars #define DEP( var ) wxString::Format( _( "Deprecated version of %s." ), var ) - aMap[wxS( "KICAD_PTEMPLATES" )] = - DEP( ENV_VAR::GetVersionedEnvVarName( wxS( "TEMPLATE_DIR" ) ) ); - aMap[wxS( "KISYS3DMOD" )] = DEP( ENV_VAR::GetVersionedEnvVarName( wxS( "3DMODEL_DIR" ) ) ); - aMap[wxS( "KISYSMOD" )] = DEP( ENV_VAR::GetVersionedEnvVarName( wxS( "FOOTPRINT_DIR" ) ) ); + aMap[wxS( "KICAD_PTEMPLATES" )] = DEP( ENV_VAR::GetVersionedEnvVarName( wxS( "TEMPLATE_DIR" ) ) ); + aMap[wxS( "KISYS3DMOD" )] = DEP( ENV_VAR::GetVersionedEnvVarName( wxS( "3DMODEL_DIR" ) ) ); + aMap[wxS( "KISYSMOD" )] = DEP( ENV_VAR::GetVersionedEnvVarName( wxS( "FOOTPRINT_DIR" ) ) ); aMap[wxS( "KICAD_SYMBOL_DIR" )] = DEP( ENV_VAR::GetVersionedEnvVarName( wxS( "SYMBOL_DIR" ) ) ); #undef DEP @@ -145,7 +157,7 @@ static void initialiseEnvVarHelp( STRING_MAP& aMap ) wxString ENV_VAR::LookUpEnvVarHelp( const wxString& aEnvVar ) { - static STRING_MAP envVarHelpText; + static std::map envVarHelpText; if( envVarHelpText.size() == 0 ) initialiseEnvVarHelp( envVarHelpText ); @@ -175,13 +187,10 @@ template<> std::optional ENV_VAR::GetEnvVar( const wxString& aEnvVarName ) { std::optional optValue; - - wxString env; + wxString env; if( wxGetEnv( aEnvVarName, &env ) ) - { optValue = env; - } return optValue; } diff --git a/common/scintilla_tricks.cpp b/common/scintilla_tricks.cpp index 67c2b0111a..08614ca0b6 100644 --- a/common/scintilla_tricks.cpp +++ b/common/scintilla_tricks.cpp @@ -572,8 +572,8 @@ void SCINTILLA_TRICKS::onScintillaUpdateUI( wxStyledTextEvent& aEvent ) } -void SCINTILLA_TRICKS::DoTextVarAutocomplete( - const std::function& getTokensFn ) +void SCINTILLA_TRICKS::DoTextVarAutocomplete( const std::function& getTokensFn ) { wxArrayString autocompleteTokens; int text_pos = m_te->GetCurrentPos(); diff --git a/include/env_vars.h b/include/env_vars.h index 68e4a6fc88..0a302ca163 100644 --- a/include/env_vars.h +++ b/include/env_vars.h @@ -28,6 +28,7 @@ #include #include +#include #include #include #include @@ -36,8 +37,6 @@ class ENV_VAR_ITEM; namespace ENV_VAR { - using ENV_VAR_LIST = std::vector; - /** * Determine if an environment variable is "predefined", i.e. if the * name of the variable is special to KiCad, and isn't just a user-specified @@ -51,7 +50,12 @@ namespace ENV_VAR /** * Get the list of pre-defined environment variables. */ - KICOMMON_API const ENV_VAR_LIST& GetPredefinedEnvVars(); + KICOMMON_API const std::vector& GetPredefinedEnvVars(); + + /** + * Return autocomplete tokens for environment variables for Scintilla. + */ + KICOMMON_API void GetEnvVarAutocompleteTokens( wxArrayString* aVars ); /** * Construct a versioned environment variable based on this KiCad major version. diff --git a/kicad/dialogs/dialog_executecommand_job_settings.cpp b/kicad/dialogs/dialog_executecommand_job_settings.cpp index aaf6855cfc..79e0a10494 100644 --- a/kicad/dialogs/dialog_executecommand_job_settings.cpp +++ b/kicad/dialogs/dialog_executecommand_job_settings.cpp @@ -20,18 +20,46 @@ #include #include +#include +#include + DIALOG_EXECUTECOMMAND_JOB_SETTINGS::DIALOG_EXECUTECOMMAND_JOB_SETTINGS( wxWindow* aParent, JOB_SPECIAL_EXECUTE* aJob ) : DIALOG_EXECUTECOMMAND_JOB_SETTINGS_BASE( aParent ), - m_job( aJob ) + m_job( aJob ), + m_scintillaTricks( nullptr ) { + m_scintillaTricks = new SCINTILLA_TRICKS( m_textCtrlCommand, wxT( "{}" ), false, + // onAcceptFn + [this]( wxKeyEvent& aEvent ) + { + wxPostEvent( this, wxCommandEvent( wxEVT_COMMAND_BUTTON_CLICKED, wxID_OK ) ); + }, + + // onCharFn + [this]( wxStyledTextEvent& aEvent ) + { + m_scintillaTricks->DoTextVarAutocomplete( + // getTokensFn + []( const wxString& xRef, wxArrayString* tokens ) + { + ENV_VAR::GetEnvVarAutocompleteTokens( tokens ); + } ); + } ); + SetupStandardButtons(); finishDialogSettings(); } +DIALOG_EXECUTECOMMAND_JOB_SETTINGS::~DIALOG_EXECUTECOMMAND_JOB_SETTINGS() +{ + delete m_scintillaTricks; +} + + bool DIALOG_EXECUTECOMMAND_JOB_SETTINGS::TransferDataFromWindow() { m_job->m_command = m_textCtrlCommand->GetValue(); diff --git a/kicad/dialogs/dialog_executecommand_job_settings.h b/kicad/dialogs/dialog_executecommand_job_settings.h index 90f534f2a6..f696b8755e 100644 --- a/kicad/dialogs/dialog_executecommand_job_settings.h +++ b/kicad/dialogs/dialog_executecommand_job_settings.h @@ -21,17 +21,23 @@ #include class JOB_SPECIAL_EXECUTE; +class SCINTILLA_TRICKS; + class DIALOG_EXECUTECOMMAND_JOB_SETTINGS: public DIALOG_EXECUTECOMMAND_JOB_SETTINGS_BASE { public: DIALOG_EXECUTECOMMAND_JOB_SETTINGS( wxWindow* aParent, JOB_SPECIAL_EXECUTE* aJob ); + ~DIALOG_EXECUTECOMMAND_JOB_SETTINGS(); bool TransferDataFromWindow() override; bool TransferDataToWindow() override; +private: + void OnRecordOutputClicked( wxCommandEvent& event ) override; + private: JOB_SPECIAL_EXECUTE* m_job; - void OnRecordOutputClicked( wxCommandEvent& event ) override; + SCINTILLA_TRICKS* m_scintillaTricks; }; \ No newline at end of file diff --git a/kicad/dialogs/dialog_executecommand_job_settings_base.cpp b/kicad/dialogs/dialog_executecommand_job_settings_base.cpp index 4657ab6993..a397dc1295 100644 --- a/kicad/dialogs/dialog_executecommand_job_settings_base.cpp +++ b/kicad/dialogs/dialog_executecommand_job_settings_base.cpp @@ -25,13 +25,39 @@ DIALOG_EXECUTECOMMAND_JOB_SETTINGS_BASE::DIALOG_EXECUTECOMMAND_JOB_SETTINGS_BASE m_textCommand = new wxStaticText( this, wxID_ANY, _("Command:"), wxDefaultPosition, wxDefaultSize, 0 ); m_textCommand->Wrap( -1 ); - fgSizer1->Add( m_textCommand, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 ); + fgSizer1->Add( m_textCommand, 0, wxALL, 5 ); - m_textCtrlCommand = new wxTextCtrl( this, wxID_ANY, _("Enter the command line to run SPICE\nUsually ' \"%I\"'\n%I will be replaced by the netlist filepath"), wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE ); - m_textCtrlCommand->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxEmptyString ) ); - m_textCtrlCommand->SetMinSize( wxSize( 400,-1 ) ); - - fgSizer1->Add( m_textCtrlCommand, 1, wxEXPAND|wxRIGHT, 5 ); + m_textCtrlCommand = new wxStyledTextCtrl( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0, wxEmptyString ); + m_textCtrlCommand->SetUseTabs( false ); + m_textCtrlCommand->SetTabWidth( 4 ); + m_textCtrlCommand->SetIndent( 4 ); + m_textCtrlCommand->SetTabIndents( false ); + m_textCtrlCommand->SetBackSpaceUnIndents( false ); + m_textCtrlCommand->SetViewEOL( false ); + m_textCtrlCommand->SetViewWhiteSpace( false ); + m_textCtrlCommand->SetMarginWidth( 2, 0 ); + m_textCtrlCommand->SetIndentationGuides( false ); + m_textCtrlCommand->SetReadOnly( false ); + m_textCtrlCommand->SetMarginWidth( 1, 0 ); + m_textCtrlCommand->SetMarginWidth( 0, 0 ); + m_textCtrlCommand->MarkerDefine( wxSTC_MARKNUM_FOLDER, wxSTC_MARK_BOXPLUS ); + m_textCtrlCommand->MarkerSetBackground( wxSTC_MARKNUM_FOLDER, wxColour( wxT("BLACK") ) ); + m_textCtrlCommand->MarkerSetForeground( wxSTC_MARKNUM_FOLDER, wxColour( wxT("WHITE") ) ); + m_textCtrlCommand->MarkerDefine( wxSTC_MARKNUM_FOLDEROPEN, wxSTC_MARK_BOXMINUS ); + m_textCtrlCommand->MarkerSetBackground( wxSTC_MARKNUM_FOLDEROPEN, wxColour( wxT("BLACK") ) ); + m_textCtrlCommand->MarkerSetForeground( wxSTC_MARKNUM_FOLDEROPEN, wxColour( wxT("WHITE") ) ); + m_textCtrlCommand->MarkerDefine( wxSTC_MARKNUM_FOLDERSUB, wxSTC_MARK_EMPTY ); + m_textCtrlCommand->MarkerDefine( wxSTC_MARKNUM_FOLDEREND, wxSTC_MARK_BOXPLUS ); + m_textCtrlCommand->MarkerSetBackground( wxSTC_MARKNUM_FOLDEREND, wxColour( wxT("BLACK") ) ); + m_textCtrlCommand->MarkerSetForeground( wxSTC_MARKNUM_FOLDEREND, wxColour( wxT("WHITE") ) ); + m_textCtrlCommand->MarkerDefine( wxSTC_MARKNUM_FOLDEROPENMID, wxSTC_MARK_BOXMINUS ); + m_textCtrlCommand->MarkerSetBackground( wxSTC_MARKNUM_FOLDEROPENMID, wxColour( wxT("BLACK") ) ); + m_textCtrlCommand->MarkerSetForeground( wxSTC_MARKNUM_FOLDEROPENMID, wxColour( wxT("WHITE") ) ); + m_textCtrlCommand->MarkerDefine( wxSTC_MARKNUM_FOLDERMIDTAIL, wxSTC_MARK_EMPTY ); + m_textCtrlCommand->MarkerDefine( wxSTC_MARKNUM_FOLDERTAIL, wxSTC_MARK_EMPTY ); + m_textCtrlCommand->SetSelBackground( true, wxSystemSettings::GetColour( wxSYS_COLOUR_HIGHLIGHT ) ); + m_textCtrlCommand->SetSelForeground( true, wxSystemSettings::GetColour( wxSYS_COLOUR_HIGHLIGHTTEXT ) ); + fgSizer1->Add( m_textCtrlCommand, 1, wxEXPAND|wxTOP|wxBOTTOM|wxRIGHT, 5 ); m_textOutputPath = new wxStaticText( this, wxID_ANY, _("Output path:"), wxDefaultPosition, wxDefaultSize, 0 ); m_textOutputPath->Wrap( -1 ); diff --git a/kicad/dialogs/dialog_executecommand_job_settings_base.fbp b/kicad/dialogs/dialog_executecommand_job_settings_base.fbp index 272bf0fdfd..cafdf167f0 100644 --- a/kicad/dialogs/dialog_executecommand_job_settings_base.fbp +++ b/kicad/dialogs/dialog_executecommand_job_settings_base.fbp @@ -82,7 +82,7 @@ 5 5 - wxALIGN_CENTER_VERTICAL|wxALL + wxALL 0 1 @@ -142,11 +142,11 @@ -1 - + 5 - wxEXPAND|wxRIGHT + wxEXPAND|wxTOP|wxBOTTOM|wxRIGHT 1 - + 1 1 1 @@ -155,6 +155,7 @@ 0 0 + 0 @@ -171,17 +172,19 @@ 1 1 - ,90,400,-1,70,0 + 0 + 0 0 wxID_ANY + 0 + 0 0 - 0 0 - 400,-1 + 1 m_textCtrlCommand 1 @@ -190,18 +193,18 @@ protected 1 + 0 Resizable 1 - wxTE_MULTILINE ; ; forward_declare + 0 + 4 0 - - wxFILTER_NONE - wxDefaultValidator - - Enter the command line to run SPICE Usually '<path to SPICE binary> "%I"' %I will be replaced by the netlist filepath + 0 + 0 + 0 diff --git a/kicad/dialogs/dialog_executecommand_job_settings_base.h b/kicad/dialogs/dialog_executecommand_job_settings_base.h index 9979ae7abc..accf922c5c 100644 --- a/kicad/dialogs/dialog_executecommand_job_settings_base.h +++ b/kicad/dialogs/dialog_executecommand_job_settings_base.h @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -34,7 +35,7 @@ class DIALOG_EXECUTECOMMAND_JOB_SETTINGS_BASE : public DIALOG_SHIM protected: wxStaticText* m_textCommand; - wxTextCtrl* m_textCtrlCommand; + wxStyledTextCtrl* m_textCtrlCommand; wxStaticText* m_textOutputPath; wxTextCtrl* m_textCtrlOutputPath; wxCheckBox* m_cbRecordOutput; diff --git a/kicad/dialogs/panel_jobset.cpp b/kicad/dialogs/panel_jobset.cpp index c86588a60d..8917c15a06 100644 --- a/kicad/dialogs/panel_jobset.cpp +++ b/kicad/dialogs/panel_jobset.cpp @@ -389,12 +389,9 @@ void JOBS_GRID_TRICKS::showPopupMenu( wxMenu& menu, wxGridEvent& aEvent ) menu.Append( JOB_DESCRIPTION, _( "Edit Job Description" ) ); menu.Append( JOB_PROPERTIES, _( "Edit Job Settings..." ) ); menu.AppendSeparator(); - menu.Append( GRIDTRICKS_ID_COPY, _( "Copy" ) + "\tCtrl+C", - _( "Copy selected cells to clipboard" ) ); - menu.Append( GRIDTRICKS_ID_DELETE, _( "Delete" ) + "\tDel", - _( "Delete selected jobs" ) ); - menu.Append( GRIDTRICKS_ID_SELECT, _( "Select All" ) + "\tCtrl+A", - _( "Select all jobs" ) ); + menu.Append( GRIDTRICKS_ID_COPY, _( "Copy" ) + "\tCtrl+C", _( "Copy selected cells to clipboard" ) ); + menu.Append( GRIDTRICKS_ID_DELETE, _( "Delete" ) + "\tDel", _( "Delete selected jobs" ) ); + menu.Append( GRIDTRICKS_ID_SELECT, _( "Select All" ) + "\tCtrl+A", _( "Select all jobs" ) ); menu.Enable( JOB_DESCRIPTION, selectedRows.size() == 1 ); menu.Enable( JOB_PROPERTIES, selectedRows.size() == 1 ); @@ -632,13 +629,13 @@ bool PANEL_JOBSET::OpenJobOptionsForListItem( size_t aItemIndex ) DIALOG_EXECUTECOMMAND_JOB_SETTINGS dialog( m_frame, specialJob ); - if( dialog.ShowModal() == wxID_OK ) + // QuasiModal for Scintilla autocomplete + if( dialog.ShowQuasiModal() == wxID_OK ) success = true; } else if( job.m_job->GetType() == "special_copyfiles" ) { - JOB_SPECIAL_COPYFILES* specialJob = - static_cast( job.m_job.get() ); + JOB_SPECIAL_COPYFILES* specialJob = static_cast( job.m_job.get() ); DIALOG_COPYFILES_JOB_SETTINGS dialog( m_frame, specialJob ); if( dialog.ShowModal() == wxID_OK )