Sign in Join
Wiki copytemporary

SynEdit: what changed

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

OldNewLine
11**SynEdit** is a [[Syntax highlighting|syntax highlighting]] edit/memo package available on the [[SynEdit tab]] with support for many languages/syntaxes.
22
33The SynEdit contained in Lazarus was forked from [SynEdit 1.0.3](http://synedit.sourceforge.net/), and was adapted and extended quite a lot. Changes are listed below.
44
55Lazarus package contains a source editor component named [[TSynEdit]], several syntax highlighters and other components used for source editing.
66
77Licensed under the same terms as the original SynEdit (MPL or GPL).
88
99## Original vs Lazarus version
1010
1111Lazarus version is maintained mostly by Martin Friebe. Martin wrote at forum, what has been added to Lazarus version since fork appeared:
1212
1313Big things added to the Lazarus version:
1414
1515- folding
1616- configurable gutter / gutter-parts
1717- shared text between several editors
1818- utf-8 support
1919- sync-editing plugin
2020- basic RTL/LTR support
2121- mouse config via MouseActions
2222- rewrite of various highlight/markup modules
2323
2424Codebases of Delphi/Lazarus versions were independently refactored. There is very few overlap left.
2525
2626## SynEdit 2.0 port
2727
2828An alternative port of the 2.0.x version of the original SynEdit exists. It's not actively maintained, last commit (now is Jun 2014) was at 2011.
2929
3030- [[SynEdit/port]]
3131- [Github page](https://github.com/rnapoles/)
3232
3333## SynEdit in the IDE
3434
3535The SynEdit in Lazarus is a built-in package, because the IDE uses it itself. Therefore the package can not be removed from the installation list. To remove the entries from the component palette, the SynEditDsgn package can be removed from installation.
3636
3737## Using SynEdit
3838
3939### Highlighters
4040
4141- There are several standard highlighters (see [[SynEdit tab]] in the [[Component Palette]])
4242- There are scriptable highlighters, which can adapt to many other file formats:
4343 - [[TSynAnySyn]] (standard, on component palette by default)
4444 - [[TSynPositionHighlighter]] (standard, **not** on component palette)
4545 - [[TSynUniHighlighter]] (standard, **not** on component palette)
4646 - SynFacilSyn ([Github](https://github.com/t-edson/SynFacilSyn))
4747- More 3rd-party highlighters exist: SynCacheSyn, SynGeneralSyn, SynRCSyn, SynRubySyn, SynSDDSyn, SynSMLSyn, SynSTSyn, SynTclTkSyn, SynUnrealSyn, SynURISyn, SynVBScriptSyn, SynVrml97Syn, [see here](http://bugs.freepascal.org/view.php?id=18248).
4848- You can write new highlighter, see info at [[SynEdit Highlighter]].
4949
5050### Markup
5151
5252Markup provides additional coloring [[SynEdit Markup]]
5353
5454### Edit an existing highlighter
5555
5656Sometimes you might want to edit existing highlighters (just like I wanted a few days ago) that already exist. In this example we're going to edit the highlighter for pascal-like code (classname: [[TSynPasSyn]]; package: SynEdit V1.0; unit: SynHighlighterPas.pas).
5757
5858Say, what we want to reach is, that our application (lazarus in this case) differs between the three types of comments, which exist in Pascal:
5959
6060``` pascal
6161 (* ansi *)
6262 { bor }
6363 // Slash
6464```
6565
6666This may be helpful, if you want to differ between different types of your comments (e.g. "Description", "Note", "Reference", etc.) and want them to be e.g. colored in different ways.
6767
6868> **Note:** Just in case you break something, I suggest to make some "NEW" and "/NEW"-comments, but you don't have to
6969
7070- First, open the unit "SynHighlighterPas" which should be located in your SynEdit-directory.
7171- As we don't want to cause incompatibilities, we're creating a new type of enumerator which helps us to identify our comment later:
7272
7373E.g. under the declaration of "tkTokenKind", write this:
7474
7575``` pascal
7676 {NEW}
7777 TtckCommentKind = (tckAnsi, tckBor, tckSlash);
7878 {/NEW}
7979```
8080
8181- In the declaration of "TSynPasSyn" search for "FTokenID" and add the following between "FTokenID" and the next field
8282
8383``` pascal
8484 {NEW}
8585 FCommentID: TtckCommentKind;
8686 {/NEW}
8787 //This creates a new field, where we can store the information, what kind of comment we have
8888```
8989
9090- In the declaration of "TSynPasSyn" search for "fCommentAttri" and add the following between "fCommentAttri" and the next field
9191
9292``` pascal
9393 {NEW}
9494 fCommentAttri_Ansi: TSynHighlighterAttributes;
9595 fCommentAttri_Bor: TSynHighlighterAttributes;
9696 fCommentAttri_Slash: TSynHighlighterAttributes;
9797 {/NEW}
9898 //This allows us, to return different Attributes, per type of comment
9999```
100100
101101- Next, search for the constructor-definition of "TSynPasSyn", which should be "constructor TSynPasSyn.Create(AOwner: TComponent);"
102102- We need to Create our new Attributes, thus we add our Attributes somewhere in the constructor (I suggest, after the default "fCommentAttri")
103103
104104``` pascal
105105 (...)
106106 AddAttribute(fCommentAttri);
107107 {NEW}
108108 fCommentAttri_Ansi := TSynHighlighterAttributes.Create(SYNS_AttrComment+'_Ansi', SYNS_XML_AttrComment+'_Ansi'); //The last two strings are the Caption and the stored name
109109 //If you want to have default settings for your attribute, you can e.g. add this:
110110 //fCommentAttri_Ansi.Background := clBlack; //Would set "Background" to "clBlack" as default
111111 AddAttribute(fCommentAttri_Ansi);
112112 fCommentAttri_Bor := TSynHighlighterAttributes.Create(SYNS_AttrComment+'_Bor', SYNS_XML_AttrComment+'_Bor');
113113 AddAttribute(fCommentAttri_Bor);
114114 fCommentAttri_Slash := TSynHighlighterAttributes.Create(SYNS_AttrComment+'_Slash', SYNS_XML_AttrComment+'_Slash');
115115 AddAttribute(fCommentAttri_Slash);
116116 {/NEW}
117117 (...)
118118```
119119
120120- The "complex" part now is, to search for the points, where "FTokenID" is set to "tkComment" and to set our "subtype", equally (of course, I've already searched them:)
121121
122122``` pascal
123123procedure TSynPasSyn.BorProc;
124124(...)
125125 fTokenID := tkComment;
126126 {NEW}
127127 FCommentID:=tckBor;
128128 {/NEW}
129129 if rsIDEDirective in fRange then
130130(...)
131131```
132132
133133``` pascal
134134procedure TSynPasSyn.AnsiProc;
135135begin
136136 fTokenID := tkComment;
137137 {NEW}
138138 FCommentID:=tckAnsi;
139139 {/NEW}
140140(...)
141141```
142142
143143``` pascal
144144procedure TSynPasSyn.RoundOpenProc;
145145(...)
146146 fTokenID := tkComment;
147147 {NEW}
148148 FCommentID:=tckAnsi;
149149 {/NEW}
150150 fStringLen := 2; // length of "(*"
151151(...)
152152```
153153
154154``` pascal
155155procedure TSynPasSyn.SlashProc;
156156begin
157157 if fLine[Run+1] = '/' then begin
158158 fTokenID := tkComment;
159159 {NEW}
160160 FCommentID:=tckSlash;
161161 {/NEW}
162162 if FAtLineStart then begin
163163(...)
164164```
165165
166166``` pascal
167167procedure TSynPasSyn.SlashContinueProc;
168168(...)
169169 fTokenID := tkComment;
170170 {NEW}
171171 FCommentID:=tckSlash;
172172 {/NEW}
173173 while not(fLine[Run] in [#0, #10, #13]) do
174174(...)
175175```
176176
177177- Now, we just have to retreve the information when "GetTokenAttribute" is called and return the right Attribute, therefore we edit "GetTokenAttribute" as follows:
178178
179179``` pascal
180180function TSynPasSyn.GetTokenAttribute: TSynHighlighterAttributes;
181181begin
182182 case GetTokenID of
183183 tkAsm: Result := fAsmAttri;
184184 {OLD
185185 tkComment: Result := fCommentAttri; //This is commented and just backup, so it'll be ignored
186186 /OLD}
187187 {NEW}
188188 tkComment: begin
189189 if (FCommentID = tckAnsi) then
190190 Result := fCommentAttri_Ansi //Type is AnsiComment
191191 else
192192 if (FCommentID = tckBor) then
193193 Result := fCommentAttri_Bor //Type is BorComment
194194 else
195195 if (FCommentID = tckSlash) then
196196 Result := fCommentAttri_Slash //Type is SlashComment
197197 else
198198 Result := fCommentAttri //If our code failed somehow, fallback to default
199199 end;
200200 {/NEW}
201201 tkIDEDirective: begin
202202(...)
203203```
204204
205205If you do use lazarus, just reinstall the SynEdit-Package, if not, recompile your project/the package/\<similar\>.
206206
207207**DONE ! No seriously, you are now ready to differ between the different types of comments.**
208208
209209The lazarus-IDE does automatically detect, what attributes exist and shows them in the options, such as saves them, if you change them. If your application/IDE doesn't do this, you will have to set Color/Font/etc. of the new Attributes somewhere manually (e.g. in the constructor of TSynPasSyn)
210210
211211### Completion plugins
212212
213213There are 3 completion plug-ins for SynEdit:
214214
215215[[TSynCompletion]]
216216
217217- Offers a list of words in a drop-down via a shortcut key combination (default: Ctrl-Space).
218218- Used in the IDE for identifier completion.
219219- Included in examples.
220220- Available on the component palette (since 0.9.3x).
221221
222222Example code to invoke the completion pop up programmatically (i.e. without pressing the keyboard shortcut):
223223
224224``` pascal
225225YourSynEdit.CommandProcessor(YourSynCompletion.ExecCommandID, '', nil)
226226```
227227
228228[[TSynAutoComplete]]
229229
230230- Replaces the current token with a piece of text. **Not** interactive. **No** drop-down.
231231- Included in examples.
232232- Available on the component palette.
233233
234234[[TSynEditAutoComplete]]
235235
236236- Basic template module. **No** drop-down.
237237- Used by IDE for code-templates. IDE contains additional code extending the feature (drop-down and syncro macros are added by IDE).
238238- **Not** included in examples.
239239
240240Todo: Differences between 2nd and 3rd need to be documented. Maybe they can be merged.
241241
242242### Logical/Physical caret position
243243
244244SynEdit offers position of the caret (text blinking cursor) in 2 different forms:
245245
246246- Physical X/Y: Corresponds to visual (canvas) position,
247247- Logical X/Y: Corresponds to byte offset of the text.
248248
249249Both are 1-based. Currently Y coordinates are always the same. This may change in future.
250250
251251The Physical coordinate
252252is the position in the display grid (ignoring any scrolling). That is:
253253
254254the letter "a" and "â" take both ONE cell on the grid, increasing physical x by 1. Even though in utf8 encoding "a" takes one byte, and "â" takes several bytes.
255255
256256however the tab char (#9), besides being just one byte and one char, can take several cells in the grid, increasing the physical x by more than one. There are also some chars in Chinese and eastern languages, that take 2 grid positions (google full-width vs half-width char)
257257
258258The Logical coordinate
259259is the byte offset in the string holding the line.
260260
261261the letter "a" has 1 byte and increases by 1
262262
263263the letter "â" has 2 (or 3) bytes, and increases by that
264264
265265tab has 1 byte and increases by that.
266266
267267Neither of the 2 give the position in UTF8 chars/code-points (e.g. for Utf8Copy or Utf8Length).
268268
269269The physical X is always counted from the left of the text, even if this is scrolled out. To get the grid-x of the currently scrolled control do:
270270
271271
272272grid-X-in-visible-part-of-synedit := PhysicalX - SynEdit.LeftChar + 1
273273
274274grid-y-in-visible-part-of-synedit := SynEdit.RowToScreenRow(PhysicalY); // includes folding
275275
276276use ScreenRowToRow for reverse
277277
278278### Change text from code
279279
280-> **Warning:** Changing text via <u>SynEdit.Lines</u> property does <u>not work with undo/redo. </u>
280+> **Warning:** Changing text via SynEdit.Lines property does not work with undo/redo.
281281
282282Text can be accessed via SynEdit.Lines. This is a TStrings based property offering read/write access to each line. It is 0 based.
283283
284284``` pascal
285285 SynEdit.Lines[0] := 'Text'; // first line
286286```
287287
288288SynEdit.Lines can be used to set the initial version of the text (e.g. loaded from file). Note that SynEdit.Lines.Add/SynEdit.Lines.Append does not support line breaks inside the added strings. You should add lines one by one.
289289
290290To modify the content of a SynEdit, and allow the user to undo the action use the following methods:
291291
292292``` pascal
293293 procedure InsertTextAtCaret(aText: String; aCaretMode: TSynCaretAdjustMode = scamEnd);
294294 property TextBetweenPoints[aStartPoint, aEndPoint: TPoint]: String // Logical Points
295295 read GetTextBetweenPoints write SetTextBetweenPointsSimple;
296296 property TextBetweenPointsEx[aStartPoint, aEndPoint: TPoint; CaretMode: TSynCaretAdjustMode]: String
297297 write SetTextBetweenPointsEx;
298298 procedure SetTextBetweenPoints(aStartPoint, aEndPoint: TPoint;
299299 const AValue: String;
300300 aFlags: TSynEditTextFlags = [];
301301 aCaretMode: TSynCaretAdjustMode = scamIgnore;
302302 aMarksMode: TSynMarksAdjustMode = smaMoveUp;
303303 aSelectionMode: TSynSelectionMode = smNormal );
304304```
305305
306306Examples:
307307
308308``` pascal
309309 // Insert text at caret
310310 SynEdit.InsertTextAtCaret('Text');
311311 // Replace text from (x=2,y=10) to (x=4,y=20) with Str
312312 SynEdit.TextBetweenPoints[Point(2,10), Point(4,20)] := Str;
313313```
314314
315315``` pascal
316316 // Delete/replace single char at caret pos
317317 var
318318 p1, p2: TPoint;
319319 begin
320320 p1 := SynEdit.LogicalCaretXY;
321321 p2 := p1;
322322 // Calculate the byte pos of the next char
323323 p2.x := p2.x + UTF8CharacterLength(@SynEdit.LineText[p2.x]);
324324 // p1 points to the first byte of char to be replaced
325325 // p2 points to the first byte of the char after the last replaceable char
326326 // Replace with "Text" (or use empty string to delete)
327327 SynEdit.TextBetweenPoints[p1, p2] := 'Text';
328328```
329329
330330### Fold/Unfold from code
331331
332332- This is still under construction.
333333- This only works if current highlighter supports folding (details at [[SynEdit Highlighter]]).
334334- Also note that some highlighters support several independent fold-trees. E.g. in Pascal you have folding on keywords (begin, end, class, procedure, etc) which is the primary fold, and folding on \$ifdef or \$region which is secondary.
335335- Folding of current selection is also different from folding on keywords.
336336
337337Methods for folding:
338338
3393391\) TSynEdit.CodeFoldAction
340340
341341Folds at the given Line. If there are more than one, folds the inner most (right most). Note: This does not work with selection, nor with Folds that hide entirely / Need testing for 2ndary folds.
342342
3433432\) TSynEdit.FindNextUnfoldedLine
344344
3453453\) TSynEdit.FoldAll / TSynEdit.UnfoldAll
346346
347347### Bookmarks
348348
349349- [Forum topic about adding gutter-marks and colored lines](http://forum.lazarus.freepascal.org/index.php/topic,14948.msg79794.html)
350350
351351### More info
352352
353353Discussions on the forum, which contain info about SynEdit:
354354
355355- [Search/replace; caret; position logical/physical](http://forum.lazarus.freepascal.org/index.php/topic,19520.msg111158.html#msg111158)
356356- [Diff between SynEdit/SynMemo](http://forum.lazarus.freepascal.org/index.php/topic,19645.msg111962.html#msg111962)
357357- [Mouse actions; disable paste on middle click](http://forum.lazarus.freepascal.org/index.php/topic,23997.msg144100.html#msg144100)
358358- [TSynEditMarkup info](http://forum.lazarus.freepascal.org/index.php?topic=24842)
359359
360360### Example apps
361361
362362Example applications can be found in the folder "lazarus/examples/synedit".
363363
364364### Adding hotkeys for Cut/Copy/Paste/etc
365365
366366Hotkeys can be implemented by using SynEdit commands.
367367
368368``` pascal
369369uses
370370 SynEdit, SynEditKeyCmds;
371371
372372procedure TForm1.SynEdit1KeyDown(Sender: TObject; var Key: Word;
373373 Shift: TShiftState);
374374begin
375375 if (Shift = [ssCtrl]) then
376376 case Key of
377377 VK_C: SynEdit1.CommandProcessor(TSynEditorCommand(ecCopy), ' ', nil);
378378 VK_V: SynEdit1.CommandProcessor(TSynEditorCommand(ecPaste), ' ', nil);
379379 VK_X: SynEdit1.CommandProcessor(TSynEditorCommand(ecCut), ' ', nil);
380380 end;
381381end;
382382```
383383
384384## Further development, discussions
385385
386386- RTL (right-to-left): started by Mazen (partly implemented on Windows)
387387- SynEdit only uses UTF8; an ASCII/ANSI version no longer exists. A font is pre-selected depending on the system. The user can choose another font, but must then take care to choose a monospaced font.
388388 - automatic monospace font selection: At the moment SynEdit starts with a font 'courier'. At the moment the LCL TFont does not provide a property to filter monospaced fonts.
389389 - automatic UTF-8 font selection: Same as above monospace, but also with an UTF-8 font, so that for example umlauts are shown correctly.
390390- Dead keys. Most keyboards support typing two or more keys to create one special character (like accented or umlaut characters). (This is handled by LCL widgedset)
391391- [[Redesign of the SynEdit component]]. The primary goal is more reliable display and navigation in the text. A more modular approach also allows for better integration of extensions, and for specialized controls, for use outside of Lazarus.
392392- [Word Wrapping](http://bugs.freepascal.org/view.php?id=30395). This is an experimental implementation following the idea of the TextTrimmer/TabExpansion classes. The linked bugtraker issue has the class and the explanation of changes required in other files for it to work.
393393- Hooks in SynEdit key/command processing. On the forum: <http://forum.lazarus-ide.org/index.php/topic,35592.msg243316.html#msg243316>
394394
395395## See also
396396
397397- [[SynEdit Highlighter]]
398398- ["Edit an existing highlighter": German forum thread at "lazarusforum.de"](http://www.lazarusforum.de/viewtopic.php?f=5&t=8723)
399399- [[ATSynEdit]]
400400
401401---
402402*From the Free Pascal wiki, page "SynEdit", as it stood on 2024-03-27 after a change by Wennerer, 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).*
403403Category: Components
404404Category: Lazarus
405405Category: SynEdit

Back to history