Question

I have a simple "language" (similar to template languages and simple markup languages such as BBcode — basically it's just normal text with some variables, tags and similar features) and I want to highlight its syntax.

Here is the thing I am stuck with.

There are variables, they are enclosed with $ sign ($var1$). I highlight them with this rule:

<RuleSet name="VariableSet">
    <Rule color="Variable">
        \$\S+?\$
    </Rule>
</RuleSet>

Some area around a variable can be surrounded with { } characters.

In other words: some variables can have its "region", it starts from the first { before the variable and ends at the first } after the variable. Multiple variables can't be in one region, so in cases like { $var1$ $var2$ } there is no any regions, { } are treated like normal characters and ignored. It is not a scope like function and local scopes in C-style languages.

Here is an example:

[b]lorem ipsum[/b] $var0$ dolor sit amet, consectetur adipisicing elit, sed do 
eiusmod tempor incididunt ut labore et dolore magna aliqua. 
{ // <— not nighlighted
{ // <— highlighted
ut enim ad minim veniam, quis nostrud 
$var1$
exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
} // <— highlighted
} // <— not nighlighted

// all brackets below also should be not highlighted
duis aute { $50, 25$} irure { dolor } excepteur $var2$ sint occaecat cupidatat non
proident, sunt in culpa qui mollit anim id { est $var2$ laborum.
{ $var3$ $var4$ }

First I tried to solve this using two Rule regexps (for { and }, of course with this approach it is impossible to skip cases with unclosed brackets like { $var$ or $var$ } but that's not a big problem). However I found out that Rule works only within one line.

Then I tried Span like this:

<Span color="VariableAreaDelimiter" multiline="true">
    <Begin>
        \{(?!\{.*?\$\S+?\$)(?=.*?\$\S+?\$)
    </Begin>
    <End>
        \}
    </End>

    <RuleSet>
        <Import ruleSet="VariableSet"/>
        <Rule foreground="Black" fontWeight="normal">
            .
        </Rule>
    </RuleSet>
</Span>

Some of the problems:

  • Although the multiline is true regexps in Begin and End don't work for multiple lines. So it doesn't match this:

    {
    $var$
    
  • If there is no closing bracket the span takes everything until end of document. That's why I added the . rule.

Was it helpful?

Solution

This is fundamentally impossible with AvalonEdit's highlighting engine.

The engine is line-based, you cannot perform any multi-line matches. The only way to carry information from one line to the next is by opening a span -- the only state maintained by the highlighting engine is the stack of currently open spans.

The highlighting engine is designed this way to allow for incremental updates (which is critical for performance with large files). If you change text in a line, only that single line is updated. If this update leads to a change in the span stack at the end of the line, the following lines are updated as well (but only if they are in the visible portion of the text area - otherwise their update is delayed until the user scrolls down).

A possible solution is to implement your own IVisualLineTransformer instead of using the syntax highlighting engine. Here is an example implementation that highlights all occurrences of the word 'AvalonEdit':

public class ColorizeAvalonEdit : DocumentColorizingTransformer
{
    protected override void ColorizeLine(DocumentLine line)
    {
        int lineStartOffset = line.Offset;
        string text = CurrentContext.Document.GetText(line);
        int start = 0;
        int index;
        while ((index = text.IndexOf("AvalonEdit", start, StringComparison.Ordinal)) >= 0) {
            base.ChangeLinePart(
                lineStartOffset + index, // startOffset
                lineStartOffset + index + 10, // endOffset
                (VisualLineElement element) => {
                    // This lambda gets called once for every VisualLineElement
                    // between the specified offsets.
                    Typeface tf = element.TextRunProperties.Typeface;
                    // Replace the typeface with a modified version of
                    // the same typeface
                    element.TextRunProperties.SetTypeface(new Typeface(
                        tf.FontFamily,
                        FontStyles.Italic,
                        FontWeights.Bold,
                        tf.Stretch
                    ));
                });
            start = index + 1; // search for next occurrence
        }
    }
}

// Usage:
textEditor.TextArea.TextView.LineTransformers.Add(new ColorizeAvalonEdit());

OTHER TIPS

Try to replace your begin pattern with:

\{(?=[^{}$]*\$[^{}$\s]+\$[^{}$]*\})
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top