Sign in Join
Wiki copytemporary

ATSynEdit

About

ATSynEdit is the multi-line editor control.

Author: Alexey Torgashin

License

ATSynEdit files licensed under MPL 2.0. You can optionally use them under LGPL if your project needs it.

EControl files licensed under special license (readme.txt), usage allowed only in open-source projects.

Keyboard shortcuts

Run demo_editor and call menu item "Help - Commands'. You'll see all shortcuts in a dialog like this:

Mouse shortcuts

Multi-carets:

Select:

Clicks:

Multi-carets

Multi-carets are several carets at once. All carets work together for many editing commands: caret moving, text typing, deleting, selection with keyboard. See "Mouse shortcuts", how to add/remove carets.

Animation:

Multi-selections

If you add caret with Ctrl+click, caret has no selection. If you add caret with Ctrl+drag, caret will have selection. You can add selections to carets later, by Shift+arrows, Shift+Home, Shift+End etc.

Multi-selections are handled specially on copy/paste. If you copy selections, then move carets, then paste, paste will insert clipboard lines into carets: line-1 at caret-1, line-2 at caret-2 etc (only if carets count equals to lines count in clipboard, otherwise result is different).

Animation shows this:

Clipboard commands with selections

Clipboard-related commands work with carets, both with selections and without them. Some details about this:

Command

Behaviour, when there're no selections

Behaviour, when at last one selection present

Copy to clipboard

Copies entire lines, containing carets.

                   (Ignores multiple carets on same line.)

Copies only selections text.

                   (Ignores carets without selections.)

Cut to clipboard

Similarly to "Copy" w/o selections.

Similarly to "Copy" with selections.

Paste from clipboard

First, selections are cleared (deleted). Then, command pastes text into each caret position. Special case is when clipboard lines count equals to carets count - in this case, first line is inserted at first caret, second line is inserted at 2nd caret, etc.

Delete char

Deletes one char at each caret position.

Deletes only selections text. (Ignores carets without selections.)

Help topics

Compilation errors

IME error on GTK2.

You may get this error:

atsynedit_adapter_gtk2ime.pas(131,5) Error: Identifier not found "IM_Context_Set_Cursor_Pos"

To fix it, edit the file "atsynedit/atsynedit_package.lpk" and remove this block there:

xml
      <Other>
        <CustomOptions Value="-dGTK2_IME_CODE"/>
        <OtherDefines Count="1">
          <Define0 Value="GTK2_IME_CODE"/>
        </OtherDefines>
      </Other>

Alternative solution when symbol "IM_Context_Set_Cursor_Pos" cannot be found: you need to open IDE dialog "Tools / Configure 'Build Lazarus'", and there enable the define: WITH_GTK2_IM; then recompile the IDE.

What limitations it has?

What features it has?

Difference from other components

Does it need to be installed?

It may be installed or not.

What parts does it have?

Strings object

type TATStrings in unit atstrings. Storage of text lines. It includes managing of Undo/Redo too.

Methods:

To get/set lines, use properties:

Auto-adjustments of other objects when lines are added/deleted:

Strings.Bookmarks object

type TATBookmarks in unit atsynedit_bookmarks. Holds list of bookmarks of type TATBookmarkItem. Each item has props:

Kind specifies background color and gutter icon for bookmarks of this kind. Kind value 0 must not be used (previously value 0 meaned "no bookmark"), value 1 should be used for "usual" bookmarks, value>1 should be used for custom application bookmarks. ATSynEdit has 2 events: to paint bookmark icon (from its kind), and to get color of bookmark (from its kind).

Bookmarks object is always sorted by line index. Duplicate bookmarks (for the same line index) are auto removed. To change bookmarks, use methods Add, Delete, DeleteForLine, Clear. Also use method Find.

Strings.Bookmarks2 object

Same type as Strings.Bookmarks object. This list holds "background bookmarks", which have lower paint priority than usual bookmarks. So if a line has usual bookmark - it is painted, and if not - background bookmark is searched for this line and painted. Bookmarks2 allows to colorize entire lines, but not to interfere with usual bookmarks - it is "passive coloring". Additionally, marks in Bookmarks2 don't have gutter icon, and mouse-over tooltip. Marks in Bookmarks/Bookmarks2 use the same Kind values space.

Carets object

type TATCarets in unit atsynedit_carets. Holds list of items of type TATCaretItem. Each caret item has properties:

For forward sel, pair (PosY, PosX) is after pair (EndY, EndX); for backward sel the order is reversed. So if you want to get pos of selection start (e.g. to delete this selection), get minimal pair.

Carets object is sorted by text position, method Sort does this. Duplicate carets (they appear during work with multi-carets) are auto removed. If you try to add overlapping caret selections, they will be auto merged to one caret. To add/delete carets, use methods Add, Clear. Also ATSynEdit has methods DoCaretNNNNN, eg DoCaretSingle makes single caret.

CaretsSel object

type TATCaretSelections in unit atsynedit_carets. It is helping object, internally it holds array of carets coordinates, sorted by text position, but only for carets with selection (carets w/o selection are not included). It is read-only, ie it's needed only to find some info about selections, not to store it. Finding some info about selections is much faster with CaretsSel object, though you can use Carets object too (slower). It uses binary search.

Note: object is updated (from contents of Carets) in Paint method, not immediately after you add/change something in Carets.

Keymap object

type TATKeymap in unit atsynedit_keymap.

Holds list of keyboard actions of type TATKeymapItem. It's inited at start. And to init items at start, two "filling" procedures exist in code: one for TATSynEdit, and simpler one for TATEdit (single line version, less commands). And you can add more keymap items or change hotkeys for any keymap item.

Each keymap item has 2 (yet) hotkeys, second is needed for e.g.: Copy-to-clipboard (Ctrl+C and Ctrl+Ins), Paste-from-clipbd (Ctrl+V and Shift+Ins) etc.

When adding keymap item, you can set simple hotkeys (e.g. "Alt+Shift+F") or key combos (several simple hotkeys which must be pressed in order). Example adds commands with simple hotkey and combo:

pascal
  var 
    M: TATKeymap;
  const 
    cmd_My1 = 3000; 
    cmd_My2 = 3001;
  begin
    M.Add(cmd_My1, 'cmd with simple hotkey', ['Ctrl+Shift+F'], []);
    M.Add(cmd_My2, 'cmd with 3 keys combo', ['Ctrl+B', 'Ctrl+B', 'Ctrl+M'], []);

After you added new keymap items, add reaction for them: use OnCommand event as usual.

WrapInfo object

type TATWrapInfo in unit atsynedit_wrapinfo. Holds list of wrap items, each is info about wrapped part of line or about entire line:

One wrap item renders on control as one short line. It is part of wrapped line or entire line. Also used for wrap-mode off, this is simple case - one wrap item per one line. Wrap items exist only for lines not hidden by folding feature (with Strings.LinesHidden[i]=true).

WrapInfo has weird property VirtualMode. This prop is set if a) no fold ranges created (ie no lexer is active, or lexer didn't create fold ranges), b) word wrapping is off. VirtualMode means that WrapInfo don't store any data in internal TList, it is empty and fast, it only reads props of Strings and returns them. Indeed, when no fold ranges, and word wrapping off, all items of WrapInfo must be simple "mirror" of Strings items props (and count is the same).

VirtualMode makes loading of huge logs (100-500Mb) ~1.5 times faster.

Undo object

Holds list of actions for Undo/Redo. You must not touch it. Filled auto by changing-adding-deleting of lines in Strings object (by any API). Also holds end-of-lines markers (changed ends-of-lines can undo too).

Items in Undo object have 'group marker'. This means that some editor commands set this marker, and items after marked item will undo as a group (if option "Group undo" on). You can set group marker (for next editor action) by method SetGroupMark of Strings object.

Gutter object

type TATGutter in unit atsynedit_gutter. Holds list of gutter items. Each gutter item has properties:

To know which band of gutter does what, use properties of ATSynEdit: GutterBandNum (index of band for line numbers), GutterBandFold (index of band for folding) etc. Example shows/hides gutter column:

pascal
  ed.Gutter[ed.GutterBandNum].Visible:= checkGutterNum.Checked;
  ed.Gutter.Update;
  ed.Update;

GutterDecor object

type TATGutterDecor in unit atsynedit_gutter_decor. Holds list of items, which are painted over gutter, for decoration purpose. Items can hold short text strings (e.g. normal/square/curly brackets to show pair brackets in text), or icon from TImageList. If item has not empty Text, then text is shown, otherwise icon is shown. Each item has props:

Items in this object are auto adjusted when text is edited. But note: for this to work, you must assign GutterDecor object to Strings object prop GutterDecor1 or GutterDecor2.

Duplicate items (for the same line index) are auto removed. TATSynEdit.GutterBandDecor sets index of gutter band for decorations. If -1, then gutter band for bookmarks is used.

Fold object

type TATSynRanges in unit atsynedit_ranges. Holds list of ranges which can be folded and show plus/minus on gutter. Each range is of type TATSynRange, it has properties:

Example adds few ranges to text:

pascal
  Edit1.Fold.Clear;
  Edit1.Fold.Add(1, 4, 15, false, ''); //line 4 to 15, range1
  Edit1.Fold.Add(1, 5, 9, false, ''); //line 5 to 9, nested into range1
  Edit1.Fold.Add(1, 7, 8, false, ''); //line 7 to 8, nested into range2

Markers object

type TATMarkers in unit atsynedit_markers. Holds list of marker items. Each marker item has properties:

Markers are painted as red triangles below their positions (PosX, PosY). They are needed e.g. for making snippets, where user needs to jump over snippet's insert points and wants to see these points. Also it's good for IDE to have commands like "drop marker here", "remove last marker", "swap caret and last marker" etc (like CodeRush).

Example code from CudaText:

pascal
procedure EditorMarkerGotoLast(Ed: TATSynEdit; AndDelete: boolean);
var
  Caret: TATCaretItem;
  Mark: TATMarkerItem;
begin
  if Ed.Carets.Count<>1 then exit;
  if Ed.Markers.Count=0 then exit;
  Caret:= Ed.Carets[0];
  Mark:= Ed.Markers[Ed.Markers.Count-1];
  Caret.PosX:= Mark.PosX;
  Caret.PosY:= Mark.PosY;
  if AndDelete then
    Ed.Markers.Delete(Ed.Markers.Count-1);
  Ed.DoGotoCaret(cEdgeTop);
  Ed.Update;
end;

Attribs object

type TATMarkers (same as for Markers). Holds list of additional color/font attribute items, these items are added to syntax hiliting and selection hiliting. Each item has properties:

Attribs object is used in CudaText: app adds attr items to hilite brackets, to hilite misspelled words, etc.

If you add attrib item, create new object of type TATLinePartClass, fill its Data, and pass object to TATMarkers.Add. This object is freed automatically if attrib item deleted or all attribs cleared.

Colors object

Contains color properties of type TColor. For ex, Colors.TextFont is text font color, Colors.TextBG is text background color.

Gaps object

type TATGaps in unit atsynedit_gaps. Holds list of items TATGapItem. Each item has properties:

Gap item paints as rectangle (it is owner-drawn), just between lines N and N+1, not overlapping text. When user adds/deletes lines, gaps auto-shift with their lines; gap is deleted if its line is deleted.

Use cases:

To add/remove gaps:

pascal
  //to add
  ed.Gaps.Add(NLine, NGapHeight, FSomeBitmap, NGapTag);
  ed.Update;
  //to delete this gap
  ed.Gaps.DeleteForLineRange(NLine, NLine);
  ed.Update;

Event OnDrawGap is used to paint gaps, it gets canvas, and rectangle on canvas. Note: if you add gap to the last line, you must turn on option OptLastLineOnTop, better don't turn it off later (to not scroll, when gap added/removed).

DimRanges object

type TATDimRanges in unit atsynedit_dimranges. Holds list of ranges which shade/dim/fade text font color, ie blend font color with background color. Each range is of type TATDimRange, it has properties:

To dim all text except current paragraph, you must add 2 ranges: before/after paragraph, then correct line indexes in them.

There is no checking for line indexes, ranges can have any indexes, but overlapping indexes not good (first found range will give dim value).

Example adds 2 ranges and changes them later:

pascal
  R1:= Ed.DimRanges.Add(0, 6, 100{dim value});
  R2:= Ed.DimRanges.Add(20, MaxInt, 100{dim value});
  Ed.Update;
  //change ranges
  R1.LineTo:= 10;
  R2.LineFrom:= 30;
  Ed.Update;

Hotspots object

type TATHotspots in unit atsynedit_hotspots. Holds list of ranges, which need special tooltip on mouse-over. Each range is of type TATHotspotItem, it has props:

You can add hotspots via Add method, and find them later by Tag, by TagString, by text position - methods FindByPos, FindByTag, FindByTagString. To support mouse-over for these ranges, two events exist: OnHotspotEnter, OnHotspotExit.

Example of usage: in HTML file, app can find all HTML color tokens #rrggbb, add hotspots for them, and store color value to Tag. Then on mouse-over app can show colored tooltip.

Micromap object

type TATMicromap in unit atsynedit_micromap. Holds list of micromap's columns (by default only one column is added, and CudaText adds 2nd column to paint marks from plugins). The micromap is shown by ATSynEdit property OptMicromapVisible and must be painted by the app via OnDrawMicromap event. Micromap object has Columns property, "array of TATMicromapColumn".

Type TATMicromapColumn is record with the fields:

Micromap object holds Columns prop, and several methods to add/delete columns: ColumnAdd, ColumnDelete, and method ColumnFromTag to find columns by int tag. Application can paint anything on micromap, but it's recommented to paint small marks per editor lines. To get coordinates of such marks, use ATSynEdit method RectMicromapMark, it does calculations to get coords for all editor lines, to fill the entire micromap.

What are the basic methods/properties?

Objects

Files

States

More

Caret

Selection

(P.Y is line index, P.X is char offset in line, 0-based)

View

Options

I cannot post here options. See ATSynEdit properties beginning with "Opt", these are props which can change in design time in IDE. You can place on form ATSynEdit and see in IDE its "Opt" properties and see their values.

Encodings

Basic example how to use?

pascal
    procedure TForm1.FormCreate(Sender: TObject);
    begin
      ed:= TATSynEdit.Create(Self);
      ed.Parent:= Self;
      ed.Align:= alClient;
      ed.Font.Name:= 'Courier New';
      ed.OptUnprintedVisible:= false;
      ed.OptRulerVisible:= false;
      ed.LoadFromFile(ExtractFilePath(Application.ExeName)+'unit1.pas');
    end;

How does it manage the coordinates?

Carets object contains list of caret items. Each caret item, e.g. Carets[0], has properties:

Strings object holds UnicodeString's, where each WideChar gives offset=1, and even Tab char gives offset=1. Component auto-calculates properties CoordX/CoordY for all carets, screen coordinates. Don't touch CoordX/CoordY, change only PosX/PosY and EndX/EndY.

Text position to/from screen coordinates

Methods allow to convert text position to/from control-relative coordinates (in pixels). Layout of gutter, ruler and other UI elements are already considered by methods.

pascal
    function CaretPosToClientPos(P: TPoint): TPoint;
    function ClientPosToCaretPos(P: TPoint;
      out ADetails: TATPosDetails;
      AGapCoordAction: TATGapCoordAction=cGapCoordToLineEnd): TPoint;

Functions give Result.Y<0 if cannot calculate pos. For example, if line index is too big or line with this index is fully folded.

Property OptCaretVirtual exists. If it's True, caret is allowed after end-of-line. Methods consider it.

Text posistion to/from column index

To convert text coordinate X to/from column index (considering Tab chars), ATSynEdit has TabHelper object and methods of Strings object. Here is example, code which converts X of first caret to its column index:

pascal
    var
      ed: TATSynEdit;
      caret: TATCaretItem;
    //...

    caret:= ed.Carets[0];
    if ed.Strings.IsIndexValid(caret.PosY) then
    begin
      //optimized for huge lines
      ColumnIndex:= ed.Strings.CharPosToColumnPos(caret.PosY, caret.PosX, ed.TabHelper)+1;
    end;

How to manage the caret, the selection?

See topic "Carets object". To change position of carets, get Carets property of editor, get each item Carets[N], and change caret items' properties PosX/PosY and/or EndX/EndY, then call editor's method Update. To change selection for any caret (each caret has own selection): get caret item, and change its propeties EndX/EndY (set both to -1 to remove selection), then call editor's method Update.

Better don't do overlapping selections (when e.g. selection of caret-2 overlaps selection of caret-3), but even here you can call Carets.Sort, which should sort carets by PosX/PosY and fix them.

You can delete and add caret items.

pascal
  var
    Caret: TATCaretItem;
    i: integer;
  begin
    for i:= 0 to edit.Carets.Count-1 do
    begin
      Caret:= edit.Carets[i];
      {
      change here Caret.PosX and Caret.PosY
      }
    end;
    edit.Update;    
  end;

How to modify text?

See topic above about "Strings object". To change text, get Strings property of editor. "Strings.Count" is number of lines in object. Then access methods of Strings: LineAdd, LineInsert, LineDelete. To get/set lines by index, use properties of Strings: Strings.Lines[i], etc, listed above.

To do advanced changes, use TATStrings.Text* methods:

pascal
    procedure TextInsert(AX, AY: integer; const AText: atString; AOverwrite: boolean;
      out AShift, APosAfter: TPoint);
    procedure TextAppend(const AText: atString; out AShift, APosAfter: TPoint);
    procedure TextInsertColumnBlock(AX, AY: integer; ABlock: TATStrings;
      AOverwrite: boolean);
    procedure TextDeleteLeft(AX, AY: integer; ALen: integer; out AShift, APosAfter: TPoint);
    procedure TextDeleteRight(AX, AY: integer; ALen: integer; out AShift,
      APosAfter: TPoint; ACanDelEol: boolean=true);
    function TextDeleteRange(AFromX, AFromY, AToX, AToY: integer; out AShift, APosAfter: TPoint): boolean;
    procedure TextInsertEol(AX, AY: integer; AKeepCaret: boolean;
      const AStrIndent: atString; out AShift, APosAfter: TPoint);
    procedure TextDeleteLine(AX, AY: integer; out AShift, APosAfter: TPoint);
    procedure TextReplaceInOneLine(AY, AX1, AX2: integer; const AText: atString);
    procedure TextReplaceRange(AFromX, AFromY, AToX, AToY: integer;
      const AText: atString; out AShift, APosAfter: TPoint);
    function TextReplaceLines_UTF8(ALineFrom, ALineTo: integer; ANewLines: TStringList): boolean;
    function TextSubstring(AX1, AY1, AX2, AY2: integer; const AEolString: string = #10): atString;

Strings.Lines[i] do not contain end-of-line chars. Usually not needed to change end-of-line chars. You can change them using Strings.LinesEnds[i].

How to highlight syntax?

There're 2 ways:

You can set handler of OnCalcHilite event. Adapter is different way to do the same. Instead of using event, you write code in the new class which is child of TATAdapterHilite. Then you link adapter to editor:

pascal
  Adapter.AddEditor(editor1);
  Adapter.AddEditor(editor2); //if 2 editors share the same text buffer

To unlink adapter from all editors, call:

pascal
  Adapter.AddEditor(nil);

If adapter assigned, editor will call adapter's methods (first called adapter, next OnCalcHilite).

See file "atsynedit_adapters.pas". It has base class for such adapters. Main method in adapter, which is called by ATSynEdit is:

pascal
  procedure OnEditorCalcHilite(Sender: TObject;
      var AParts: TATLineParts;
      ALineIndex, ACharIndex, ALineLen: integer;
      var AColorAfterEol: TColor);

Parameters here:

Real string which you must hilite: see example project how to get it.

How to set same text-source to several editors?

Several editors can have same text source, text source is ATStrings object which holds

Each editor holds its own WrapInfo object so wrapped states are separate. Each editor holds its own Carets object, so carets/selections are separate.

Tech details: items in ATStrings object have fields:

pascal
    ItemHidden: packed array[0..cMaxStringsClients-1] of ByteBool;
      // this line is fully hidden
    ItemFoldFrom: packed array[0..cMaxStringsClients-1] of byte;
      //  0: line not folded
      // >0: line folded from this char-pos

(constant can be changed in source). These fields allow to hold separate fold-info for each "editor client" of ATStrings. Each client must have it's own EditorIndex (0 to cMaxStringsClients-1). Example, make 4 editors ed0..ed3.

If user changes text in ed3, ed3 will update but others will not: you need to force it:

How to add/change commands?

To change action of internal commands (file atsynedit_commands.pas) you need to use event OnCommand which can do any action for any command (and return AHandled=True to disable default code). Better use OnCommand handler for all commands, even new ones. So you can call DoCommand() and have one handler to do anything.

Example func which adds new commands to Keymap object:

pascal
const
  cmd_FileNew = 2500;
  ...

procedure InitKeymapApp(M: TATKeymap);
begin
  M.Add(cmd_FileNew, 'file: new file', ['Ctrl+N'], []);
  M.Add(cmd_FileOpen, 'file: open file', ['Ctrl+O'], []);
  M.Add(cmd_FileSave, 'file: save file', ['Ctrl+S'], []);
  ...
end;

Example handler for new commands: some close editor tabs (special case: cannot close tabs just inside OnCommand) and some commands copy something to clipboard:

pascal
procedure TfmMain.EditorOnCommand(Sender: TObject; Cmd: integer; var Handled: boolean);
begin
  Handled:= true; //by default command marked as handled
  case Cmd of
    cmd_FileNew,
    cmd_FileOpen,
    cmd_FileClose,
    cmd_FileCloseAll,
    cmd_FileReopen:
      begin
        //these commands close tabs so cannot handle here.
        //we start timer which will handle them.
    TimerCmd.Tag:= Cmd;
    TimerCmd.Enabled:= true;
      end;

    cmd_CopyLine:         DoCopyLine;
    cmd_CopyFilenameFull: DoCopyFilenameFull;
    cmd_CopyFilenameDir:  DoCopyFilenameDir;
    cmd_CopyFilenameName: DoCopyFilenameName;

    else
      Handled:= false; //unknown command: don't mark handled
  end;
end;

How to emulate Edit, Combobox?

Special child of ATSynEdit exists: ATEdit. It's single line control in which all multiline text editing is disabled. And paste can give only single line. And scrollbars are disabled. And many commands (like "Move lines up/down") are removed from Keymap object.

Special child of ATEdit exists: ATComboEdit. It has micromap area which paints dropdown arrow to look like combobox. Click on this arrow gives popup-menu with entered variants.

Variants must be added to history by user code, using OnCommand handler: react to command "cCommand_KeyEnter". Example of handler:

pascal
procedure TfmCombo.ComboCommand(Sender: TObject; ACmd: integer;
  var AHandled: boolean);
var
  s: string;
  n: integer;
begin
  if ACmd=cCommand_KeyEnter then
  begin
    with ed do
    begin
      s:= UTF8Encode(Trim(Text));

      Text:= '';
      DoCaretSingle(0, 0);

      n:= Items.IndexOf(s);
      if n>=0 then Items.Delete(n);
      Items.Insert(0, s);
    end;
    AHandled:= true;
  end;
end;

What props are useful for statusbar?

How to customize popup menu?

These properties allow to change all menus in control (you cannot modify default menu):

Prop OptMouse* exists, which allows to show menus on mouse-down or on mouse-up.

How to know indexes of changed lines in OnChange?

You have event Strings.OnLog. It gives parameters:

Get the log, save it, and then parse it during OnChange.

How to export text to HTML?

Unit atsynedit_export_html has the function which does HTML report. It has several parameters to customize output. See example of usual parameters in the demo_editor.

ATSynEdit has API which allows to make report to any format. It's function DoCalcLineHiliteEx. It returns syntax highlight info for any line. Info is stored in TATLineParts struct, see HTML exporter code how to use it.

Short description of events?

What is logic of grouped-undo?

There're kinds of group-marks in undo-items: hard-marks, soft-marks.

Example 1. OptUndoGrouped=true, such undo-items exist:

Undo:

Example 2. OptUndoGrouped=false, undo-items:

Undo:

Soft-marks also used together with hard-marks. Soft-mark is placed with first hard-mark after call Strings.BeginUndoGroup. This allows to undo N groups of edits, if many items hard-marked (w/out gaps).

Example 3. Undo-items:

Undo:

How to record/playback macros?

Macro support must be done on app level, component does support by event OnCommand. Handle OnCommand and you get all command codes which run. Real app adds more commands in Keymap object and it must decide which of them it wants to record (e.g. don't record commands to show dialogs, and record commands which comment/uncomment lines).

To playback macro, call DoCommand for all codes in macro.

Hint. CudaText also adds 3 commands to help with macros:

How to configure modifier-keys with click?

You can configure some of mouse clicks with modifier keys: simple click, Ctrl+click, Shift+click, Ctrl+Shift+click, middle click, etc. Property MouseMap configures it, it's array, type TATMouseActions. Default code configures it in InitMouseActions like this:

pascal
procedure InitMouseActions(var M: TATMouseActions);
//...
begin
  SetLength(M, 0);
  Add(cMouseActionClickSimple, [ssLeft]);
  Add(cMouseActionClickRight, [ssRight]);
  Add(cMouseActionClickAndSelBlock, [ssLeft, ssShift]);
  Add(cMouseActionMakeCaret, [ssLeft, ssXControl]);
  Add(cMouseActionMakeCaretsColumn, [ssLeft, ssXControl, ssShift]);
  Add(cMouseActionNiceScrolling, [ssMiddle]);
  //...

Does it use PrimarySelection on gtk?

It can use it, all methods which work with clipboard, get AClipboardObject:TClipboard, here you can pass Clipboard, PrimarySelection, SecondarySelection.

Command codes for Paste: exist for Clipboard, and for PrimarySelection. This is part which reacts to command codes:

pascal
    cCommand_ClipboardCopy:            Res:= DoCommand_ClipboardCopy(false, Clipboard);
    cCommand_ClipboardCopyAdd:         Res:= DoCommand_ClipboardCopy(true, Clipboard);
    cCommand_ClipboardCut:             Res:= DoCommand_ClipboardCut(Clipboard);

    //use Clipboard:TClipboard
    cCommand_ClipboardPaste:                 Res:= DoCommand_ClipboardPaste(false, false, Clipboard);
    cCommand_ClipboardPaste_Select:          Res:= DoCommand_ClipboardPaste(false, true, Clipboard);
    cCommand_ClipboardPaste_KeepCaret:       Res:= DoCommand_ClipboardPaste(true, false, Clipboard);
    cCommand_ClipboardPaste_Column:          Res:= DoCommand_ClipboardPasteColumnBlock(false, Clipboard);
    cCommand_ClipboardPaste_ColumnKeepCaret: Res:= DoCommand_ClipboardPasteColumnBlock(true, Clipboard);

    //same, but use PrimarySelection:TClipboard
    cCommand_ClipboardAltPaste:                 Res:= DoCommand_ClipboardPaste(false, false, PrimarySelection);
    cCommand_ClipboardAltPaste_Select:          Res:= DoCommand_ClipboardPaste(false, true, PrimarySelection);
    cCommand_ClipboardAltPaste_KeepCaret:       Res:= DoCommand_ClipboardPaste(true, false, PrimarySelection);
    cCommand_ClipboardAltPaste_Column:          Res:= DoCommand_ClipboardPasteColumnBlock(false, PrimarySelection);
    cCommand_ClipboardAltPaste_ColumnKeepCaret: Res:= DoCommand_ClipboardPasteColumnBlock(true, PrimarySelection);

Default code uses PrimarySelection in one place: on copy, it copies to Clipboard, then (under Linux/Mac) copies the same to PrimarySelection.

To make middle-button-click paste from PrimarySelection, you need to handle OnClickMiddle, here's example from CudaText:

pascal
procedure TEditorFrame.EditorOnClickMiddle(Sender: TObject; var AHandled: boolean);
begin
  AHandled:= false;
  if EditorOps.OpMouseMiddleClickPaste then
  begin
    AHandled:= true;
    (Sender as TATSynEdit).DoCommand(cmd_MouseClickAtCursor);
    (Sender as TATSynEdit).DoCommand(cCommand_ClipboardAltPaste); //uses PrimarySelection:TClipboard
    exit;
  end;
end;

How to handle OnCalcLineHilite event

This event has type:

pascal
type
  TATSynEditCalcHiliteEvent = procedure(Sender: TObject; var AParts: TATLineParts;
    ALineIndex, ACharIndex, ALineLen: integer; var AColorAfterEol: TColor) of object;

Handler gets:

Handler must fill:

Handler must handle the situation of line-wrapping inside long comments (or long string constants, etc), when one fragment gives part for ALineIndex=N, and next fragment gives part for ALineIndex=N+1.

Example. Handler gets string with Pascal syntax like "procedure AB(name: word)". It must create such "parts" for this string:

Details about highlighting rendering

Central procedure is DoCalcLineHilite. For items of WrapInfo object, it calculates "line parts" (array TATLineParts of record TATLinePart). Each "part" here is one colored/styled fragment of string. After line parts are calculated, they pass to ATSynEdit_CanvasProc.CanvasTextOut to render.

DoCalcLineHilite works like this:

DoPartCalc_ApplySelectionOver is complex function. It updates TATLineParts. How it works:

To find/insert parts to TATLineParts, funcs DoPartFind/ DoPartInsert are used.

How to control vert/horz scrolling?

Property LineTop is index of top visible line, it is not precise vert scroll position, if long wrapped lines present.

Public prop: WrapInfo, it's list of wrapinfo items. You can read it to get all info about wrapped lines, at which offsets they are wrapped. This allows to make smart jump of caret on wrapped lines.

Public props: ScrollVert, ScrollHorz. They are records, NPos is scroll pos. ScrollVert.NPos is index in WrapInfo list, change it to make precise scrolling (call Update() then). ScrollHorz.NPos is horz scroll pos, now it's the same as prop ColumnLeft.

Method DoScrollByDelta(Dx, Dy) allows to increment/decrement horz (Dx) and vert (Dy) scroll positions.

When Update call is needed?

First param of Update() method is boolean: force updating the WrapInfo object.

Behaviour of column selection

ATSynEdit gives two modes of column selection, which have differences when you select over wrapped lines, or lines with full-width characters. This is controlled by property OptCaretsPrimitiveColumnSelection.

It is not a bug. In this example, user selected column block from column 7 (at line 1) until column 20 (at line 6), so column block takes columns 7...20 from all lines. On first ASCII lines, columns 7...20 take different visual area, than columns on last lines. When you copy/paste that block to another program, block may look differently. But that block contains equal number of chars on each line.

Even more weird look happens when user selects column block over word-wrapped lines.

Here is the code's logic in all these cases (with full-width characters and with word-wrapped lines). Code calculates (line1, column1) text position of column block left-top edge. Then code calculates (line2, column2) text position of column block right-bottom edge. Then code selects characters in range column1...column2 in all those affected lines line1...line2. And this code logic produces so weird look in word-wrapped mode.

Horizontal scrollbar only increases but never shrinks?

Horizontal scrollbar has unusual behaviour: its width only increases (when long lines appear in the visible area). It never shrinks when we scroll to shorter lines. It was made as a workaround to GTK2 widgetset freezes - it freezes when scrollbar shows/hides/shows/hides/etc on scrolling.

You can try to disable this workaround in file atsynedit_defines.inc:

pascal
{$define fix_horzscroll} //workaround for gtk2 widgetset unstable: it freezes app
                         //when horz-scroll hides/shows/hides/...
                         //ok also for win32

more

Auto-completion lists

General auto-completion API

You need to install package ATSynEdit_Cmp, from repo https://github.com/Alexey-T/ATSynEdit_Cmp .

Unit atsynedit_cmp_form (from ATSynEdit_Cmp package) has the function to show completion listbox in the editor. This listbox is not modal, it only emulates modality of some degree. It keeps editor caret blinking. While completion listbox is shown, editor doesn't get keyboard input, but listbox passes typing letters/Backspace/Left/Right to the editor, so it looks like editor gets some part of keyboard input. Listbox performs insertion of chosen item on Enter/Tab keys, and it hides on Esc-key or clicking outside of the listbox.

pascal
type
  TATCompletionPropEvent = procedure (Sender: TObject;
    AContent: TStringList; out ACharsLeft, ACharsRight: integer) of object;
  TATCompletionResultEvent = procedure (Sender: TObject;
    const ASnippetId: string; ASnippetIndex: integer) of object;

procedure EditorShowCompletionListbox(AEd: TATSynEdit;
  AOnGetProp: TATCompletionPropEvent;
  AOnResult: TATCompletionResultEvent = nil;
  AOnChoose: TATCompletionResultEvent = nil;
  const ASnippetId: string = '';
  ASelectedIndex: integer = 0;
  AAllowCarets: boolean = false);

Callback AOnGetProp params:

pascal
  'func|FuncName|(param1, param2)'#9'Func description';

Notes:

Auto-completion using acp-files

Unit atsynedit_cmp_acpfiles has the function to show auto-completion listbox, from CudaText/SynWrite .acp files. Acp file must be in the format described in the SynWrite help file. You may copy acp files from CudaText distro, or from additional lexers, ie from CudaText lexers on CudaText.SF.net.

Auto-completion for CSS

Unit atsynedit_cmp_css has the function to show auto-completion listbox, for CSS syntax. You must pass path of the file "data/autocompletespec/css_list.ini" from CudaText distro. CSS completion supports several modes:

Auto-completion for HTML

Unit atsynedit_cmp_html has the function to show auto-completion listbox, for HTML syntax. You must pass path of the file "data/autocompletespec/html_list.ini" from CudaText distro. HTML completion supports several modes:

Finder

Finder class TATEditorFinder is in unit atsynedit_finder. To use it, create Finder object and set its props:

Dialog of demo:

Methods to find/replace text (they will place selection on found match, and scroll editor to caret, except method CountAll):

pascal
function DoAction_FindSimple(const APosStart: TPoint): boolean;
function DoAction_FindOrReplace(AReplace, AForMany: boolean; out AChanged: boolean;
  AUpdateCaret: boolean): boolean;
function DoAction_ReplaceSelected(AUpdateCaret: boolean): boolean;
procedure DoAction_FindAll(AResults: TATFinderResults; AWithEvent: boolean);
function DoAction_CountAll(AWithEvent: boolean): integer;
procedure DoAction_ExtractAll(AWithEvent: boolean; AMatches: TStringList; ASorted: boolean;
  ADuplicates: TDuplicates);
function DoAction_ReplaceAll: integer;

Found position and len are saved in props: MatchPos, MatchLen. Internally Finder object uses ATStringBuffer to read editor lines into buffer and search inside buffer. Then ATStringBuffer methods used to calculate caret position (from buffer offset) and place caret/selection. Note for regex search: buffer contains only "\n" line ends, other ends don't happen (original file may have any ends).

Finder has events:

Finder can handle multi-selections, by option OptInSelection. Selections are handled by CountAll, ReplaceAll. For other actions, selections don't make sense, because find-next makes new smaller selection (for found fragment) so prev big selection is lost. Tech note for replace-all: selections are looked from top to bottom, if they don't have common lines, and looked from bottom to top, if they have. This is done to not break lines, which are touched by 2+ selections. Count-all handles selections from top to bottom.

Adapters

Adapters description

To support syntax-highlighting in some way, you need

pascal
type
  TATAdapterHilite = class(TComponent)
  ...
  public
    constructor Create(AOwner: TComponent); override;
    //
    procedure OnEditorChange(Sender: TObject); virtual;
    //called when editor's text changes.

    procedure OnEditorChangeEx(Sender: TObject; AChange: TATLineChangeKind; ALine, AItemCount: integer); virtual;
    //detailed version of OnEditorChange

    procedure OnEditorIdle(Sender: TObject); virtual;
    //called after text is changed, and pause passed (OptIdleInterval)
    //fast changes (faster than OptIdleInterval): called only after last change

    procedure OnEditorCalcHilite(Sender: TObject;
      var AParts: TATLineParts;
      ALineIndex, ACharIndex, ALineLen: integer;
      var AColorAfterEol: TColor;
      AMainText: boolean); virtual;
    //called to calculate hilite of entire line.
    //ACharIndex is starting offset in this line, >0 if editor scrolled horizontally.
    //ALineLen is len of line part, starting from ACharIndex.
    ...

You need your custom adapter class, child of TATAdapterHilite, which must implement some of methods. You don't have to implement all of them, if your adapter doesn't need that. Then you must create an object of your adapter class, and assign this object to editor's AdapterForHilite property.

The package ATSynEdit_Ex contains 2 ready adapter classes: for complex EControl .lcf lexers, and for CudaText simplified "lite" lexers. The lexer files are distributed in the CudaText repos on SourceForge.

Adapter for EControl lexers

See ATSynEdit EControl adapter

How to adapt TRegExpr upstream for ATSynEdit

TRegExpr upstream is https://github.com/andgineer/TRegExpr . How to convert it to atsynedit_regexpr unit.

pascal
  RegExprModifierI: boolean = False; // default value for ModifierI
  RegExprModifierR: boolean = True; // default value for ModifierR
  RegExprModifierS: boolean = False; // default value for ModifierS
  RegExprModifierG: boolean = True; // default value for ModifierG
  RegExprModifierM: boolean = True; // default value for ModifierM
  RegExprModifierX: boolean = False; // default value for ModifierX

  RegExprLineSeparators: RegExprString = #$a#$b#$c
    {$IFDEF UniCode}
    + #$2028#$2029#$85
    {$ENDIF}; 
  RegExprLinePairedSeparator: RegExprString = ''; //#$d#$a;  

  FUseOsLineEndOnReplace := False;
  FReplaceLineEnd := #10; //not sLineBreak, it is CR LF on Windows

From the Free Pascal wiki, page "ATSynEdit", as it stood on 2024-04-04 after a change by Alextp, 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.

Revision 2, last changed by WikiArchive .

Talk about this page

Nobody has opened a bench for this page yet. Sign in to open one.