This site hosted by Free.ProHosting.com
Google

RichEdit Syntax Highlighting

Applies To: C++Builder 1 or higher
Category: Technical Articles
Download: Source code

 
Overview
 
Many code editors, including C++Builder, offer syntax-highlighting capabilities. I recently wanted to create a simple editor that displays keywords in a specific color. Although this may seem like a simple task, it isn't. Unless done correctly, syntax highlighting may be very slow and extremely flickery.
 
Changing Color
 
Before starting the actual project, you should of course know how to change the color of particular text in the RichEdit component. This is done by using the SelAttributes property. SelAttributes modifies the font characteristics of the currently selected text. If, for example, the text contained in your RichEdit component is "This is a text.", then you can use the following code to change the font color and style of the word "text":
 
RichEdit1->SelStart = 10;
RichEdit1->SelLength = 4;
RichEdit1->SelAttributes->Color = clBlue;
RichEdit1->SelAttributes->Style = TFontStyles() <<< fsBold;
 
Unfortunately, VCL methods and properties like this one are slower then the corresponding API calls. For this reason, I've decided to use only API functions and messages. Here's how the previous code should look like:
 
CHARRANGE Range;
Range.cpMin = 10;
Range.cpMax = 14;
RichEdit1->Perform(EM_EXSETSEL, 0, (LPARAM)&Range);
 
CHARFORMAT Format;
memset(&Format, 0, sizeof(Format));
Format.cbSize = sizeof(Format);
Format.dwMask = CFM_BOLD | CFM_COLOR;
Format.dwEffects = CFE_BOLD;
Format.crTextColor = clBlue;
RichEdit1->Perform(EM_SETCHARFORMAT, SCF_SELECTION,
    (LPARAM)&Format);
 
Finding Keywords
 
All keywords to be highlighted are stored in a simple array:
 
char *Keywords[8] =
{
    "for", "int", "while", "do", "switch", "break", "case", "if"
};
 
To check if a particular keyword is found in the text, you should use the EM_FINDTEXT message. For example:
 
        FINDTEXT FindText;
        FindText.lpstrText = Keywords[x];
        FindText.chrg.cpMin = Start;
        FindText.chrg.cpMax = End;
        FoundPos = RichEdit1->Perform(EM_FINDTEXT,
                FT_WHOLEWORD, (LPARAM)&FindText);
 
        while(FoundPos > -1)
        {
            ...
 
I don't have place here to discuss each line of code. A complete description of messages and functions used here can be found in the C++Builder help files.
 
Line By Line
 
A common error made when creating syntax highlighting editors is to parse the entire text. You should highlight words only in the current line. To do so, you need to know three things: the current line, the index of the first character and the index of the last character of this line. I've simplified this process by creating two functions:
 
int TForm1::GetFirstPos(int Line)
{
    int Pos = RichEdit1->Perform(EM_LINEINDEX, Line, 0);
    return Pos;
}
 
int TForm1::GetLastPos(int Line)
{
    if(Line == RichEdit1->Lines->Count - 1)
        return RichEdit1->Text.Length();
    else
    {
        int Pos = RichEdit1->Perform(EM_LINEINDEX, Line + 1, 0);
        return (Pos - 1);
    }
}
 
Here's how you should use them:
 
    int CurrentLine = RichEdit1->Perform(EM_LINEFROMCHAR,
        -1, 0);
    int Start = GetFirstPos(CurrentLine);
    int End = GetLastPos(CurrentLine);
 
Improving Performance
 
If, at this time, you try to build and execute this project, you'll see that the highlighting is slow and often flickers. To correct this problem, I've found three ways.
 
The first one is to modify the event mask of your RichEdit component. The event mask specifies which notification messages RichEdit sends to its parent window. While you're syntax highlighting the text, no messages have to be sent to your form therefore, you can increase your application's performance using:
 
RichEdit1->Perform(EM_SETEVENTMASK, 0, ENM_NONE);
 
and restoring the previous event mask once the process is finished.
 
A second way to improve the look of your editor is by using the LockWindowUpdate API function. It simply disables the drawing of the RichEdit component.
 
Finally, the last trick is to avoid repainting the current line if there are no new keywords. Each time you parse a certain line, you can store the number of keywords found in it. Then, the next time this line is modified, you could count the new number of keywords and compare it to the previous number. This operation may consume some memory but it greatly improves the look of your editor.
 
Conclusion
 
The complete source code is available for download here. This sample code editor is almost flicker-less and pretty fast, but there are many ways to improve it. You should, first of all, take care of the Paste and Undo commands since they're not correctly handled. You could then improve the text parsing or add new features to your editor. Good luck!

C++Builder Developer's Network
Copyright © Yoto Yotov