-
Notifications
You must be signed in to change notification settings - Fork 245
Brace Matching
So you wanna highlight matching braces? No problem. But it doesn't come for free. What may be a brace character in one programming language may be an operator in another. Thus, Scintilla provides facilities for you to highlight matching braces but it doesn't do it for you.
Our basic approach will be this: listen to the UpdateUI
event to know when the caret changes position, determine if the caret is adjacent to a brace character, find the matching brace character using BraceMatch
, and then highlight those characters with BraceHighlight
. This may sound like a lot of work but it's actually pretty easy. Let's start by setting the brace styles:
scintilla.Styles[Style.BraceLight].BackColor = Color.LightGray;
scintilla.Styles[Style.BraceLight].ForeColor = Color.BlueViolet;
scintilla.Styles[Style.BraceBad].ForeColor = Color.Red;
You'll notice that we also set the style for an unmatched brace. We'll indicate those in red.
For the purposes of this recipe we'll assume the following characters are braces: '(', ')', '[', ']', '{', '}', '<', and '>'. This is convenient because that's also what the BraceMatch
method supports, as you'll see in a minute. To determine if a character is one of the brace characters we want to process we'll use a simple helper method:
private static bool IsBrace(int c)
{
switch (c)
{
case '(':
case ')':
case '[':
case ']':
case '{':
case '}':
case '<':
case '>':
return true;
}
return false;
}
All that remains now is to handle the UpdateUI
event:
int lastCaretPos = 0;
private void scintilla_UpdateUI(object sender, UpdateUIEventArgs e)
{
// Has the caret changed position?
var caretPos = scintilla.CurrentPosition;
if (lastCaretPos != caretPos)
{
lastCaretPos = caretPos;
var bracePos1 = -1;
var bracePos2 = -1;
// Is there a brace to the left or right?
if (caretPos > 0 && IsBrace(scintilla.GetCharAt(caretPos - 1)))
bracePos1 = (caretPos - 1);
else if (IsBrace(scintilla.GetCharAt(caretPos)))
bracePos1 = caretPos;
if (bracePos1 >= 0)
{
// Find the matching brace
bracePos2 = scintilla.BraceMatch(bracePos1);
if (bracePos2 == Scintilla.InvalidPosition)
scintilla.BraceBadLight(bracePos1);
else
scintilla.BraceHighlight(bracePos1, bracePos2);
}
else
{
// Turn off brace matching
scintilla.BraceHighlight(Scintilla.InvalidPosition, Scintilla.InvalidPosition);
}
}
}
The UpdateUI
event can be called for many reasons--not just because the caret moved. So at the top of the handler is a check to see if the caret has moved since the last time the UpdateUI
event fired. Next we peek at the character before the current caret position and at the current caret position to determine if either is a brace character using our simple IsBrace
helper method. If it is a brace character, bracePos
will be set and we can then look for the matching brace character. Scintilla makes this easy by providing the BraceMatch
method which looks for the same brace characters that our IsBrace
method does and knows whether to scan backwards or forwards depending on the start character. It's also smart enough to skip over nested braces. If you wanted to find a character that the BraceMatch
method doesn't support you would have to roll your own scanning logic. If a matching brace is found we highlight both brace positions using the BraceHighlight
method, if not, we highlight the orphaned brace with the BraceBadLight
method.
That's it for basic brace matching, but we could go one step further. Scintilla also provides the ability to show an indentation guide, which is a vertical line at different indentation levels. This is a good companion feature for brace matching. When we do brace matching we can also highlight the corresponding indentation guide in the BraceLight
style. To enable indentation guides we use the IndentationGuides
property:
scintilla.IndentationGuides = IndentView.LookBoth;
To highlight an indentation guide we use the HighlightGuide
property. The value of this property is the column number of the guide to highlight. Since indentation guides are vertical lines at different indentation levels, it makes sense that we have to tell Scintilla which indentation level should be highlighted by providing it with the column (read level) of the indentation. The column number of a document position can be determined using the GetColumn
method.
Putting that all together, here is the complete recipe:
int lastCaretPos = 0;
private void form_Load(object sender, EventArgs e)
{
scintilla.IndentationGuides = IndentView.LookBoth;
scintilla.Styles[Style.BraceLight].BackColor = Color.LightGray;
scintilla.Styles[Style.BraceLight].ForeColor = Color.BlueViolet;
scintilla.Styles[Style.BraceBad].ForeColor = Color.Red;
}
private static bool IsBrace(int c)
{
switch (c)
{
case '(':
case ')':
case '[':
case ']':
case '{':
case '}':
case '<':
case '>':
return true;
}
return false;
}
private void scintilla_UpdateUI(object sender, UpdateUIEventArgs e)
{
// Has the caret changed position?
var caretPos = scintilla.CurrentPosition;
if (lastCaretPos != caretPos)
{
lastCaretPos = caretPos;
var bracePos1 = -1;
var bracePos2 = -1;
// Is there a brace to the left or right?
if (caretPos > 0 && IsBrace(scintilla.GetCharAt(caretPos - 1)))
bracePos1 = (caretPos - 1);
else if (IsBrace(scintilla.GetCharAt(caretPos)))
bracePos1 = caretPos;
if (bracePos1 >= 0)
{
// Find the matching brace
bracePos2 = scintilla.BraceMatch(bracePos1);
if (bracePos2 == Scintilla.InvalidPosition)
{
scintilla.BraceBadLight(bracePos1);
scintilla.HighlightGuide = 0;
}
else
{
scintilla.BraceHighlight(bracePos1, bracePos2);
scintilla.HighlightGuide = scintilla.GetColumn(bracePos1);
}
}
else
{
// Turn off brace matching
scintilla.BraceHighlight(Scintilla.InvalidPosition, Scintilla.InvalidPosition);
scintilla.HighlightGuide = 0;
}
}
}