Sign in Join
Wiki copytemporary

SynEdit Highlighter: what changed

Revision 2 to 3. Removed lines have a minus sign; added lines have a plus sign.

OldNewLine
11For more info on SynEdit go to: [[SynEdit]]\
22Also see [[SynEdit Markup]]
33
44------------------------------------------------------------------------
55
66# Understanding the SynEdit Highlighter
77
88## SynEdit - Highlighter relationship
99
1010**SynEdit to Highlighter** have an **N to 1** relationship.
1111
1212- One instance of a Highlighter can serve **N** (many) instances of SynEdits
1313- Each SynEdit only has one Highlighter
1414- But: one text (text-buffer) can have many highlighters, if shared by several SynEdit (each SynEdit will have one HL, but all HL will work on the same document)
1515
1616As a result of this:
1717
1818- no Highlighter Instance has a (fixed) reference to the SynEdit.
1919
2020
2121(Highlighters however keep a list of SynEditTextBuffers to which they are attached)
2222
2323- All data for the Highlighter is (and must be) stored on the SynEdit (actually on the TextBuffer of SynEdit (referred to as "Lines").
2424
2525However, before each call to the Highlighter, SynEdit ensures that Highlighter.CurrentLines is set to the current SynEdits Lines. This way the highlighter can access the data whenever needed. The Format of the data storage is determined by the highlighter (TSynCustomHighlighter.AttachToLines).
2626
2727## Scanning and Returning Highlight attributes
2828
2929The Highlighter is expected to work on a per Line base.
3030
3131If any text was modified, SynEdit will call (TSynCustomHighlighter.ScanFrom / Currently called from TSynEdit.ScanFrom) with the line range. The Highlighter should know the state of the previous line.
3232
3333If Highlight attributes are required, SynEdit will request them per Line too. SynEdit will loop through individual tokens on a line. This currently happens from nested proc PaintLines in SynEdit.PaintTextLines. It calls TSynCustomHighlighter.StartAtLineIndex, followed by HL.GetTokenEx/HL.GetTokenAttribute for as long as HL.GetEol is false.
3434
3535Also the BaseClass for the Highlighter's data (see AttachToLines) is based on per line storage, and SynEdit's TextBuffer (Lines) do maintenance on this data to keep it synchronized. That is: whenever lines of text are inserted or removed, so are entries inserted or removed from the highlighters data (hence it must have one entry per line).
3636
3737Usually Highlighters store the end-of-line-status in this field. So if the highlighter is going to work on a line, it will continue with the state-entry from the previous line.
3838
3939## Folding
4040
4141SynEdit's folding is handled by unit SynEditFoldedView and SynGutterCodeFolding. Highlighters that implement folding are to be based on TSynCustomFoldHighlighter.
4242
4343The basic information for communication between SynEditFoldedView and the HL requires 2 values stored for each line. (Of course the highlighter itself can store more information):
4444
4545- FoldLevel at the end of line
4646- Minimum FoldLevel encountered anywhere on the line
4747
4848The Foldlevel indicates how many (nested) folds exist. It goes up whenever a fold begins, and down when a fold ends:
4949
50-`                           EndLvl   MinLvl`\
51-` Procedure a;               1 -      0`\
52-` Begin                      2 --     1 -`\
53-`   b:= 1;                   2 --     2 --`\
54-`   if c > b then begin      3 ---    2 --`\
55-`     c:=b;                  3 ---    3 ---`\
56-`   end else begin           3 ---    2 --`\
57-`     b:=c;                  3 ---    3 ---`\
58-`   end;                     2 --     2 --`\
59-` end;                       0        0  // The end closes both: begin and procedure fold`
50+``` pascal
51+ EndLvl MinLvl
52+ Procedure a; 1 - 0
53+ Begin 2 -- 1 -
54+ b:= 1; 2 -- 2 --
55+ if c > b then begin 3 --- 2 --
56+ c:=b; 3 --- 3 ---
57+ end else begin 3 --- 2 --
58+ b:=c; 3 --- 3 ---
59+ end; 2 -- 2 --
60+ end; 0 0 // The end closes both: begin and procedure fold
61+```
6062
6163In the line
6264
63-` Procedure a;               1 -      0`
65+``` pascal
66+ Procedure a; 1 - 0
67+```
6468
6569the MinLvl is 0, because the line started with a Level of 0 (and it never went down / no folds closed). Similar in all lines where there is only an opening fold keyword ("begin").
6670
6771But the line
6872
69-`   end else begin           3 ---    2 --`
73+``` pascal
74+ end else begin 3 --- 2 --
75+```
7076
7177starts with a level of 3, and also ends with it (one close, one open). But since it went down first, the minimum level encountered anywhere on the line is 2.
7278
7379Without the MinLvl it would not be possible to tell that a fold ends in this line.
7480
7581There is no such thing as a MaxLvl, because folds that start and end on the same line can not be folded anyway. No need to detect them.
7682
77-` if a then begin b:=1; c:=2; end; // no fold on that line`
83+``` pascal
84+ if a then begin b:=1; c:=2; end; // no fold on that line
85+```
7886
7987# Creating a SynEdit Highlighter
8088
8189Since 0.9.31 Revision 35115 the fold-highlighter has changed. Implementing basic folding is now easier.
8290
8391All Sources can be found in the Lazarus installation directory under:
8492
8593:* Lazarus 2.3 and up: examples\Components\SynEdit\NewHighlighterTutorial
8694
8795:* Lazarus before 2.2.*: examples\SynEdit\NewHighlighterTutorial\\
8896
8997The project HighlighterTutorial contains 3 difference example highlighters:
9098
9199:* SimpleHl: used in Step 1 below
92100
93101:* ContextHl: used in Step 2 below
94102
95103:* FoldHl: used in Step 3 below
96104
97105SimpleHl and ContextHl will work with 0.9.30 too
98106
99107The folding highlighter in action:
100108
101109![](/wiki-images/a/a2/SynEditFoldingHighlighterDemo.png)
102110
103111## The Basics: Returning Tokens and Attributes
104112
105113As indicated, the **SimpleHl** unit demonstrates this process.
106114
107115### What it does
108116
109117- It splits each line into words and spaces (or tabs)
110118 - The spaces are part of the text, and must be highlighted too
111119- This example allows specifying different colors for
112120
113121
114122
115123\- text (defaults to not-highlighted)
116124
117125\- spaces (defaults to silver frame)
118126
119127\- words, separated by spaces, that start with a,e,i,o,u (defaults to bold)
120128
121129\- the word "not" (defaults to red background)
122130
123131### How it works
124132
125133- Creation
126134
127135The Highlighter creates Attributes that it can return the Words and Spaces.
128136
129137- SetLine
130138
131139Is called by SynEdit before a line gets painted (or before highlight info is needed)
132140
133141- GetTokenEx, GetTokenAttribute, Next, GetEol
134142
135143Are used by SynEdit to iterate over the Line. Note that the first Token (Word or Spaces) must be ready after SetLine, without a call to Next.
136144
137145[[Important:]] The tokens returned for each line must represent the original line-text, and be returned in the correct order.
138146
139147- GetToken, GetTokenPos, GetTokenKind
140148
141149SynEdit uses them e.g for finding matching brackets. If tokenKind returns different values per Attribute, then brackets only match, if they are of the same kind (e.g. if there was a string attribute, brackets outside a string would not match brackets inside a string).
142150
143151### Other notes
144152
145153For readability, the highlighter has no optimization, so it may be very slow on larger texts. Many of the supplied highlighters use hash functions, to find what word (or any group of chars) is.
146154
147155## Step 2: Using Ranges
148156
149157As indicated, the **ContextHl** unit demonstrates this process
150158
151159The next example allows content of a line that influences other lines that follow. An example: a "(*" in Pascal makes all following lines a comment until a "*)" is found.
152160
153161This example extends the **SimpleHl** show above: The tokens -- and ++ (must be surrounded by space or line-begin/end to be a token of their own) will toggle words that start with a,e,i,o,u
154162
155163Multiple ++ and -- can be nested. Then for each -- a ++ must be given, before the words highlight again.
156164
157165Then we extend the scanner. The pre-scan to store the information calls the same functions as the highlighter. It is automatically called, if anything changes. (It is called for all lines below the changed line, until a line returns the same Range-value as it already had)
158166
159167The current amount of "--" is counted in
160168
161169``` pascal
162170 FCurRange: Integer;
163171```
164172
165173The amount is decreased by "++"
166174
167175To store the info we use:
168176
169177GetRange
170178Called after a line is completely scanned, to get the value at the end of the line. The value will be stored.
171179
172180SetRange
173181Called before a line gets scanned. Sets the value stored from the end of the previous line.
174182
175183ResetRange
176184Called before the 1st line is scanned (as there is no previous line).
177185
178186> **Note:** A scan is triggered by *every* change to a line (every keystroke). It scans the current line, and all lines below, until a line returns the same range that it already had. See: <http://forum.lazarus.freepascal.org/index.php/topic,21727.msg139420.html#msg139420>
179187
180188### Important note on Ranges
181189
182190The purpose of the range is to allow the HL to start scanning in any line. The HL will not never need to look at a previous line. Any information needed to scan the current line can be derived from the ranges value.
183191
184192Example:
185193
186194``` pascal
187195 writeln; (*
188196 readln;
189197 *)
190198```
191199
192200when scanning the "readln" the HL knows from the range that it is in a comment, it does not need to look back at previous lines.
193201
194202Therefore scanning can start at any line.
195203
196204This also explains the note in the previous chapter. "until a line returns the same range that it already had". For even if the text of a line was not changed, if the value of the range at the lines start changed, then the scan result will change too.
197205
198206## Step 3: Add Folding
199207
200208As indicated, the **FoldHl** unit demonstrates this process
201209
202210For the example, the highlighter should fold everything between free-standing "-(-", "-)-".
203211
204212Change inheritance:
205213
206214``` pascal
207215 uses SynEditHighlighterFoldBase;
208216 ...
209217 TSynDemoHl = class(TSynCustomFoldHighlighter)
210218```
211219
212220Change the way range info is stored, since the base class uses it for fold-info:
213221
214222``` pascal
215223procedure TSynDemoHl.SetRange(Value: Pointer);
216224begin
217225 inherited;
218226 FCurRange := PtrInt(CodeFoldRange.RangeType);
219227end;
220228
221229procedure TSynDemoHl.ResetRange;
222230begin
223231 inherited;
224232 FCurRange := 0;
225233end;
226234
227235function TSynDemoHl.GetRange: Pointer;
228236begin
229237 CodeFoldRange.RangeType := Pointer(PtrInt(FCurRange));
230238 inherited;
231239end;
232240```
233241
234242Now add code to the scanner which tells the highlighter about opening and closing folds:
235243
236244``` pascal
237245procedure TSynDemoHl.FindTokenEnd;
238246begin
239247 ...
240248
241249 if (FTokenEnd = FTokenPos+1) and (FLineText[FTokenPos] = '[') then
242250 StartCodeFoldBlock(nil);
243251 if (FTokenEnd = FTokenPos+1) and (FLineText[FTokenPos] = ']') then
244252 EndCodeFoldBlock();
245253end;
246254```
247255
248256- **For 0.9.30**
249257
250258Please see the history of this page, if you are using a 0.9.30 version [[http://wiki.lazarus.freepascal.org/index.php?title=SynEdit Highlighter&amp;oldid=76020]]
251259
252260#### More info on StartCodeFoldBlock / EndCodeFoldBlock
253261
254262``` pascal
255263 function StartCodeFoldBlock(ABlockType: Pointer; IncreaseLevel: Boolean = true): TSynCustomCodeFoldBlock; virtual;
256264```
257265
258266ABlockType
259267Can be used to specify an ID for the block.\
260268
261269The field is not normally used as a pointer (though this is permitted). Normally IDs are a numeric enumeration.\
262270If you have different types of block (e.g. in Pascal: begin/end; repeat/until, ...), and you do not want a block being closed by the wrong keyword ("end" should not close "repeat"), then you can give them IDs:\
263271StartCodeFoldBlock(PtrUInt(1)) // or other numbers\
264272
265273IncreaseLevel
266274If set to False, then a block will be inserted that can not be folded.\
267275Blocks like that can be used for internal tracking.
268276
269277NOTE: All folds must be nested, they can not overlap. That is, the last opened fold must be closed first.\
270278This refers to the "EndLvl" as shown in "Folding"(1.3) above\
271279Overlaps (like IFDEF and begin in the IDE) can not be done this way.
272280
273281``` pascal
274282 procedure EndCodeFoldBlock(DecreaseLevel: Boolean = True); virtual;
275283```
276284
277285DecreaseLevel
278286This *must* match IncreaseLevel, as it was given on the StartCodeFoldBlock\
279287
280288True means a fold ends; False means an internal block is ended. If mismatched then folds will either continue, or end early.\
281289TopCodeFoldBlockType can be used to indicate the ID of the innermost open block. One can use different IDs for internal blocks, and use this to set the value.
282290
283291``` pascal
284292 function TopCodeFoldBlockType(DownIndex: Integer = 0): Pointer;
285293```
286294
287295Returns the ID of the innermost block.
288296
289297DownIndex
290298can be used to get ID for the other block.
291299
292300DownIndex=1 means the block that is surrounding the innermost.
293301
294302# Configurable Highlighters (incl. 3rd party)
295303
296304## SynAnySyn
297305
298306A real simple Highlighter. More a reference implementation, than a real life useable Highlighter.
299307
300308## SynUniHighlighter
301309
302310Uses XML-based configurations files. [old official link](http://web.archive.org/web/20090610040212/http://www.delphist.com:80/UniHighlighter.html)
303311
304312## SynFacilSyn
305313
306314Flexible fully configurable Highlighter. <https://github.com/t-edson/SynFacilSyn>
307315
308316# References
309317
310318Threads on the forum:
311319
312320- "Making highligher for C syntaxis" [[http://www.lazarus.freepascal.org/index.php/topic,10260.0.html topic,10260]]
313321- "Creating a Syntax Highlighter" [[http://www.lazarus.freepascal.org/index.php/topic,10959.msg54714 topic,10959.msg54714]]
314322- obtaining highlight for printing [[http://www.lazarus.freepascal.org/index.php/topic,11384.msg57160.html|http://www.lazarus.freepascal.org/index.php/topic,11384.msg57160.html#msg57160 11384.msg57160]]
315323- Some details on ranges, and using objects to store more info [[http://forum.lazarus.freepascal.org/index.php/topic,21727.msg139354.html|http://forum.lazarus.freepascal.org/index.php/topic,21727.msg139354.html#msg139354 topic,21727.msg139354]] and [[http://forum.lazarus.freepascal.org/index.php/topic,21727.msg139419.html|http://forum.lazarus.freepascal.org/index.php/topic,21727.msg139419.html#msg139419 topic,21727.msg139419]]
316324- More on ranges, working copy vs immutable, foldblocks... [[http://forum.lazarus.freepascal.org/index.php/topic,30685.msg195464.html|http://forum.lazarus.freepascal.org/index.php/topic,30685.msg195464.html#msg195464 http://forum.lazarus.freepascal.org/index.php/topic,30685]]
317325- Markup (Highlight) all occurrences of a token (or regex) [[http://forum.lazarus.freepascal.org/index.php?topic=26833 topic=26833]]
318326- Markup and foldinfo: [[http://forum.lazarus.freepascal.org/index.php?topic=30122]]
319327- How to do invalidation in TSynEditMarkup [[http://forum.lazarus.freepascal.org/index.php/topic,30122.msg207931.html|http://forum.lazarus.freepascal.org/index.php/topic,30122.msg207931.html#msg207931]]
320328
321329- Folding
322330 - "SynEdit - improved highlighters to handle Code Folding?" [[http://www.lazarus.freepascal.org/index.php/topic,7879.0.html topic,7879]]
323331 - "SynEdit - Add support code folding for Java" [[http://www.lazarus.freepascal.org/index.php/topic,7338.0.html topic,7338]]
324332 - "CodeFolding" Config [[http://www.lazarus.freepascal.org/index.php/topic,11064 topic,11064]]
325333 - Fold blocks "end" keyword (end on the line before the next keyword) [[http://forum.lazarus.freepascal.org/index.php/topic,23411.msg139621.html|http://forum.lazarus.freepascal.org/index.php/topic,23411.msg139621.html#msg139621 topic,23411.msg139621]]
326334 - Folding selected text from code (user/application code): [[http://forum.lazarus.freepascal.org/index.php/topic,24473.msg147312.html|http://forum.lazarus.freepascal.org/index.php/topic,24473.msg147312.html#msg147312 24473.msg147312]]
327335 - Obtaining state of folding (save fold state with session) [[http://forum.lazarus.freepascal.org/index.php?topic=26748 topic=26748]]
328336
329337---
330338*From the Free Pascal wiki, page "SynEdit Highlighter", as it stood on 2022-05-04 after a change by Martin, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*
331339Category: SynEdit

Back to history