Difference between revisions of "ATSynEdit"

From Free Pascal wiki
Jump to navigationJump to search
(Keyboard shortcuts)
Line 444: Line 444:
 
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 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:
+
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.  
 +
 
 +
[[File:atsynedit_combo.png]]
 +
 
 +
Variants must be added to history by user code, using OnCommand handler: react to command "cCommand_KeyEnter". Example of handler:
  
 
<syntaxhighlight>
 
<syntaxhighlight>

Revision as of 14:53, 21 July 2015

About

ATSynEdit is the multi-line edit control, with fixed font.

(press "Download Zip" button at right)

Author: Alexey Torgashin

atsynedit.png

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.

Requirements

Lazarus: 1.4.0.

One of the last versions was tested on:

  • Windows 7
  • Linux GTK2 (Ubuntu 14.04)
  • QT (Win7) (main problem was moving all painting code into Paint event, it's done)
  • MacOS X 10.8:
    • a) same prob as QT,
    • b) problems showing minimap with minimal font size, just set bigger font,
    • c) many hotkeys with Ctrl don't work, changed Ctrl+keys to Meta+keys

Keyboard shortcuts

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

atsynedit keymap.png

Mouse shortcuts

Carets:

  • Ctrl+click - add/delete caret
  • Ctrl+drag - add caret with selection
  • Ctrl+Shift+click - add carets column in several lines (from previous caret to clicked line)

Select:

  • Alt+drag - select column of text (Note: it may look weird if word-wrap on, because wrap is not considered here at all. Simple rectangle of coordinates [x1,y1]-[x2,y2] is always selected, even if this gives bad looking screen)
  • drag on gutter's line numbers - select by entire lines
  • double-click and immediately drag - select text by words

Clicks:

  • double-click - select clicked word
  • triple-click - select entire line (limited by end-of-lines)
  • middle-button click - start "Browser Scroll" mode: circle mark appears and mouse moving around this mark auto-scrolls text in 4 directions; speed of scrolling depends on distance of cursor from circle mark (any click to turn off)

Help topics

What limitations it has?

  • Cannot set different fonts for different text parts
  • All lines have the same height
  • No highlighter components like SynEdit (other method used)
  • Not supported RTL mode, limited support for Arabic text

What features it has?

  • Fast editing of big texts (tested 10M log)
  • Syntax highliting using 'adapters'
  • Multi-carets from birth. All code carets aware.
  • Strings object is holder of text. 2+ editors can have 1 strings obj. (Split editor.)
  • Unicode (used UnicodeString in Strings obj)
  • Word-wrap (wrap at control edge, wrap at column)
  • Undo/redo
  • Column blocks
  • Gutter (line numbers, fold, colored line states)
  • Bookmarks
  • Supports Win/Unix/Mac line ends
  • Minimap like Sublime Text
  • Micromap (owner drawn)
  • Ruler (at top edge)
  • Renders wrapped indent (line contains some indent, wrapped parts contain the same indent)
  • Renders CJK chars with big width
  • Renders some Unicode chars as "AABB" hex code
  • Renders unprintable chars (space, tab, line end)

Difference from other components?

  • Adapters to support syntax hilight; possible to support other hiliters: from SynEdit, from EControl, from Sublime Text, from EmEditor etc
  • Carets and selections
  • Minimap
  • Diff from SynEdit: simpler set of objects, no 'markup objects'

Does it need to be installed?

It may be installed or not.

  • Use not installed: see below "Basic example how to use".
  • Use installed: in Lazarus IDE, open package file "atsynedit\atsynedit_package". Install it in Package dialog. You 'll have components installed in "Misc" page of component pallette: TATSynEdit, TATEdit (single line edit), etc.

What parts does it have?

Strings object

type TATStrings in unit atstrings. Holds text lines.

  • To add text lines, use procedures of Strings: LineAdd, LineInsert, LineDelete.
  • To get/set text lines, use properties:
    • Lines[i]: type UnicodeString
    • LinesEnds[i]: enum (kind of end-of-line)
    • LinesBm[i]: integer (bookmark index, or 0 if no bookmark)
    • LinesBmColor[i]: color of bookmark (used if bookmark set)
    • LinesHidden[i]: integer (0: line is fully visible, -1: line is fully hidden, >0: line is hidden from this char-index)
    • LinesState[i]: enum (colored state of line shown on gutter)


Carets object

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

  • PosY: index of line in Strings object, 0-based
  • PosX: offset of caret from line start, 0-based
  • EndY, EndX: position for selection end for this caret (used if multi-carets have selections); -1 of selection not exists for caret
  • CoordY, CoordX: screen coordinates (don't use it)

Keymap object

type TATKeymap in unit atsynedit_keymap.

Holds list of keyboard actions of type TATKeymapItem. It's inited at start. 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 Copy-to-clip (Ctrl-C and Ctrl-Ins), Paste-from-clip (Ctrl-V and Shift-Ins) etc.

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

  var M: TATKeymap;
  ...
  M.Add(cmd_mycmd, 'some command', ['Ctrl+B', 'Ctrl+B', 'Ctrl+M'], []);

WrapInfo object

type TATSynWrapInfo in unit atsynedit_wrapinfo. Holds list of wrap items, which have info about wrapped lines:

  • its original line index in Strings object (0-base)
  • its char offset in original line (0-base)
  • its length
  • its indent size (if feature "wrapped indent" is on)
  • its final-state: is it first/middle part of wrapped line, or final part

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 (Strings object has LinesHidden[i]>=0).

Undo object

Holds list of actions for Undo/redo. You must not touch it. Filled auto by changing-adding-del lines in Strings object (by any method). Also holds end-of-lines markers (they can undo too).

Items in Undo object have 'group marker'. This means that some keymap commands set this marker and actions after this command will undo as group (if option "Group undo" on).

Gutter object

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

  • Visible: gutter band is shown on gutter
  • Size: width of gutter band
  • Left/Right: calculated auto, from visible and size, coordinates of band on control

To know which band of gutter does what, use properties of ATSynEdit: GutterBandNums (index of band for line numbers), GutterBandFold (index of band for folding) etc. To hide line numbers, just set to gutter item with index GutterBandNums Visible=false and call gutter's Update (to update Left/Right of all gutter items).

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:

  • Y: starting line of range (0-based)
  • Y2: ending line of range (if same as Y, plus/minus not shown on gutter)
  • X: char-offset in starting line (must be >0, 0-based)
  • Folded: boolean, state: folded/unfolded
  • Staple: boolean, true if range has staple shown (vertical line on text area)
  • Hint: string, shown in rectangle when range is folded

Colors object

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

What are the basic methods/properties?

Objects

  • property Strings: Strings object
  • property Carets: Carets object
  • property Gutter: Gutter object
  • property Keymap: Keymap object
  • property Fold: Fold object
  • property Colors: Colors object

Files

  • procedure LoadFromFile(const AFilename: string): load text from file (filename uses utf-8)
  • procedure SaveToFile(const AFilename: string): save text to file

States

  • property Modified: modified state (cleared on file loading)
  • property ModeOverwrite: mode insert/overwrite state (Ins key toggles it)
  • property ModeReadOnly: mode read-only state

More

  • procedure Update(AUpdateWrapInfo: boolean = false): use it to repaint, pass true to force update of WrapInfo object (if detection of text change cannot work)
  • procedure DoCommand(ACmd: integer; const AText: UnicodeString = ""): command runner, pass command code to run it (see file atsynedit_commands)
  • property Text: unicodestring which gives entire editor text with LF separator

Caret

  • procedure DoCaretSingle(AX, AY: integer): make single caret
  • function CaretPosToClientPos(P: TPoint): TPoint: convert caret coords to screen coords
  • function ClientPosToCaretPos(P: TPoint; out AEndOfLinePos: boolean): TPoint: convert screen coords to caret coords
  • function IsLineWithCaret(ALine: integer): boolean: is line index contains any caret


Selection

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

  • property SelRect: for column selection, rectangle of column block
  • function IsSelRectEmpty: is column selection exists
  • procedure DoSelect_All: select all text (makes single caret)
  • procedure DoSelect_Line(P: TPoint): select single line
  • procedure DoSelect_Word(P: TPoint): select single word
  • procedure DoSelect_LineRange(ALineFrom: integer; P: TPoint): select several lines from line index to given point

View

  • property LineTop: index of line visible at top of control
  • property LineBottom: index of line visible at bottom of control (because of folding you cannot simply calculate it)
  • procedure DoRangeFold(ARange: TATSynRange): fold range, which exists in the Fold object
  • procedure DoRangeUnfold(ARange: TATSynRange): unfold range
  • procedure DoScrollByDelta(Dx, Dy: integer): scroll editor area by Dx chars right and Dy lines down
  • procedure DoGotoPos(APnt: TPoint; AIndentHorz, AIndentVert: integer): scroll editor to given caret pos

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

  • Strings.EncodingDetect: boolean
  • Strings.Encoding: enum (cEncAnsi, cEncUTF8, cEncWideLE, cEncWideBE)
  • Strings.EncodingCodepage: string (used for Encoding=cEncAnsi)
  • Strings.SaveSignUtf8: boolean

Basic example how to use?

  • Add "uses ATSynEdit".
  • Add variable "ed".
  • Add OnCreate event to new form.
  • How to load file "unit1.pas":
   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 to save file: call "ed.SaveToFile()".

How does it manage the coordinates?

Carets object contains list of caret items. Each caret item has props PosY (line index in Strings object), PosX (char offset from line start). Strings object holds UnicodeString's, where each WideChar gives offset=1, and even Tab char gives offset=1. Before painting, PosX/PosY converted to properties CoordX/CoordY, screen coordinates. CoordY is pixels from top control edge (you don't need to change by ruler height), CoordX is pixels from left edge (you don't need to change by gutter width). Don't touch CoordX/CoordY. Change PosX/PosY (caret item position) and EndX/EndY (selection end for caret item, both -1 if no selection present for this item).

Use these funcs if needed to convert caret-pos to screen-pos:

  • function CaretPosToClientPos(P: TPoint): TPoint.
  • function ClientPosToCaretPos(P: TPoint; out AEndOfLinePos: boolean): TPoint. Last param: returned true if caret pos is after real end-of-line, false if caret pos is inside line.

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 true, caret is allowed after end-of-line. Functions (above) consider it.

How to manage the caret, the selection?

See topic above about "Carets object". To change position of carets, get Carets property of editor, and change caret items' properties PosX/PosY, then call editor's Update (repaint). To change selection for any caret (each caret has own selection), do the same: get any caret item, and change its propeties EndX/EndY (-1 to delete selection), then call editor's Update.

Better don't do overlapping selections (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 caret items. Or add caret items.

Example:


 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.

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

  • You can read Strings from one editor1 and set it to Strings of editor2. This will make editor2 having same strings object (needed for split-tab).
  • Do call editor's Update method to repaint. Call Update(true) to force invalidate WrapInfo object and repaint.

How to find/replace text?

Finder class TATEditorFinder exists in unit atsynedit_finder. To use it, create Finder object and pass to it:

  • Editor: editor object (text will be read automatically)
  • StrFind: find-string (simple string or regex)
  • StrReplace: replace-string (simple string or some regex eg with "$0", "$1" etc)
  • flags:
    • OptRegex
    • OptCase (for normal and regex modes)
    • OptWords (for non-regex mode, for regex you must use "\b" specifiers in find-string)
    • OptBack (for non-regex mode)
    • OptFromCaret (search goes from position of first caret)
    • OptConfirmReplace (each replace will occur after event OnConfirmReplace of Finder)

atsynedit finder.png

Then call method which will find/replace text (and place selection over found match, and scroll editor to caret).

  • function FindAction(ANext: boolean; AFlags: TATEditorFinderFlags): boolean;

AFlags is set of:

  • fflagReplace: perform replace
  • fflagDontRereadBuffer: needed only on counting matches (don't reread editor lines)
  • fflagDontMoveCaret: needed only on counting matches

Found position and len are 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).

How to highlight syntax?

To syntax hilite text, you must 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, in method OnEditorCalcHilite. Same code as in OnCalcHilite. Then you set this adapter to editor:

 atsynedit1.AdapterHilite:= MyAdapter;

Editor will call adapter method. Adapter class has more: see small file "atsynedit_adapters.pas". Tiny class with empty code. Other methods needed for adapters to react to text change, etc.

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

Parameters here:

  • Sender: editor object.
  • AParts: array of "parts": each part has Offset, Len, colors, font style. Adapter must create parts for entire string (1st part takes 1st chars of string, last part takes last chars of string). See code about TATLineParts.
  • ALineIndex: index of passed line (0-based).
  • ACharIndex: index of 1st char in line (it's not 0 if wrapped part of line is shown, 0-based).
  • ALineLen: length of substring to hilite.
  • AColorAfterEol: return here color for background of line after end-of-line. Return clNone if no color needed.

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

  • same text for all editors
  • separate folding states for each editor

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

In ATStrings object this field exists:

  • ItemHidden: array[0..cMaxStringsClients-1] of smallint;

(constant can be changed in source). This field allows 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.

  • change cMaxStringsClients to 4
  • set ed0.EditorIndex:=0
  • set ed1.EditorIndex:=1
  • set ed2.EditorIndex:=2
  • set ed3.EditorIndex:=3
  • set ed1.Strings:=ed0.Strings
  • set ed2.Strings:=ed0.Strings
  • set ed3.Strings:=ed0.Strings

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

  • ed0.Update(true)
  • ed1.Update(true)
  • ed2.Update(true)

How to add/change commands?

To change action of internal commands (file atsynedit_commands.pas) you need to use event OnCommand which can make any action for any command and return Handled=True to disable internal code. Better use this event for all commands, even added ones. So you can call edit1.DoCommand() and have one place to call anything. Example shows adding of N new commands which close editor tabs (special case: cannot close tabs just inside OnCommand) and N commands which just copy something to clipboard.

 	procedure TfmMain.DoEditorCommand(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.

atsynedit combo.png

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

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(Text);
      //ShowMessage('Enter: '+s);

      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?

  • number of carets: Carets.Count
  • positions of carets: Carets.Items[i].PosX (column), PosY (line)
  • text encoding: Strings.Encoding
    • cEncAnsi: then codepage is given by Strings.EncodingCodepage
    • cEncUTF8: then presence of utf8 bom is given by Strings.SaveSignUtf8
    • cEncWideLE, cEncWideBE: then presense of Unicode bom is given by Strings.SaveSignWide
  • text line endings (win/unix/mac): Strings.Endings
  • tab-char width: OptTabSize
  • tab-char entered by spaces: OptTabSpaces
  • index of top visible line: LineTop
  • modified state: Modified
  • read-only mode: ModeReadOnly
  • insert/overwrite mode: ModeOverwrite
  • word-wrap mode: OptWrapMode
  • unprinted chars shown: OptUnprintedVisible
  • selection: get Carets.Items[i].PosX/PosY/EndX/EndY, for state of column selection get IsSelRectEmpty, SelRect

more