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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user