Good time of the day Habrazhiteli!
I want to tell you about my experience in developing an editor with syntax highlighting or code editor. This is a custom language editor. The essence of this post is not in the detailed description of the development process - I will focus only on the most interesting points, poorly consecrated or generally omitted from the documentation.
Scintilla is one of the most well-known freeware components for creating code editors,
wxStyledTextCtrl its wrapper in the
wxWidgets
.
Scintilla supports most of the programming languages used, so adding a new lexer is often done by analogy with the closest language. In more detail this process is consecrated
here . This method is very simple to implement, but it has one major drawback - you have to include all the scintilla code in your project. For small and relatively stable projects this is not very significant, but for cumbersome and actively developing it causes a lot of problems. Support is even more complicated if you use not the Scintilla component itself, but its wrapper, for example,
wxStyledTextCtrl
. An alternative is the so-called
implementation of the lexer in the container .
Integration of lexer into scintilla
First, a few words about a simple way. In general, to create your lexer you need to write only one function that will be responsible for coloring the changed part of the code:
static void ColouriseDoc (unsigned int startPos, int length, int initStyle, WordList *keywordlists[], Accessor &styler)
. To color or stylize the specified range means to specify a style for each character. It does not worth the component itself optimizes the drawing of the text, and also ensures that the range
[startPos, startPos + length - 1]
is an integer number of lines, which facilitates the implementation of the parser.
Implementing a lexer in a container
I started by creating a class - a descendant of
wxStyledTextCtrl
as indicated in the
example . As in the previous method, the lexer requires only one function, which in this case will be called on the
EVT_STC_STYLENEEDED
event.
Experience has shown that if the size of the edited files does not exceed 50-60 lines, then you can safely ignore the range transmitted by the event and stylize the entire contents of the file, which greatly simplifies the lexer. If the number of lines is more, then it is better to update the style not from the line
GetEndStyled()
, but from the previous line. As in the previous method, the event is called for an integer number of lines.
')
What forgot to mention in the manual
One of the difficulties I encountered at this stage is that the event is triggered too often. A small study of the behavior of the component in the debugger showed that after styling the range [n, m], the last stylized symbol is not m at all. Most often it was the previous character, sometimes the character is 2-3 characters away from it. There were several reasons:
- all custom styles must have indices greater than 0,
- you need to stylize everything, including spaces and line breaks
- styles should be applied consistently, i.e. if you first apply the style to the range [0, 100], and then to [0, 10] - the last stylized will be the 10th, not the 100th
I implemented a string parser and in order to avoid unnecessary event calls, I had to store the index of the last stylized character.
SetStyling via SetStyleBytes
In almost all examples, the use of style is indicated as a sequence of commands:
StartStyling(...);
SetStyling(...);
in this case, a character-by-character style is used, which is completely inefficient and it is better to use this one, which allows you to transfer to the component a previously prepared vector of style indices
StartStyling(...);
SetStyleBytes(...);
To be honest, I never found cases in which the SetStyling function should have been used.
Russian letters
In order for styles to correctly begin to use words with Russian letters, it is necessary to correctly calculate the index of the last character by sequentially performing
pos = PositionAfter(pos)
. A similar method of calculating the position should be used to get a part of the word when invoking the automatic substitution
pos = PositionBefore(pos)
.
Hiding part of the code (folding)
The implementation of this function did not cause any special problems, and there were no gaps in the documentation. I can only say that folding is based on the levels of the
SetFoldLevel(...)
lines, which, for example, I set right in the lexer (this is why the line-based parser is more convenient).
Automatic substitution function (autocomplete)
Although the function is relatively detailed in the documentation, some things are missing. First of all, it turned out that the standard feature of the
AutoCompShow(...)
component does not filter by the entered part of the word, but only by positioning. Then the
AutoCompSelect()
function which should substitute the user-selected word was useless, because it often caused an exception for no apparent reason. Instead, it is better to set the flag AutoCompSetChooseSingle ().
Call Tips
Another equally
wxStyledTextCtrl
function of
wxStyledTextCtrl
is the display of the CallTipShow () call hints. Working with it is similar to working with the
AutoCompShow()
substitution function. It should be noted that they are combined simultaneously.
Given the above features,
wxStyledTextCtrl
allows
wxStyledTextCtrl
to implement a fairly complex lexer in a container.