DRC Rule Editor: Add autocomplete to custom rule panel

Add context-sensitive autocomplete suggestions for the custom rule
text editor using SCINTILLA_TRICKS. The autocomplete parses S-expressions
to understand the current context and suggests appropriate keywords:

- At top level after "(": suggests "rule" or "version"
- Inside "(rule ...)": suggests "condition", "constraint", "layer", "severity"
- Inside "(constraint ...)": suggests "min", "max", "opt"
- After "(constraint ": suggests all constraint type keywords
- After "(layer ": suggests "inner", "outer"
- After "(severity ": suggests "error", "warning", "ignore", "exclusion"
This commit is contained in:
Seth Hillbrand
2026-02-15 14:01:01 -08:00
parent 554dc9ac60
commit 2ea7d85c94
2 changed files with 190 additions and 1 deletions
@@ -23,7 +23,11 @@
#include "drc_re_custom_rule_panel.h"
#include <stack>
#include <scintilla_tricks.h>
#include <wx/sizer.h>
#include <wx/stc/stc.h>
DRC_RE_CUSTOM_RULE_PANEL::DRC_RE_CUSTOM_RULE_PANEL(
wxWindow* aParent, std::shared_ptr<DRC_RE_CUSTOM_RULE_CONSTRAINT_DATA> 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<SCINTILLA_TRICKS>(
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<wxString> 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, '|' ) );
}
@@ -24,12 +24,16 @@
#ifndef DRC_RE_CUSTOM_RULE_PANEL_H
#define DRC_RE_CUSTOM_RULE_PANEL_H
#include <memory>
#include <wx/panel.h>
#include <wx/stc/stc.h>
#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<DRC_RE_CUSTOM_RULE_CONSTRAINT_DATA> 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<DRC_RE_CUSTOM_RULE_CONSTRAINT_DATA> m_constraintData;
wxStyledTextCtrl* m_textCtrl;
std::unique_ptr<SCINTILLA_TRICKS> m_scintillaTricks;
};
#endif // DRC_RE_CUSTOM_RULE_PANEL_H