Skip to main content
A newer version of this page is available. .

Syntax Highlighting

  • 9 minutes to read

Syntax highlighting enables you to display text in different colors and fonts according to the category of text tokens.

Overview

To accomplish this task in the RichEditControl, you should perform the following steps:

  • Make sure that the RichEditControl’s document contains only plain text.
  • Parse text into tokens according to certain terms or specific grammar.
  • Sort the collection of tokens so that the tokens are ordered by their start position in the document.
  • Pass the ordered collection to the SubDocument.ApplySyntaxHighlight method.

A correct implementation involves the use of a custom service that implements the ISyntaxHighlightService interface. Its ISyntaxHighlightService.Execute method should create a collection of tokens for the current document and call the ApplySyntaxHighlight method. The custom syntax highlighting service is registered as usual, by using the RichEditControl.ReplaceService<T> method.

What is Token?

A token used in syntax highlighting is a range of text (defined by its start position and length) with format settings. Format settings specify how this range of text should be displayed. A token is an object of the SyntaxHighlightToken type, its format settings are contained within the object of the SyntaxHighlightProperties type.

The token itself does not contain any portion of document’s text. Only start position and length matter. It means that the original text should not contain complex structures such as fields, which can shift the document position. Bookmarks or hyperlinks are also not allowed.

Note

Do not load formatted text in the RichEditControl with syntax highlighting.

The following piece of code illustrates how the plain text can be parsed into tokens. We search for text enclosed with quotation marks, then for keywords. The remaining parts of the text are marked as plain tokens to fill gaps in token continuity. The resulting token collection should be ordered by its position in the text.

       readonly Document document;
       SyntaxHighlightProperties defaultSettings = new SyntaxHighlightProperties() { ForeColor = Color.Black };
       SyntaxHighlightProperties keywordSettings = new SyntaxHighlightProperties() { ForeColor = Color.Blue };
       SyntaxHighlightProperties stringSettings = new SyntaxHighlightProperties() { ForeColor = Color.Green };

       string[] keywords = new string[] { 
               "INSERT", "SELECT", "CREATE", "TABLE", "USE", "IDENTITY", "ON", "OFF", "NOT", "NULL", "WITH", "SET" };

       public CustomSyntaxHighlightService(Document document)
       {
           this.document = document;
       }

       private List<SyntaxHighlightToken> ParseTokens()
       {
           List<SyntaxHighlightToken> tokens = new List<SyntaxHighlightToken>();
           DocumentRange[] ranges = null;
           // search for quotation marks
           ranges = document.FindAll("'", SearchOptions.None);
           for (int i = 0; i < ranges.Length / 2; i++)
           {
               tokens.Add(new SyntaxHighlightToken(ranges[i * 2].Start.ToInt(),
                   ranges[i * 2 + 1].Start.ToInt() - ranges[i * 2].Start.ToInt() + 1, stringSettings));
           }
           // search for keywords
           for (int i = 0; i < keywords.Length; i++)
           {
               ranges = document.FindAll(keywords[i], SearchOptions.CaseSensitive | SearchOptions.WholeWord);

               for (int j = 0; j < ranges.Length; j++)
               {
                   if (!IsRangeInTokens(ranges[j], tokens))
                       tokens.Add(new SyntaxHighlightToken(ranges[j].Start.ToInt(), ranges[j].Length, keywordSettings));
               }
           }
           // order tokens by their start position
           tokens.Sort(new SyntaxHighlightTokenComparer());
           // fill in gaps in document coverage
           AddPlainTextTokens(tokens);
           return tokens;
       }

       private void AddPlainTextTokens(List<SyntaxHighlightToken> tokens)
       {
           int count = tokens.Count;
           if (count == 0)
           {
               tokens.Add(new SyntaxHighlightToken(0, document.Range.End.ToInt(), defaultSettings));
               return;
           }
           tokens.Insert(0, new SyntaxHighlightToken(0, tokens[0].Start, defaultSettings));
           for (int i = 1; i < count; i++)
           {
               tokens.Insert(i * 2, new SyntaxHighlightToken(tokens[i * 2 - 1].End,
tokens[i * 2].Start - tokens[i * 2 - 1].End, defaultSettings));
           }
           tokens.Add(new SyntaxHighlightToken(tokens[count * 2 - 1].End,
document.Range.End.ToInt() - tokens[count * 2 - 1].End, defaultSettings));
       }

       private bool IsRangeInTokens(DocumentRange range, List<SyntaxHighlightToken> tokens)
       {
               return tokens.Any(t => IsIntersect(range, t));
       }
       bool IsIntersect(DocumentRange range, SyntaxHighlightToken token) {
           int start = range.Start.ToInt();
           if (start >= token.Start && start < token.End)
               return true;
           int end = range.End.ToInt() - 1;
           if (end >= token.Start && end < token.End)
               return true;
           return false;
       }
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using DevExpress.Office.Utils;
using DevExpress.XtraRichEdit.API.Native;
using DevExpress.XtraRichEdit.Services;

namespace RichEditSyntaxSample
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            richEditControl1.ActiveViewType = DevExpress.XtraRichEdit.RichEditViewType.Draft;
            richEditControl1.ReplaceService<ISyntaxHighlightService>(new CustomSyntaxHighlightService(richEditControl1.Document));
            richEditControl1.LoadDocument("CarsXtraScheduling.sql");
            richEditControl1.Document.Sections[0].Page.Width = Units.InchesToDocumentsF(80f);
            richEditControl1.Document.DefaultCharacterProperties.FontName = "Courier New";
        }
    }
}

Note

You can use the DocumentRange.Freeze() or DocumentRangeExtensions class methods to improve performance during syntax highlight.

After one of these methods is called, RichEditControl stops tracking the actual document position for this range, and the target ranges cannot be modified. The frozen document ranges become invalid after the document is modified. Don’t use these ranges for further document processing operations.

DevExpress.CodeParser Library

A custom search for keywords is not the fastest way to parse text into tokens. You can use the DevExpress.CodeParser library to accomplish this task more effectively.

The code parser library is contained within the DevExpress.CodeParser.v18.2.dll assembly. Currently, it can be utilized to parse text in the following languages:

  • C#
  • Visual Basic
  • JavaScript
  • HTML
  • XAML
  • CSS

The library contains token category helper objects designed to parse text into a collection of tokens. The helper object is different for different languages, but exposes the same DevExpress.CodeParser.ITokenCategoryHelper interface. To obtain the helper object, use the static methods of the DevExpress.CodeParser.TokenCategoryHelperFactory class. The CreateHelperForFileExtensions(string extension) method creates a helper for the language determined by the file extension. The CreateHelper(ParserLanguageID languageID) method requires a language ID to create a proper helper. You can use another helper class, the DevExpress.CodeParser.ParserLanguage and its FromFileExtension method to obtain a language ID for the specified extension.

This technique is illustrated by the following code snippet.

public void Execute()
{
    string newText = syntaxEditor.Text;
    // Determine language by file extension.
    string ext = System.IO.Path.GetExtension(syntaxEditor.Options.DocumentSaveOptions.CurrentFileName);
    ParserLanguageID lang_ID = ParserLanguage.FromFileExtension(ext);
    // Do not parse HTML or XML.
    if (lang_ID == ParserLanguageID.Html || 
        lang_ID == ParserLanguageID.Xml || 
        lang_ID== ParserLanguageID.None) return;
    // Use DevExpress.CodeParser to parse text into tokens.
    ITokenCategoryHelper tokenHelper = TokenCategoryHelperFactory.CreateHelper(lang_ID);
    TokenCollection highlightTokens;
    highlightTokens = tokenHelper.GetTokens(newText);
    HighlightSyntax(highlightTokens);
}

public void ForceExecute()
{
    Execute();
}

Apply Syntax Highlight

When the text is parsed into tokens, you should specify the desired color and other formatting for each token according to its type, via the SyntaxHighlightToken.Properties. After that, the collection of tokens has to be ordered by the token position in the original text (a mandatory requirement). To display the text as the token formatting suggests, you should call a special method named SubDocument.ApplySyntaxHighlight.

This method changes formatting for the text visible in the RichEditControl’s view. A special ISyntaxHighlightService service operates to ensure that formatting is always applied to the text in situations when the text is modified or the visible region is changed. You should call the ApplySyntaxHighlight method within the ISyntaxHighlightService.Execute method of this service, as illustrated in the DevExpress.CodeParser Library section earlier in this document.

To accomplish this task, you should create a custom service inherited from the ISyntaxHighlightService and implement the Execute and the ForceExecute methods. To register a custom service, use the RichEditControl.ReplaceService<T> method.

The picture below illustrates syntax highlighting for the text in C# displayed within the RichEditControl’s SimpleView with custom line numbering.

SyntaxHighlight

See Also