diff --git a/pcbnew/drc/rule_editor/drc_re_custom_rule_panel.cpp b/pcbnew/drc/rule_editor/drc_re_custom_rule_panel.cpp index 6fe4b9d517..d7c0e81b04 100644 --- a/pcbnew/drc/rule_editor/drc_re_custom_rule_panel.cpp +++ b/pcbnew/drc/rule_editor/drc_re_custom_rule_panel.cpp @@ -23,7 +23,11 @@ #include "drc_re_custom_rule_panel.h" +#include + +#include #include +#include DRC_RE_CUSTOM_RULE_PANEL::DRC_RE_CUSTOM_RULE_PANEL( wxWindow* aParent, std::shared_ptr aConstraintData ) : @@ -34,22 +38,47 @@ DRC_RE_CUSTOM_RULE_PANEL::DRC_RE_CUSTOM_RULE_PANEL( m_textCtrl = new wxStyledTextCtrl( this, wxID_ANY ); sizer->Add( m_textCtrl, 1, wxEXPAND | wxALL, 5 ); SetSizer( sizer ); + + m_scintillaTricks = std::make_unique( + m_textCtrl, wxT( "()" ), false, + // onAcceptFn + []( wxKeyEvent& aEvent ) + { + aEvent.Skip(); + }, + // onCharFn + [this]( wxStyledTextEvent& aEvent ) + { + onScintillaCharAdded( aEvent ); + } ); + + m_textCtrl->AutoCompSetSeparator( '|' ); } + +DRC_RE_CUSTOM_RULE_PANEL::~DRC_RE_CUSTOM_RULE_PANEL() +{ +} + + bool DRC_RE_CUSTOM_RULE_PANEL::TransferDataToWindow() { if( m_constraintData ) m_textCtrl->SetValue( m_constraintData->GetRuleText() ); + return true; } + bool DRC_RE_CUSTOM_RULE_PANEL::TransferDataFromWindow() { if( m_constraintData ) m_constraintData->SetRuleText( m_textCtrl->GetValue() ); + return true; } + bool DRC_RE_CUSTOM_RULE_PANEL::ValidateInputs( int* aErrorCount, std::string* aValidationMessage ) { (void) aErrorCount; @@ -70,3 +99,156 @@ wxString DRC_RE_CUSTOM_RULE_PANEL::GenerateRule( const RULE_GENERATION_CONTEXT& return wxEmptyString; } + + +void DRC_RE_CUSTOM_RULE_PANEL::onScintillaCharAdded( wxStyledTextEvent& aEvent ) +{ + if( aEvent.GetModifiers() == wxMOD_CONTROL && aEvent.GetKey() == ' ' ) + { + // Short-cut for do-auto-complete + } + + m_textCtrl->SearchAnchor(); + + wxString rules = m_textCtrl->GetText(); + int currentPos = m_textCtrl->GetCurrentPos(); + int startPos = 0; + + enum class EXPR_CONTEXT_T : int + { + NONE, + STRING, + SEXPR_OPEN, + SEXPR_TOKEN, + }; + + std::stack sexprs; + wxString partial; + EXPR_CONTEXT_T context = EXPR_CONTEXT_T::NONE; + + for( int i = startPos; i < currentPos; ++i ) + { + wxChar c = m_textCtrl->GetCharAt( i ); + + if( c == '\\' ) + { + i++; // skip escaped char + } + else if( context == EXPR_CONTEXT_T::STRING ) + { + if( c == '"' ) + context = EXPR_CONTEXT_T::NONE; + } + else if( c == '"' ) + { + partial = wxEmptyString; + context = EXPR_CONTEXT_T::STRING; + } + else if( c == '(' ) + { + if( context == EXPR_CONTEXT_T::SEXPR_OPEN && !partial.IsEmpty() ) + { + m_textCtrl->AutoCompCancel(); + sexprs.push( partial ); + } + + partial = wxEmptyString; + context = EXPR_CONTEXT_T::SEXPR_OPEN; + } + else if( c == ')' ) + { + if( !sexprs.empty() ) + sexprs.pop(); + + context = EXPR_CONTEXT_T::NONE; + } + else if( c == ' ' ) + { + if( context == EXPR_CONTEXT_T::SEXPR_OPEN && !partial.IsEmpty() ) + { + m_textCtrl->AutoCompCancel(); + sexprs.push( partial ); + context = EXPR_CONTEXT_T::SEXPR_TOKEN; + partial = wxEmptyString; + continue; + } + + context = EXPR_CONTEXT_T::NONE; + } + else + { + partial += c; + } + } + + wxString tokens; + + if( context == EXPR_CONTEXT_T::SEXPR_OPEN ) + { + if( sexprs.empty() ) + { + // Top level - suggest (rule or (version + tokens = wxT( "rule|version" ); + } + else if( sexprs.top() == wxT( "rule" ) ) + { + // Inside (rule ...) - suggest sub-expressions + tokens = wxT( "condition|constraint|layer|severity" ); + } + else if( sexprs.top() == wxT( "constraint" ) ) + { + // Inside (constraint ...) - suggest constraint parameters + tokens = wxT( "min|max|opt" ); + } + } + else if( context == EXPR_CONTEXT_T::SEXPR_TOKEN ) + { + if( !sexprs.empty() && sexprs.top() == wxT( "constraint" ) ) + { + // After (constraint - suggest constraint types + tokens = wxT( "annular_width|" + "assertion|" + "clearance|" + "connection_width|" + "courtyard_clearance|" + "creepage|" + "diff_pair_gap|" + "diff_pair_uncoupled|" + "disallow|" + "edge_clearance|" + "hole_clearance|" + "hole_size|" + "hole_to_hole|" + "length|" + "max_error|" + "min_resolved_spokes|" + "physical_clearance|" + "physical_hole_clearance|" + "silk_clearance|" + "skew|" + "text_height|" + "text_thickness|" + "thermal_relief_gap|" + "thermal_spoke_width|" + "track_angle|" + "track_segment_length|" + "track_width|" + "via_count|" + "via_diameter|" + "zone_connection" ); + } + else if( !sexprs.empty() && sexprs.top() == wxT( "layer" ) ) + { + // After (layer - suggest layer keywords + tokens = wxT( "inner|outer" ); + } + else if( !sexprs.empty() && sexprs.top() == wxT( "severity" ) ) + { + // After (severity - suggest severity levels + tokens = wxT( "error|warning|ignore|exclusion" ); + } + } + + if( !tokens.IsEmpty() ) + m_scintillaTricks->DoAutocomplete( partial, wxSplit( tokens, '|' ) ); +} diff --git a/pcbnew/drc/rule_editor/drc_re_custom_rule_panel.h b/pcbnew/drc/rule_editor/drc_re_custom_rule_panel.h index 4e2be18b12..05b49e6e01 100644 --- a/pcbnew/drc/rule_editor/drc_re_custom_rule_panel.h +++ b/pcbnew/drc/rule_editor/drc_re_custom_rule_panel.h @@ -24,12 +24,16 @@ #ifndef DRC_RE_CUSTOM_RULE_PANEL_H #define DRC_RE_CUSTOM_RULE_PANEL_H +#include + #include #include #include "drc_re_content_panel_base.h" #include "drc_re_custom_rule_constraint_data.h" +class SCINTILLA_TRICKS; + /** * Simple panel used for editing custom rule text. The panel consists of a * single wxStyledTextCtrl allowing free-form rule entry. @@ -38,7 +42,7 @@ class DRC_RE_CUSTOM_RULE_PANEL : public wxPanel, public DRC_RULE_EDITOR_CONTENT_ { public: DRC_RE_CUSTOM_RULE_PANEL( wxWindow* aParent, std::shared_ptr aConstraintData ); - ~DRC_RE_CUSTOM_RULE_PANEL() override = default; + ~DRC_RE_CUSTOM_RULE_PANEL() override; bool TransferDataToWindow() override; bool TransferDataFromWindow() override; @@ -47,8 +51,11 @@ public: wxString GenerateRule( const RULE_GENERATION_CONTEXT& aContext ) override; private: + void onScintillaCharAdded( wxStyledTextEvent& aEvent ); + std::shared_ptr m_constraintData; wxStyledTextCtrl* m_textCtrl; + std::unique_ptr m_scintillaTricks; }; #endif // DRC_RE_CUSTOM_RULE_PANEL_H