Sign in Join
Wiki copytemporary

Revision 3 of 3, by WikiArchive on 11-9-2026. The page as it is now.

SynEdit

SynEdit is a syntax highlighting edit/memo package available on the SynEdit tab with support for many languages/syntaxes.

The SynEdit contained in Lazarus was forked from SynEdit 1.0.3, and was adapted and extended quite a lot. Changes are listed below.

Lazarus package contains a source editor component named TSynEdit, several syntax highlighters and other components used for source editing.

Licensed under the same terms as the original SynEdit (MPL or GPL).

Original vs Lazarus version

Lazarus version is maintained mostly by Martin Friebe. Martin wrote at forum, what has been added to Lazarus version since fork appeared:

Big things added to the Lazarus version:

Codebases of Delphi/Lazarus versions were independently refactored. There is very few overlap left.

SynEdit 2.0 port

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.

SynEdit in the IDE

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.

Using SynEdit

Highlighters

Markup

Markup provides additional coloring SynEdit Markup

Edit an existing highlighter

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).

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:

pascal
(* ansi *)
      { bor }
      // Slash

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.

https://upload.wikimedia.org/wikipedia/commons/d/d8/Nuvola_apps_ktip.png Note: Just in case you break something, I suggest to make some "NEW" and "/NEW"-comments, but you don't have to

E.g. under the declaration of "tkTokenKind", write this:

pascal
{NEW}
      TtckCommentKind = (tckAnsi, tckBor, tckSlash);
      {/NEW}
pascal
{NEW}
      FCommentID: TtckCommentKind;
      {/NEW}
      //This creates a new field, where we can store the information, what kind of comment we have
pascal
{NEW}
      fCommentAttri_Ansi: TSynHighlighterAttributes;
      fCommentAttri_Bor: TSynHighlighterAttributes;
      fCommentAttri_Slash: TSynHighlighterAttributes;
      {/NEW}
      //This allows us, to return different Attributes, per type of comment
pascal
(...)
      AddAttribute(fCommentAttri);
      {NEW}
      fCommentAttri_Ansi := TSynHighlighterAttributes.Create(SYNS_AttrComment+'_Ansi', SYNS_XML_AttrComment+'_Ansi'); //The last two strings are the Caption and the stored name
      //If you want to have default settings for your attribute, you can e.g. add this:
      //fCommentAttri_Ansi.Background := clBlack; //Would set "Background" to "clBlack" as default
      AddAttribute(fCommentAttri_Ansi);
      fCommentAttri_Bor := TSynHighlighterAttributes.Create(SYNS_AttrComment+'_Bor', SYNS_XML_AttrComment+'_Bor');
      AddAttribute(fCommentAttri_Bor);
      fCommentAttri_Slash := TSynHighlighterAttributes.Create(SYNS_AttrComment+'_Slash', SYNS_XML_AttrComment+'_Slash');
      AddAttribute(fCommentAttri_Slash);
      {/NEW}
      (...)
pascal
procedure TSynPasSyn.BorProc;
    (...)
      fTokenID := tkComment;
      {NEW}
      FCommentID:=tckBor;
      {/NEW}
      if rsIDEDirective in fRange then
    (...)
pascal
procedure TSynPasSyn.AnsiProc;
    begin
      fTokenID := tkComment;
      {NEW}
      FCommentID:=tckAnsi;
      {/NEW}
    (...)
pascal
procedure TSynPasSyn.RoundOpenProc;
    (...)
            fTokenID := tkComment;
            {NEW}
            FCommentID:=tckAnsi;
            {/NEW}
            fStringLen := 2; // length of "(*"
    (...)
pascal
procedure TSynPasSyn.SlashProc;
    begin
      if fLine[Run+1] = '/' then begin
        fTokenID := tkComment;
        {NEW}
        FCommentID:=tckSlash;
        {/NEW}
        if FAtLineStart then begin
    (...)
pascal
procedure TSynPasSyn.SlashContinueProc;
    (...)
        fTokenID := tkComment;
        {NEW}
        FCommentID:=tckSlash;
        {/NEW}
        while not(fLine[Run] in [#0, #10, #13]) do
    (...)
pascal
function TSynPasSyn.GetTokenAttribute: TSynHighlighterAttributes;
    begin
      case GetTokenID of
        tkAsm: Result := fAsmAttri;
        {OLD
        tkComment: Result := fCommentAttri; //This is commented and just backup, so it'll be ignored
        /OLD}
        {NEW}
        tkComment: begin
          if (FCommentID = tckAnsi) then
            Result := fCommentAttri_Ansi //Type is AnsiComment
          else
            if (FCommentID = tckBor) then
              Result := fCommentAttri_Bor //Type is BorComment
            else
              if (FCommentID = tckSlash) then 
                Result := fCommentAttri_Slash //Type is SlashComment
              else
                Result := fCommentAttri //If our code failed somehow, fallback to default
        end;
        {/NEW}
        tkIDEDirective: begin
    (...)

If you do use lazarus, just reinstall the SynEdit-Package, if not, recompile your project/the package/<similar>.

DONE ! No seriously, you are now ready to differ between the different types of comments.

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)

Completion plugins

There are 3 completion plug-ins for SynEdit:

TSynCompletion

Example code to invoke the completion pop up programmatically (i.e. without pressing the keyboard shortcut):

pascal
YourSynEdit.CommandProcessor(YourSynCompletion.ExecCommandID, '', nil)

TSynAutoComplete

TSynEditAutoComplete

Todo: Differences between 2nd and 3rd need to be documented. Maybe they can be merged.

Logical, Physical or Viewed caret position

SynEdit offers position of the caret (text blinking cursor) in different forms:

All are 1-based.

The Logical coordinate

the letter "a" has 1 byte and increases by 1

the letter "â" has 2 (or 3) bytes, and increases by that

tab has 1 byte and increases by that.

each and all lines count as one. Lines on/off screen count, Hidden/Folded lines count, wrapped lines count as one single line.

The Physical coordinate

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.

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)

each and all lines count as one. Lines on/off screen count, Hidden/Folded lines count, wrapped lines count as one single line.

same as Logical

The Viewed coordinate

is the position in the display grid (ignoring any scrolling). That is:

cell coordinate, same as Physical. But wrapped.

paintable lines: includes on/off screen lines

excludes folded lines

counts a wrapped line as multiple lines

Neither of them give the position in UTF8 chars/code-points (e.g. for Utf8Copy or Utf8Length).

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:

grid-X-in-visible-part-of-synedit := PhysicalX - SynEdit.LeftChar + 1

grid-y-in-visible-part-of-synedit := SynEdit.RowToScreenRow(PhysicalY); // includes folding

use ScreenRowToRow for reverse

Change text from code

Warning-icon.png

Warning: Changing text via SynEdit.Lines property does not work with undo/redo.

Text can be accessed via SynEdit.Lines. This is a TStrings based property offering read/write access to each line. It is 0 based.

pascal
SynEdit.Lines[0] := 'Text'; // first line

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.

To modify the content of a SynEdit, and allow the user to undo the action use the following methods:

pascal
procedure InsertTextAtCaret(aText: String; aCaretMode: TSynCaretAdjustMode = scamEnd);
        property TextBetweenPoints[aStartPoint, aEndPoint: TPoint]: String // Logical Points
          read GetTextBetweenPoints write SetTextBetweenPointsSimple;
        property TextBetweenPointsEx[aStartPoint, aEndPoint: TPoint; CaretMode: TSynCaretAdjustMode]: String
          write SetTextBetweenPointsEx;
        procedure SetTextBetweenPoints(aStartPoint, aEndPoint: TPoint;
                                       const AValue: String;
                                       aFlags: TSynEditTextFlags = [];
                                       aCaretMode: TSynCaretAdjustMode = scamIgnore;
                                       aMarksMode: TSynMarksAdjustMode = smaMoveUp;
                                       aSelectionMode: TSynSelectionMode = smNormal );

Examples:

pascal
// Insert text at caret
      SynEdit.InsertTextAtCaret('Text');
      // Replace text from (x=2,y=10) to (x=4,y=20) with Str
      SynEdit.TextBetweenPoints[Point(2,10), Point(4,20)] := Str;
pascal
// Delete/replace single char at caret pos
      var
        p1, p2: TPoint;
      begin
        p1 := SynEdit.LogicalCaretXY;
        p2 := p1;
        // Calculate the byte pos of the next char 
        p2.x := p2.x + UTF8CharacterLength(@SynEdit.LineText[p2.x]);
        // p1 points to the first byte of char to be replaced
        // p2 points to the first byte of the char after the last replaceable char
        // Replace with "Text" (or use empty string to delete)
        SynEdit.TextBetweenPoints[p1, p2] := 'Text';

Fold/Unfold from code

Methods for folding:

  1. TSynEdit.CodeFoldAction

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.

  1. TSynEdit.FindNextUnfoldedLine
  2. TSynEdit.FoldAll / TSynEdit.UnfoldAll

Bookmarks

More info

Discussions on the forum, which contain info about SynEdit:

Example apps

Example applications can be found in the folder "lazarus/examples/synedit".

Adding hotkeys for Cut/Copy/Paste/etc

Hotkeys can be implemented by using SynEdit commands.

pascal
uses
      SynEdit, SynEditKeyCmds;
    
    procedure TForm1.SynEdit1KeyDown(Sender: TObject; var Key: Word;
      Shift: TShiftState);
    begin
      if (Shift = [ssCtrl]) then
        case Key of
          VK_C: SynEdit1.CommandProcessor(TSynEditorCommand(ecCopy), ' ', nil);
          VK_V: SynEdit1.CommandProcessor(TSynEditorCommand(ecPaste), ' ', nil);
          VK_X: SynEdit1.CommandProcessor(TSynEditorCommand(ecCut), ' ', nil);
        end;
    end;

Further development, discussions

See also


From the Free Pascal wiki, page "SynEdit", as the Internet Archive kept it on 2025-06-16, last changed on 2025-02-06. 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.