Sign in Join
Wiki copytemporary

SynEdit: what changed

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

Title: SynEdit

OldNewLine
1+**SynEdit** is a [[Syntax highlighting|syntax highlighting]] edit/memo package available on the [[SynEdit tab]] with support for many languages/syntaxes.
2+
3+The 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.
4+
5+Lazarus package contains a source editor component named [[TSynEdit]], several syntax highlighters and other components used for source editing.
6+
7+Licensed under the same terms as the original SynEdit (MPL or GPL).
8+
9+## Original vs Lazarus version
10+
11+Lazarus version is maintained mostly by Martin Friebe. Martin wrote at forum, what has been added to Lazarus version since fork appeared:
12+
13+Big things added to the Lazarus version:
14+
15+- folding
16+- configurable gutter / gutter-parts
17+- shared text between several editors
18+- utf-8 support
19+- sync-editing plugin
20+- basic RTL/LTR support
21+- mouse config via MouseActions
22+- rewrite of various highlight/markup modules
23+
24+Codebases of Delphi/Lazarus versions were independently refactored. There is very few overlap left.
25+
26+## SynEdit 2.0 port
27+
28+An 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.
29+
30+- [[SynEdit/port]]
31+- [Github page](https://github.com/rnapoles/)
32+
33+## SynEdit in the IDE
34+
35+The 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.
36+
37+## Using SynEdit
38+
39+### Highlighters
40+
41+- There are several standard highlighters (see [[SynEdit tab]] in the [[Component Palette]])
42+- There are scriptable highlighters, which can adapt to many other file formats:
43+ - [[TSynAnySyn]] (standard, on component palette by default)
44+ - [[TSynPositionHighlighter]] (standard, **not** on component palette)
45+ - [[TSynUniHighlighter]] (standard, **not** on component palette)
46+ - SynFacilSyn ([Github](https://github.com/t-edson/SynFacilSyn))
47+- 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).
48+- You can write new highlighter, see info at [[SynEdit Highlighter]].
49+
50+### Markup
51+
52+Markup provides additional coloring [[SynEdit Markup]]
53+
54+### Edit an existing highlighter
55+
56+Sometimes 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).
57+
58+Say, what we want to reach is, that our application (lazarus in this case) differs between the three types of comments, which exist in Pascal:
59+
60+``` pascal
61+ (* ansi *)
62+ { bor }
63+ // Slash
64+```
65+
66+This 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.
67+
68+> **Note:** Just in case you break something, I suggest to make some "NEW" and "/NEW"-comments, but you don't have to
69+
70+- First, open the unit "SynHighlighterPas" which should be located in your SynEdit-directory.
71+- As we don't want to cause incompatibilities, we're creating a new type of enumerator which helps us to identify our comment later:
72+
73+E.g. under the declaration of "tkTokenKind", write this:
74+
75+``` pascal
76+ {NEW}
77+ TtckCommentKind = (tckAnsi, tckBor, tckSlash);
78+ {/NEW}
79+```
80+
81+- In the declaration of "TSynPasSyn" search for "FTokenID" and add the following between "FTokenID" and the next field
82+
83+``` pascal
84+ {NEW}
85+ FCommentID: TtckCommentKind;
86+ {/NEW}
87+ //This creates a new field, where we can store the information, what kind of comment we have
88+```
89+
90+- In the declaration of "TSynPasSyn" search for "fCommentAttri" and add the following between "fCommentAttri" and the next field
91+
92+``` pascal
93+ {NEW}
94+ fCommentAttri_Ansi: TSynHighlighterAttributes;
95+ fCommentAttri_Bor: TSynHighlighterAttributes;
96+ fCommentAttri_Slash: TSynHighlighterAttributes;
97+ {/NEW}
98+ //This allows us, to return different Attributes, per type of comment
99+```
100+
101+- Next, search for the constructor-definition of "TSynPasSyn", which should be "constructor TSynPasSyn.Create(AOwner: TComponent);"
102+- We need to Create our new Attributes, thus we add our Attributes somewhere in the constructor (I suggest, after the default "fCommentAttri")
103+
104+``` pascal
105+ (...)
106+ AddAttribute(fCommentAttri);
107+ {NEW}
108+ fCommentAttri_Ansi := TSynHighlighterAttributes.Create(SYNS_AttrComment+'_Ansi', SYNS_XML_AttrComment+'_Ansi'); //The last two strings are the Caption and the stored name
109+ //If you want to have default settings for your attribute, you can e.g. add this:
110+ //fCommentAttri_Ansi.Background := clBlack; //Would set "Background" to "clBlack" as default
111+ AddAttribute(fCommentAttri_Ansi);
112+ fCommentAttri_Bor := TSynHighlighterAttributes.Create(SYNS_AttrComment+'_Bor', SYNS_XML_AttrComment+'_Bor');
113+ AddAttribute(fCommentAttri_Bor);
114+ fCommentAttri_Slash := TSynHighlighterAttributes.Create(SYNS_AttrComment+'_Slash', SYNS_XML_AttrComment+'_Slash');
115+ AddAttribute(fCommentAttri_Slash);
116+ {/NEW}
117+ (...)
118+```
119+
120+- 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:)
121+
122+``` pascal
123+procedure TSynPasSyn.BorProc;
124+(...)
125+ fTokenID := tkComment;
126+ {NEW}
127+ FCommentID:=tckBor;
128+ {/NEW}
129+ if rsIDEDirective in fRange then
130+(...)
131+```
132+
133+``` pascal
134+procedure TSynPasSyn.AnsiProc;
135+begin
136+ fTokenID := tkComment;
137+ {NEW}
138+ FCommentID:=tckAnsi;
139+ {/NEW}
140+(...)
141+```
142+
143+``` pascal
144+procedure TSynPasSyn.RoundOpenProc;
145+(...)
146+ fTokenID := tkComment;
147+ {NEW}
148+ FCommentID:=tckAnsi;
149+ {/NEW}
150+ fStringLen := 2; // length of "(*"
151+(...)
152+```
153+
154+``` pascal
155+procedure TSynPasSyn.SlashProc;
156+begin
157+ if fLine[Run+1] = '/' then begin
158+ fTokenID := tkComment;
159+ {NEW}
160+ FCommentID:=tckSlash;
161+ {/NEW}
162+ if FAtLineStart then begin
163+(...)
164+```
165+
166+``` pascal
167+procedure TSynPasSyn.SlashContinueProc;
168+(...)
169+ fTokenID := tkComment;
170+ {NEW}
171+ FCommentID:=tckSlash;
172+ {/NEW}
173+ while not(fLine[Run] in [#0, #10, #13]) do
174+(...)
175+```
176+
177+- Now, we just have to retreve the information when "GetTokenAttribute" is called and return the right Attribute, therefore we edit "GetTokenAttribute" as follows:
178+
179+``` pascal
180+function TSynPasSyn.GetTokenAttribute: TSynHighlighterAttributes;
181+begin
182+ case GetTokenID of
183+ tkAsm: Result := fAsmAttri;
184+ {OLD
185+ tkComment: Result := fCommentAttri; //This is commented and just backup, so it'll be ignored
186+ /OLD}
187+ {NEW}
188+ tkComment: begin
189+ if (FCommentID = tckAnsi) then
190+ Result := fCommentAttri_Ansi //Type is AnsiComment
191+ else
192+ if (FCommentID = tckBor) then
193+ Result := fCommentAttri_Bor //Type is BorComment
194+ else
195+ if (FCommentID = tckSlash) then
196+ Result := fCommentAttri_Slash //Type is SlashComment
197+ else
198+ Result := fCommentAttri //If our code failed somehow, fallback to default
199+ end;
200+ {/NEW}
201+ tkIDEDirective: begin
202+(...)
203+```
204+
205+If you do use lazarus, just reinstall the SynEdit-Package, if not, recompile your project/the package/\<similar\>.
206+
207+**DONE ! No seriously, you are now ready to differ between the different types of comments.**
208+
209+The 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)
210+
211+### Completion plugins
212+
213+There are 3 completion plug-ins for SynEdit:
214+
215+[[TSynCompletion]]
216+
217+- Offers a list of words in a drop-down via a shortcut key combination (default: Ctrl-Space).
218+- Used in the IDE for identifier completion.
219+- Included in examples.
220+- Available on the component palette (since 0.9.3x).
221+
222+Example code to invoke the completion pop up programmatically (i.e. without pressing the keyboard shortcut):
223+
224+``` pascal
225+YourSynEdit.CommandProcessor(YourSynCompletion.ExecCommandID, '', nil)
226+```
227+
228+[[TSynAutoComplete]]
229+
230+- Replaces the current token with a piece of text. **Not** interactive. **No** drop-down.
231+- Included in examples.
232+- Available on the component palette.
233+
234+[[TSynEditAutoComplete]]
235+
236+- Basic template module. **No** drop-down.
237+- Used by IDE for code-templates. IDE contains additional code extending the feature (drop-down and syncro macros are added by IDE).
238+- **Not** included in examples.
239+
240+Todo: Differences between 2nd and 3rd need to be documented. Maybe they can be merged.
241+
242+### Logical/Physical caret position
243+
244+SynEdit offers position of the caret (text blinking cursor) in 2 different forms:
245+
246+- Physical X/Y: Corresponds to visual (canvas) position,
247+- Logical X/Y: Corresponds to byte offset of the text.
248+
249+Both are 1-based. Currently Y coordinates are always the same. This may change in future.
250+
251+The Physical coordinate
252+is the position in the display grid (ignoring any scrolling). That is:
253+
254+the 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.
255+
256+however 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)
257+
258+The Logical coordinate
259+is the byte offset in the string holding the line.
260+
261+the letter "a" has 1 byte and increases by 1
262+
263+the letter "â" has 2 (or 3) bytes, and increases by that
264+
265+tab has 1 byte and increases by that.
266+
267+Neither of the 2 give the position in UTF8 chars/code-points (e.g. for Utf8Copy or Utf8Length).
268+
269+The 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:
270+
271+
272+grid-X-in-visible-part-of-synedit := PhysicalX - SynEdit.LeftChar + 1
273+
274+grid-y-in-visible-part-of-synedit := SynEdit.RowToScreenRow(PhysicalY); // includes folding
275+
276+use ScreenRowToRow for reverse
277+
278+### Change text from code
279+
280+> **Warning:** Changing text via <u>SynEdit.Lines</u> property does <u>not work with undo/redo. </u>
281+
282+Text can be accessed via SynEdit.Lines. This is a TStrings based property offering read/write access to each line. It is 0 based.
283+
284+``` pascal
285+ SynEdit.Lines[0] := 'Text'; // first line
286+```
287+
288+SynEdit.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.
289+
290+To modify the content of a SynEdit, and allow the user to undo the action use the following methods:
291+
292+``` pascal
293+ procedure InsertTextAtCaret(aText: String; aCaretMode: TSynCaretAdjustMode = scamEnd);
294+ property TextBetweenPoints[aStartPoint, aEndPoint: TPoint]: String // Logical Points
295+ read GetTextBetweenPoints write SetTextBetweenPointsSimple;
296+ property TextBetweenPointsEx[aStartPoint, aEndPoint: TPoint; CaretMode: TSynCaretAdjustMode]: String
297+ write SetTextBetweenPointsEx;
298+ procedure SetTextBetweenPoints(aStartPoint, aEndPoint: TPoint;
299+ const AValue: String;
300+ aFlags: TSynEditTextFlags = [];
301+ aCaretMode: TSynCaretAdjustMode = scamIgnore;
302+ aMarksMode: TSynMarksAdjustMode = smaMoveUp;
303+ aSelectionMode: TSynSelectionMode = smNormal );
304+```
305+
306+Examples:
307+
308+``` pascal
309+ // Insert text at caret
310+ SynEdit.InsertTextAtCaret('Text');
311+ // Replace text from (x=2,y=10) to (x=4,y=20) with Str
312+ SynEdit.TextBetweenPoints[Point(2,10), Point(4,20)] := Str;
313+```
314+
315+``` pascal
316+ // Delete/replace single char at caret pos
317+ var
318+ p1, p2: TPoint;
319+ begin
320+ p1 := SynEdit.LogicalCaretXY;
321+ p2 := p1;
322+ // Calculate the byte pos of the next char
323+ p2.x := p2.x + UTF8CharacterLength(@SynEdit.LineText[p2.x]);
324+ // p1 points to the first byte of char to be replaced
325+ // p2 points to the first byte of the char after the last replaceable char
326+ // Replace with "Text" (or use empty string to delete)
327+ SynEdit.TextBetweenPoints[p1, p2] := 'Text';
328+```
329+
330+### Fold/Unfold from code
331+
332+- This is still under construction.
333+- This only works if current highlighter supports folding (details at [[SynEdit Highlighter]]).
334+- 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.
335+- Folding of current selection is also different from folding on keywords.
336+
337+Methods for folding:
338+
339+1\) TSynEdit.CodeFoldAction
340+
341+Folds 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.
342+
343+2\) TSynEdit.FindNextUnfoldedLine
344+
345+3\) TSynEdit.FoldAll / TSynEdit.UnfoldAll
346+
347+### Bookmarks
348+
349+- [Forum topic about adding gutter-marks and colored lines](http://forum.lazarus.freepascal.org/index.php/topic,14948.msg79794.html)
350+
351+### More info
352+
353+Discussions on the forum, which contain info about SynEdit:
354+
355+- [Search/replace; caret; position logical/physical](http://forum.lazarus.freepascal.org/index.php/topic,19520.msg111158.html#msg111158)
356+- [Diff between SynEdit/SynMemo](http://forum.lazarus.freepascal.org/index.php/topic,19645.msg111962.html#msg111962)
357+- [Mouse actions; disable paste on middle click](http://forum.lazarus.freepascal.org/index.php/topic,23997.msg144100.html#msg144100)
358+- [TSynEditMarkup info](http://forum.lazarus.freepascal.org/index.php?topic=24842)
359+
360+### Example apps
361+
362+Example applications can be found in the folder "lazarus/examples/synedit".
363+
364+### Adding hotkeys for Cut/Copy/Paste/etc
365+
366+Hotkeys can be implemented by using SynEdit commands.
367+
368+``` pascal
369+uses
370+ SynEdit, SynEditKeyCmds;
371+
372+procedure TForm1.SynEdit1KeyDown(Sender: TObject; var Key: Word;
373+ Shift: TShiftState);
374+begin
375+ if (Shift = [ssCtrl]) then
376+ case Key of
377+ VK_C: SynEdit1.CommandProcessor(TSynEditorCommand(ecCopy), ' ', nil);
378+ VK_V: SynEdit1.CommandProcessor(TSynEditorCommand(ecPaste), ' ', nil);
379+ VK_X: SynEdit1.CommandProcessor(TSynEditorCommand(ecCut), ' ', nil);
380+ end;
381+end;
382+```
383+
384+## Further development, discussions
385+
386+- RTL (right-to-left): started by Mazen (partly implemented on Windows)
387+- 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.
388+ - 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.
389+ - automatic UTF-8 font selection: Same as above monospace, but also with an UTF-8 font, so that for example umlauts are shown correctly.
390+- 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)
391+- [[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.
392+- [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.
393+- Hooks in SynEdit key/command processing. On the forum: <http://forum.lazarus-ide.org/index.php/topic,35592.msg243316.html#msg243316>
394+
395+## See also
396+
397+- [[SynEdit Highlighter]]
398+- ["Edit an existing highlighter": German forum thread at "lazarusforum.de"](http://www.lazarusforum.de/viewtopic.php?f=5&t=8723)
399+- [[ATSynEdit]]
400+
401+---
402+*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).*
403+Category: Components
404+Category: Lazarus
405+Category: SynEdit

Back to history