TextEditor.pas

File name
C:\Users\Andreas Rejbrand\Documents\Dev\rtesrc 3.1.2\TextEditor\TextEditor.pas
Date exported
Time exported
Formatting processor
TPascalFormattingProcessor
{******************************************************************************}
{                                                                              }
{ Rejbrand Text Editor Control 3.1                                             }
{                                                                              }
{ Copyright © 2015-2019 Andreas Rejbrand                                       }
{                                                                              }
{ https://english.rejbrand.se/                                                 }
{                                                                              }
{******************************************************************************}

unit TextEditor;

interface

uses
  Windows, SysUtils, Classes, Types, Controls, Graphics, Messages, Menus,
  ExtCtrls, RichEdit, CommCtrl, Forms, BitmapEffects, TextEncodings,
  UnicodeData, ActiveX, System.Win.ComObj, ShlObj, Generics.Defaults,
  Generics.Collections, UITypes, Math;

resourcestring
  SNumOnlyErrorTitle = 'Character not allowed';
  SNumOnlyErrorText = 'You can only type a number here.';
  SControlLineInputTitle = 'Text input not allowed';
  SControlLineInputText = 'You cannot enter text on a control line.';
  SPicture = 'Picture';
  SImageFilter = 'All images|*.bmp; *.jpg; *.jpeg; *.jpe; *.png; *.gif; *.ico|Bitmap images|*.bmp|JPEG images|*.jpg; *.jpeg; *.jpe|PNG images|*.png|GIF images|*.gif|Icons|*.ico';
  SOpenImageDialogCaption = 'Select image';
  SControl = 'Control';
  SRemovedControl = 'Removed control';
  SCliHistoryDialogCaption = 'Command History';
  SMultiSelectCaption = 'Character Selection';
  SMultiCharDlgLvColumnTitleDescription = 'Description';
  SMultiCharDlgLvColumnTitleCodepoint = 'Codepoint';
  SMultiCharDlgLvColumnTitleBlock = 'Block';
  SNewFileName = 'New file %d';

  SMenuOpenURL = 'Open URL';
  SMenuOpenURLHint = 'Opens %s.';
  SMenuUndo = 'Undo';
  SMenuUndoHint = 'Undoes the previous operation in the undo list.';
  SMenuRedo = 'Redo';
  SMenuRedoHint = 'Redoes the next operation in the undo list.';
  SMenuCut = 'Cut';
  SMenuCutHint = 'Moves the selected text to the clipboard.';
  SMenuCopy = 'Copy';
  SMenuCopyHint = 'Copies the selected text to the clipboard.';
  SMenuPaste = 'Paste';
  SMenuPasteHint = 'Pastes the contents of the clipboard at the caret, replacing any selection.';
  SMenuClear = 'Clear';
  SMenuClearHint = 'Removes the selected text.';
  SMenuSelectAll = 'Select all';
  SMenuSelectAllHint = 'Selects all text.';
  SMenuSetBookmark = 'Set bookmark';
  SMenuSetBookmarkItemHint = 'Sets this bookmark to the current caret position.';
  SMenuGotoBookmark = 'Goto bookmark';
  SMenuGotoBookmarkItemHint = 'Moves the caret to this bookmark.';
  SMenuClearBookmark = 'Clear bookmark';
  SMenuClearBookmarkItemHint = 'Clears (removes) this bookmark.';
  SMenuClasses = 'Classes';
  SMenuClassesItemHint = 'Assigns this class to the current line.';
  SMenuActivateControl = 'Activate control';
  SMenuActivateControlHint = 'Activates this control.';
  SMenuTransform = 'Transform';
  SMenuTransformUpperCase = 'To upper case';
  SMenuTransformUpperCaseHint = 'Converts the selected text to upper case.';
  SMenuTransformLowerCase = 'To lower case';
  SMenuTransformLowerCaseHint = 'Converts the selected text to lower case.';
  SMenuTransformInvertCase = 'Invert case';
  SMenuTransformInvertCaseHint = 'Inverts the case of the characters in the selection.';
  SMenuTransformCamelCase = 'To camel case';
  SMenuTransformCamelCaseHint = 'Enforces camel case (Camel Case) in the selection.';
  SMenuTransformSentenceCase = 'To sentence case';
  SMenuTransformSentenceCaseHint = 'Enforces sentence case in the selection.';
  SMenuTransformReverse = 'Reverse text';
  SMenuTransformReverseHint = 'Reverses the selected text.';
  SMenuTransformROT13 = 'Perform ROT-13';
  SMenuTransformROT13Hint = 'Performs ROT-13 on the selected text.';
  SMenuTransformCaesar = 'Apply Caesar cipher...';
  SMenuTransformCaesarHint = 'Performs the Caesar cipher on the selected text.';
  SMenuTransformVigenere = 'Apply Vigenère cipher...';
  SMenuTransformVigenereHint = 'Performs the Vigenère cipher on the selected text.';
  SMenuTransformVigenereInverse = 'Apply inverse Vigenère cipher...';
  SMenuTransformVigenereInverseHint = 'Performs the inverse Vigenère cipher on the selected text.';
  SCaesarNTitle = 'Caesar Cipher';
  SCaesarNText = 'Please enter the parameter of the Caesar cipher:';
  SVigenereTitle = 'Vigenère Cipher';
  SVigenereText = 'Please enter the parameter of the Vigenère cipher:';
  SMenuNoBookmarksSetParen = '(No bookmarks set.)';
  SMenuClearAllBookmarks = 'Clear all';
  SMenuClearAllBookmarksHint = 'Clears all bookmarks.';
  SMenuUseNoClass = 'No class';
  SMenuUseNoClassHint = 'Assigns no class to the current line.';

  SBookmarkDescriptionInvalid = 'Invalid bookmark';
  SBookmarkDescription = 'Bookmark %d (%d, %d)';
  SBookmarkDescriptionEmpty = 'Bookmark %d (empty)';

  SMenuCopyImage = 'Copy image';
  SMenuCopyImageHint = 'Copies the image to the clipboard.';
  SMenuDeleteImage = 'Remove image';
  SMenuDeleteImageHint = 'Removes the image.';
  SMenuChangeImage = 'Change image...';
  SMenuChangeImageHint = 'Replaces the image with a different one.';

  SMenuRulerProperties = 'Properties';
  SMenuRulerPropertiesHint = 'Displays or modifies the ruler properties.';

  SMoveHere = 'Move here';
  SCopyHere = 'Copy here';
  SCancel = 'Cancel';

  SFPSlowTitle = 'Text Editor Control';
  SFPSlowText = 'The formatting processor used for interactive formatting (syntax highlighting) appears to be slow. Do you want to disable interactive formatting?';
  SRestoreWrapAtText = 'Do you want to restore the default wrap at characters?';
  SRestoreWrapAtCaption = 'TTextEditor control';

  SNotifyDragMove = 'Drag to move the selection to the new position. (Press Esc to abort.)';
  SNotifyDragCopy = 'Drag to copy the selection to the new position. (Press Esc to abort.)';
  SNotifyReadOnlyError = 'You cannot edit a read-only line.';
  SNotifyInputError = 'Invalid operation.';
  SNotifyPrinting = 'Printing...';
  SNotifyScrollMode = 'Scroll mode';
  SNotifyScript = 'Script running... (Press Esc to abort.)';
  SNotifyMultiCharSelect = 'Press F9 to show a list of related characters.';
  SNotifyReadOnlyMode = 'Read-only mode';
  SNotifyMultiCaretMode = 'Multi-caret mode. (Press Esc to abort.)';

  SReadOnlyErrorText = 'You cannot edit a read-only line.';
  SReadOnlyErrorTitle = 'Read only';

  SUndoSelectionTransformed = 'Selection transformed (%s)';
  SUndoTextTransformed = 'Text transformed (%s)';
  SUndoLineCleared = 'Line cleared';
  SUndoSelectionCleared = 'Selection cleared';
  SUndoFirstPost = 'First version since undo history was removed';
  SUndoTyped = 'Typed';
  SUndoInitialText = 'Initial text';
  SUndoCutToClipboard = 'Cut to clipboard';
  SUndoTrimRight = 'Lines trimmed to the right';
  SUndoTextCleared = 'Document cleared';
  SUndoBookmarksCleared = 'Bookmarks cleared';
  SUndoWordWrap = 'Word wrap performed';
  SUndoTextInserted = 'Text inserted';
  SUndoTextSurrounded = 'Selection surrounded by "%s" and "%s"';
  SUndoReplacedAll = 'Replaced %d instance(s) of "%s" by "%s"';
  SUndoUnicodeReplacedCodepoint = 'Unicode codepoint replaced by character';
  SUndoLinesSwapped = 'Lines swapped';
  SUndoMouseMove = 'Selection moved using mouse';
  SUndoMouseCopy = 'Selection copied using mouse';
  SUndoMouseMoveExtSrc = 'External text moved here using mouse';
  SUndoMouseCopyExtSrc = 'External text copied here using mouse';
  SUndoMouseMoveExtDst = 'Selection moved to external document';
  SUndoNewFile = 'Document created';
  SUndoBookmarkCleared = 'Bookmark cleared';
  SUndoBookmarkAdded = 'Bookmark added';
  SUndoIndentIncreased = 'Indent increased';
  SUndoDocumentLoaded = 'Document loaded';
  SUndoPastedFromClipboard = 'Pasted from clipboard';
  SUndoIndentRemoved = 'Indent removed';
  SUndoIndentDecreased = 'Indent decreased';
  SUndoReverted = 'Reverted to version #%d from %s';
  SUndoSorted = 'Sorted lines';
  SUndoMadeLinesUnique = 'Removed duplicates, made lines unique';
  SUndoScript = 'Script executed';
  SUndoScriptAbort = 'Script partially executed before being aborted';
  SUndoCliHistory = 'Command-line history item recalled';
  SUndoFillWithChar = 'Filled selection with character %s';
  SUndoLinesTruncated = 'Lines truncated';
  SUndoLinesFiltered = 'Lines filtered';
  SUndoAutoReplaced = 'Auto-replaced code';

  SDefaultPrintJobTitle = 'Text file';
  SDefaultFileName = 'Untitled file';

  STransformNameUpperCase = 'upper case';
  STransformNameLowerCase = 'lower case';
  STransformNameInvertCase = 'invert case';
  STransformNameCamelCase = 'camel case';
  STransformNameSentenceCase = 'sentence case';
  STransformNameReverse = 'reverse text';
  STransformNameRot13 = 'ROT-13';
  STransformNameCaesarN = 'Caesar %d';
  STransformNameVigenere = 'Vigenère';

  SNoInteractiveFormattingParen = '(No interactive formatting)';

  SHTMLExportFileName = 'File name';
  SHTMLExportDate = 'Date exported';
  SHTMLExportTime = 'Time exported';
  SHTMLExportFP = 'Formatting processor';

  SInvalidOpMsgSingleLineModeInsertLine = 'Cannot insert line in single-line mode.';
  SInvalidOpMsgInvalidChrIndex = 'TTextFile: Invalid character index %d.';
  SNoLineComparer = 'Cannot sort because no line comparison function has been assigned.';
  SIllegalLineComparer = 'Illegal line-comparing function assigned.';

const
  FORMATETC_UNICODETEXT: TFormatEtc =
    (
      cfFormat: CF_UNICODETEXT;
      ptd: nil;
      dwAspect: DVASPECT_CONTENT;
      lindex: -1;
      tymed: TYMED_HGLOBAL
    );

type
  TChangeType = (ctNone, ctFile, ctLineRange, ctBlock, ctLine, ctLineFrom,
    ctChar, ctTwoChars, ctPostFile);
  TChangeRecord = record
    ChangeType: TChangeType;
    Data1, Data2, Data3, Data4: integer;
  { -      -      -      -      ctNone      }
  { -      -      -      -      ctFile      }
  { y1,    y2     A      -      ctLineRange }
  { ymin   ymax   xmin   xmax   ctBlock     }
  { y      -      -      -      ctLine      }
  { y      x      -      -      ctLineFrom  }
  { y      x      -      -      ctChar      }
  { y1     x1     y2     x2     ctTwoChars  }
  { n      -      -      -      ctPostFile  }
  { A = 0 // unspecified cause              }
  {     1 // caused by a single VK_RETURN   }
  {       // on y1                          }
  {     2 // caused by removal of #13#10 at }
  {       // the end of y1                  }
  { Unused parameters MUST be zero.         }
  end;
  TChangeRecords = array of TChangeRecord;

function MakeChangeRecord(ChangeType: TChangeType; Data1, Data2, Data3, Data4: integer): TChangeRecord;

const
  NO_CHANGE_RECORD: TChangeRecord = (ChangeType: ctNone; Data1: 0; Data2: 0; Data3: 0; Data4: 0);
  FILE_CHANGE_RECORD: TChangeRecord = (ChangeType: ctFile; Data1: 0; Data2: 0; Data3: 0; Data4: 0);

type
  TChangeEvent = procedure(Sender: TObject; ChangeType: TChangeType; Data1,
    Data2, Data3, Data4: integer) of object;
  // Caution! A line that has changed ***need not exist*** in the new text file!
  // Still, the visual owner component may need to clear a previously occupied line on the screen.

function ChangeUnion(const ChangeRecord1, ChangeRecord2: TChangeRecord): TChangeRecord;

type
  TSelectionType = (stLineBased, stBlock);

  TCaretPos = class
  private
    FCaretPos: TPoint;
    FSelStartPos: TPoint;
    FSelectionType: TSelectionType;
    FOnChange: TNotifyEvent;
    FOnSelChange: TChangeEvent;
    FSavedSelExtent: TChangeRecord;
    procedure SetCaretPos(const Value: TPoint);
    procedure Changed;
    procedure SelChanged(ChangeType: TChangeType; Data1, Data2, Data3, Data4: integer);
    procedure SelRemoved;
    procedure SetSelEndPos(const Value: TPoint);
    procedure SetSelectionType(const Value: TSelectionType);
    function GetSelExtent(ACaretPos, ASelEndPos: TPoint; ASelectionType: TSelectionType): TChangeRecord;
    procedure SaveSelExtent; inline;
    function GetFirstPoint: TPoint;
    function GetLastPoint: TPoint;
  public
    constructor Create;
    property Data: TPoint read FCaretPos;
    property SelEnd: TPoint read FSelStartPos;
    property FirstPoint: TPoint read GetFirstPoint;
    property LastPoint: TPoint read GetLastPoint;
    property X: Integer read FCaretPos.X;
    property Y: Integer read FCaretPos.Y;
    procedure SetPoint(X, Y: integer; SelEnd: boolean = false); overload;
    procedure SetPoint(Point: TPoint; SelEnd: boolean = false); overload; inline;
    procedure SetX(X: integer; SelEnd: boolean = false);
    procedure SetY(Y: integer; SelEnd: boolean = false);
    procedure CreateSelection(const ASelStart, ASelEnd: TPoint;
      const ASelectionType: TSelectionType = stLineBased);
    procedure RemoveSelection;
    property SelectionType: TSelectionType read FSelectionType write SetSelectionType default stLineBased;
    procedure GetSelBdry(const PointA, PointB: TPoint; out FirstPoint, SecondPoint: TPoint); overload;
    procedure GetSelBdry(out FirstPoint, SecondPoint: TPoint); overload;
    procedure InternalPush(Size: integer; LastLine: boolean = true);
    property OnChange: TNotifyEvent read FOnChange write FOnChange;
    property OnSelChange: TChangeEvent read FOnSelChange write FOnSelChange;
  end;

  TEditMode = (emText, emConsole, emReadOnly);
  EReadOnlyViolation = class(Exception);

  TAutoReplaceItem = record
    Token,
    ReplacedValue: string;
  end;
  TAutoReplaceItems = array of TAutoReplaceItem;

const
  NUM_BOOKMARKS = 17;
  INTERNAL_BOOKMARK = -1;

type
  TBookmarkList = array[-1..NUM_BOOKMARKS - 1] of TPoint;

const
  EMPTY_BOOKMARK: TPoint = (X: -1; Y: -1);

type
  TUndoDataItem = record
    Text: string;
    Classes: string;
    CaretPos: TPoint;
    SelStartPos: TPoint;
    SelType: TSelectionType;
    Time: TDateTime;
    Comment: string;
    UID: integer; { RichEdit UNDONAMEID }
    Bookmarks: TBookmarkList;
  end;
  TUndoData = array of TUndoDataItem;

  THistoryManager = class
  public const
    UNDO_SIGNATURE = $4F444E55;
    UNDO_SIGNATURE_ITEM = $4D455449;
  strict private const
    DEFAULT_MAX_UNDO_SIZE = 536870912;
    HISTORY_ALLOC_BY = 1024;
  strict private
    FUndoData: TUndoData;
    FMaxSize: integer;
    FActualLength: integer;
    FSize: integer;
    FFirstItem: integer;
    FHistoryIndex: integer;
    function GetLength: integer;
    function SizeOfItem(ItemIndex: integer): integer;
    procedure SetMaxSize(Value: integer);
    procedure ClearItem(ItemIndex: integer);
    function RemoveFirstItem: boolean;
    procedure TrimLeft;
    function GetUndoData(Index: integer): TUndoDataItem;
    function GetLastItem: integer;
    procedure Revert;
  public
    constructor Create;
    destructor Destroy; override;
    procedure Add(AUndoDataItem: TUndoDataItem); overload;
    procedure Add(const AText: string; const AClasses: string;
      const ACaretPos, ASelStartPos: TPoint; ASelType: TSelectionType;
      const ATime: TDateTime; const AComment: string;
      const ABookmarks: TBookmarkList; AUID: integer = 0); overload;
    procedure Clear;
    function Undo(out UndoData: TUndoDataItem): boolean;
    function CanUndo: boolean;
    function Redo(out UndoData: TUndoDataItem): boolean;
    function CanRedo: boolean;
    function GotoVersion(Index: integer; out UndoData: TUndoDataItem): boolean;
    procedure CreateDataStream(out Data: pointer; out Len: UInt64);
    procedure SaveToStream(AStream: TStream);
    procedure LoadFromStream(AStream: TStream);
    procedure LoadFromBuffer(const Data: pointer; const Len: UInt64);
    property UndoData[Index: integer]: TUndoDataItem read GetUndoData;
    property Sizes[Index: integer]: integer read SizeOfItem;
    property Count: integer read GetLength;
    property Size: integer read FSize;
    property FirstItem: integer read FFirstItem;
    property LastItem: integer read GetLastItem;
    property HistoryIndex: integer read FHistoryIndex;
    property MaxSize: integer read FMaxSize write SetMaxSize default DEFAULT_MAX_UNDO_SIZE;
  end;

  TFindQuery = record
    SearchString: string;
    MatchCase: boolean;
    MatchWord: boolean;
    Linebreak: boolean;
    UCBlock: integer;
  end;

const
  FQ_NULL = 0;
  FQ_NONASCII = -1;
  FQ_CONTROL = -2;
  FQ_NONCHAR = -3;
  FQ_MIN = -3;

function MakeFindQuery(const ASearchString: string; AMatchCase, AMatchWord, ALinebreak: boolean): TFindQuery; overload;
function MakeFindQuery(UCBlock: integer): TFindQuery; overload;

type
  TTextSpan = record
    A, B: TPoint;
  end;

  TFindData = array of TTextSpan;

  TFileStatisticsFlags = set of (fsfCharTypes, fsfLines, fsfWords);

const
  FILE_STAT_ALL = [fsfCharTypes, fsfLines, fsfWords];
  FILE_STAT_CHARS = [fsfCharTypes];
  FILE_STAT_LINES = [fsfLines];
  FILE_STAT_WORDS = [fsfWords];

const
  LINE_CONTROL_CLASS = #$FFFF {noncharacter};
  LINE_CONTROL_PREFIX = #$FFFC#$FFFF {object replacement char + noncharacter};
  LINE_CLASS_INDICATOR = #$FFFE {noncharacter};

type
  TFileStatistics = record
    Flags: TFileStatisticsFlags;
    NumLines: integer;
    NumChars: integer;
    NumLetters: integer;
    NumDigits: integer;
    NumWhitespace: integer;
    NumInterpunct: integer;
    MaxLineLength: integer;
    AvgLineLength: real;
    LineLengthDistr: array of integer;
    NumWords: integer;
    MaxWordLength: integer;
    AvgWordLength: real;
    WordLengthDistr: array of integer;
  end;

  TCharTestFunction = function(C: char): boolean;

  TLineChangeType = (lctAll, lctAppend, lctChangeFrom);
  TLineChangeEvent = procedure(Sender: TObject; ChangeType: TLineChangeType; From: integer) of object;

  TControlEvent = procedure(Sender: TObject; ControlID: integer) of object;
  TLineEvent = procedure(Sender: TObject; LineIndex: integer) of object;

  TGetControlTextEvent = procedure(Sender: TObject; LineIndex: integer; var ControlText: string) of object;

  TChrTransformFunc = reference to function(C: char): char;
  TTextTransformFunc = reference to function(const AText: string): string;

function ChrUpperCase(C: char): char;
function ChrLowerCase(C: char): char;
function ChrInvertCase(C: char): char;
function ChrROT13(C: char): char;
function ChrCaesar(N: integer): TChrTransformFunc;
function TxtVigenère(const Key: string; decode: boolean = false): TTextTransformFunc;
function TxtCamelCase(const AText: string): string;
function TxtSentenceCase(const AText: string): string;
function ReverseText(const AText: string): string;

const
  UNICODE_RETURN_SYMBOL = #$23CE;
  UNICODE_RETURN_SYMBOL_ALTERNATIVE = #$21B5;

const
  DEFAULT_WRAP_AT = #$9#$20#$1680#$2000#$2001#$2002#$2003#$2004#$2005#$2006 +
   #$2008#$2009#$200A#$205F#$3000#$180E#$200B#$200C#$200D#$002D#$00AD#$2010 +
   #$2013#$2014';:@?=>)]}';

type
  TEditorState = class
  private var
    FValid: boolean;
    FFormattingProcessorClassName: string;
    FScrollPos: TPoint;
    FMultiSize: boolean;
    FOverwrite: boolean;
    FHiddenChrs: boolean;
    FRulerVisible: boolean;
    FZoomLevel: integer;
    FFPCache: PByte;
    FFPCacheLen: integer;
  public
    constructor Create;
    destructor Destroy; override;
    property Valid: boolean read FValid write FValid;
    property FormattingProcessor: string read FFormattingProcessorClassName
      write FFormattingProcessorClassName;
    property ScrollPos: TPoint read FScrollPos write FScrollPos;
    property MultiSize: boolean read FMultiSize write FMultiSize;
    property Overwrite: boolean read FOverwrite write FOverwrite;
    property HiddenChrs: boolean read FHiddenChrs write FHiddenChrs;
    property RulerVisible: boolean read FRulerVisible write FRulerVisible;
    property ZoomLevel: integer read FZoomLevel write FZoomLevel;
    property FPCache: PByte read FFPCache write FFPCache;
    property FPCacheLen: integer read FFPCacheLen write FFPCacheLen;
  end;

  TLineComparer = function(const LineA, LineB: string): integer;

  PFilterOptions = ^TFilterOptions;
  TFilterOptions = record
    RemoveMatchingLines: boolean; // if not, remove all lines that do NOT match the criteria
    Contains,
    StartsWith,
    EndsWith: string;
    MatchCase: boolean;
  end;

  TPointArray = array of TPoint;

  TTextFile = class
  private var
    FFindData: TFindData; // TTextEditor needs fast access from 20160728
  strict private const
    TEXTFILE_SIGNATURE: cardinal = $53455452;
    TEXTFILE_SIGNATURE_FPCACHE: cardinal = $41435046;
    TEXTFILE_SIGNATURE_UNDO: cardinal = $4F444E55;
  strict private type
    TFixedString = record
      strict private const
        MAXLEN = 1024;
      public
        Data: array[0..MAXLEN] of char;
        class operator Implicit(const S: string): TFixedString;
        class operator Implicit(const S: TFixedString): string;
    end;
    TStreamHeader = record
      Signature: cardinal;
      CaretPos,
      SelEndPos: TPoint;
      SelectionType: TSelectionType;
      EditMode: TEditMode;
      Modified: boolean;
      FileName: TFixedString;
      LineCount: integer;
      Bookmarks: TBookmarkList;
      ScrollPos: TPoint;
      MultiSize,
      Overwrite,
      HiddenChars,
      RulerVisible: boolean;
      ZoomLevel: integer;
      Encoding: TTextFileFormatInfo;
      RecentlyOpened: boolean;
      StrictReadOnly: boolean;
      UseLineClasses: boolean;
      FPClassName: TFixedString;
      // array[0..LineCount - 1] of record LineTxt, LineClass: string end;
    end;
  strict private var
    FLines: array of string;
    FClasses: array of string;
    FCaretPos: TCaretPos;
    FOnCaretPosChange: TNotifyEvent;
    FOnCaretPosSelChange: TChangeEvent;
    FOnChange: TChangeEvent;
    FCaretAfterEOL: boolean;
    FOnInputError: TNotifyEvent;
    FOnReadOnlyError: TNotifyEvent;
    FAutoIndent: boolean;
    FIndentSize: integer;
    FEditMode: TEditMode;
    FModified: boolean;
    FFileName: TFileName;
    FOnModified: TNotifyEvent;
    FAutoReplaceItems: TAutoReplaceItems;
    FHistoryManager: THistoryManager;
    FFindDataActualLength: integer;
    FOnFindDataClear: TNotifyEvent;
    FFindResultValid: boolean;
    FFindQuery: TFindQuery; // stored by Find, used by ReplaceAll immediately afterwards
    FSingleLine: boolean;
    FOnLineChange: TLineChangeEvent;
    FOnControlRemoved: TControlEvent;
    FOnLineClassChange: TLineEvent;
    FControlAware: boolean;
    FOnGetControlText: TGetControlTextEvent;
    FBookmarks: TBookmarkList;
    FOnBookmarksMoved: TNotifyEvent;
    FWrapAt: string;
    FEditorState: TEditorState;
    FLineComparer: TLineComparer;
    FSortReverseOrder: boolean;
    FDesiredCol: integer; {in chars}
    FPreserveDesiredCol: boolean;
    FMultiAddLineMode: boolean;
    FEncoding: TTextFileFormatInfo;
    FRecentlyOpened: boolean;
    FOnLockVisualUpdates,
    FOnUnlockVisualUpdates: TNotifyEvent;
    FStrictReadOnly: boolean;
    FUseLineClasses: boolean;
    function GetLine(Index: Integer): string;
    procedure SetLine(Index: Integer; const Value: string);
    function GetClass(Index: Integer): string;
    procedure SetClass(Index: Integer; const Value: string);
    function GetLineCount: integer; inline;
    function GetPhysicalLineWidth(Index: Integer): integer; inline;
    function GetVirtualLineWidth(Index: Integer): integer; inline;
    function GetMaxLineWidth: integer;
    function GetChar(Y, X: Integer): char; overload;
    function GetChar(APoint: TPoint): char; overload; inline;
    procedure CaretPosChange(Sender: TObject);
    procedure CaretPosSelChange(Sender: TObject; ChangeType: TChangeType; Data1,
      Data2, Data3, Data4: integer);
    procedure Changed(ChangeType: TChangeType; Data1: integer = 0; Data2: integer = 0;
      Data3: integer = 0; Data4: integer = 0);
    procedure PostFileChanged(const NumLines: integer);
    procedure InternalAddLine(const ALine: string; const AClassName: string);
    procedure InternalAddLines(const NumLines: integer);
    procedure InternalInsertLine(const LineIndex: integer; const ALine: string; const AClassName: string);
    procedure InternalInsertLines(const LineIndex: integer; const NumLines: integer);
    procedure InternalDeleteLine(const LineIndex: integer); inline;
    procedure InternalDeleteLines(const LineIndex: integer; const NumLines: integer);
    procedure IssueInputError;
    function GetIndentOnReturn(out Len: integer): string; overload;
    function TextIsMultiline(const AText: string): boolean;
    function GetFirstLine(const AText: string): string;
    function GetVirtualSpace: string; overload;
    function GetVirtualSpace(LineIndex, Col: integer): string; overload;
    function GetText: string;
    function GetClassesAsText: string;
    procedure SetText(const Value: string); overload;
    procedure SetText(const Value, Classes: string); overload;
    function GetNumCharacters: integer;
    function GetVirtualTextLength: integer;
    function GetPhysicalTextLength: integer;
    function GetClassLength: integer;
    function GetSelText: string;
    procedure InternalZero;
    function GetSelLength: integer;
    procedure SetSelLength(const Value: integer);
    procedure IssueReadOnlyError;
    procedure Modified;
    function PrevChar(const APoint: TPoint): TPoint;
    function NextChar(const APoint: TPoint): TPoint;
    procedure LoadAutoReplaceItems;
    function GetSelStart: integer;
    procedure SetSelStart(const Value: integer);
    procedure InternalClearFindData;
    procedure ClearFindData;
    procedure AddFindData(const A, B: TPoint); overload; inline;
    procedure AddFindData(const A: TPoint); overload; inline;
    procedure EndAddFindData;
    function GetFindData(Index: integer): TTextSpan;
    function GetFindCount: integer;
    procedure ReplaceInLineDiffWidth(const ReplaceText: string);
    procedure ReplaceInLineSameWidth(const ReplaceText: string);
    procedure ReplaceMultilineDiffWidth(const ReplaceText: string);
    procedure ReplaceMultilineSameWidth(const ReplaceText: string);
    function CompareFindQuery(const AFindQuery: TFindQuery): boolean;
    function HasQueryResult(const AFindQuery: TFindQuery): boolean;
    function GetSingleLineText: string;
    procedure SetSingleLine(const Value: boolean);
    procedure SetCaretAfterEOL(const Value: boolean);
    procedure LineArrayChanged;
    function IsControlLine: boolean; overload; inline;
    function IsControlLine(LineIndex: integer): boolean; overload; inline;
    procedure LineClassChanged(LineIndex: integer);
    function GetCurrentClass: string;
    function GetControlText(LineIndex: integer): string;
    function GetDecoratedControlText(LineIndex: integer): string; inline;
    function GetEmptyBookmarkIndex: integer;
    function GetBookmark(Index: Integer): TPoint;
    function GetBookmarkCount: integer;
    function GetUsedBookmarkCount: integer;
    function GetHasBookmarks: boolean;
    function PushBookmarks(LineIndex, ColIndex: integer; NumChars: integer = 1): boolean; // e.g. InsertChar
    function PushMultiCarets(var ACarets: TPointArray; LineIndex, ColIndex: integer; NumChars: integer = 1): boolean;
    function PushBookmarksInternal(LineIndex: integer; NumChars: integer = 1): boolean; // e.g. RemoveIndent
    function PushBookmarksEx(FirstLine, LastLine: integer; NumChars: integer = 1): boolean; // e.g. AddIndent
    function QushBookmarks(LineIndex, ColIndex: integer): boolean; // e.g. Backspace #13#10
    function QushBookmarksEx(SelectionType: TSelectionType; // e.g. ClearSelection
      const FirstPoint, SecondPoint: TPoint): boolean;
    function RushBookmarks(const FirstPoint: TPoint): boolean; // e.g. Return
    function RushBookmarksInternal(const FirstPoint: TPoint): boolean; // e.g. Return
    function RushBookmarksEx(const FirstPoint, SecondPoint: TPoint): boolean; // e.g. InsertText
    function SushBookmarks(FirstLine, SecondLine: integer; Silent: boolean = false): boolean; // e.g. SwapLines
    function TushBookmarks(Line: integer; NumLines: integer = 1): boolean; // e.g. InsertLine
    function TushBookmarksInternal(Line: integer; NumLines: integer = 1): boolean; // e.g. InsertLine
    function DeleteBookmarksOnLine(Line: integer): boolean;
    procedure BookmarksMoved;
    procedure InternalSwapLines(FirstLine, SecondLine: integer;
      BookmarkAware: boolean = true);
    procedure SortRecursive(AFirstLine, ALastLine: integer; BookmarkAware: boolean = true);
    procedure SetChar(Y, X: Integer; const Value: char);
    procedure IntersectFindDataWithSelection;
    procedure ApplyUndoRecord(const UndoData: TUndoDataItem);
    procedure LockVisualUpdates;
    procedure UnlockVisualUpdates;
    function LineMatches(LineIndex: integer;
      const ACriteria: TFilterOptions): boolean;
    function GetAutoReplaceItem(Index: integer): TAutoReplaceItem;
    function GetAutoReplaceItemCount: integer;
    procedure SanitizeSelection;
  public
    constructor Create;
    destructor Destroy; override;
    procedure Clear;
    function GetIndexOfPoint(const APoint: TPoint): integer;
    function GetPhysicalIndexOfPoint(const APoint: TPoint): integer; { physical in the sense that #13#10 is included; controls are still 1 char }
    function GetPhysicalPhysicalIndexOfPoint(const APoint: TPoint): integer; { physical in both senses }
    function GetPointOfIndex(const Index: integer): TPoint;
    function CharacterExists(Y, X: integer): boolean; overload; inline;
    function CharacterExists(APoint: TPoint): boolean; overload; inline;
    function CharacterExistsEx(Y, X: integer): boolean; overload; inline;
    function CharacterExistsEx(APoint: TPoint): boolean; overload; inline;
    function ValidCaretPos(APoint: TPoint): boolean; inline;
    function AtEOL: boolean; inline;
    function AtOrBeyondEOL: boolean; inline;
    function BeyondEOL: boolean; inline;
    function AtEOF: boolean; inline;
    function AtOrBeyondEOF: boolean; inline;
    function AtLastLine: boolean; inline;
    function AtSOF: boolean; inline;
    function CurrentLine: string;
    function LineToRight: string;
    function LineToLeft: string;
    procedure GotoSOF(Selection: boolean = false);
    procedure GotoEOF(Selection: boolean = false);
    procedure GotoBottomRight(Selection: boolean = false);
    procedure AddLine(const ALine: string; const AClassName: string); overload;
    procedure AddLine(const ALine: string); overload;
    procedure BeginAddLine;
    procedure EndAddLine;
    procedure InsertLine(const ALine: string; const AClassName: string; LineIndex: integer); overload;
    procedure InsertLine(const ALine: string; LineIndex: integer); overload;
    procedure InsertChar(const AChar: char; const Overwrite: boolean = false);
    procedure MultiInsertChar(var ACarets: TPointArray; const AChar: char; const Overwrite: boolean = false);
    procedure Backspace(Word: boolean = false);
    procedure MultiBackspace(var ACarets: TPointArray);
    procedure Delete(Word: boolean = false);
    procedure Left(Word: boolean = false; Selection: boolean = false; Block: boolean = false);
    procedure Right(Word: boolean = false; Selection: boolean = false; Block: boolean = false);
    procedure Up(Selection: boolean = false; Block: boolean = false);
    procedure Down(Selection: boolean = false; Block: boolean = false);
    procedure Return;
    procedure Home(AFile: boolean = false; Selection: boolean = false);
    procedure KEnd(AFile: boolean = false; Selection: boolean = false);
    function HasSelection: boolean; inline;
    function SelectionIsMultiline: boolean; inline;
    procedure ClearSelection;
    function PrevWordBoundary(Point: TPoint): integer; overload; inline;
    function NextWordBoundary(Point: TPoint): integer; overload; inline;
    function PrevWordBoundary(Y, X: integer): integer; overload;
    function NextWordBoundary(Y, X: integer): integer; overload;
    function PrevWordBoundary: integer; overload;
    function NextWordBoundary: integer; overload;
    function GetIndent(LineIndex: integer): integer; overload;
    function GetIndent: integer; overload;
    function LineIsEmpty(const LineIndex: integer): boolean;
    procedure InsertText(const AText: string);
    procedure MultiInsertText(var ACarets: TPointArray; const AText: string);
    procedure InsertTextAsBlock(const AText: string);
    procedure SurroundText(const APrefix, APostfix: string);
    procedure CutToClipboard;
    procedure CopyToClipboard;
    function PasteFromClipboard: boolean;
    function PasteFromClipboardAsBlock: boolean;
    procedure ClearLine(LineIndex: integer); overload;
    procedure ClearLine; overload;
    function SwapLines(FirstLine, SecondLine: integer): boolean;
    function SwapLinesAbove: boolean;
    function SwapLinesBelow: boolean;
    function ReplaceCodepoint: boolean;
    function IsCharInRgn(X, Y: integer; SelectionType: TSelectionType; const FirstPoint, SecondPoint: TPoint): boolean; overload;
    function IsCharInRgn(const Point: TPoint; SelectionType: TSelectionType; const FirstPoint, SecondPoint: TPoint): boolean; overload; inline;
    function IsCharSel(const X, Y: integer): boolean; overload;
    function IsCharSel(const Point: TPoint): boolean; overload; inline;
    function IsCharFound(const X, Y: integer): boolean;
    procedure SelectAll;
    procedure SelectNone;
    procedure SelectAllNone;
    function AllSelected: boolean;
    function GetWordBoundary(const Point: TPoint; out StartPos, EndPos: integer; PascalIdent: boolean = false): boolean; overload;
    function GetWordBoundary(out StartPos, EndPos: integer; PascalIdent: boolean = false): boolean; overload;
    function GetWord(const Point: TPoint; PascalIdent: boolean = false): string; overload;
    function GetWord(PascalIdent: boolean = false): string; overload;
    function GetURLAtCaret(out AURL: string): boolean;
    function SelectWord: boolean;
    procedure SelectLines(const ALineA, ALineB: integer);
    procedure SelectLine(const ALineIndex: integer); overload;
    procedure SelectLine; overload;
    procedure NewFile;
    procedure NewFileAndInitUndo;
    procedure SaveToFile(const FileName: TFileName; TrimRight: boolean = false;
      AExport: boolean = false);
    procedure LoadFromFile(const FileName: TFileName; Encoding: TEncoding;
      ClassAware: boolean = false);
    procedure LoadFromFileAndInitUndo(const FileName: TFileName; Encoding: TEncoding);
    procedure AddIndent;
    procedure RemoveIndent;
    procedure RemoveAllIndent;
    function CanAutoReplace(out StartPos, Index: integer): boolean;
    procedure DoAutoReplace(const StartPos, Index: integer);
    function AutoReplace: boolean;
    function MatchBracket(const BracketPoint: TPoint): TPoint;
    procedure AddUndoRecord(const AComment: string; UID: UNDONAMEID); overload;
    procedure AddUndoRecord; overload;
    procedure ClearUndoHistory;
    function CanUndo: boolean;
    function Undo: boolean;
    function CanRedo: boolean;
    function Redo: boolean;
    function GotoHistoryVersion(Index: integer): boolean;
    function Find(AFindData: TFindQuery; AInternal: boolean = false): integer;
    function ReplaceAll(const ReplaceText: string; SelOnly: boolean = false): integer;
    function NumCharsOfType(CharTestFunction: TCharTestFunction): integer;
    function GetFileStatistics(AFileStatisticsFlags: TFileStatisticsFlags = FILE_STAT_ALL): TFileStatistics;
    function GetUnicodeBlockStatistics: TIntegerArray;
    function GetControlCharCount: integer;
    function GetNoncharacterCount: integer;
    function LineExists(LineIndex: integer): boolean; inline;
    function DeleteControlAtLine(const LineIndex: integer): boolean;
    procedure TrimRight;
    procedure ClearBookmarks;
    procedure AddBookmark(AIndex: integer; const APoint: TPoint); overload;
    procedure AddBookmark(AIndex: integer); overload;
    function AddBookmark(const APoint: TPoint): integer; overload;
    function AddBookmark: integer; overload;
    function GotoBookmark(AIndex: integer): boolean;
    function RemoveGhostBookmarks: boolean;
    function ChrTransform(Transformation: TChrTransformFunc): boolean;
    procedure ChrTransformText(Transformation: TChrTransformFunc);
    function FillWithChar(const AChar: char): boolean;
    property CaretPos: TCaretPos read FCaretPos;
    property Lines[Index: Integer]: string read GetLine write SetLine;
    property Classes[Index: Integer]: string read GetClass write SetClass;
    property PhysicalLineWidths[Index: Integer]: integer read GetPhysicalLineWidth;
    property VirtualLineWidths[Index: Integer]: integer read GetVirtualLineWidth;
    property Character[Y, X: Integer]: char read GetChar write SetChar;
    function UnsafeGetChar(Y, X: integer): char; inline;
    function IsWrappable(const AChar: char): boolean; inline;
    procedure FindWhereToWrap(ALineIndex: integer; MaxLength: integer;
      var AWrapList: TIntegerDynArray);
    procedure WordWrap(ALineLength: integer = 80; ANice: boolean = true;
      AChr: char = #0);
    function Sort(AFirstLine, ALastLine: integer; BookmarkAware: boolean): boolean; overload;
    function Sort(BookmarkAware: boolean): boolean; overload;
    function SortSelection(BookmarkAware: boolean): boolean;
    property LineComparer: TLineComparer read FLineComparer write FLineComparer;
    property SortReverseOrder: boolean read FSortReverseOrder write FSortReverseOrder default false;
    function MakeLinesUnique: boolean;
    procedure TruncateAt(AFirstLine, ALastLine, AIndex: integer;
      AChar: char = #0; PreserveChar: boolean = false; AReverse: boolean = false);
    procedure Filter(const AFilterOptions: TFilterOptions);

    procedure SaveToStream(AStream: TStream);
    procedure CreateDataStream(out Data: pointer; out Len: UInt64);
    procedure LoadFromStream(AStream: TStream);
    procedure LoadFromBuffer(const Data: pointer; const Len: UInt64);

    property MaxLineWidth: integer read GetMaxLineWidth;
    property LineCount: integer read GetLineCount;
    property CaretAfterEOL: boolean read FCaretAfterEOL write SetCaretAfterEOL default true;
    property AutoIndent: boolean read FAutoIndent write FAutoIndent;
    property NumCharacters: integer read GetNumCharacters;
    property VirtualTextLength: integer read GetVirtualTextLength;
    property PhysicalTextLength: integer read GetPhysicalTextLength;
    property PlainText: string read GetText write SetText;
    property SelText: string read GetSelText write InsertText;
    property SelStart: integer read GetSelStart write SetSelStart;
    property SelLength: integer read GetSelLength write SetSelLength;
    property IndentSize: integer read FIndentSize write FIndentSize;
    property EditMode: TEditMode read FEditMode write FEditMode default emText;
    property FileModified: boolean read FModified write FModified default false;
    property FileName: TFileName read FFileName write FFileName;
    property HistoryManager: THistoryManager read FHistoryManager;
    property FindData[Index: integer]: TTextSpan read GetFindData;
    property FindCount: integer read GetFindCount;
    property SingleLine: boolean read FSingleLine write SetSingleLine default false;

    { Setting ControlAware to false while line controls are present will cause undefined
      behaviour. Remove all line controls prior to setting this flag to false. At
      text-editor level and above, use public method DeleteAllLineControls. }
    property ControlAware: boolean read FControlAware write FControlAware default false;

    function Empty: boolean;
    property Bookmarks[Index: Integer]: TPoint read GetBookmark write AddBookmark;
    property BookmarkCount: integer read GetBookmarkCount;
    property UsedBookmarkCount: integer read GetUsedBookmarkCount;
    property HasBookmarks: boolean read GetHasBookmarks;
    property WrapAt: string read FWrapAt write FWrapAt;
    property EditorState: TEditorState read FEditorState write FEditorState;
    property Encoding: TTextFileFormatInfo read FEncoding write FEncoding;
    property RecentlyOpened: boolean read FRecentlyOpened;
    property StrictReadOnly: boolean read FStrictReadOnly write FStrictReadOnly default false;
    property UseLineClasses: boolean read FUseLineClasses write FUseLineClasses default false;

    property AutoReplaceItems[Index: integer]: TAutoReplaceItem read GetAutoReplaceItem;
    property AutoReplaceItemCount: integer read GetAutoReplaceItemCount;

    property OnChange: TChangeEvent read FOnChange write FOnChange;
    property OnCaretPosChange: TNotifyEvent read FOnCaretPosChange write FOnCaretPosChange;
    property OnCaretPosSelChange: TChangeEvent read FOnCaretPosSelChange write FOnCaretPosSelChange;
    property OnFileModified: TNotifyEvent read FOnModified write FOnModified;
    property OnInputError: TNotifyEvent read FOnInputError write FOnInputError;
    property OnReadOnlyError: TNotifyEvent read FOnReadOnlyError write FOnReadOnlyError;
    property OnFindDataClear: TNotifyEvent read FOnFindDataClear write FOnFindDataClear;
    property OnLineChange: TLineChangeEvent read FOnLineChange write FOnLineChange;
    property OnControlRemoved: TControlEvent read FOnControlRemoved write FOnControlRemoved;
    property OnLineClassChange: TLineEvent read FOnLineClassChange write FOnLineClassChange;
    property OnGetControlText: TGetControlTextEvent read FOnGetControlText write FOnGetControlText;
    property OnBookmarksMoved: TNotifyEvent read FOnBookmarksMoved write FOnBookmarksMoved;
    property OnLockVisualUpdates: TNotifyEvent read FOnLockVisualUpdates write FOnLockVisualUpdates;
    property OnUnlockVisualUpdates: TNotifyEvent read FOnUnlockVisualUpdates write FOnUnlockVisualUpdates;
  end;

const
  EDITOR_COMMAND_RIGHT = 1;
  EDITOR_COMMAND_LEFT = 2;
  EDITOR_COMMAND_DOWN = 3;
  EDITOR_COMMAND_UP = 4;
  EDITOR_COMMAND_HOME = 5;
  EDITOR_COMMAND_END = 6;
  EDITOR_COMMAND_PAGE_UP = 7;
  EDITOR_COMMAND_PAGE_DOWN = 8;
  EDITOR_COMMAND_BACKSPACE = 9;
  EDITOR_COMMAND_DELETE = 10;
  EDITOR_COMMAND_CLEAR_SELECTION = 11;
  EDITOR_COMMAND_SELECT_ALL = 12;
  EDITOR_COMMAND_SELECT_NONE = 13;
  EDITOR_COMMAND_SELECT_ALL_NONE = 14;
  EDITOR_COMMAND_SELECT_WORD = 15;
  EDITOR_COMMAND_SELECT_LINE = 16;
  EDITOR_COMMAND_CLEAR_LINE = 17;
  EDITOR_COMMAND_CUT = 18;
  EDITOR_COMMAND_COPY = 19;
  EDITOR_COMMAND_PASTE = 20;
  EDITOR_COMMAND_UNDO = 21;
  EDITOR_COMMAND_REDO = 22;
  EDITOR_COMMAND_CLEAR_UNDO_BUFFER = 23;
  EDITOR_COMMAND_GOTO_SOF = 24;
  EDITOR_COMMAND_GOTO_EOF = 25;
  EDITOR_COMMAND_RETURN = 26;
  EDITOR_COMMAND_CHAR = 27;
  EDITOR_COMMAND_GET_AT_SOF = 28;
  EDITOR_COMMAND_GET_AT_EOL = 29;
  EDITOR_COMMAND_GET_BEYOND_EOL = 30;
  EDITOR_COMMAND_GET_AT_EOF = 31;
  EDITOR_COMMAND_GET_AT_LAST_LINE = 32;
  EDITOR_COMMAND_GET_HAS_SELECTION = 33;
  EDITOR_COMMAND_GET_LINE_NUMBER_0 = 34;
  EDITOR_COMMAND_GET_COL_NUMBER_0 = 35;
  EDITOR_COMMAND_GET_CHR_INDEX = 36;
  EDITOR_COMMAND_GOTO_POINT = 37;
  EDITOR_COMMAND_GOTO_INDEX = 38;
  EDITOR_COMMAND_GET_SEL_LENGTH = 39;
  EDITOR_COMMAND_SET_SEL_LENGTH = 40;
  EDITOR_COMMAND_GET_EDIT_MODE = 41;
  EDITOR_COMMAND_SET_EDIT_MODE = 42;
  EDITOR_COMMAND_GET_SELECTION_MODE = 43;
  EDITOR_COMMAND_SET_SELECTION_MODE = 44;
  EDITOR_COMMAND_GET_OVERWRITE = 45;
  EDITOR_COMMAND_SET_OVERWRITE = 46;
  EDITOR_COMMAND_GET_AUTO_REPLACE = 47;
  EDITOR_COMMAND_SET_AUTO_REPLACE = 48;
  EDITOR_COMMAND_GET_CHAR = 49;
  EDITOR_COMMAND_ADD_INDENT = 50;
  EDITOR_COMMAND_REMOVE_INDENT = 51;
  EDITOR_COMMAND_TRIM_INDENT = 52;
  EDITOR_COMMAND_SWAP_UP = 53;
  EDITOR_COMMAND_SWAP_DOWN = 54;
  EDITOR_COMMAND_GET_AUTO_INDENT = 55;
  EDITOR_COMMAND_SET_AUTO_INDENT = 56;
  EDITOR_COMMAND_GET_CARET_BEYOND_EOL = 57;
  EDITOR_COMMAND_SET_CARET_BEYOND_EOL = 58;
  EDITOR_COMMAND_GET_NUM_CHARACTERS = 59;
  EDITOR_COMMAND_GET_TEXT_SIZE = 60;
  EDITOR_COMMAND_GET_NUM_LINES = 61;
  EDITOR_COMMAND_GET_MAX_WIDTH = 62;
  EDITOR_COMMAND_SCROLL_TO_CARET = 63;
  EDITOR_COMMAND_REPLACE_TOKEN = 64;
  EDITOR_COMMAND_REPLACE_CODEPOINT = 65;
  EDITOR_COMMAND_UPDATE_SCROLLBARS = 66;
  EDITOR_COMMAND_UPDATE_CARET = 67;
  EDITOR_COMMAND_UPDATE_CURSOR = 68;
  EDITOR_COMMAND_REDRAW = 69;
  EDITOR_COMMAND_REDRAW_LINE = 70;
  EDITOR_COMMAND_REDRAW_LINE_RANGE = 71;
  EDITOR_COMMAND_REDRAW_BLOCK = 72;
  EDITOR_COMMAND_GET_MODIFIED = 73;
  EDITOR_COMMAND_SET_MODIFIED = 74;
  EDITOR_COMMAND_NEW = 75;
  EDITOR_COMMAND_CLEAR = 76;
  EDITOR_COMMAND_OPEN = 77;
  EDITOR_COMMAND_SAVE = 78;
  EDITOR_COMMAND_GET_HIDDEN = 79;
  EDITOR_COMMAND_SET_HIDDEN = 80;
  EDITOR_COMMAND_SET_SELECTION = 81;
  EDITOR_COMMAND_GET_MATCH_BRACKETS = 82;
  EDITOR_COMMAND_SET_MATCH_BRACKETS = 83;
  EDITOR_COMMAND_GET_BRACKET_HIGHLIGHT = 84;
  EDITOR_COMMAND_GET_SCROLL_POS_X = 85;
  EDITOR_COMMAND_GET_SCROLL_POS_Y = 86;
  EDITOR_COMMAND_SET_SCROLL_POS = 87;
  EDITOR_COMMAND_REDRAW_CHAR = 88;
  EDITOR_COMMAND_REDRAW_CHARS = 89;
  EDITOR_COMMAND_GET_INDENT = 90;
  EDITOR_COMMAND_SET_INDENT = 91;
  EDITOR_COMMAND_GET_TAB_LENGTH = 92;
  EDITOR_COMMAND_SET_TAB_LENGTH = 93;
  EDITOR_COMMAND_GET_SINGLE_LINE = 94;
  EDITOR_COMMAND_SET_SINGLE_LINE = 95;
  EDITOR_COMMAND_GET_LABEL_MODE = 96;
  EDITOR_COMMAND_SET_LABEL_MODE = 97;
  EDITOR_COMMAND_GET_ELLIPSIS_MODE = 98;
  EDITOR_COMMAND_SET_ELLIPSIS_MODE = 99;
  EDITOR_COMMAND_GET_INPUT_TRANSFORM = 100;
  EDITOR_COMMAND_SET_INPUT_TRANSFORM = 101;
  EDITOR_COMMAND_GET_NUMBERS_ONLY = 102;
  EDITOR_COMMAND_SET_NUMBERS_ONLY = 103;
  EDITOR_COMMAND_GET_PASSWORD_CHAR = 104;
  EDITOR_COMMAND_SET_PASSWORD_CHAR = 105;
  EDITOR_COMMAND_GET_UNICODE_FALLBACK = 106;
  EDITOR_COMMAND_SET_UNICODE_FALLBACK = 107;
  EDITOR_COMMAND_ESCAPE = 108;
  EDITOR_COMMAND_USE_DEFAULT_FALLBACK_FONTS = 109;
  EDITOR_COMMAND_SHOW_BALLOON = 110;
  EDITOR_COMMAND_HIDE_BALLOON = 111;
  EDITOR_COMMAND_SHOW_BALLOON_POS = 112;
  EDITOR_COMMAND_IS_BALLOON_VISIBLE = 113;
  EDITOR_COMMAND_ADJUST_HEIGHT = 114;
  EDITOR_COMMAND_GET_UNDO_LENGTH = 115;
  EDITOR_COMMAND_GET_UNDO_SIZE = 116;
  EDITOR_COMMAND_GET_UNDO_MAX_SIZE = 117;
  EDITOR_COMMAND_SET_UNDO_MAX_SIZE = 118;
  EDITOR_COMMAND_GET_UNDO_FIRST_INDEX = 119;
  EDITOR_COMMAND_GET_UNDO_LAST_INDEX = 120;
  EDITOR_COMMAND_GET_UNDO_POSITION = 121;
  EDITOR_COMMAND_WINDOWS_MESSAGE = 122;
  EDITOR_COMMAND_COPY_ALL = 123;
  EDITOR_COMMAND_FIND = 124;
  EDITOR_COMMAND_GET_FIND_COUNT = 125;
  EDITOR_COMMAND_FIND_NEXT = 126;
  EDITOR_COMMAND_FIND_PREV = 127;
  EDITOR_COMMAND_FIND_FROM_TOP = 128;
  EDITOR_COMMAND_GET_START_OVER = 129;
  EDITOR_COMMAND_SET_START_OVER = 130;
  EDITOR_COMMAND_REPLACE_ALL = 131;
  EDITOR_COMMAND_ADD_UNDO_RECORD = 132;
  EDITOR_COMMAND_POSTTYPE = 133;
  EDITOR_COMMAND_TYPE_TIMER_EMD = 134;
  EDITOR_COMMAND_TYPE_TIMER_DISABLE = 135;
  EDITOR_COMMAND_TYPE_TIMER_DISCONNECT = 136;
  EDITOR_COMMAND_TYPE_TIMER_CONNECT = 137;
  EDITOR_COMMAND_GET_ENABLED = 138;
  EDITOR_COMMAND_SET_ENABLED = 139;
  EDITOR_COMMAND_IS_FOCUSED = 140;
  EDITOR_COMMAND_TRY_FOCUS = 141;
  EDITOR_COMMAND_GET_FIRST_VISIBLE_LINE = 142;
  EDITOR_COMMAND_GET_LAST_VISIBLE_LINE = 143;
  EDITOR_COMMAND_RECOMPUTE_HOR_EXTENT = 144;
  EDITOR_COMMAND_ACTIVATE_CONTROL = 145;
  EDITOR_COMMAND_REMOVE_LINE_CONTROL = 146;
  EDITOR_COMMAND_ADD_LINE_CONTROL = 147;
  EDITOR_COMMAND_ADD_GRAPHICS = 148;
  EDITOR_COMMAND_INSERT_LINE_CONTROL = 149;
  EDITOR_COMMAND_INSERT_GRAPHICS = 150;
  EDITOR_COMMAND_TRIM_RIGHT = 151;
  EDITOR_COMMAND_BOOKMARK_SET_MENU = 152;
  EDITOR_COMMAND_BOOKMARK_GO_MENU = 153;
  EDITOR_COMMAND_BOOKMARK_CLEAR_MENU = 154;
  EDITOR_COMMAND_BOOKMARK_SET = 155;
  EDITOR_COMMAND_BOOKMARK_GO = 156;
  EDITOR_COMMAND_BOOKMARK_CLEAR = 157;
  EDITOR_COMMAND_BOOKMARK_CLEAR_ALL = 158;
  EDITOR_COMMAND_CLASS_MENU = 159;
  EDITOR_COMMAND_CLASS_USE = 160;
  EDITOR_COMMAND_CLASS_REMOVE = 161;
  EDITOR_COMMAND_SET_FP = 162;
  EDITOR_COMMAND_EXPORT_HTML = 163;
  EDITOR_COMMAND_OPEN_URL_AT_CARET = 164;
  EDITOR_COMMAND_SELECT_LINE_INDEX = 165;
  EDITOR_COMMAND_SELECT_LINE_RANGE = 166;
  EDITOR_COMMAND_DISABLE_SCROLL_TO_CARET = 167;
  EDITOR_COMMAND_ENABLE_SCROLL_TO_CARET = 168;
  EDITOR_COMMAND_CREATE_SELECTION = 169;
  EDITOR_COMMAND_CREATE_BLOCK_SELECTION = 170;
  EDITOR_COMMAND_GET_LINE_HIGHLIGHT = 171;
  EDITOR_COMMAND_SET_LINE_HIGHLIGHT = 172;
  EDITOR_COMMAND_REDRAW_RULER = 173;
  EDITOR_COMMAND_REDRAW_RULER_LINE = 174;
  EDITOR_COMMAND_PRINT = 175;
  EDITOR_COMMAND_PRINT_SELECTION = 176;
  EDITOR_COMMAND_SET_PRINT_MARGINS = 177;
  EDITOR_COMMAND_SET_PRINT_WW_OPTIONS = 178;
  EDITOR_COMMAND_PRINT_DIALOG = 179;
  EDITOR_COMMAND_GET_PRINT_VMARGIN = 180;
  EDITOR_COMMAND_GET_PRINT_HMARGIN = 181;
  EDITOR_COMMAND_GET_PRINT_WW_OPTIONS = 182;
  EDITOR_COMMAND_GET_PRINT_WW_CHAR = 183;
  EDITOR_COMMAND_GET_PRINT_WW_COLOR = 184;
  EDITOR_COMMAND_WORDWRAP = 185;
  EDITOR_COMMAND_UPPER_CASE = 186;
  EDITOR_COMMAND_LOWER_CASE = 187;
  EDITOR_COMMAND_INVERT_CASE = 188;
  EDITOR_COMMAND_SEL_UPPER_CASE = 189;
  EDITOR_COMMAND_SEL_LOWER_CASE = 190;
  EDITOR_COMMAND_SEL_INVERT_CASE = 191;
  EDITOR_COMMAND_CAMEL_CASE = 192;
  EDITOR_COMMAND_SENTENCE_CASE = 193;
  EDITOR_COMMAND_SEL_CAMEL_CASE = 194;
  EDITOR_COMMAND_SEL_SENTENCE_CASE = 195;
  EDITOR_COMMAND_SEL_TRANSFORM_MENU = 196;
  EDITOR_COMMAND_ROT13 = 197;
  EDITOR_COMMAND_CAESAR = 198;
  EDITOR_COMMAND_VIGENERE = 199;
  EDITOR_COMMAND_SEL_ROT13 = 200;
  EDITOR_COMMAND_SEL_CAESAR = 201;
  EDITOR_COMMAND_SEL_VIGENERE = 202;
  EDITOR_COMMAND_UPDATE_SCROLL_MODE = 203;
  EDITOR_COMMAND_GET_SCROLL_MODE = 204;
  EDITOR_COMMAND_SORT = 205;
  EDITOR_COMMAND_SORT_ALL = 206;
  EDITOR_COMMAND_SORT_SEL = 207;
  EDITOR_COMMAND_SET_LINE_COMPARER = 208;
  EDITOR_COMMAND_GET_LINE_COMPARER = 209;
  EDITOR_COMMAND_SET_SORT_REVERSE = 210;
  EDITOR_COMMAND_GET_SORT_REVERSE = 211;
  EDITOR_COMMAND_MAKE_LINES_UNIQUE = 212;
  EDITOR_COMMAND_CLI_NEW_PROMPT = 213;
  EDITOR_COMMAND_CLI_WRITELN = 214;
  EDITOR_COMMAND_ABORT_SCRIPT = 215;
  EDITOR_COMMAND_WRITE_INT = 216;
  EDITOR_COMMAND_ABORT_SCRIPT_IF_EOL = 217;
  EDITOR_COMMAND_ABORT_SCRIPT_IF_LL = 218;
  EDITOR_COMMAND_ABORT_SCRIPT_IF_EOF = 219;
  EDITOR_COMMAND_ABORT_SCRIPT_IF_SOF = 220;
  EDITOR_COMMAND_SET_SCRIPT_COUNTER = 221;
  EDITOR_COMMAND_GET_SCRIPT_COUNTER = 222;
  EDITOR_COMMAND_GET_LINE_NUMBER_1 = 223;
  EDITOR_COMMAND_GET_COL_NUMBER_1 = 224;
  EDITOR_COMMAND_WRITE_DATE = 225;
  EDITOR_COMMAND_WRITE_TIME = 226;
  EDITOR_COMMAND_WRITE_DATETIME = 227;
  EDITOR_COMMAND_GET_TICKCOUNT = 228;
  EDITOR_COMMAND_GET_RANDOM_INTEGER = 229;
  EDITOR_COMMAND_FIX_REMOVED_LINE_CONTROLS = 230;
  EDITOR_COMMAND_CLI_HISTORY_UP = 231;
  EDITOR_COMMAND_CLI_HISTORY_DOWN = 232;
  EDITOR_COMMAND_CLI_HISTORY_CLEAR = 233;
  EDITOR_COMMAND_CLI_HISTORY_ADD = 234;
  EDITOR_COMMAND_CLI_GET_HISTORY_LENGTH = 235;
  EDITOR_COMMAND_CLI_GET_HISTORY_INDEX = 236;
  EDITOR_COMMAND_CLI_HISTORY_RECALL = 237;
  EDITOR_COMMAND_BEGIN_ADD_LINES = 238;
  EDITOR_COMMAND_END_ADD_LINES = 239;
  EDITOR_COMMAND_GET_LISTBOX_MODE = 240;
  EDITOR_COMMAND_SET_LISTBOX_MODE = 241;
  EDITOR_COMMAND_WRITE_STRING = 242;
  EDITOR_COMMAND_WRITE_INPUT_DIALOG = 243;
  EDITOR_COMMAND_SET_AS_HYPHEN_ASTERISK_TOGGLE = 244;
  EDITOR_COMMAND_SET_MULTI_CHAR_SELECT = 245;
  EDITOR_COMMAND_GET_MULTI_CHAR_SELECT = 246;
  EDITOR_COMMAND_SET_MULTI_CHAR_REPORT_VIEW = 247;
  EDITOR_COMMAND_GET_MULTI_CHAR_REPORT_VIEW = 248;
  EDITOR_COMMAND_SET_NO_VERIFY_FONT = 249;
  EDITOR_COMMAND_SET_DOUBLE_BUFFERING = 250;
  EDITOR_COMMAND_REPLACE_ALL_IN_SELECTION = 251;
  EDITOR_COMMAND_SET_BITMAP_EFFECT = 252;
  EDITOR_COMMAND_GET_BITMAP_EFFECT = 253;
  EDITOR_COMMAND_SET_DISABLED_EFFECT = 254;
  EDITOR_COMMAND_GET_DISABLED_EFFECT = 255;
  EDITOR_COMMAND_REPEAT = 256;
  EDITOR_COMMAND_REPEAT_EX_SET_NUM = 257;
  EDITOR_COMMAND_REPEAT_EX_SET_COMMAND = 258;
  EDITOR_COMMAND_REPEAT_EX = 259;
  EDITOR_COMMAND_RESTORE_MARGINS = 260;
  EDITOR_COMMAND_FILL_WITH_CHAR = 261;
  EDITOR_COMMAND_PASTE_AS_BLOCK = 262;
  EDITOR_COMMAND_TRUNCATE_AT = 263;
  EDITOR_COMMAND_TRUNCATE_AT_IN_FILE = 264;
  EDITOR_COMMAND_TRUNCATE_AT_IN_SELECTION = 265;
  EDITOR_COMMAND_GET_JUST_OPENED = 266;
  EDITOR_COMMAND_LOAD_DEFAULT_CLASSES = 267;
  EDITOR_COMMAND_BEGIN_VISUAL_UPDATE = 268;
  EDITOR_COMMAND_END_VISUAL_UPDATE = 269;
  EDITOR_COMMAND_SURROUND_SEL = 270;
  EDITOR_COMMAND_FILTER_LINES = 271;
  EDITOR_COMMAND_UPDATE_SPI = 272;
  EDITOR_COMMAND_SET_STRICT_READONLY = 273;
  EDITOR_COMMAND_REMOVE_GHOST_BOOKMARKS = 274;
  EDITOR_COMMAND_CHARACTER_FIND = 275;
  EDITOR_COMMAND_SEL_REVERSE = 276;
  EDITOR_COMMAND_CENTER_ON_SELECTION = 277;

  IMAGE_COMMAND_COPY = 1;
  IMAGE_COMMAND_REMOVE = 2;
  IMAGE_COMMAND_CHANGE = 3;

  RULER_COMMAND_PROPERTIES = 1;

type
  TFontRecord = record
    Size: integer;
    Style: TFontStyles;
    Color: TColor;
    BoxSize: TSize;
  end;

  TClassRecord = record
    Name: string;
    Format: TFontRecord;
  end;

  TClassArray = array of TClassRecord;

function FontRecord(ASize: integer; AStyle: TFontStyles; AColor: TColor): TFontRecord;
function MakeClass(const AName: string; ASize: integer; AStyle: TFontStyles; AColor: TColor): TClassRecord;

type
  TInputTransform = (itNone, itUpperCase, itLowerCase, itSuperscript,
    itSubscript, itCircled, itParenthesized, itFullStop, itDoublyCircled);

  TBalloonPersistence = (bpTime, bpScroll, bpCaretPos, bpModify, bpRemain);
  TBalloonIconKind = (bikNone = TTI_NONE, bikInfo = TTI_INFO,
    bikWarning = TTI_WARNING, bikError = TTI_ERROR, bikInfoLarge = TTI_INFO_LARGE,
    bikWarningLarge = TTI_WARNING_LARGE, bikErrorLarge = TTI_ERROR_LARGE);

  TLineControlRecord = record
    ID: integer;
    Control: TControl;
  end;

  TCSSDeclaration = record
    CSSProperty: string;
    Value: string;
  end;

  TCSSDeclarationBlock = array of TCSSDeclaration;

  TCSSRule = record
    Selector: string;
    Declarations: TCSSDeclarationBlock;
  end;

  TCSSRules = array of TCSSRule;

function MakeCSSDeclaration(const AProperty, AValue: string): TCSSDeclaration;
function MakeCSSOptionalDeclaration(const AUse: boolean; const AProperty, AValue: string): TCSSDeclaration;
function MakeCSSRule(const ASelector: string; const ADeclarations: array of TCSSDeclaration): TCSSRule;
function CSSColor(const AColor: TColor): string;

const
  SC_BLACK = $00000000;
  SC_BLUE = $00FF0000;
  SC_RED = $00000099;
  SC_GREEN = $00008000;
  SC_GRAY = $00999999;
  SC_INTENSE_RED = $000000FF;

type
  TColorScheme = record
    Default,
    Accent1,
    Accent2,
    Accent3,
    Soft,
    Intense: TColor;
  end;

const
  DEFAULT_COLORS: TColorScheme = (Default: SC_BLACK; Accent1: SC_BLUE; Accent2:
    SC_GREEN; Accent3: SC_RED; Soft: SC_GRAY; Intense: SC_INTENSE_RED);

type
  TGetLineWidthEvent = function(ALineIndex: integer): integer of object;
  TGetCharEvent = function(ALineIndex, ACol: integer): char of object;
  TGetLineCountEvent = function: integer of object;
  TGetWordEvent = function(const APoint: TPoint; APascalIdent: boolean = false): string of object;
  TGetWordBoundaryEvent = function(const APoint: TPoint; out SP, EP: integer): boolean of object;

  TFormattingProcessor = class(TComponent)
  strict private
    FUpdateLevel: integer;
    FOnChange: TNotifyEvent;
    FOnGetLineWidth: TGetLineWidthEvent;
    FOnGetChar: TGetCharEvent;
    FOnGetLineCount: TGetLineCountEvent;
    FOnGetWord: TGetWordEvent;
    FOnGetWordBoundary: TGetWordBoundaryEvent;
  strict protected
    procedure Changed;
    function TextLineWidth(ALineIndex: integer): integer; inline;
    function TextChar(ALineIndex, ACol: integer): char; inline;
    function TextLineCount: integer; inline;
    function TextGetWord(const APoint: TPoint; APascalIdent: boolean = false): string; inline;
    function TextGetWordBoundary(const APoint: TPoint; out SP, EP: integer): boolean; inline;
  public
    constructor Create(AOwner: TComponent); override;
    procedure GetCharFormat(ALineIndex: integer; ACol: integer; AChar: char;
      var AFontRecord: TFontRecord); virtual; abstract;
    function FileChangeNotification(ChangeType: TChangeType;
      Data1, Data2, Data3, Data4: Integer): TChangeRecord; virtual;
    function GetCSSRules: TCSSRules; virtual; abstract;
    function GetCharCSSClass(ALineIndex: Integer; ACol: Integer;
      AChar: Char): integer; virtual; abstract;
    procedure BeginUpdate;
    procedure EndUpdate;
    procedure ApplyColorScheme(const AColorScheme: TColorScheme); virtual; abstract;
    function GetCache(out ACache: PByte): integer; virtual; { Returns size. Returns nil and 0 if not implemented. }
    function RestoreCache(ACache: PByte; ASize: integer): boolean; virtual; { Returns true iff implemented }
    procedure ClearCache; virtual;
    property OnChange: TNotifyEvent read FOnChange write FOnChange;
    property OnGetLineWidth: TGetLineWidthEvent read FOnGetLineWidth write FOnGetLineWidth;
    property OnGetChar: TGetCharEvent read FOnGetChar write FOnGetChar;
    property OnGetLineCount: TGetLineCountEvent read FOnGetLineCount write FOnGetLineCount;
    property OnGetWord: TGetWordEvent read FOnGetWord write FOnGetWord;
    property OnGetWordBoundary: TGetWordBoundaryEvent read FOnGetWordBoundary write FOnGetWordBoundary;
  end;

  TVowelsAndConsonantsFormattingProcessor = class(TFormattingProcessor)
  private const
    DEFAULT_VOWEL_COLOR = SC_RED;
    DEFAULT_CONSONANT_COLOR = SC_GREEN;
    DEFAULT_VOWELS_BOLD = true;
    DEFAULT_CONSONANTS_BOLD = false;
  private const
    CSS_CLASS_VOWEL = 0;
    CSS_CLASS_CONSONANT = 1;

    CSS_CLASS_HIGH = CSS_CLASS_CONSONANT;
    CSS_CLASS_LENGTH = CSS_CLASS_HIGH + 1;
  private var
    FVowelColor,
    FConsonantColor: TColor;
    FVowelsBold: boolean;
    FConsonantsBold: boolean;
    procedure SetConsonantColor(const Value: TColor);
    procedure SetVowelColor(const Value: TColor);
    procedure SetConsonantsBold(const Value: boolean);
    procedure SetVowelsBold(const Value: boolean);
    function IsVowel(const AChar: Char): boolean;
  public
    constructor Create(AOwner: TComponent); override;
    procedure GetCharFormat(ALineIndex: Integer; ACol: Integer; AChar: Char;
      var AFontRecord: TFontRecord); override;
    function GetCSSRules: TCSSRules; override;
    function GetCharCSSClass(ALineIndex: Integer; ACol: Integer; AChar: Char): integer; override;
    procedure Assign(Source: TPersistent); override;
    procedure ApplyColorScheme(const AColorScheme: TColorScheme); override;
  published
    property VowelColor: TColor read FVowelColor write SetVowelColor default DEFAULT_VOWEL_COLOR;
    property ConsonantColor: TColor read FConsonantColor write SetConsonantColor default DEFAULT_CONSONANT_COLOR;
    property VowelsBold: boolean read FVowelsBold write SetVowelsBold default DEFAULT_VOWELS_BOLD;
    property ConsonantBold: boolean read FConsonantsBold write SetConsonantsBold default DEFAULT_CONSONANTS_BOLD;
  end;

  TXMLFormattingProcessor = class(TFormattingProcessor)
  private type
    TXMLChrKind = (ckXmlUndefined = -1, ckXmlText, ckXmlTag, ckXmlTagName,
      ckXmlParam, ckXmlValue, ckXmlComment, ckCDATAMarker, ckCDATA,
      ckXmlSignatureOnly);
    TFmtBreak = record
      x: integer;
      kind: TXMLChrKind;
      signature: cardinal;
    end;
  private const
    DEFAULT_TAG_COLOR = SC_BLUE;
    DEFAULT_TAG_NAME_COLOR = SC_RED;
    DEFAULT_TAG_NAME_BOLD = false;
    DEFAULT_PARAM_COLOR = SC_BLUE;
    DEFAULT_VALUE_COLOR = SC_GREEN;
    DEFAULT_COMMENT_COLOR = SC_GRAY;
    DEFAULT_CDATAM_COLOR = SC_GRAY;
    DEFAULT_CDATAM_BOLD = true;
    DEFAULT_CDATA_COLOR = SC_BLACK;
  private const
    CSS_CLASS_TEXT = 0;
    CSS_CLASS_TAG = 1;
    CSS_CLASS_TAG_NAME = 2;
    CSS_CLASS_PARAM = 3;
    CSS_CLASS_VALUE = 4;
    CSS_CLASS_COMMENT = 5;
    CSS_CLASS_CDATAMARKER = 6;
    CSS_CLASS_CDATA = 7;

    CSS_CLASS_HIGH = CSS_CLASS_CDATA;
    CSS_CLASS_LENGTH = CSS_CLASS_HIGH + 1;
  private var
    FValueColor: TColor;
    FParamColor: TColor;
    FTagColor: TColor;
    FCommentColor: TColor;
    FTokens: array of array of TFmtBreak;
    FTagNameColor: TColor;
    FTagNameBold: boolean;
    FCDATAMColor: TColor;
    FCDATAMBold: boolean;
    FCDATAColor: TColor;
    procedure SetParamColor(const Value: TColor);
    procedure SetTagColor(const Value: TColor);
    procedure SetValueColor(const Value: TColor);
    function ParseText(AFromLine: integer = 0;
      SingleLinePossibility: boolean = false; ANumLines: integer = 1): integer;
    function GetChrKind(ALineIndex, ACol: Integer): TXMLChrKind;
    procedure SetCommentColor(const Value: TColor);
    procedure PushTokensDownFrom(ALineIndex: integer);
    procedure PushTokensUpFrom(ALineIndex: integer);
    procedure SetTagNameBold(const Value: boolean);
    procedure SetTagNameColor(const Value: TColor);
    procedure SetCDATAMBold(const Value: boolean);
    procedure SetCDATAMColor(const Value: TColor);
    procedure SetCDATAColor(const Value: TColor);
  public
    constructor Create(AOwner: TComponent); override;
    procedure GetCharFormat(ALineIndex: Integer; ACol: Integer; AChar: Char;
      var AFontRecord: TFontRecord); override;
    function FileChangeNotification(ChangeType: TChangeType; Data1: Integer;
      Data2: Integer; Data3: Integer; Data4: Integer): TChangeRecord; override;
    function GetCSSRules: TCSSRules; override;
    function GetCharCSSClass(ALineIndex: Integer; ACol: Integer;
      AChar: Char): Integer; override;
    procedure Assign(Source: TPersistent); override;
    procedure ApplyColorScheme(const AColorScheme: TColorScheme); override;
  published
    property TagColor: TColor read FTagColor write SetTagColor default DEFAULT_TAG_COLOR;
    property TagNameColor: TColor read FTagNameColor write SetTagNameColor default DEFAULT_TAG_NAME_COLOR;
    property TagNameBold: boolean read FTagNameBold write SetTagNameBold default DEFAULT_TAG_NAME_BOLD;
    property ParamColor: TColor read FParamColor write SetParamColor default DEFAULT_PARAM_COLOR;
    property ValueColor: TColor read FValueColor write SetValueColor default DEFAULT_VALUE_COLOR;
    property CommentColor: TColor read FCommentColor write SetCommentColor default DEFAULT_COMMENT_COLOR;
    property CDATAMarkerColor: TColor read FCDATAMColor write SetCDATAMColor default DEFAULT_CDATAM_COLOR;
    property CDATAMarkerBold: boolean read FCDATAMBold write SetCDATAMBold default DEFAULT_CDATAM_BOLD;
    property CDATAColor: TColor read FCDATAColor write SetCDATAColor default DEFAULT_CDATA_COLOR;
  end;

  TCSSFormattingProcessor = class(TFormattingProcessor)
  private type
    TCSSChrKind = (ckCssUndefined = -1, ckCssSelector, ckCssBlockDelim,
      ckCssProperty, ckCssValue, ckCssImportant, ckCssComment);
    TFmtBreak = record
      x: integer;
      kind: TCSSChrKind;
      signature: cardinal;
    end;
  private const
    DEFAULT_SELECTOR_COLOR = SC_RED;
    DEFAULT_SELECTOR_BOLD = false;
    DEFAULT_PROPERTY_COLOR = SC_BLUE;
    DEFAULT_VALUE_COLOR = SC_BLACK;
    DEFAULT_COMMENT_COLOR = SC_GRAY;
    DEFAULT_BLOCK_DELIM_COLOR = SC_RED;
    DEFAULT_BLOCK_DELIM_BOLD = false;
    DEFAULT_IMPORTANT_COLOR = SC_INTENSE_RED;
    DEFAULT_IMPORTANT_BOLD = true;
  private const
    CSS_CLASS_SELECTOR = 0;
    CSS_CLASS_PROPERTY = 1;
    CSS_CLASS_VALUE = 2;
    CSS_CLASS_COMMENT = 3;
    CSS_CLASS_BLOCK_DELIM = 4;
    CSS_CLASS_IMPORTANT = 5;

    CSS_CLASS_HIGH = CSS_CLASS_IMPORTANT;
    CSS_CLASS_LENGTH = CSS_CLASS_HIGH + 1;
  private var
    FSelectorColor: TColor;
    FSelectorBold: boolean;
    FPropertyColor: TColor;
    FValueColor: TColor;
    FCommentColor: TColor;
    FTokens: array of array of TFmtBreak;
    FBlockDelimColor: TColor;
    FBlockDelimBold: boolean;
    FImportantBold: boolean;
    FImportantColor: TColor;
    procedure SetSelectorColor(const Value: TColor);
    procedure SetSelectorBold(const Value: boolean);
    procedure SetPropertyColor(const Value: TColor);
    procedure SetValueColor(const Value: TColor);
    procedure SetCommentColor(const Value: TColor);
    function GetChrKind(ALineIndex, ACol: Integer): TCSSChrKind;
    function ParseText(AFromLine: integer = 0;
      SingleLinePossibility: boolean = false; ANumLines: integer = 1): integer;
    procedure SetBlockDelimColor(const Value: TColor);
    procedure SetBlockDelimBold(const Value: boolean);
    procedure SetImportantBold(const Value: boolean);
    procedure SetImportantColor(const Value: TColor);
    procedure PushTokensDownFrom(ALineIndex: integer);
    procedure PushTokensUpFrom(ALineIndex: integer);
  public
    constructor Create(AOwner: TComponent); override;
    procedure GetCharFormat(ALineIndex: Integer; ACol: Integer; AChar: Char;
      var AFontRecord: TFontRecord); override;
    function FileChangeNotification(ChangeType: TChangeType; Data1: Integer;
      Data2: Integer; Data3: Integer; Data4: Integer): TChangeRecord; override;
    function GetCSSRules: TCSSRules; override;
    function GetCharCSSClass(ALineIndex: Integer; ACol: Integer;
      AChar: Char): Integer; override;
    procedure Assign(Source: TPersistent); override;
    procedure ApplyColorScheme(const AColorScheme: TColorScheme); override;
  published
    property SelectorColor: TColor read FSelectorColor write SetSelectorColor default DEFAULT_SELECTOR_COLOR;
    property SelectorBold: boolean read FSelectorBold write SetSelectorBold default DEFAULT_SELECTOR_BOLD;
    property PropertyColor: TColor read FPropertyColor write SetPropertyColor default DEFAULT_PROPERTY_COLOR;
    property ValueColor: TColor read FValueColor write SetValueColor default DEFAULT_VALUE_COLOR;
    property CommentColor: TColor read FCommentColor write SetCommentColor default DEFAULT_COMMENT_COLOR;
    property BlockDelimColor: TColor read FBlockDelimColor write SetBlockDelimColor default DEFAULT_BLOCK_DELIM_COLOR;
    property BlockDelimBold: boolean read FBlockDelimBold write SetBlockDelimBold default DEFAULT_BLOCK_DELIM_BOLD;
    property ImportantColor: TColor read FImportantColor write SetImportantColor default DEFAULT_IMPORTANT_COLOR;
    property ImportantBold: boolean read FImportantBold write SetImportantBold default DEFAULT_IMPORTANT_BOLD;
  end;

  TINIFormattingProcessor = class(TFormattingProcessor)
  private const
    DEFAULT_SECTION_COLOR = SC_RED;
    DEFAULT_SECTION_BOLD = true;
    DEFAULT_NAME_COLOR = SC_BLUE;
    DEFAULT_VALUE_COLOR = SC_BLACK;
    DEFAULT_COMMENT_COLOR = SC_GRAY;
  private const
    CSS_CLASS_SECTION = 0;
    CSS_CLASS_NAME = 1;
    CSS_CLASS_VALUE = 2;
    CSS_CLASS_COMMENT = 3;
    CSS_CLASS_EQUALS = 4;

    CSS_CLASS_HIGH = CSS_CLASS_EQUALS;
    CSS_CLASS_LENGTH = CSS_CLASS_HIGH + 1;
  private var
    FSectionColor: TColor;
    FSectionBold: boolean;
    FValueColor: TColor;
    FNameColor: TColor;
    FCommentColor: TColor;
    procedure SetSectionColor(const Value: TColor);
    procedure SetSectionBold(const Value: boolean);
    procedure SetCommentColor(const Value: TColor);
    procedure SetNameColor(const Value: TColor);
    procedure SetValueColor(const Value: TColor);
  public
    constructor Create(AOwner: TComponent); override;
    procedure GetCharFormat(ALineIndex: Integer; ACol: Integer; AChar: char;
      var AFontRecord: TFontRecord); override;
    function FileChangeNotification(ChangeType: TChangeType; Data1: Integer;
      Data2: Integer; Data3: Integer; Data4: Integer): TChangeRecord; override;
    function GetCSSRules: TCSSRules; override;
    function GetCharCSSClass(ALineIndex: Integer; ACol: Integer;
      AChar: Char): Integer; override;
    procedure Assign(Source: TPersistent); override;
    procedure ApplyColorScheme(const AColorScheme: TColorScheme); override;
  published
    property SectionColor: TColor read FSectionColor write SetSectionColor default DEFAULT_SECTION_COLOR;
    property SectionBold: boolean read FSectionBold write SetSectionBold default DEFAULT_SECTION_BOLD;
    property NameColor: TColor read FNameColor write SetNameColor default DEFAULT_NAME_COLOR;
    property ValueColor: TColor read FValueColor write SetValueColor default DEFAULT_VALUE_COLOR;
    property CommentColor: TColor read FCommentColor write SetCommentColor DEFAULT DEFAULT_COMMENT_COLOR;
  end;

  TPascalFormattingProcessor = class(TFormattingProcessor)
  private type
    TPascalChrKind = (ckPasUndefined = -1, ckPasKeyword, ckPasString,
      ckPasNumber, ckPasComment, ckPasCompilerDirective);
    TFmtBreak = record
      x: integer;
      kind: TPascalChrKind;
      signature: cardinal;
    end;
  private const
    PASCAL_IDENTS: array[0..118] of string = ('absolute', 'abstract', 'and',
      'array', 'as', 'asm', 'assembler', 'automated', 'begin', 'case', 'cdecl',
      'class', 'const', 'constructor', 'contains', 'default', 'delayed',
      'deprecated', 'destructor', 'dispid', 'dispinterface', 'div', 'do',
      'downto', 'dynamic', 'else', 'end', 'except', 'experimental', 'export',
      'exports', 'external', 'far', 'file', 'final', 'finalization', 'finally',
      'for', 'forward', 'function', 'goto', 'helper', 'if', 'implementation',
      'implements', 'in', 'index', 'inherited', 'initialization', 'inline',
      'interface', 'is', 'label', 'library', 'local', 'message', 'mod', 'name',
      'near', 'nil', 'nodefault', 'not', 'object', 'of', 'operator', 'or', 'out',
      'overload', 'override', 'package', 'packed', 'pascal', 'platform',
      'private', 'procedure', 'program', 'property', 'protected', 'public',
      'published', 'raise', 'read', 'readonly', 'record', 'reference',
      'register', 'reintroduce', 'repeat', 'requires', 'resident',
      'resourcestring', 'safecall', 'sealed', 'set', 'shl', 'shr', 'static',
      'stdcall', 'stored', 'strict', 'string', 'then', 'threadvar', 'to', 'try',
      'type', 'unit', 'unsafe', 'until', 'uses', 'var', 'varargs', 'while',
      'winapi', 'virtual', 'with', 'write', 'writeonly', 'xor');
  private const
    DEFAULT_KEYWORD_COLOR = SC_RED;
    DEFAULT_KEYWORD_BOLD = true;
    DEFAULT_STRING_COLOR = SC_BLUE;
    DEFAULT_NUMBER_COLOR = SC_BLUE;
    DEFAULT_COMMENT_COLOR = SC_GREEN;
    DEFAULT_COMPILER_DIRECTIVE_COLOR = SC_RED;
  private const
    CSS_CLASS_DEFAULT = 0;
    CSS_CLASS_KEYWORD = 1;
    CSS_CLASS_STRING = 2;
    CSS_CLASS_NUMBER = 3;
    CSS_CLASS_COMMENT = 4;
    CSS_CLASS_COMPILER_DIRECTIVE = 5;

    CSS_CLASS_HIGH = CSS_CLASS_COMPILER_DIRECTIVE;
    CSS_CLASS_LENGTH = CSS_CLASS_HIGH + 1;
  private var
    FKeywordColor: TColor;
    FKeywordBold: boolean;
    FStringColor: TColor;
    FNumberColor: TColor;
    FCommentColor: TColor;
    FTokens: array of array of TFmtBreak;
    FCompilerDirectiveColor: TColor;
    procedure SetKeywordColor(const Value: TColor);
    procedure SetKeywordBold(const Value: boolean);
    procedure SetStringColor(const Value: TColor);
    procedure SetNumberColor(const Value: TColor);
    procedure SetCommentColor(const Value: TColor);
    procedure SetCompilerDirectiveColor(const Value: TColor);
    function ParseText(AFromLine: integer = 0;
      SingleLinePossibility: boolean = false; ANumLines: integer = 1): integer;
    function GetChrKind(ALineIndex, ACol: Integer): TPascalChrKind;
    procedure PushTokensDownFrom(ALineIndex: integer);
    procedure PushTokensUpFrom(ALineIndex: integer);
  public
    constructor Create(AOwner: TComponent); override;
    function FileChangeNotification(ChangeType: TChangeType; Data1: Integer;
      Data2: Integer; Data3: Integer; Data4: Integer): TChangeRecord; override;
    procedure GetCharFormat(ALineIndex: Integer; ACol: Integer; AChar: Char;
      var AFontRecord: TFontRecord); override;
    function GetCharCSSClass(ALineIndex: Integer; ACol: Integer;
      AChar: Char): Integer; override;
    function GetCSSRules: TCSSRules; override;
    procedure Assign(Source: TPersistent); override;
    procedure ApplyColorScheme(const AColorScheme: TColorScheme); override;
    function GetCache(out ACache: PByte): Integer; override;
    function RestoreCache(ACache: PByte; ASize: Integer): Boolean; override;
    procedure ClearCache; override;
  published
    property KeywordColor: TColor read FKeywordColor write SetKeywordColor default DEFAULT_KEYWORD_COLOR;
    property KeywordBold: boolean read FKeywordBold write SetKeywordBold default DEFAULT_KEYWORD_BOLD;
    property StringColor: TColor read FStringColor write SetStringColor default DEFAULT_STRING_COLOR;
    property NumberColor: TColor read FNumberColor write SetNumberColor default DEFAULT_NUMBER_COLOR;
    property CommentColor: TColor read FCommentColor write SetCommentColor default DEFAULT_COMMENT_COLOR;
    property CompilerDirectiveColor: TColor read FCompilerDirectiveColor write SetCompilerDirectiveColor default DEFAULT_COMPILER_DIRECTIVE_COLOR;
  end;

  TAlgoSimFormattingProcessor = class(TFormattingProcessor)
  private const
    DEFAULT_KEYWORD_COLOR = SC_RED;
    DEFAULT_KEYWORD_BOLD = true;
    DEFAULT_STRING_COLOR = SC_BLUE;
    DEFAULT_NUMBER_COLOR = SC_BLUE;
    DEFAULT_COMMENT_COLOR = SC_GREEN;
  private const
    CSS_CLASS_DEFAULT = 0;
    CSS_CLASS_KEYWORD = 1;
    CSS_CLASS_STRING = 2;
    CSS_CLASS_NUMBER = 3;
    CSS_CLASS_COMMENT = 4;

    CSS_CLASS_HIGH = CSS_CLASS_COMMENT;
    CSS_CLASS_LENGTH = CSS_CLASS_HIGH + 1;
  private var
    FKeywordColor: TColor;
    FKeywordBold: boolean;
    FStringColor: TColor;
    FNumberColor: TColor;
    FCommentColor: TColor;
    procedure SetKeywordColor(const Value: TColor);
    procedure SetKeywordBold(const Value: boolean);
    procedure SetStringColor(const Value: TColor);
    procedure SetNumberColor(const Value: TColor);
    procedure SetCommentColor(const Value: TColor);
  public
    constructor Create(AOwner: TComponent); override;
    procedure GetCharFormat(ALineIndex: Integer; ACol: Integer; AChar: Char;
      var AFontRecord: TFontRecord); override;
    function FileChangeNotification(ChangeType: TChangeType; Data1: Integer;
      Data2: Integer; Data3: Integer; Data4: Integer): TChangeRecord; override;
    function GetCSSRules: TCSSRules; override;
    function GetCharCSSClass(ALineIndex: Integer; ACol: Integer;
      AChar: Char): Integer; override;
    procedure Assign(Source: TPersistent); override;
    procedure ApplyColorScheme(const AColorScheme: TColorScheme); override;
  published
    property KeywordColor: TColor read FKeywordColor write SetKeywordColor default DEFAULT_KEYWORD_COLOR;
    property KeywordBold: boolean read FKeywordBold write SetKeywordBold default DEFAULT_KEYWORD_BOLD;
    property StringColor: TColor read FStringColor write SetStringColor default DEFAULT_STRING_COLOR;
    property NumberColor: TColor read FNumberColor write SetNumberColor default DEFAULT_NUMBER_COLOR;
    property CommentColor: TColor read FCommentColor write SetCommentColor default DEFAULT_COMMENT_COLOR;
  end;

  THTMLFormattingProcessor = class(TFormattingProcessor)
  private type
    THTMLChrKind = (ckHtmlUndefined = -1, ckHtmlText, ckHtmlTag, ckHtmlTagName, ckHtmlParam,
      ckHtmlValue, ckHtmlComment, ckHtmlDoctype, ckHtmlCssSelector, ckHtmlCssBlockDelim,
      ckHtmlCssProperty, ckHtmlCssValue, ckHtmlCssImportant, ckHtmlCssComment,
      ckHtmlScript);
    TFmtBreak = record
      x: integer;
      kind: THtmlChrKind;
      signature: cardinal;
    end;
  private const
    DEFAULT_TAG_COLOR = SC_BLUE;
    DEFAULT_TAG_NAME_COLOR = SC_RED;
    DEFAULT_TAG_NAME_BOLD = false;
    DEFAULT_PARAM_COLOR = SC_BLUE;
    DEFAULT_VALUE_COLOR = SC_GREEN;
    DEFAULT_COMMENT_COLOR = SC_GRAY;
    DEFAULT_CSS_SELECTOR_COLOR = SC_RED;
    DEFAULT_CSS_SELECTOR_BOLD = false;
    DEFAULT_CSS_PROPERTY_COLOR = SC_BLUE;
    DEFAULT_CSS_VALUE_COLOR = SC_GREEN;
    DEFAULT_CSS_COMMENT_COLOR = SC_GRAY;
    DEFAULT_CSS_BLOCK_DELIM_COLOR = SC_RED;
    DEFAULT_CSS_BLOCK_DELIM_BOLD = false;
    DEFAULT_CSS_IMPORTANT_COLOR = SC_INTENSE_RED;
    DEFAULT_CSS_IMPORTANT_BOLD = true;
    DEFAULT_DOCTYPE_COLOR = SC_GRAY;
    DEFAULT_DOCTYPE_BOLD = false;
    DEFAULT_DOCTYPE_ITALICS = true;

  private const
    CSS_CLASS_TEXT = 0;
    CSS_CLASS_TAG = 1;
    CSS_CLASS_TAG_NAME = 2;
    CSS_CLASS_PARAM = 3;
    CSS_CLASS_VALUE = 4;
    CSS_CLASS_COMMENT = 5;
    CSS_CLASS_CSS_SELECTOR = 6;
    CSS_CLASS_CSS_PROPERTY = 7;
    CSS_CLASS_CSS_VALUE = 8;
    CSS_CLASS_CSS_COMMENT = 9;
    CSS_CLASS_CSS_BLOCK_DELIM = 10;
    CSS_CLASS_CSS_IMPORTANT = 11;
    CSS_CLASS_DOCTYPE = 12;
    CSS_CLASS_SCRIPT = 13;

    CSS_CLASS_HIGH = CSS_CLASS_SCRIPT;
    CSS_CLASS_LENGTH = CSS_CLASS_HIGH + 1;
  private var
    FValueColor: TColor;
    FParamColor: TColor;
    FTagColor: TColor;
    FTagNameColor: TColor;
    FTagNameBold: boolean;
    FCommentColor: TColor;
    FCssSelectorColor: TColor;
    FCssSelectorBold: boolean;
    FCssPropertyColor: TColor;
    FCssValueColor: TColor;
    FCssCommentColor: TColor;
    FCssBlockDelimColor: TColor;
    FCssBlockDelimBold: boolean;
    FCssImportantBold: boolean;
    FCssImportantColor: TColor;
    FDoctypeColor: TColor;
    FDoctypeBold: boolean;
    FDoctypeItalics: boolean;
    FTokens: array of array of TFmtBreak;
    procedure SetParamColor(const Value: TColor);
    procedure SetTagColor(const Value: TColor);
    procedure SetValueColor(const Value: TColor);
    function ParseText(AFromLine: integer = 0;
      SingleLinePossibility: boolean = false; ANumLines: integer = 1): integer;
    function GetChrKind(ALineIndex, ACol: Integer): THtmlChrKind;
    procedure SetCommentColor(const Value: TColor);
    procedure SetCssBlockDelimBold(const Value: boolean);
    procedure SetCssBlockDelimColor(const Value: TColor);
    procedure SetCssCommentColor(const Value: TColor);
    procedure SetCssImportantBold(const Value: boolean);
    procedure SetCssImportantColor(const Value: TColor);
    procedure SetCssPropertyColor(const Value: TColor);
    procedure SetCssSelectorBold(const Value: boolean);
    procedure SetCssSelectorColor(const Value: TColor);
    procedure SetCssValueColor(const Value: TColor);
    procedure SetDoctypeBold(const Value: boolean);
    procedure SetDoctypeColor(const Value: TColor);
    procedure SetDoctypeItalics(const Value: boolean);
    procedure SetTagNameBold(const Value: boolean);
    procedure SetTagNameColor(const Value: TColor);
    procedure PushTokensDownFrom(ALineIndex: integer);
    procedure PushTokensUpFrom(ALineIndex: integer);
  public
    constructor Create(AOwner: TComponent); override;
    procedure GetCharFormat(ALineIndex: Integer; ACol: Integer; AChar: Char;
      var AFontRecord: TFontRecord); override;
    function FileChangeNotification(ChangeType: TChangeType; Data1: Integer;
      Data2: Integer; Data3: Integer; Data4: Integer): TChangeRecord; override;
    function GetCSSRules: TCSSRules; override;
    function GetCharCSSClass(ALineIndex: Integer; ACol: Integer;
      AChar: Char): Integer; override;
    procedure Assign(Source: TPersistent); override;
    procedure ApplyColorScheme(const AColorScheme: TColorScheme); override;
  published
    property TagColor: TColor read FTagColor write SetTagColor default DEFAULT_TAG_COLOR;
    property TagNameColor: TColor read FTagNameColor write SetTagNameColor default DEFAULT_TAG_NAME_COLOR;
    property TagNameBold: boolean read FTagNameBold write SetTagNameBold default DEFAULT_TAG_NAME_BOLD;
    property ParamColor: TColor read FParamColor write SetParamColor default DEFAULT_PARAM_COLOR;
    property ValueColor: TColor read FValueColor write SetValueColor default DEFAULT_VALUE_COLOR;
    property CommentColor: TColor read FCommentColor write SetCommentColor default DEFAULT_COMMENT_COLOR;
    property CssSelectorColor: TColor read FCssSelectorColor write SetCssSelectorColor default DEFAULT_CSS_SELECTOR_COLOR;
    property CssSelectorBold: boolean read FCssSelectorBold write SetCssSelectorBold default DEFAULT_CSS_SELECTOR_BOLD;
    property CssPropertyColor: TColor read FCssPropertyColor write SetCssPropertyColor default DEFAULT_CSS_PROPERTY_COLOR;
    property CssValueColor: TColor read FCssValueColor write SetCssValueColor default DEFAULT_CSS_VALUE_COLOR;
    property CssCommentColor: TColor read FCssCommentColor write SetCssCommentColor default DEFAULT_CSS_COMMENT_COLOR;
    property CssBlockDelimColor: TColor read FCssBlockDelimColor write SetCssBlockDelimColor default DEFAULT_CSS_BLOCK_DELIM_COLOR;
    property CssBlockDelimBold: boolean read FCssBlockDelimBold write SetCssBlockDelimBold default DEFAULT_CSS_BLOCK_DELIM_BOLD;
    property CssImportantColor: TColor read FCssImportantColor write SetCssImportantColor default DEFAULT_CSS_IMPORTANT_COLOR;
    property CssImportantBold: boolean read FCssImportantBold write SetCssImportantBold default DEFAULT_CSS_IMPORTANT_BOLD;
    property DoctypeColor: TColor read FDoctypeColor write SetDoctypeColor default DEFAULT_DOCTYPE_COLOR;
    property DoctypeBold: boolean read FDoctypeBold write SetDoctypeBold default DEFAULT_DOCTYPE_Bold;
    property DoctypeItalics: boolean read FDoctypeItalics write SetDoctypeItalics default DEFAULT_DOCTYPE_Italics;
  end;

  TMediaWikiFormattingProcessor = class(TFormattingProcessor)
  private const
    DEFAULT_HEADING1_COLOR = SC_BLUE;
    DEFAULT_HEADING1_BOLD = true;
    DEFAULT_HEADING1_ITALICS = true;
    DEFAULT_HEADING2_COLOR = SC_BLUE;
    DEFAULT_HEADING2_BOLD = true;
    DEFAULT_HEADING2_ITALICS = false;
    DEFAULT_HEADING3_COLOR = SC_BLUE;
    DEFAULT_HEADING3_BOLD = false;
    DEFAULT_HEADING3_ITALICS = true;
    DEFAULT_HEADING4_COLOR = SC_BLUE;
    DEFAULT_HEADING4_BOLD = false;
    DEFAULT_HEADING4_ITALICS = false;
    DEFAULT_HEADING5_COLOR = SC_GRAY;
    DEFAULT_HEADING5_BOLD = false;
    DEFAULT_HEADING5_ITALICS = true;
    DEFAULT_HEADING6_COLOR = SC_GRAY;
    DEFAULT_HEADING6_BOLD = false;
    DEFAULT_HEADING6_ITALICS = false;
    DEFAULT_WIKILINK_COLOR = SC_RED;
    DEFAULT_EXTLINK_COLOR = SC_RED;
    DEFAULT_TEMPLATE_COLOR = SC_GREEN;
    DEFAULT_TEMPLATE_NAME_BOLD = true;
    DEFAULT_BOLD_BOLD = true;
    DEFAULT_ITALICS_ITALICS = true;
    DEFAULT_INDENT_COLOR = SC_GRAY;
  private const
    CSS_CLASS_TEXT = 0;
    CSS_CLASS_HEADING1 = 1;
    CSS_CLASS_HEADING2 = 2;
    CSS_CLASS_HEADING3 = 3;
    CSS_CLASS_HEADING4 = 4;
    CSS_CLASS_HEADING5 = 5;
    CSS_CLASS_HEADING6 = 6;
    CSS_CLASS_WIKILINK = 7;
    CSS_CLASS_EXTLINK = 8;
    CSS_CLASS_TEMPLATE_NAME = 9;
    CSS_CLASS_TEMPLATE = 10;
    CSS_CLASS_BOLD = 11;
    CSS_CLASS_ITALICS = 12;
    CSS_CLASS_BOLDITALICS = 13;
    CSS_CLASS_INDENT = 14;

    CSS_CLASS_HIGH = CSS_CLASS_INDENT;
    CSS_CLASS_LENGTH = CSS_CLASS_HIGH + 1;
  private var
    FHeading4Color: TColor;
    FHeading1Italics: boolean;
    FHeading4Italics: boolean;
    FHeading2Bold: boolean;
    FHeading3Bold: boolean;
    FHeading1Bold: boolean;
    FHeading4Bold: boolean;
    FHeading2Color: TColor;
    FHeading3Color: TColor;
    FHeading1Color: TColor;
    FHeading2Italics: boolean;
    FHeading3Italics: boolean;
    FWikilinkColor: TColor;
    FTemplateColor: TColor;
    FBoldBold: boolean;
    FItalicsItalics: boolean;
    FExtlinkColor: TColor;
    FIndentColor: TColor;
    FTemplateNameBold: boolean;
    FHeading5Color: TColor;
    FHeading5Italics: boolean;
    FHeading5Bold: boolean;
    FHeading6Italics: boolean;
    FHeading6Bold: boolean;
    FHeading6Color: TColor;
    procedure SetHeading1Bold(const Value: boolean);
    procedure SetHeading1Color(const Value: TColor);
    procedure SetHeading1Italics(const Value: boolean);
    procedure SetHeading2Bold(const Value: boolean);
    procedure SetHeading2Color(const Value: TColor);
    procedure SetHeading2Italics(const Value: boolean);
    procedure SetHeading3Bold(const Value: boolean);
    procedure SetHeading3Color(const Value: TColor);
    procedure SetHeading3Italics(const Value: boolean);
    procedure SetHeading4Bold(const Value: boolean);
    procedure SetHeading4Color(const Value: TColor);
    procedure SetHeading4Italics(const Value: boolean);
    procedure SetWikilinkColor(const Value: TColor);
    procedure SetTemplateColor(const Value: TColor);
    procedure SetBoldBold(const Value: boolean);
    procedure SetItalicsItalics(const Value: boolean);
    procedure SetExtlinkColor(const Value: TColor);
    procedure SetIndentColor(const Value: TColor);
    procedure SetTemplateNameBold(const Value: boolean);
    procedure SetHeading5Bold(const Value: boolean);
    procedure SetHeading5Color(const Value: TColor);
    procedure SetHeading5Italics(const Value: boolean);
    procedure SetHeading6Bold(const Value: boolean);
    procedure SetHeading6Color(const Value: TColor);
    procedure SetHeading6Italics(const Value: boolean);
  public
    constructor Create(AOwner: TComponent); override;
    function GetCSSRules: TCSSRules; override;
    function GetCharCSSClass(ALineIndex: Integer; ACol: Integer;
      AChar: Char): Integer; override;
    procedure GetCharFormat(ALineIndex: Integer; ACol: Integer; AChar: Char;
      var AFontRecord: TFontRecord); override;
    function FileChangeNotification(ChangeType: TChangeType; Data1: Integer;
      Data2: Integer; Data3: Integer; Data4: Integer): TChangeRecord; override;
    procedure Assign(Source: TPersistent); override;
    procedure ApplyColorScheme(const AColorScheme: TColorScheme); override;
  published
    property Heading1Color: TColor read FHeading1Color write SetHeading1Color default DEFAULT_HEADING1_COLOR;
    property Heading1Bold: boolean read FHeading1Bold write SetHeading1Bold default DEFAULT_HEADING1_BOLD;
    property Heading1Italics: boolean read FHeading1Italics write SetHeading1Italics default DEFAULT_HEADING1_BOLD;
    property Heading2Color: TColor read FHeading2Color write SetHeading2Color default DEFAULT_HEADING2_COLOR;
    property Heading2Bold: boolean read FHeading2Bold write SetHeading2Bold default DEFAULT_HEADING2_BOLD;
    property Heading2Italics: boolean read FHeading2Italics write SetHeading2Italics default DEFAULT_HEADING2_BOLD;
    property Heading3Color: TColor read FHeading3Color write SetHeading3Color default DEFAULT_HEADING3_COLOR;
    property Heading3Bold: boolean read FHeading3Bold write SetHeading3Bold default DEFAULT_HEADING3_BOLD;
    property Heading3Italics: boolean read FHeading3Italics write SetHeading3Italics default DEFAULT_HEADING3_BOLD;
    property Heading4Color: TColor read FHeading4Color write SetHeading4Color default DEFAULT_HEADING4_COLOR;
    property Heading4Bold: boolean read FHeading4Bold write SetHeading4Bold default DEFAULT_HEADING4_BOLD;
    property Heading4Italics: boolean read FHeading4Italics write SetHeading4Italics default DEFAULT_HEADING4_BOLD;
    property Heading5Color: TColor read FHeading5Color write SetHeading5Color default DEFAULT_HEADING5_COLOR;
    property Heading5Bold: boolean read FHeading5Bold write SetHeading5Bold default DEFAULT_HEADING5_BOLD;
    property Heading5Italics: boolean read FHeading5Italics write SetHeading5Italics default DEFAULT_HEADING5_BOLD;
    property Heading6Color: TColor read FHeading6Color write SetHeading6Color default DEFAULT_HEADING6_COLOR;
    property Heading6Bold: boolean read FHeading6Bold write SetHeading6Bold default DEFAULT_HEADING6_BOLD;
    property Heading6Italics: boolean read FHeading6Italics write SetHeading6Italics default DEFAULT_HEADING6_BOLD;
    property WikilinkColor: TColor read FWikilinkColor write SetWikilinkColor default DEFAULT_WIKILINK_COLOR;
    property ExtlinkColor: TColor read FExtlinkColor write SetExtlinkColor default DEFAULT_EXTLINK_COLOR;
    property TemplateColor: TColor read FTemplateColor write SetTemplateColor default DEFAULT_TEMPLATE_COLOR;
    property TemplateNameBold: boolean read FTemplateNameBold write SetTemplateNameBold default DEFAULT_TEMPLATE_NAME_BOLD;
    property BoldBold: boolean read FBoldBold write SetBoldBold default DEFAULT_BOLD_BOLD;
    property ItalicsItalics: boolean read FItalicsItalics write SetItalicsItalics default DEFAULT_ITALICS_ITALICS;
    property IndentColor: TColor read FIndentColor write SetIndentColor default DEFAULT_INDENT_COLOR;
  end;

  TPrintSettings = class(TPersistent)
  strict private var
    FVerticalMargin: integer;
    FHorizontalMargin: integer;
    FWordWrap: boolean;
    FNiceWordWrap: boolean;
    FWordWrapChar: char;
    FShowWordWrapIcon: boolean;
    FWordWrapIconColor: TColor;
  public const
    DEFAULT_VERTICAL_MARGIN = 200;
    DEFAULT_HORIZONTAL_MARGIN = 220;
    DEFAULT_WORD_WRAP = true;
    DEFAULT_NICE_WORD_WRAP = true;
    DEFAULT_WORD_WRAP_CHAR = '↳';
    DEFAULT_SHOW_WORD_WRAP_ICON = false;
    DEFAULT_WORD_WRAP_ICON_COLOR = clBlack;
  public
    constructor Create;
    procedure Assign(Source: TPersistent); override;
  published
    property VerticalMargin: integer read FVerticalMargin write FVerticalMargin default DEFAULT_VERTICAL_MARGIN;
    property HorizontalMargin: integer read FHorizontalMargin write FHorizontalMargin default DEFAULT_HORIZONTAL_MARGIN;
    property WordWrap: boolean read FWordWrap write FWordWrap default DEFAULT_WORD_WRAP;
    property NiceWordWrap: boolean read FNiceWordWrap write FNiceWordWrap default DEFAULT_NICE_WORD_WRAP;
    property WordWrapIcon: char read FWordWrapChar write FWordWrapChar default DEFAULT_WORD_WRAP_CHAR;
    property ShowWordWrapIcon: boolean read FShowWordWrapIcon write FShowWordWrapIcon default DEFAULT_SHOW_WORD_WRAP_ICON;
    property WordWrapIconColor: TColor read FWordWrapIconColor write FWordWrapIconColor default DEFAULT_WORD_WRAP_ICON_COLOR;
  end;

  TProgressStartEvent = procedure(Sender: TObject; NumSteps: integer) of object;
  TProgressEvent = function(Sender: TObject; CurStep: integer; NumSteps: integer): boolean of object;
  TProgressCompleteEvent = TNotifyEvent;

  TNotificationMessage = procedure(Sender: TObject; MsgID: cardinal; AClear: boolean = false) of object;
  TSimpleNotificationMessage = procedure(Sender: TObject; MsgID: cardinal; const AStr: string) of object;

  TGraphicControlCracker = type TGraphicControl;

  TBorderType = (btNone, btWin32ThinLine, btWin32SunkenEdge, btThemeBorder,
    btSimpleColor);

  TTextFileOwner = (tfoEditor, tfoApplication);

  TSelectionBarBehaviour = (sbbAlwaysSelect, sbbNeverSelect, sbbAuto, sbbAutoMixed);

  TCliGetPromptClassEvent = procedure(Sender: TObject; var AClassName: string) of object;
  TCliInputEvent = procedure(Sender: TObject; const AInput: string; var NewPrompt: boolean) of object;

  TParamType = (ptConstant, ptCommand);
  TScriptParam = record
    ParamType: TParamType;
    ParamValue: integer;
  end;

  TEditorCommand = record
    Verb: integer;
    Param1,
    Param2,
    Param3,
    Param4: TScriptParam;
  end;
  TEditorScript = array of TEditorCommand;

function MakeEditorCommand(Verb: integer; Param1: integer = 0;
  ParamType1: TParamType = ptConstant; Param2: integer = 0;
  ParamType2: TParamType = ptConstant; Param3: integer = 0;
  ParamType3: TParamType = ptConstant; Param4: integer = 0;
  ParamType4: TParamType = ptConstant): TEditorCommand;

const
  MultiCharHyphen: array[0..6] of char = (#$002D, #$2010, #$2011, #$00AD, #$2013, #$2014, #$2212);
  MultiCharAsterisk: array[0..4] of char = (#$002A, #$2022, #$22C5, #$00D7, #$2219);
  MultiCharDoubleQuote: array[0..5] of char = (#$0022, #$201C, #$201D, #$201E, #$00BB, #$00AB);
  MultiCharSingleQuote: array[0..5] of char = (#$0027, #$2018, #$2019, #$201A, #$203A, #$2039);

type
  TScrollBehaviour = (sbDefault, sbLine, sbPixel);

  TTextEditor = class;

  TTextEditorDataObject = class(TInterfacedObject, IDataObject)
  strict private type
    TEnumFormatEtc = class(TInterfacedObject, IEnumFORMATETC)
      strict private
        FIndex: integer;
      public
        function Next(celt: Longint; out elt;
          pceltFetched: PLongint): HResult; stdcall;
        function Skip(celt: Longint): HResult; stdcall;
        function Reset: HResult; stdcall;
        function Clone(out Enum: IEnumFormatEtc): HResult; stdcall;
    end;
  public const
    FMT_UNICODETEXT = 0;
  class var
    Formats: TFormatEtcArray;
  private
    FTextEditor: TTextEditor;
    FBuffer: string;
    function GetMatchingFormatIdx(const AFormatEtc: TFormatEtc): integer;
  public
    class constructor ClassCreate;
    class function CreateHGlobal(Data: pointer; Len: UInt64; uFlags: DWORD;
      out hGlobal: HGLOBAL): HRESULT; static;
    constructor Create(AEditor: TTextEditor);
    function DAdvise(const formatetc: tagFORMATETC; advf: Integer;
      const advSink: IAdviseSink; out dwConnection: Integer): HRESULT; stdcall;
    function DUnadvise(dwConnection: Integer): HRESULT; stdcall;
    function EnumDAdvise(out enumAdvise: IEnumSTATDATA): HRESULT; stdcall;
    function EnumFormatEtc(dwDirection: Integer;
      out enumFormatEtc: IEnumFORMATETC): HRESULT; stdcall;
    function GetCanonicalFormatEtc(const formatetc: tagFORMATETC;
      out formatetcOut: tagFORMATETC): HRESULT; stdcall;
    function GetData(const formatetcIn: tagFORMATETC;
      out medium: tagSTGMEDIUM): HRESULT; stdcall;
    function GetDataHere(const formatetc: tagFORMATETC;
      out medium: tagSTGMEDIUM): HRESULT; stdcall;
    function QueryGetData(const formatetc: tagFORMATETC): HRESULT; stdcall;
    function SetData(const formatetc: tagFORMATETC; var medium: tagSTGMEDIUM;
      fRelease: LongBool): HRESULT; stdcall;
  end;

  TTextEditor = class(TCustomControl, IDropTarget, IDropSource)
  private type
    TTextEditorRegion = (terText, terSelectionBar);
  public const
    DEFAULT_LINE_HIGHLIGHT_COLOR = $00EEEEEE;
    DEFAULT_BRACKET_HIGHLIGHT_COLOR = $0077D9F5;
    DEFAULT_RULER_WIDTH = 60;
    DEFAULT_MARGIN_LEFT = 80;
    DEFAULT_MARGIN_TOP = 4;
    DEFAULT_MARGIN_RIGHT = 4;
    DEFAULT_MARGIN_BOTTOM = 4;
    DEFAULT_NOTIFICATION_MSG_DURATION = 5000;
  public const
    EN_NULL = 0;
    EN_DRAG_MOVE = 1;
    EN_DRAG_COPY = 2;
    EN_READ_ONLY_ERROR = 3;
    EN_INPUT_ERROR = 4;
    EN_PRINTING = 5;
    EN_SCROLL_MODE = 6;
    EN_SCRIPT = 7;
    EN_MULTICHAR = 8;
    EN_READONLY = 9;
    EN_MULTICARET = 10;
    EN_MAX = 10;
  private const
    FNotificationStrs: array[0..EN_MAX] of string =
      ('',
       SNotifyDragMove,
       SNotifyDragCopy,
       SNotifyReadOnlyError,
       SNotifyInputError,
       SNotifyPrinting,
       SNotifyScrollMode,
       SNotifyScript,
       SNotifyMultiCharSelect,
       SNotifyReadOnlyMode,
       SNotifyMultiCaretMode);
  private const
    EDITOR_NOTIFY = $10000;
  private const
    WM_MOUSEHWHEEL = $020E;
  private var
    FTextFile: TTextFile;
    FClassArray: TClassArray;
    FBkColor: TColor;
    FFgColor: TColor;
    FSelBkColor: TColor;
    FSelFgColor: TColor;
    FFndBkColor: TColor;
    FFndFgColor: TColor;
    FUseSystemColors: boolean;
    FForegroundColor: TColor;
    FBackgroundColor: TColor;
    FFontSize: TSize;
    FFont: TFont;
    FLetterSpacing: integer;
    FLineSpacing: integer;
    FAutoIndent: boolean;
    FHandleHotkeys: boolean;
    FHandleBookmarkHotkeys: boolean;
    FSelForegroundColor: TColor;
    FSelBackgroundColor: TColor;
    FOnSelChange: TNotifyEvent;
    FOnChange: TNotifyEvent;
    FBeepOnInputError: boolean;
    FErrorMessageOnReadOnlyError: boolean;
    FOverwrite: boolean;
    FOLEDragging: boolean;
    FOLEInternalDrop: boolean;
    FDropLocation: TPoint;
    FLastDropEffect: integer;
    FDoubleClicking: Boolean;
    FIndentSize: integer;
    FScrollPos: TPoint;
    FPrevCursorX, FPrevCursorY: integer;
    FMouseDownX, FMouseDownY: integer;
    FShowHiddenCharacters: boolean;
    FOnModified: TNotifyEvent;
    FLineHighlight: boolean;
    FLineHighlightColor: TColor;
    FOldCaretPosY: integer;
    FMatchBrackets: boolean;
    FBracketHighlight: boolean;
    FBracketPos1, FBracketPos2: TPoint;
    FBracketHighlightColor: TColor;
    FAutoReplace: boolean;
    FPopupMenu: TPopupMenu;
    FRulerMenu: TPopupMenu;
    FImagePopup: TPopupMenu;
    FTypeTimer: TTimer;
    FMessageInterface: boolean;
    FInputTransform: TInputTransform;
    FStartOver: boolean;
    FGLYPHBM: TBitmap;
    FFONTBM: TBitmap;
    FFallbackFonts: TStringList;
    FGlyphSets: array of PGlyphSet;
    FUnicodeFallback: boolean;
    FPasswordChar: char;
    FNumbersOnly: boolean;
    FHintWindow: HWND;
    FToolInfo: TToolInfo;
    FBalloonPoint: TPoint;
    FBalloonPersistence: TBalloonPersistence;
    FBalloonTimer: TTimer;
    FTabLength: integer;
    FLabelStyle: boolean;
    FLabelEllipsis: boolean;
    FBlinkRemover: TTimer;
    FAutoHeight: boolean;
    FMultiSize: boolean;
    FCurrentFormat: TFontRecord;
    FFontSizes: array of TSize;
    FAccumLineHeights: array of integer;
    FCachedHorizontalExtent: integer;
    FLineControls: array of TLineControlRecord;
    FNextControlID: cardinal;
    FOnBookmarksMoved: TNotifyEvent;
    FFormattingProcessor: TFormattingProcessor;
    FRulerWidth,
    FMarginLeft,
    FMarginRight,
    FMarginTop,
    FMarginBottom: integer;
    FRegion: TTextEditorRegion;
    FSelectionBarInitialLine: integer;
    FRulerFont: TFont;
    FCaretVisible: boolean;
    FNoScrollToCaret: boolean;
    FRulerColor: TColor;
    FZoom: integer;
    FOnZoomChange: TNotifyEvent;
    FPrintSettings: TPrintSettings;
    FOnPrintProgress: TProgressEvent;
    FOnPrintEnd: TProgressCompleteEvent;
    FOnPrintBegin: TProgressStartEvent;
    FRightLineColor: TColor;
    FRightLinePos: integer;
    FRightLine: boolean;
    FBorderColor: TColor;
    FBorderType: TBorderType;
    FTextFileOwner: TTextFileOwner;
    FRulerPopupMenu: TPopupMenu;
    FSelectionBarBehaviour: TSelectionBarBehaviour;
    FNotifications: array of integer;
    FOnNotification: TNotificationMessage;
    FOnSimpleNotification: TSimpleNotificationMessage;
    FScrollMode: boolean;
    FNotifyMsgDuration: integer;
    FOnCliGetPromptClass: TCliGetPromptClassEvent;
    FOnCliInput: TCliInputEvent;
    FScriptRunning: Boolean;
    FScriptCounter: integer;
    FAbortScript: Boolean;
    FDesiredColumn: integer; {in px}
    FPreserveDesiredColumn: boolean;
    FCliHistory: array of string;
    FCliHistoryIndex: integer;
    FListBoxMode: boolean;
    FOnListBoxChange: TNotifyEvent;
    FOnListBoxSelect: TNotifyEvent;
    FValidPaintState: boolean;
    FCliMultiOutput: boolean;
    FMultiCharSelect: boolean;
    FMultiCharSelectDlgFrm: TForm;
    FMultiCharReportView: boolean;
    Flv: HWND;
    FMultiCharSelectDlgDefaultWndProc: TWndMethod;
    FASHyphenAsteriskToggle: boolean;
    FNoVerifyFont: boolean;
    FDisabledEffect: TBitmapEffect;
    FBitmapEffect: TBitmapEffect;
    FRepeatExNum: integer;
    FRepeatExCommand: integer;
    FOnFindDataClear: TNotifyEvent;
    FOnOverwriteChange: TNotifyEvent;
    FVisualUpdateLock: integer;
    FScrollBehaviour: TScrollBehaviour;
    FSPIScrollLines: integer;
    FCaretAfterEOL: boolean;
    FMultipleCarets: boolean;
    FCarets: TPointArray;
    FAllowBitmapPaste: boolean;
    FWantTab, FWantReturn: boolean;
    FDragDataObj: IDataObject;
    FDragCompatFmt: boolean;
    FRightDrag: boolean;
    FInsertionPoint: TPoint;
    FDropMenu: TPopupMenu;
    FDropMenuMove, FDropMenuCopy: TMenuItem;
    FDropTargetHelper: IDropTargetHelper;
    FExpectDragDrop: boolean;
    FDragButton: TMouseButton;
    FDragButtonOLE: integer;
    FMouseContSel: boolean;
    FPDict: TDictionary<string, TFormattingProcessor>;
    FDragScrollFirstChance: UInt64;
    FXDRAG, FYDRAG: integer;
    procedure SetUseSystemColors(const Value: boolean);
    procedure SetupColors;
    procedure SetBackgroundColor(const Value: TColor);
    procedure SetForegroundColor(const Value: TColor);
    procedure SetupFontMetrics;
    procedure SetFont(const Value: TFont);
    procedure DrawLine(LineIndex: Integer; From, ATo: integer; AForceFindHighlight: boolean = false); overload;
    procedure DrawSpan(const ATextSpan: TTextSpan; AForceFindHighlight: boolean = false);
    procedure ApplyFont(const AClassName: string; ATo: TCanvas = nil);
    procedure ReapplyFont(ATo: TCanvas = nil); inline;
    procedure FontChange(Sender: TObject);
    procedure SetLetterSpacing(const Value: integer);
    procedure SetLineSpacing(const Value: integer);
    procedure TextFileChange(Sender: TObject; ChangeType: TChangeType; Data1,
      Data2, Data3, Data4: Integer);
    procedure TextFileCaretPosSelChange(Sender: TObject; ChangeType: TChangeType; Data1,
      Data2, Data3, Data4: Integer);
    procedure TextFileCaretPosChange(Sender: TObject);
    procedure SetCaretAfterEOL(const Value: boolean);
    procedure TextFileInputError(Sender: TObject);
    procedure SetAutoIndent(const Value: boolean);
    function GetText: string;
    procedure SetText(const Value: string);
    procedure SetSelBackgroundColor(const Value: TColor);
    procedure SetSelForegroundColor(const Value: TColor);
    procedure ApplyInteractiveFormatting(const X, Y: integer; ATo: TCanvas = nil);
    procedure ApplyCharacterColors(const X, Y: integer; AForceFindHighlight: boolean = false);
    function GetCaretPos: TPoint;
    function GetSelEndPos: TPoint;
    procedure SetCaretPos(const Value: TPoint);
    procedure SetSelEndPos(const Value: TPoint);
    function GetSelType: TSelectionType;
    procedure SetSelType(const Value: TSelectionType);
    procedure VisualUpdate(ChangeType: TChangeType; Data1, Data2, Data3, Data4: integer);
    procedure UpdateCaret;
    function GetSelText: string;
    procedure ChangeCursor(Shift: TShiftState; Y: Integer; X: Integer); overload;
    procedure ChangeCursor(Shift: TShiftState); overload;
    procedure ChangeCursor; overload;
    procedure SetIndentSize(const Value: integer);
    function GetSelLength: integer;
    function SafeSelLength: integer;
    procedure SetSelLength(const Value: integer);
    procedure UpdateScrollBars;
    procedure SetScrollPosY(Value: integer; Lim: boolean = false);
    procedure SetScrollPosX(Value: integer);
    function FirstVisibleLine(TrueValue: boolean = false): integer;
    function LastVisibleLine(TrueValue: boolean = false): integer;
    procedure DrawVisibleLine(LineIndex: Integer; From: Integer = 0);
    function ScrollToCaret: boolean;
    procedure SetScrollPosXY(X, Y: integer; Lim: boolean = false);
    procedure Escape(AAll: boolean = false);
    procedure SetEditMode(const Value: TEditMode);
    procedure SetShowHiddenCharacters(const Value: boolean);
    function Reveal(const C: char): char;
    procedure TextFileModified(Sender: TObject);
    procedure SetLineHighlight(const Value: boolean);
    procedure SetLineHighlightColor(const Value: TColor);
    procedure SetMatchBrackets(const Value: boolean);
    function IsBracketHighlight(const X, Y: integer): boolean; inline;
    procedure SetBracketHighlightColor(const Value: TColor);
    procedure MenuPopup(Sender: TObject);
    procedure MenuItemMessage(Sender: TObject);
    function GetSelStart: integer;
    procedure SetSelStart(const Value: integer);
    procedure SetOverwrite(const Value: boolean);
    procedure TypeTimerTimer(Sender: TObject);
    procedure PostType;
    procedure SetFindBackgroundColor(const Value: TColor);
    procedure SetFindForegroundColor(const Value: TColor);
    procedure SelectFindItem(ItemIndex: integer);
    procedure SetSingleLine(const Value: boolean);
    function GetSingleLine: boolean;
    function GetFontChrs(const AFontName: TFontName; out GlyphSet: PGlyphSet): boolean;
    function ChrInGlyphSet(GlyphSet: PGlyphSet; Codepoint: integer): boolean; overload;
    function ChrInGlyphSet(GlyphSet: PGlyphSet; Character: char): boolean; overload; inline;
//    function FontHasGlyph(const AFontName: TFontName; const AGlyph: char): boolean;
    procedure UseBestFont(const AChar: Char);
    procedure SetFallbackFonts(const Value: TStringList);
    procedure FallbackFontsChange(Sender: TObject);
    procedure BuildFontDataArray;
    procedure FreeFontDataArray;
    procedure SetUnicodeFallback(const Value: boolean);
    procedure SetPasswordChar(const Value: char);
    function GetBalloonPosition: TPoint;
    procedure BalloonTimerTimer(Sender: TObject);
    procedure SetLabelStyle(const Value: boolean);
    procedure SetLabelEllipsis(const Value: boolean);
    procedure BlinkBracket;
    procedure BlinkRemoverTimer(Sender: TObject);
    procedure SetBracketHighlight(const PointA, PointB: TPoint);
    procedure ClearBracketHighlight;
    procedure HighlightCurrentBracket;
    procedure SetAutoHeight(const Value: boolean);
    procedure AdjustHeight;
    procedure SetMultiSize(const Value: boolean);
    function GetClassRecord(Index: integer): TClassRecord;
    function GetNumClasses: integer;
    procedure UpdateFontBoxSize(ClassIndex: integer);
    procedure UpdateFontBoxSizes;
    procedure TextFileLineChange(Sender: TObject;
      LineChangeType: TLineChangeType; From: integer);
    procedure RebuildLineCache;
    procedure DoSetCaretPos; inline;
    function GetTotalVerticalExtent: integer;
    function GetTotalHorizontalExtent: integer;
    procedure RecomputeHorizontalExtent;
    function GetControlFromID(ID: integer): TControl;
    function GetIDFromControl(AControl: TControl): integer;
    function GetControlIDFromLine(LineIndex: integer): integer;
    function GetLineFromControlID(ID: integer): integer;
    function GetLineControlSize(LineIndex: integer): TSize;
    procedure ClearControls;
    procedure UpdateLineControls;
    procedure FixRemovedLineControlLines;
    function LineIsControl(LineIndex: integer): boolean;
    function LineIsWinControlOrHasPopup(LineIndex: integer): boolean;
    function GetControlFromLine(LineIndex: integer): TControl;
    function LineWidths(LineIndex: integer): integer; // in px, control-aware
    function MaxLineWidth: integer;                   // in px, control-aware
    procedure TextFileLineClassChange(Sender: TObject; LineIndex: integer);
    procedure TextFileControlRemoved(Sender: TObject; ControlID: integer);
    procedure TextFileGetControlText(Sender: TObject; LineIndex: integer;
      var ControlText: string);
    procedure InvalidateLineControl(LineIndex: integer);
    function GetLineTop(LineIndex: integer): integer;
    function GetLineBottom(LineIndex: integer): integer;
    function GetLineBottomVirtual(LineIndex: integer): integer;
    function GetCharLeft(LineIndex, ColIndex: integer): integer;
    function GetCharRight(LineIndex, ColIndex: integer): integer;
    function ActivateControl: HWND;
    procedure ImageMenuCommand(Sender: TObject);
    function GetLine(Index: integer): string;
    procedure SetLine(Index: integer; const Value: string);
    function GetClass(Index: integer): string;
    procedure SetClass(Index: integer; const Value: string);
    procedure ForceSetClass(Index: integer; const Value: string);
    function GetLineCount: integer;
    function FileIsEmpty: boolean;
    procedure GotoSamePixelAtPrevLine(Shift: boolean);
    procedure GotoSamePixelAtNextLine(Shift: boolean);
    function GetBookmark(Index: integer): TPoint;
    function GetBookmarkCount: integer;
    function GetUsedBookmarkCount: integer;
    procedure TextFileBookmarksMoved(Sender: TObject);
    function GetBookmarkDescr(BookmarkIndex: integer): string;
    procedure BookmarkHistoryRecord(const APoint: TPoint);
    procedure SetFormattingProcessor(const Value: TFormattingProcessor;
      AInitialize: boolean = true);
    procedure FormattingProcessorChanged(Sender: TObject);
    function FormattingProcessorGetLineWidth(ALineIndex: integer): integer;
    function FormattingProcessorGetChar(ALineIndex, ACol: integer): char;
    function FormattingProcessorGetLineCount: integer;
    function FormattingProcessorGetWord(const APoint: TPoint;
      APascalIdent: boolean = false): string;
    function FormattingProcessorGetWordBoundary(const APoint: TPoint; out SP,
      EP: integer): boolean;
    function FPFileChangeNotification(ChangeType: TChangeType; Data1, Data2,
      Data3, Data4: Integer): TChangeRecord;
    function TextContentRect: TRect;
    function NonRulerRect: TRect;
    function RulerRect: TRect;
    function LeftColumnRect: TRect;
    procedure DrawRuler;
    procedure RulerFontChange(Sender: TObject);
    procedure UpdateRuler;
    procedure UpdateRulerLine(const ALineIndex: integer);
    procedure BinaryHideCaret;
    procedure BinaryShowCaret;
    procedure SetMarginBottom(const Value: integer);
    procedure SetMarginLeft(const Value: integer);
    procedure SetMarginRight(const Value: integer);
    procedure SetMarginTop(const Value: integer);
    procedure SetRulerWidth(const Value: integer);
    function GetRulerVisible: boolean;
    procedure SetRulerVisible(const Value: boolean);
    procedure SetRulerColor(const Value: TColor);
    procedure SetZoom(const Value: integer);
    procedure MoveBalloonPostScroll;
    procedure SetRightLine(const Value: boolean);
    procedure SetRightLineColor(const Value: TColor);
    procedure SetRightLinePos(const Value: integer);
    procedure SetBorderColor(const Value: TColor);
    procedure SetBorderType(const Value: TBorderType);
    function GetWrapAt: string;
    procedure SetWrapAt(const Value: string);
    function GetFalse: boolean;
    procedure RestoreWrapAt(const Value: boolean);
    procedure SetTextFile(const Value: TTextFile);
    procedure ConnectTextFileToEditor;
    procedure DisconnectTextFileFromEditor;
    function GetEditMode: TEditMode;
    procedure SetFormattingProcessorSmple(const Value: TFormattingProcessor);
    procedure RulerMenuCommand(Sender: TObject);
    procedure RulerPropertiesApply(Sender: TObject);
    function GetFunctionalSelectionBarWidth: integer;
    function CharAtVirtualPixelEx(Pixel: TPoint; const CP: boolean = false): TPoint;
    function CharAtPhysicalPixelEx(Pixel: TPoint; const CP: boolean = false): TPoint;
    function NotifyApp(const MsgID: integer): boolean;
    function NotifyAppWithTimer(const MsgID: integer): boolean;
    procedure RemoveNotification(const MsgID: integer);
    function GetNotification(AIndex: integer): integer;
    function GetNotificationCount: integer;
    function GetNotificationStr(MsgID: integer): string;
    procedure TextFileReadOnlyError(Sender: TObject);
    procedure UpdateScrollMode;
    function GetLineComparer: TLineComparer;
    procedure SetLineComparer(const Value: TLineComparer);
    function GetSortReverseOrder: boolean;
    procedure SetSortReverseOrder(const Value: boolean);
    function CliHistoryUp: boolean;
    function CliHistoryDown: boolean;
    function GetCliHistory(Index: integer): string;
    procedure SetCliHistory(Index: integer; const Value: string);
    function GetCliHistoryCount: integer;
    function GetCliHistoryIndex: integer;
    procedure SetListBoxMode(const Value: boolean);
    procedure SetListBoxItemIndex(const Value: integer);
    function GetListBoxItemIndex: integer;
    procedure CliHistoryDialogListBoxSelect(Sender: TObject);
    procedure NeedValidPaintState;
    procedure CliHistoryDialogListBoxKeyDown(Sender: TObject; var Key: Word;
      Shift: TShiftState);
    procedure DoMultiCharSelect(AChrs: array of char);
    procedure MultiCharSelectDlgResize(Sender: TObject);
    procedure MultiCharSelectDlgWndProc(var Message: TMessage);
    procedure MultiCharSelectDlgActivate(Sender: TObject);
    function CharInSet(AChar: char; ASet: array of char): boolean;
    function CharInAnyMultiCharSet(AChar: char): boolean;
    procedure VerifyFont;
    procedure SetDisabledEffect(const Value: TBitmapEffect);
    procedure SetBitmapEffect(const Value: TBitmapEffect);
    procedure TextFileFindDataClear(Sender: TObject);
    procedure CheckCaretBeyondEOL;
    procedure TextFileLockVisualUpdates(Sender: TObject);
    procedure TextFileUnlockVisualUpdates(Sender: TObject);
    procedure UpdateSPI;
    procedure DoAutoReplace;
    function GetCaretAfterEOL: boolean;
    procedure CreateNewCaretAt(const APoint: TPoint);
    procedure EnterMultiCaretMode;
    function GetLastMultiCaret: TPoint;
    function IsCaretVisible: boolean;
    procedure IndicateInsertionPoint(const APoint: TPoint);
    procedure RemoveInsertionPoint;
    {$HINTS OFF}
    procedure InvalidateChar(const AChar: TPoint);
    {$HINTS ON}
    procedure InvalidateCharAndPrev(const AChar: TPoint);
    procedure GetDragDropEffect(var dwEffect: Integer; grfKeyState: Integer);
    procedure DragDropNotify(dwEffect: Integer);
    function FPFromString(const FPClassName: string): TFormattingProcessor;
  protected
    procedure Paint; override;
    procedure KeyPress(var Key: Char); override;
    procedure WMPaint(var Message: TWMPaint); message WM_PAINT;
    procedure WMGetDlgCode(var Message: TWMGetDlgCode); message WM_GETDLGCODE;
    procedure WMSetFocus(var Message: TWMSetFocus); message WM_SETFOCUS;
    procedure WMKillFocus(var Message: TWMKillFocus); message WM_KILLFOCUS;
    procedure KeyDown(var Key: Word; Shift: TShiftState); override;
    procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X: Integer;
      Y: Integer); override;
    procedure MouseMove(Shift: TShiftState; X: Integer; Y: Integer); override;
    procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X: Integer;
      Y: Integer); override;
    procedure KeyUp(var Key: Word; Shift: TShiftState); override;
    procedure DblClick; override;
    procedure CreateParams(var Params: TCreateParams); override;
    procedure WMSize(var Message: TWMSize); message WM_SIZE;
    procedure WMVScroll(var Message: TWMVScroll); message WM_VSCROLL;
    procedure WMHScroll(var Message: TWMHScroll); message WM_HSCROLL;
    procedure WMMouseWheel(var Message: TWMMouseWheel); message WM_MOUSEWHEEL;
    procedure WMMouseHWheel(var Message: TWMMouseWheel); message WM_MOUSEHWHEEL;
    procedure WndProc(var Message: TMessage); override;
    procedure Loaded; override;
    procedure CreateWnd; override;
    procedure DestroyWnd; override;
    procedure WMEnable(var Message: TWMEnable); message WM_ENABLE;
    procedure WMContextMenu(var Message: TWMContextMenu);
      message WM_CONTEXTMENU;
    procedure WMEraseBkgnd(var Message: TWMEraseBkgnd); message WM_ERASEBKGND;
    procedure WMNCPaint(var Message: TWMNCPaint); message WM_NCPAINT;
    procedure WMNCHitTest(var Message: TWMNCHitTest); message WM_NCHITTEST;
    procedure WMNCActivate(var Message: TWMNCActivate); message WM_NCACTIVATE;
    procedure WMNCCalcSize(var Message: TWMNCCalcSize); message WM_NCCALCSIZE;

    { IDropTarget }
    function IDropTarget.DragEnter = DropTargetDragEnter;
    function DropTargetDragEnter(const dataObj: IDataObject;
      grfKeyState: Longint; pt: TPoint; var dwEffect: Longint): HRESULT;
      stdcall;
    function IDropTarget.DragOver = DropTargetDragOver;
    function DropTargetDragOver(grfKeyState: Longint; pt: TPoint;
      var dwEffect: Longint): HRESULT;
      stdcall;
    function IDropTarget.DragLeave = DropTargetDragLeave;
    function DropTargetDragLeave: HRESULT;
      stdcall;
    function IDropTarget.Drop = DropTargetDrop;
    function DropTargetDrop(const dataObj: IDataObject; grfKeyState:
      Longint; pt: TPoint; var dwEffect: Longint): HRESULT; stdcall;

    { IDropSource }
    function GiveFeedback(dwEffect: Longint): HRESULT; stdcall;
    function QueryContinueDrag(fEscapePRessed: BOOL; grfKeyState: Longint): HRESULT;
      stdcall;

  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure UseDefaultFallbackFonts;
    function EditorCommand(Command: integer; Param1: integer = 0;
      Param2: integer = 0; Param3: integer = 0; Param4: integer = 0): integer;
    function CommandEnabled(Command: integer): boolean;
    function CommandVisible(Command: integer): boolean;
    procedure BeginVisualUpdate;
    procedure EndVisualUpdate(AUpdate: boolean = false);
    procedure CutToClipboard;
    procedure CopyToClipboard;
    procedure CopyAll;
    function PasteFromClipboard: boolean;
    function PasteFromClipboardAsBlock: boolean;
    procedure ClearLine(LineIndex: integer); overload;
    procedure ClearLine; overload;
    procedure AddLine(const AText: string; const AClassName: string); overload;
    procedure AddLine(const AText: string); overload;
    procedure AddLine; overload;
    procedure BeginAddLine;
    procedure EndAddLine;
    procedure InsertLine(const AText: string; const AClassName: string; LineIndex: integer); overload;
    procedure InsertLine(const AText: string; LineIndex: integer); overload;
    procedure InsertLine(LineIndex: integer); overload;
    procedure InsertChar(const AChar: Char; AOverwrite: boolean = false);
    procedure InsertText(const AText: string);
    procedure InsertTextAsBlock(const AText: string);
    procedure SurroundText(const APrefix, APostfix: string);
    procedure TransformText(Transformation: TTextTransformFunc;
      const TransformName: string);
    function TransformSelection(Transformation: TTextTransformFunc;
      const TransformName: string): boolean;
    procedure ChrTransformText(Transformation: TChrTransformFunc;
      const TransformName: string);
    function ChrTransformSelection(Transformation: TChrTransformFunc;
      const TransformName: string): boolean;
    function FillWithChar(const AChar: char): boolean;
    property TextFile: TTextFile read FTextFile write SetTextFile;
    property CaretPos: TPoint read GetCaretPos write SetCaretPos;
    property SelEndPos: TPoint read GetSelEndPos write SetSelEndPos;
    property SelectionType: TSelectionType read GetSelType write SetSelType;
    function GetWordBoundary(const Point: TPoint; out StartPos, EndPos: integer): boolean; overload;
    function GetWordBoundary(out StartPos, EndPos: integer): boolean; overload;
    function GetWord(const Point: TPoint): string; overload;
    function GetWord: string; overload;
    function GetURLAtCaret(out AURL: string): boolean;
    function OpenURLAtCaret: boolean;
    function SelectWord: boolean;
    procedure SwapLinesAbove;
    procedure SwapLinesBelow;
    procedure SelectLines(const ALineA, ALineB: integer);
    procedure SelectLine(ALineIndex: integer); overload;
    procedure SelectLine; overload;
    procedure SelectAll;
    procedure SelectNone;
    procedure SelectAllNone;
    procedure Backspace(Word: boolean = false);
    procedure Delete(Word: boolean = false);
    procedure Return;
    procedure ReplaceCodepoint;
    function CharAtVirtualPixel(Pixel: TPoint): TPoint;
    function CharAtPhysicalPixel(Pixel: TPoint): TPoint;
    function CaretPosAtVirtualPixel(Pixel: TPoint): TPoint; {top-left of possible caret}
    function CaretPosAtPhysicalPixel(Pixel: TPoint): TPoint;
    function VirtualPixelAtChar(const Point: TPoint): TPoint; {top-left of chr box}
    function PhysicalPixelAtChar(const Point: TPoint): TPoint;
    function GetCharAtCaret: char;
    function GetCharBeforeCaret: char;
    procedure ClearSelection;
    procedure MoveSelection(const ANewPos: TPoint);
    procedure CopySelection(const ANewPos: TPoint);
    procedure AddIndent;
    procedure RemoveIndent;
    procedure RemoveAllIndent;
    procedure PageUp(Selection: boolean = false);
    procedure PageDown(Selection: boolean = false);
    function CanUndo: boolean;
    function Undo: boolean;
    function CanRedo: boolean;
    function Redo: boolean;
    function GotoHistoryVersion(Index: integer): boolean;
    procedure ClearUndoHistory;
    procedure MakeUndoRoot;
    procedure TypeTimerEnd;
    procedure AddUndoRecord(const AComment: string; UID: UNDONAMEID);
    procedure Clear;
    procedure NewFile;
    procedure LoadFromFile(const FileName: TFileName; const Encoding: TEncoding);
    procedure SaveToFile(const FileName: TFileName; TrimRight: boolean = false);
    procedure Print(const ATitle: string;
      AFirstLine: integer = 0; ALastLine: integer = -1); overload;
    procedure Print(AFirstLine: integer = 0; ALastLine: integer = -1); overload;
    procedure PrintSelection(const ATitle: string); overload;
    procedure PrintSelection; overload;
    property SelText: string read GetSelText write InsertText;
    property SelStart: integer read GetSelStart write SetSelStart;
    property SelLength: integer read GetSelLength write SetSelLength;
    function Find(AFindQuery: TFindQuery): integer;
    function FindNext: integer;
    function FindPrevious: integer;
    function FindFromTop: integer;
    property StartOver: boolean read FStartOver write FStartOver;
    function ReplaceAll(const FindQuery: TFindQuery;
      const ReplaceText: string; SelOnly: boolean = false): integer;
    function ShowBalloon(const ATitle, AText: string; AKind: TBalloonIconKind;
      APersistence: TBalloonPersistence; const APoint: TPoint): boolean;
    procedure HideBalloon;
    function BalloonVisible: boolean;
    procedure AddClass(const AClassRecord: TClassRecord);
    function RemoveClass(const AClassName: string): boolean;
    procedure ClearClasses;
    function GetClassFromName(const AClassName: string; out AClassRecord: TClassRecord): boolean;
    function GetClassIndex(const AClassName: string): integer;
    function ClassExists(const AClassName: string): boolean; overload; inline;
    function ClassExists(const AClassName: string; out Index: integer): boolean; overload; inline;
    property Classes[Index: integer]: TClassRecord read GetClassRecord;
    property ClassCount: integer read GetNumClasses;
    procedure AddLineControl(AControl: TControl);
    procedure InsertLineControl(AControl: TControl; LineIndex: integer);
    procedure AddGraphic(AGraphic: TGraphic);
    function DeleteControlAtLine(const LineIndex: integer): boolean;
    procedure CenterOnSelection(AReducedScroll: boolean = false);

    { Removes all line controls and resets the FTextFile.ControlAware flag. }
    procedure DeleteAllLineControls;

    procedure InsertGraphic(AGraphic: TGraphic; LineIndex: integer);
    function ControlInSelection: boolean;
    procedure TrimRight;
    procedure ClearBookmarks; inline;
    procedure AddBookmark(AIndex: integer; const APoint: TPoint); overload; inline;
    procedure AddBookmark(AIndex: integer); overload; inline;
    function AddBookmark(const APoint: TPoint): integer; overload; inline;
    function AddBookmark: integer; overload; inline;
    function GotoBookmark(AIndex: integer): boolean; inline;
    function BookmarkUsed(AIndex: integer): boolean; inline;
    function GetLineBookmark(ALineIndex: integer): integer;
    property LineCount: integer read GetLineCount;
    property Lines[Index: integer]: string read GetLine write SetLine;
    property LineClasses[Index: integer]: string read GetClass write SetClass; { Does not add an undo item. Should it?! }
    property Bookmarks[Index: integer]: TPoint read GetBookmark;
    property BookmarkCount: integer read GetBookmarkCount;
    property UsedBookmarkCount: integer read GetUsedBookmarkCount;
    procedure ExportToHTML(const FileName: TFileName);
    procedure RemoveAllMargins;
    procedure RestoreAllMargins;
    procedure ZoomIn;
    procedure ZoomOut;
    procedure ResetZoom;
    procedure WordWrap(ALineLength: integer = 80; ANice: boolean = true;
      AChr: char = #0);

    property Notifications[AIndex: integer]: integer read GetNotification;
    property NotificationCount: integer read GetNotificationCount;
    property NotificationStr[MsgID: integer]: string read GetNotificationStr;

    function HasNotificationMessage(MsgID: integer): boolean;
    function Sort(AFirstLine, ALastLine: integer): boolean; overload;
    function Sort: boolean; overload;
    function SortSelection: boolean;
    property LineComparer: TLineComparer read GetLineComparer write SetLineComparer;
    property SortReverseOrder: boolean read GetSortReverseOrder write SetSortReverseOrder;
    function MakeLinesUnique: boolean;
    procedure TruncateAt(AFirstLine, ALastLine, AIndex: integer;
      AChar: char = #0; PreserveChar: boolean = false; AReverse: boolean = false); overload;
    procedure TruncateAt(AIndex: integer;
      AChar: char = #0; PreserveChar: boolean = false; AReverse: boolean = false); overload;
    procedure TruncateAtInSelection(AIndex: integer;
      AChar: char = #0; PreserveChar: boolean = false; AReverse: boolean = false);
    procedure Filter(const AFilterOptions: TFilterOptions); overload;
    procedure Filter(const Contains, Starts, Ends: string; CaseSensitive: boolean;
      RemoveMatches: boolean); overload;

    procedure CliNewPrompt;
    procedure CliBeginOutput;
    procedure CliEndOutput;
    procedure CliWriteLn(const AStr, AClass: string); overload;
    procedure CliWriteLn(const AStr: string); overload;
    procedure CliWriteLn; overload;
    procedure CliAddHistory(const AStr: string);
    procedure CliClearHistory;
    property CliHistory[Index: integer]: string read GetCliHistory write SetCliHistory;
    property CliHistoryCount: integer read GetCliHistoryCount;
    property CliHistoryIndex: integer read GetCliHistoryIndex;
    function CliHistoryRecall(Index: integer): boolean;
    function CliHistoryDialog: integer;
    function CliHistoryDialogSelect: boolean;

    procedure RunScript(const AScript: TEditorScript; AIterations: integer = 1;
      ACounterInit: integer = 1; ACounterInc: integer = 1);
    procedure AbortScript;

    procedure LoadDefaultClasses;

    procedure PushEditorState;

    procedure RegisterFP(FormattingProcessor: TFormattingProcessor);
  published
    property Align;
    property AlignWithMargins;
    property Anchors;
    property BorderType: TBorderType read FBorderType write SetBorderType default btThemeBorder;
    property BorderWidth;
    property BorderColor: TColor read FBorderColor write SetBorderColor default clSilver;
    property Enabled;
    property Margins;
    property PopupMenu;
    property RulerPopupMenu: TPopupMenu read FRulerPopupMenu write FRulerPopupMenu;
    property TabStop default true;
    property TabOrder;
    property OnClick;
    property OnDblClick;
    property OnKeyDown;
    property OnKeyPress;
    property OnKeyUp;
    property OnMouseEnter;
    property OnMouseLeave;
    property OnMouseMove;
    property OnMouseDown;
    property OnMouseUp;
    property OnMouseActivate;
    property OnEnter;
    property OnExit;
    property BitmapEffect: TBitmapEffect read FBitmapEffect write SetBitmapEffect default beNone;
    property DisabledEffect: TBitmapEffect read FDisabledEffect write SetDisabledEffect default beGrayscale;
    property HandleHotkeys: boolean read FHandleHotkeys write FHandleHotkeys default true;
    property HandleBookmarkHotkeys: boolean read FHandleBookmarkHotkeys write FHandleBookmarkHotkeys default true;
    property UseSystemColors: boolean read FUseSystemColors write SetUseSystemColors default true;
    property BackgroundColor: TColor read FBackgroundColor write SetBackgroundColor default clWhite;
    property ForegroundColor: TColor read FForegroundColor write SetForegroundColor default clBlack;
    property SelBackgroundColor: TColor read FSelBackgroundColor write SetSelBackgroundColor default clBlack;
    property SelForegroundColor: TColor read FSelForegroundColor write SetSelForegroundColor default clWhite;
    property FindBackgroundColor: TColor read FFndBkColor write SetFindBackgroundColor default clYellow;
    property FindForegroundColor: TColor read FFndFgColor write SetFindForegroundColor default clBlack;
    property Font: TFont read FFont write SetFont;
    property LetterSpacing: integer read FLetterSpacing write SetLetterSpacing default 1;
    property LineSpacing: integer read FLineSpacing write SetLineSpacing default 1;
    property CaretAfterEOL: boolean read GetCaretAfterEOL write SetCaretAfterEOL default true;
    property AutoIndent: boolean read FAutoIndent write SetAutoIndent default true;
    property PlainText: string read GetText write SetText;
    property BeepOnInputError: boolean read FBeepOnInputError write FBeepOnInputError default true;
    property ErrorMessageOnReadOnlyError: boolean read FErrorMessageOnReadOnlyError write FErrorMessageOnReadOnlyError default true;
    property Overwrite: boolean read FOverwrite write SetOverwrite default false;
    property IndentSize: integer read FIndentSize write SetIndentSize default 2;
    property EditMode: TEditMode read GetEditMode write SetEditMode default emText;
    property ShowHiddenCharacters: boolean read FShowHiddenCharacters write SetShowHiddenCharacters default false;
    property LineHighlight: boolean read FLineHighlight write SetLineHighlight default false;
    property LineHighlightColor: TColor read FLineHighlightColor write SetLineHighlightColor default DEFAULT_LINE_HIGHLIGHT_COLOR;
    property MatchBrackets: boolean read FMatchBrackets write SetMatchBrackets default true;
    property BracketHighlightColor: TColor read FBracketHighlightColor write SetBracketHighlightColor default DEFAULT_BRACKET_HIGHLIGHT_COLOR;
    property AutoReplace: boolean read FAutoReplace write FAutoReplace default false;
    property MessageInterface: boolean read FMessageInterface write FMessageInterface default true;
    property InputTransform: TInputTransform read FInputTransform write FInputTransform default itNone;
    property SingleLine: boolean read GetSingleLine write SetSingleLine default false;
    property FallbackFonts: TStringList read FFallbackFonts write SetFallbackFonts;
    property UnicodeFallback: boolean read FUnicodeFallback write SetUnicodeFallback default true;
    property PasswordChar: char read FPasswordChar write SetPasswordChar default #0;
    property NumbersOnly: boolean read FNumbersOnly write FNumbersOnly default false;
    property TabLength: integer read FTabLength write FTabLength default 2;
    property LabelStyle: boolean read FLabelStyle write SetLabelStyle default false;
    property LabelEllipsis: boolean read FLabelEllipsis write SetLabelEllipsis default true;
    property AutoHeight: boolean read FAutoHeight write SetAutoHeight default true;
    property MultiSize: boolean read FMultiSize write SetMultiSize default false;
    property FormattingProcessor: TFormattingProcessor read FFormattingProcessor write SetFormattingProcessorSmple;
    property RulerFont: TFont read FRulerFont write FRulerFont;
    property RulerVisible: boolean read GetRulerVisible write SetRulerVisible stored false;
    property MarginLeft: integer read FMarginLeft write SetMarginLeft default DEFAULT_MARGIN_LEFT;
    property MarginRight: integer read FMarginRight write SetMarginRight default DEFAULT_MARGIN_Right;
    property MarginTop: integer read FMarginTop write SetMarginTop default DEFAULT_MARGIN_Top;
    property MarginBottom: integer read FMarginBottom write SetMarginBottom default DEFAULT_MARGIN_Bottom;
    property RulerWidth: integer read FRulerWidth write SetRulerWidth default DEFAULT_RULER_WIDTH;
    property RulerColor: TColor read FRulerColor write SetRulerColor default clDefault;
    property PrintSettings: TPrintSettings read FPrintSettings;
    property Zoom: integer read FZoom write SetZoom default 100;
    property RightLine: boolean read FRightLine write SetRightLine default false;
    property RightLinePos: integer read FRightLinePos write SetRightLinePos default 580;
    property RightLineColor: TColor read FRightLineColor write SetRightLineColor default clSilver;
    property WrapAt: string read GetWrapAt write SetWrapAt;
    property WrapAtRestore: boolean read GetFalse write RestoreWrapAt default false;
    property TextFileOwner: TTextFileOwner read FTextFileOwner write FTextFileOwner default tfoEditor;
    property SelectionBarBehaviour: TSelectionBarBehaviour read FSelectionBarBehaviour write FSelectionBarBehaviour default sbbAutoMixed;
    property NotificationMsgDuration: integer read FNotifyMsgDuration write FNotifyMsgDuration default DEFAULT_NOTIFICATION_MSG_DURATION;
    property ListBoxMode: boolean read FListBoxMode write SetListBoxMode;
    property ListBoxItemIndex: integer read GetListBoxItemIndex write SetListBoxItemIndex;
    property MultiCharSelect: boolean read FMultiCharSelect write FMultiCharSelect default true;
    property MultiCharReportView: boolean read FMultiCharReportView write FMultiCharReportView default false;
    property ScrollBehaviour: TScrollBehaviour read FScrollBehaviour write FScrollBehaviour default sbDefault;
    property AllowBitmapPaste: boolean read FAllowBitmapPaste write FAllowBitmapPaste default false;
    property WantTab: boolean read FWantTab write FWantTab default true;
    property WantReturn: boolean read FWantReturn write FWantReturn default true;
    property OnChange: TNotifyEvent read FOnChange write FOnChange;
    property OnSelChange: TNotifyEvent read FOnSelChange write FOnSelChange;
    property OnModified: TNotifyEvent read FOnModified write FOnModified;
    property OnOverwriteChange: TNotifyEvent read FOnOverwriteChange write FOnOverwriteChange;
    property OnBookmarksMoved: TNotifyEvent read FOnBookmarksMoved write FOnBookmarksMoved;
    property OnZoomChange: TNotifyEvent read FOnZoomChange write FOnZoomChange;
    property OnPrintBegin: TProgressStartEvent read FOnPrintBegin write FOnPrintBegin;
    property OnPrintProgress: TProgressEvent read FOnPrintProgress write FOnPrintProgress;
    property OnPrintEnd: TProgressCompleteEvent read FOnPrintEnd write FOnPrintEnd;
    property OnNotification: TNotificationMessage read FOnNotification write FOnNotification;
    property OnSimpleNotification: TSimpleNotificationMessage read FOnSimpleNotification write FOnSimpleNotification;
    property OnCliGetPromptClass: TCliGetPromptClassEvent read FOnCliGetPromptClass write FOnCliGetPromptClass;
    property OnCliInput: TCliInputEvent read FOnCliInput write FOnCliInput;
    property OnListBoxChange: TNotifyEvent read FOnListBoxChange write FOnListBoxChange;
    property OnListBoxSelect: TNotifyEvent read FOnListBoxSelect write FOnListBoxSelect;
    property OnFindDataClear: TNotifyEvent read FOnFindDataClear write FOnFindDataClear;
  end;

procedure Register;

const
  // Cursors
  RTE_CURSOR_BASE = 10;
  crBlock = TCursor(RTE_CURSOR_BASE + 1);
  crBlockCopy = TCursor(RTE_CURSOR_BASE + 2);
  crHand = TCursor(RTE_CURSOR_BASE + 3);
  crHandHold = TCursor(RTE_CURSOR_BASE + 4);
  crLineSel = TCursor(RTE_CURSOR_BASE + 5);

var
  FixedWidthFonts: TStrings;
  GlobalFileNumber: integer = 1;

type
  DynIntegerArray = array of integer;
  DynStringArray = array of string;

function FormatDataSize(const ASize: Int64): string;
function Split(const Str: string; const Delim: string): DynStringArray;

implementation

{$R TextEditorCursors.res}

{$WARN WIDECHAR_REDUCED OFF}

uses Character, StrUtils, Clipbrd, IMouse, SHFolder, Dialogs, ShellAPI,
  Printers, Themes, RulerPropertiesWin, MultiInput, StdCtrls, ComCtrls;

const
  CARET_WIDTH = 2;
  SCROLL_EXTRA = 120;
  AUTO_HEIGHT_PADDING = 2;

procedure Register;
begin
  RegisterComponents('Rejbrand 2015', [TTextEditor,
    TVowelsAndConsonantsFormattingProcessor, TXMLFormattingProcessor,
    TCSSFormattingProcessor, TINIFormattingProcessor, TPascalFormattingProcessor,
    TAlgoSimFormattingProcessor, THTMLFormattingProcessor,
    TMediaWikiFormattingProcessor]);
end;

function B(A: boolean): integer;
begin
  if A then
    result := 1
  else
    result := 0;
end;

function GetAppDataFolder: string;
var
  i: integer;
begin
  SetLength(result, MAX_PATH);
  if SHGetFolderPath(0, CSIDL_APPDATA, 0, SHGFP_TYPE_CURRENT, PChar(result)) <> S_OK then
    Exit('');
  i := Pos(#0, result);
  if i > 0 then
    SetLength(result, i-1);
end;

function GetAutoReplaceDataFileName: string;
var
  AppData: string;
begin
  AppData := GetAppDataFolder;
  if AppData = '' then Exit('');
  result := AppData + '\Rejbrand\AutoReplace\1.0\autoreplace.dat';
end;

function FontRecord(ASize: integer; AStyle: TFontStyles; AColor: TColor): TFontRecord;
begin
  result.Size := ASize;
  result.Style := AStyle;
  result.Color := AColor;
end;

function MakeClass(const AName: string; ASize: integer; AStyle: TFontStyles; AColor: TColor): TClassRecord;
begin
  result.Name := AName;
  result.Format := FontRecord(ASize, AStyle, AColor);
end;

function MakeFindQuery(const ASearchString: string; AMatchCase, AMatchWord, ALinebreak: boolean): TFindQuery;
begin
  result.SearchString := ASearchString;
  result.MatchCase := AMatchCase;
  result.MatchWord := AMatchWord;
  result.Linebreak := ALinebreak;
  result.UCBlock := 0;
end;

function MakeFindQuery(UCBlock: integer): TFindQuery; overload;
begin
  result.SearchString := '';
  result.MatchCase := false;
  result.MatchWord := false;
  result.Linebreak := false;
  result.UCBlock := UCBlock;
end;

function SamePoint(const Point1, Point2: TPoint): boolean; inline;
begin
  result := (Point1.X = Point2.X) and (Point1.Y = Point2.Y);
end;

function SameChangeRecord(const ChangeRecord1, ChangeRecord2: TChangeRecord): boolean; inline;
begin
  result := (ChangeRecord1.ChangeType = ChangeRecord2.ChangeType) and
            (ChangeRecord1.Data1      = ChangeRecord2.Data1)      and
            (ChangeRecord1.Data2      = ChangeRecord2.Data2)      and
            (ChangeRecord1.Data3      = ChangeRecord2.Data3)      and
            (ChangeRecord1.Data4      = ChangeRecord2.Data4);
end;

// No false positives, but (rare) false negatives are possible
function ChangeSubset(const ChangeRecord1, ChangeRecord2: TChangeRecord): boolean;

  function IsOneOfThem(const Y, X: integer): boolean;
  begin
    // Assuming: ChangeRecord2.ChangeType = ctTwoChars
    result := ((Y = ChangeRecord2.Data1) and (X = ChangeRecord2.Data2))
                                         OR
              ((Y = ChangeRecord2.Data3) and (X = ChangeRecord2.Data4));
  end;

begin
  result := false;
  case ChangeRecord2.ChangeType of
    ctFile:
      Exit(true);
    ctLineRange:
      case ChangeRecord1.ChangeType of
        ctNone:
          Exit(true);
        ctFile:
          Exit(false);
        ctLineRange:
          Exit((ChangeRecord1.Data1 >= ChangeRecord2.Data1) and (ChangeRecord1.Data2 <= ChangeRecord2.Data2));
        ctBlock:
          Exit((ChangeRecord1.Data1 >= ChangeRecord2.Data1) and (ChangeRecord1.Data2 <= ChangeRecord2.Data2));
        ctLine:
          Exit(InRange(ChangeRecord1.Data1, ChangeRecord2.Data1, ChangeRecord2.Data2));
        ctLineFrom:
          Exit(InRange(ChangeRecord1.Data1, ChangeRecord2.Data1, ChangeRecord2.Data2));
        ctChar:
          Exit(InRange(ChangeRecord1.Data1, ChangeRecord2.Data1, ChangeRecord2.Data2));
        ctTwoChars:
          Exit(InRange(ChangeRecord1.Data1, ChangeRecord2.Data1, ChangeRecord2.Data2) and InRange(ChangeRecord1.Data3, ChangeRecord2.Data1, ChangeRecord2.Data2));
      end;
    ctBlock:
      case ChangeRecord1.ChangeType of
        ctNone:
          Exit(true);
        ctFile:
          Exit(false);
        ctLineRange:
          Exit(false);
        ctBlock:
          Exit((ChangeRecord1.Data1 >= ChangeRecord2.Data1) and (ChangeRecord1.Data2 <= ChangeRecord2.Data2) and (ChangeRecord1.Data3 >= ChangeRecord2.Data3) and (ChangeRecord1.Data4 <= ChangeRecord2.Data4));
        ctLine:
          Exit(false);
        ctLineFrom:
          Exit(false);
        ctChar:
          Exit(InRange(ChangeRecord1.Data1, ChangeRecord2.Data1, ChangeRecord2.Data2) and InRange(ChangeRecord1.Data2, ChangeRecord2.Data3, ChangeRecord2.Data4));
        ctTwoChars:
          Exit(InRange(ChangeRecord1.Data1, ChangeRecord2.Data1, ChangeRecord2.Data2) and InRange(ChangeRecord1.Data2, ChangeRecord2.Data3, ChangeRecord2.Data4) and InRange(ChangeRecord1.Data3, ChangeRecord2.Data1, ChangeRecord2.Data2) and InRange(ChangeRecord1.Data4, ChangeRecord2.Data3, ChangeRecord2.Data4));
      end;
    ctLine:
      case ChangeRecord1.ChangeType of
        ctNone:
          Exit(true);
        ctFile:
          Exit(false);
        ctLineRange:
          Exit((ChangeRecord1.Data1 = ChangeRecord2.Data1) and (ChangeRecord1.Data2 = ChangeRecord2.Data1));
        ctBlock:
          Exit((ChangeRecord1.Data1 = ChangeRecord2.Data1) and (ChangeRecord1.Data2 = ChangeRecord2.Data1));
        ctLine:
          Exit(ChangeRecord1.Data1 = ChangeRecord2.Data1);
        ctLineFrom:
          Exit(ChangeRecord1.Data1 = ChangeRecord2.Data1);
        ctChar:
          Exit(ChangeRecord1.Data1 = ChangeRecord2.Data1);
        ctTwoChars:
          Exit((ChangeRecord1.Data1 = ChangeRecord2.Data1) and (ChangeRecord1.Data3 = ChangeRecord2.Data1));
      end;
    ctLineFrom:
      case ChangeRecord1.ChangeType of
        ctNone:
          Exit(true);
        ctFile:
          Exit(false);
        ctLineRange:
          Exit((ChangeRecord2.Data2 = 0) and (ChangeRecord1.Data1 = ChangeRecord1.Data2) and (ChangeRecord1.Data1 = ChangeRecord2.Data1));
        ctBlock:
          Exit((ChangeRecord1.Data1 = ChangeRecord1.Data2) and (ChangeRecord1.Data1 = ChangeRecord2.Data1) and (ChangeRecord1.Data3 >= ChangeRecord2.Data2));
        ctLine:
          Exit((ChangeRecord1.Data1 = ChangeRecord2.Data1) and (ChangeRecord2.Data2 = 0));
        ctLineFrom:
          Exit((ChangeRecord1.Data1 = ChangeRecord2.Data1) and (ChangeRecord1.Data2 >= ChangeRecord2.Data2));
        ctChar:
          Exit((ChangeRecord1.Data1 = ChangeRecord2.Data1) and (ChangeRecord1.Data2 >= ChangeRecord2.Data2));
        ctTwoChars:
          Exit((ChangeRecord1.Data1 = ChangeRecord2.Data1) and (ChangeRecord1.Data2 >= ChangeRecord2.Data2) and (ChangeRecord1.Data3 = ChangeRecord2.Data1) and (ChangeRecord1.Data4 >= ChangeRecord2.Data2));
      end;
    ctChar:
      case ChangeRecord1.ChangeType of
        ctNone:
          Exit(true);
        ctFile:
          Exit(false);
        ctLineRange:
          Exit(false);
        ctBlock:
          Exit((ChangeRecord1.Data1 = ChangeRecord2.Data1) and (ChangeRecord2.Data1 = ChangeRecord2.Data1) and (ChangeRecord1.Data3 = ChangeRecord2.Data2) and (ChangeRecord2.Data3 = ChangeRecord2.Data2));
        ctLine:
          Exit(false);
        ctLineFrom:
          Exit(false);
        ctChar:
          Exit((ChangeRecord1.Data1 = ChangeRecord2.Data1) and (ChangeRecord1.Data2 = ChangeRecord2.Data2));
        ctTwoChars:
          Exit((ChangeRecord1.Data1 = ChangeRecord2.Data1) and (ChangeRecord1.Data2 = ChangeRecord2.Data2) and (ChangeRecord1.Data3 = ChangeRecord2.Data1) and (ChangeRecord1.Data4 = ChangeRecord2.Data2));
      end;
    ctTwoChars:
      case ChangeRecord1.ChangeType of
        ctNone:
          Exit(true);
        ctFile:
          Exit(false);
        ctLineRange:
          Exit(false);
        ctBlock:
          Exit(((ChangeRecord1.Data1 = ChangeRecord2.Data1) and (ChangeRecord2.Data1 = ChangeRecord2.Data1) and (ChangeRecord1.Data3 = ChangeRecord2.Data2) and (ChangeRecord2.Data3 = ChangeRecord2.Data2))   OR   ((ChangeRecord1.Data1 = ChangeRecord2.Data3) and (ChangeRecord2.Data1 = ChangeRecord2.Data3) and (ChangeRecord1.Data3 = ChangeRecord2.Data4) and (ChangeRecord2.Data3 = ChangeRecord2.Data4)));
        ctLine:
          Exit(false);
        ctLineFrom:
          Exit(false);
        ctChar:
          Exit(((ChangeRecord1.Data1 = ChangeRecord2.Data1) and (ChangeRecord1.Data2 = ChangeRecord2.Data2))   OR   ((ChangeRecord1.Data1 = ChangeRecord2.Data3) and (ChangeRecord1.Data2 = ChangeRecord2.Data4)));
        ctTwoChars:
          Exit(IsOneOfThem(ChangeRecord1.Data1, ChangeRecord1.Data2) and IsOneOfThem(ChangeRecord1.Data3, ChangeRecord1.Data4));
      end;
  end;
end;

function MakeChangeRecord(ChangeType: TChangeType; Data1, Data2, Data3, Data4: integer): TChangeRecord;
begin
  result.ChangeType := ChangeType;
  result.Data1 := Data1;
  result.Data2 := Data2;
  result.Data3 := Data3;
  result.Data4 := Data4;
end;

function ChangeUnion(const ChangeRecord1, ChangeRecord2: TChangeRecord): TChangeRecord;
begin

  if (ChangeRecord1.ChangeType = ctFile) or (ChangeRecord2.ChangeType = ctFile) then
    Exit(FILE_CHANGE_RECORD);

  if ChangeSubset(ChangeRecord1, ChangeRecord2) then
    Exit(ChangeRecord2);

  if ChangeSubset(ChangeRecord2, ChangeRecord1) then
    Exit(ChangeRecord1);

  if (ChangeRecord1.ChangeType = ctChar) and (ChangeRecord1.ChangeType = ctChar) then
  begin
    result.ChangeType := ctTwoChars;
    result.Data1 := ChangeRecord1.Data1;
    result.Data2 := ChangeRecord1.Data2;
    result.Data3 := ChangeRecord2.Data1;
    result.Data4 := ChangeRecord2.Data2;
    Exit;
  end;

  if (ChangeRecord1.ChangeType = ctBlock) and (ChangeRecord2.ChangeType = ctBlock) then
  begin
    result.ChangeType := ctBlock;
    result.Data1 := Min(ChangeRecord1.Data1, ChangeRecord2.Data1);
    result.Data2 := Max(ChangeRecord1.Data2, ChangeRecord2.Data2);
    result.Data3 := Min(ChangeRecord1.Data3, ChangeRecord2.Data3);
    result.Data4 := Max(ChangeRecord1.Data4, ChangeRecord2.Data4);
    Exit;
  end;

  if (ChangeRecord1.ChangeType = ctLineRange) and (ChangeRecord2.ChangeType = ctLineRange) then
  begin
    result.ChangeType := ctLineRange;
    result.Data1 := Min(ChangeRecord1.Data1, ChangeRecord2.Data1);
    result.Data2 := Max(ChangeRecord1.Data2, ChangeRecord2.Data2);
    Exit;
  end;

  if (ChangeRecord1.ChangeType = ctLineRange) and (ChangeRecord2.ChangeType in [ctLine, ctLineFrom, ctChar]) then
  begin
    result.ChangeType := ctLineRange;
    result.Data1 := Min(ChangeRecord1.Data1, ChangeRecord2.Data1);
    result.Data2 := Max(ChangeRecord1.Data2, ChangeRecord2.Data1);
    Exit;
  end;

  if (ChangeRecord2.ChangeType = ctLineRange) and (ChangeRecord1.ChangeType in [ctLine, ctLineFrom, ctChar]) then
  begin
    result.ChangeType := ctLineRange;
    result.Data1 := Min(ChangeRecord2.Data1, ChangeRecord1.Data1);
    result.Data2 := Max(ChangeRecord2.Data2, ChangeRecord1.Data1);
    Exit;
  end;

  result := FILE_CHANGE_RECORD;

end;

function MakeEditorCommand(Verb: integer; Param1: integer = 0;
  ParamType1: TParamType = ptConstant; Param2: integer = 0;
  ParamType2: TParamType = ptConstant; Param3: integer = 0;
  ParamType3: TParamType = ptConstant; Param4: integer = 0;
  ParamType4: TParamType = ptConstant): TEditorCommand;
begin
  result.Verb := Verb;
  result.Param1.ParamType := ParamType1;
  result.Param1.ParamValue := Param1;
  result.Param2.ParamType := ParamType2;
  result.Param2.ParamValue := Param2;
  result.Param3.ParamType := ParamType3;
  result.Param3.ParamValue := Param3;
  result.Param4.ParamType := ParamType4;
  result.Param4.ParamValue := Param4;
end;

// Private utilities

function Occurrences(const Str: string; const Chr: char): integer; overload;
var
  i: integer;
begin
  result := 0;
  for i := 1 to Str.Length do
    if Str[i] = Chr then
      inc(result);
end;

function Occurrences(const Str: string; const SubStr: string;
  out indices: DynIntegerArray): integer; overload;
const
  ALLOC_BY = 4096;
var
  i: integer;
begin
  result := 0;
  i := 0;
  SetLength(indices, ALLOC_BY);
  repeat
    i := PosEx(SubStr, Str, i + 1);
    if i > 0 then
    begin
      if result = length(indices) then
        SetLength(indices, Length(indices) + ALLOC_BY);
      indices[result] := i;
      inc(result);
    end;
  until i = 0;
  SetLength(indices, result);
end;

function Occurrences2(const Str: string; const SubStr: string;
  out indices: DynIntegerArray): integer; overload;
const
  ALLOC_BY = 4096;
var
  i: integer;
begin
  result := 0;
  i := 0;
  SetLength(indices, ALLOC_BY);
  repeat
    i := PosEx(SubStr, Str, i + 1);
    if i > 0 then
    begin
      if result = length(indices) then
        SetLength(indices, Length(indices) + ALLOC_BY);
      indices[result] := i;
      inc(result);
    end;
  until i = 0;
  SetLength(indices, result + 1);
  indices[result] := Length(Str) + 1;
end;

function FormatDataSize(const ASize: Int64): string;
const
  prefixes: array[0..6] of string = ('', 'k', 'M', 'G', 'T', 'P', 'E');
var
  val: real;
  n: integer;
begin
  if ASize < 0 then
    raise Exception.CreateFmt('FormatDataSize: Invalid data size %d.', [ASize]);
  val := ASize;
  for n := 0 to high(prefixes) do
  begin
    if (val <= 1000) or (n = high(prefixes)) then
    begin
      result := FormatFloat('0.##', val) + #32 + prefixes[n] + 'B';
      break;
    end;
    val := val / 1024;
  end;
end;

function Split(const Str: string; const Delim: string): DynStringArray;
var
  n: integer;
  indices: DynIntegerArray;
  i: Integer;
begin
  n := Occurrences2(Str, Delim, indices);
  SetLength(result, n + 1);
  if n = 0 then
    result[0] := Str
  else
  begin
    result[0] := Copy(Str, 1, indices[0] - 1);
    for i := 0 to n - 1 do
      result[i+1] := Copy(Str, indices[i] + Length(Delim), indices[i + 1] - indices[i] - Length(Delim));
  end;
end;

{ Text transformation functions }

function imod(const x: integer; const y: integer): integer;
begin
  if x >= 0 then
    imod := x - floor(x/y) * y
  else
    imod := x + ceil(-x/y) * y;
end;

function ChrUpperCase(C: char): char;
begin
  result := AnsiUpperCase(C)[1];
end;

function ChrLowerCase(C: char): char;
begin
  result := AnsiLowerCase(C)[1];
end;

function ChrInvertCase(C: char): char;
begin
  if C.IsUpper then
    result := AnsiLowerCase(C)[1]
  else
    result := AnsiUpperCase(C)[1];
end;

function ChrROT13(C: char): char;
begin
  if InRange(ord(C), ord('A'), ord('Z')) then
    result := Chr(ord('A') + (ord(C) - ord('A') + 13) mod 26)
  else if InRange(ord(C), ord('a'), ord('z')) then
    result := Chr(ord('a') + (ord(C) - ord('a') + 13) mod 26)
  else
    result := C;
end;

function ChrCaesar(N: integer): TChrTransformFunc;
begin
  result := function(C: char): char
    begin
      if InRange(ord(C), ord('A'), ord('Z')) then
        result := Chr(ord('A') + imod((ord(C) - ord('A') + N), 26))
      else if InRange(ord(C), ord('a'), ord('z')) then
        result := Chr(ord('a') + imod((ord(C) - ord('a') + N), 26))
      else
        result := C;
    end;
end;

function TxtVigenère(const Key: string; decode: boolean = false): TTextTransformFunc;
var
  n: integer;
  KeyChrs: array of byte;
  i: Integer;
  factor: integer;
begin
  n := Length(Key);
  SetLength(KeyChrs, n);
  for i := 1 to n do
    if InRange(ord(Key[i]), ord('A'), ord('Z')) then
      KeyChrs[i - 1] := ord(Key[i]) - ord('A')
    else
      raise Exception.Create('Invalid character in Vigenère key.');

  factor := IfThen(decode, -1, 1);
  result := function(const AText: string): string
    var
      j: Integer;
    begin
      SetLength(result, Length(AText));
      for j := 1 to Length(AText) do
      begin
        if InRange(ord(AText[j]), ord('A'), ord('Z')) then
          result[j] := Chr(ord('A') + imod(ord(AText[j]) - ord('A') + factor * KeyChrs[(j - 1) mod n], 26))
        else if InRange(ord(AText[j]), ord('a'), ord('z')) then
          result[j] := Chr(ord('a') + imod(ord(AText[j]) - ord('a') + factor * KeyChrs[(j - 1) mod n], 26))
        else
          result[j] := AText[j];
      end;
    end;
end;

function TxtCamelCase(const AText: string): string;
var
  i: Integer;
  StartOfWord: boolean;
begin
  StartOfWord := true;
  result := AText;
  for i := 1 to Length(AText) do
  begin
    if StartOfWord and IsCharAlpha(AText[i]) then
    begin
      result[i] := AnsiUpperCase(AText[i])[1];
      StartOfWord := false;
    end
    else if AText[i].IsWhiteSpace then
      StartOfWord := true;
  end;
end;

function TxtSentenceCase(const AText: string): string;
var
  StartOfSentence: boolean;
  i: Integer;
begin
  StartOfSentence := true;
  result := AText;
  for i := 1 to AText.Length do
    if StartOfSentence and IsCharAlpha(AText[i]) then
    begin
      result[i] := AnsiUpperCase(AText[i])[1];
      StartOfSentence := false;
    end
    else if AText[i] in ['.', '!', '?'] then
      StartOfSentence := true;
end;

function ReverseText(const AText: string): string;
var
  i, j: integer;
begin
  SetLength(result, AText.Length);
  if AText.IsEmpty then
    Exit;
  i := 1;
  j := result.Length;
  while j > 0 do
  begin
    if (AText[i] = #13) and (i < AText.Length) and (AText[i + 1] = #10) then
    begin
      result[j] := #10;
      result[j - 1] := #13;
      inc(i);
      dec(j);
    end
    else
      result[j] := AText[i];
    inc(i);
    dec(j);
  end;
end;

{ TTextEditor }

function IsKeyDown(const AKey: integer): boolean;
begin
  result := GetKeyState(AKey) and $8000 <> 0;
end;

function IsKeyOn(const AKey: integer): boolean;
begin
  result := GetKeyState(AKey) and 1 = 1;
end;

function GetScrollMode: boolean;
var
  AltDown, ScrlLockOn: boolean;
begin
  AltDown := IsKeyDown(VK_LMENU);
  ScrlLockOn := IsKeyOn(VK_SCROLL);
  result := (AltDown xor ScrlLockOn) and not IsKeyDown(VK_SHIFT);
end;

procedure TTextEditor.ChangeCursor(Shift: TShiftState);
var
  P: TPoint;
begin
  if GetCursorPos(P) then
    with ScreenToClient(P) do
      ChangeCursor(Shift, Y, X);
end;

procedure TTextEditor.ChangeCursor(Shift: TShiftState; Y, X: Integer);
var
  CP: TPoint;
begin

  if not (FValidPaintState and Visible) then Exit;

  if FScriptRunning then
  begin
    Cursor := crHourGlass;
    Exit;
  end;

  CP := CharAtPhysicalPixel(Point(X, Y));

  if FScrollMode then
    if csLButtonDown in ControlState then
      Cursor := crHandHold
    else
      Cursor := crHand
  else if X < GetFunctionalSelectionBarWidth then
    Cursor := crLineSel
  else
    if ((X >= FMarginLeft) and FTextFile.IsCharSel(CP)) and not SingleLine then
      if ssCtrl in Shift then
        Cursor := crBlockCopy
      else
        Cursor := crBlock
    else
      Cursor := crIBeam;

end;

function TTextEditor.CaretPosAtVirtualPixel(Pixel: TPoint): TPoint;
begin
  result := CharAtVirtualPixelEx(Pixel, true);
end;

procedure TTextEditor.CenterOnSelection(AReducedScroll: boolean);
var
  p1, p2, p: TPoint;
begin

  p1 := VirtualPixelAtChar(TextFile.CaretPos.Data);

  if TextFile.HasSelection then
    p2 := VirtualPixelAtChar(TextFile.CaretPos.SelEnd)
  else
    p2 := p1;

  p.X := p1.X + (p2.X - p1.X) div 2 - ClientWidth div 2;
  p.Y := p1.Y + (p2.Y - p1.Y) div 2 - ClientHeight div 2;

  if AReducedScroll then
  begin
    if Abs(FScrollPos.X - p.X) < ClientWidth div 3 then
      p.X := FScrollPos.X;
    if Abs(FScrollPos.Y - p.Y) < ClientHeight div 3 then
      p.Y := FScrollPos.Y;
  end;

  SetScrollPosXY(p.X, p.Y);

end;

function TTextEditor.CaretPosAtPhysicalPixel(Pixel: TPoint): TPoint;
begin
  result := CharAtPhysicalPixelEx(Pixel, true);
end;

function TTextEditor.ChrInGlyphSet(GlyphSet: PGlyphSet;
  Codepoint: integer): boolean;

  function InInterval(Val, Start, Length: integer): boolean; inline;
  begin
    InInterval := (Val >= Start) and (Val < Start + Length);
  end;

var
  i: Integer;
begin
  result := false;
  if GlyphSet = nil then Exit;
  for i := 0 to GlyphSet^.cRanges - 1 do
    with GlyphSet^.ranges[i] do
      if InInterval(Codepoint, ord(wcLow), cGlyphs) then
        Exit(true);
end;

function TTextEditor.ChrInGlyphSet(GlyphSet: PGlyphSet;
  Character: char): boolean;
begin
  result := ChrInGlyphSet(GlyphSet, ord(Character));
end;

function TTextEditor.ChrTransformSelection(Transformation: TChrTransformFunc;
  const TransformName: string): boolean;
begin
  TypeTimerEnd;
  result := FTextFile.ChrTransform(Transformation);
  if result then
    AddUndoRecord(Format(SUndoSelectionTransformed, [TransformName]), UID_UNKNOWN);
end;

procedure TTextEditor.ChrTransformText(Transformation: TChrTransformFunc;
  const TransformName: string);
begin
  TypeTimerEnd;
  FTextFile.ChrTransformText(Transformation);
  AddUndoRecord(Format(SUndoTextTransformed, [TransformName]), UID_UNKNOWN);
end;

procedure TTextEditor.ClearLine;
begin
  TypeTimerEnd;
  FTextFile.ClearLine;
  AddUndoRecord(SUndoLineCleared, UID_DELETE);
end;

procedure TTextEditor.ClearSelection;
begin
  TypeTimerEnd;
  FTextFile.ClearSelection;
  AddUndoRecord(SUndoSelectionCleared, UID_DELETE);
end;

procedure TTextEditor.ClearUndoHistory;
begin
  FTypeTimer.Enabled := false;
  FTextFile.ClearUndoHistory;
  AddUndoRecord(SUndoFirstPost, UID_UNKNOWN);
end;

procedure TTextEditor.CliAddHistory(const AStr: string);
begin
  SetLength(FCliHistory, Length(FCliHistory) + 1);
  FCliHistory[high(FCliHistory)] := AStr;
end;

procedure TTextEditor.CliBeginOutput;
begin
  FCliMultiOutput := true;
end;

procedure TTextEditor.CliClearHistory;
begin
  SetLength(FCliHistory, 0);
end;

procedure TTextEditor.CliEndOutput;
begin
  FCliMultiOutput := false;
  CliNewPrompt;
end;

procedure TTextEditor.CliHistoryDialogListBoxSelect(Sender: TObject);
begin
  if (Sender is TTextEditor) and (TTextEditor(Sender).Parent is TForm) then
    TForm(TTextEditor(Sender).Parent).ModalResult := mrOk;
end;

procedure TTextEditor.CliHistoryDialogListBoxKeyDown(Sender: TObject;
  var Key: Word; Shift: TShiftState);
begin
  if Key = VK_ESCAPE then
    if (Sender is TTextEditor) and (TTextEditor(Sender).Parent is TForm) then
      TForm(TTextEditor(Sender).Parent).ModalResult := mrCancel;
end;

function TTextEditor.CliHistoryDialog: integer;
var
  frm: TForm;
  lb: TTextEditor;
  i: Integer;
begin
  result := -1;
  frm :=  TForm.Create(nil);
  try
    frm.Caption := SCliHistoryDialogCaption;
    frm.BorderStyle := bsSizeToolWin;
    frm.ClientWidth := 512;
    frm.ClientHeight := 256;
    with ClientToScreen(Point(ClientWidth div 2 - frm.ClientWidth div 2,
      ClientHeight div 2 - frm.ClientHeight div 2)) do
    begin
      frm.Left := X;
      frm.Top := Y;
    end;
    lb := TTextEditor.Create(frm);
    lb.Parent := frm;
    lb.Align := alClient;
    lb.TabStop := true;
    lb.UseSystemColors := Self.UseSystemColors;
    lb.Color := Self.Color;
    lb.Font.Assign(Self.Font);
    lb.LineHighlightColor := Self.LineHighlightColor;
    if lb.Color = lb.LineHighlightColor then
    begin
      lb.Color := clWhite;
      lb.Font.Color := clBlack;
      lb.LineHighlightColor := DEFAULT_LINE_HIGHLIGHT_COLOR;
    end;
    lb.ListBoxMode := true;
    lb.RulerWidth := 32;
    lb.MarginLeft := 40;
    lb.RulerColor := Self.RulerColor;
    lb.OnKeyDown := CliHistoryDialogListBoxKeyDown;
    lb.OnListBoxSelect := CliHistoryDialogListBoxSelect;

    lb.BeginAddLine;
    for i := 0 to CliHistoryCount - 1 do
      lb.AddLine(CliHistory[i]);
    lb.EndAddLine;

    if FCliHistoryIndex <> -1 then
      lb.ListBoxItemIndex := FCliHistoryIndex;

    if frm.ShowModal = mrOk then
      result := lb.ListBoxItemIndex;
  finally
    frm.Free;
  end;
end;

function TTextEditor.CliHistoryDialogSelect: boolean;
var
  index: integer;
begin
  index := CliHistoryDialog;
  result := index <> -1;
  if result then
    CliHistoryRecall(index);
end;

function TTextEditor.CliHistoryDown: boolean;
begin
  result := InRange(FCliHistoryIndex, 0, high(FCliHistory) - 1);
  if result then
  begin
    inc(FCliHistoryIndex);
    TypeTimerEnd;
    Lines[LineCount - 1] := FCliHistory[FCliHistoryIndex];
    FTextFile.GotoEOF;
    AddUndoRecord(SUndoCliHistory, UID_UNKNOWN);
  end;
end;

function TTextEditor.CliHistoryRecall(Index: integer): boolean;
begin
  result := InRange(Index, 0, high(FCliHistory));
  if result then
  begin
    FCliHistoryIndex := Index;
    TypeTimerEnd;
    Lines[LineCount - 1] := FCliHistory[FCliHistoryIndex];
    FTextFile.GotoEOF;
    AddUndoRecord(SUndoCliHistory, UID_UNKNOWN);
  end;
end;

function TTextEditor.CliHistoryUp: boolean;
begin
  result := InRange(FCliHistoryIndex, 1, high(FCliHistory) + 1);
  if result then
  begin
    dec(FCliHistoryIndex);
    TypeTimerEnd;
    Lines[LineCount - 1] := FCliHistory[FCliHistoryIndex];
    FTextFile.GotoEOF;
    AddUndoRecord(SUndoCliHistory, UID_UNKNOWN);
  end;
end;

procedure TTextEditor.CliNewPrompt;
var
  ClassName: string;
begin
  FTypeTimer.Enabled := false;
  ClassName := '';
  if Assigned(FOnCliGetPromptClass) then
    FOnCliGetPromptClass(Self, ClassName);
  AddLine('', ClassName);
  ClearUndoHistory;
  FCliHistoryIndex := high(FCliHistory) + 1;
end;

procedure TTextEditor.CliWriteLn(const AStr, AClass: string);
begin
  if FTextFile.LineIsEmpty(LineCount - 1) then
  begin
    Lines[LineCount - 1] := AStr;
    LineClasses[LineCount - 1] := AClass;
  end
  else
    AddLine(AStr, AClass);
  if not FCliMultiOutput then
    CliNewPrompt;
end;

procedure TTextEditor.CliWriteLn(const AStr: string);
begin
  CliWriteLn(AStr, '');
end;

procedure TTextEditor.CliWriteLn;
begin
  CliWriteLn('', '');
end;

procedure TTextEditor.ClearLine(LineIndex: integer);
begin
  FTextFile.ClearLine(LineIndex);
end;

function TTextEditor.CommandEnabled(Command: integer): boolean;
var
  dummy: string;
begin
  result := true;
  case Command and $FFFF of
    EDITOR_COMMAND_PASTE:
      result := (Clipboard.HasFormat(CF_TEXT) or (FAllowBitmapPaste and Clipboard.HasFormat(CF_BITMAP) and not SingleLine)) and Enabled;
    EDITOR_COMMAND_PASTE_AS_BLOCK:
      result := Clipboard.HasFormat(CF_TEXT) and Enabled;
    EDITOR_COMMAND_CUT, EDITOR_COMMAND_COPY, EDITOR_COMMAND_CLEAR_SELECTION:
      result := FTextFile.HasSelection and Enabled;
    EDITOR_COMMAND_UNDO:
      result := CanUndo;
    EDITOR_COMMAND_REDO:
      result := CanRedo;
    EDITOR_COMMAND_SELECT_ALL:
      result := (not FTextFile.Empty) and Enabled;
    EDITOR_COMMAND_ACTIVATE_CONTROL:
      result := LineIsWinControlOrHasPopup(CaretPos.Y);
    EDITOR_COMMAND_CLASS_MENU:
      result := not LineIsControl(CaretPos.Y);
    EDITOR_COMMAND_OPEN_URL_AT_CARET:
      result := GetURLAtCaret(dummy);
    EDITOR_COMMAND_SEL_UPPER_CASE .. EDITOR_COMMAND_SEL_INVERT_CASE,
    EDITOR_COMMAND_SEL_ROT13 .. EDITOR_COMMAND_SEL_CAESAR:
      result := FTextFile.HasSelection;
    EDITOR_COMMAND_SEL_CAMEL_CASE .. EDITOR_COMMAND_SEL_SENTENCE_CASE,
    EDITOR_COMMAND_SEL_VIGENERE, EDITOR_COMMAND_SEL_REVERSE:
      result := FTextFile.HasSelection and (FTextFile.CaretPos.SelectionType = stLineBased);
    EDITOR_COMMAND_SORT_ALL, EDITOR_COMMAND_SORT:
      result := FTextFile.LineCount >= 2;
    EDITOR_COMMAND_SORT_SEL:
      result := FTextFile.HasSelection;
    EDITOR_COMMAND_ABORT_SCRIPT:
      result := FScriptRunning;
    EDITOR_COMMAND_SAVE, EDITOR_COMMAND_SET_EDIT_MODE:
      result := not FTextFile.StrictReadOnly;
  end;
end;

function TTextEditor.CommandVisible(Command: integer): boolean;
var
  dummy: string;
begin
  result := true;
  case Command and $FFFF of
    EDITOR_COMMAND_ACTIVATE_CONTROL:
      result := LineIsWinControlOrHasPopup(CaretPos.Y);
    EDITOR_COMMAND_BOOKMARK_SET_MENU,
    EDITOR_COMMAND_BOOKMARK_GO_MENU,
    EDITOR_COMMAND_BOOKMARK_CLEAR_MENU:
      result := not SingleLine;
    EDITOR_COMMAND_CLASS_MENU:
      result := (ClassCount > 0) and FTextFile.UseLineClasses;
    EDITOR_COMMAND_OPEN_URL_AT_CARET:
      result := GetURLAtCaret(dummy);
    EDITOR_COMMAND_SEL_TRANSFORM_MENU:
      result := FTextFile.HasSelection;
  end;
end;

function TTextEditor.ControlInSelection: boolean;
var
  FirstPoint, SecondPoint: TPoint;
  i: Integer;
begin
  result := false;
  if not FTextFile.ControlAware then Exit;
  FTextFile.CaretPos.GetSelBdry(FirstPoint, SecondPoint);
  for i := FirstPoint.Y to SecondPoint.Y do
    if LineIsControl(i) then
      Exit(true);
end;

procedure TTextEditor.CopyAll;
begin
  Clipboard.AsText := GetText;
end;

procedure TTextEditor.CopySelection(const ANewPos: TPoint);
var
  S: string;
begin
  if FTextFile.IsCharSel(ANewPos) then Exit;
  TypeTimerEnd;
  S := SelText;
  FTextFile.CaretPos.SetPoint(ANewPos);
  FTextFile.InsertText(S);
  AddUndoRecord(SUndoMouseCopy, UID_DRAGDROP);
end;

procedure TTextEditor.CopyToClipboard;
begin
  FTextFile.CopyToClipboard;
end;

procedure TTextEditor.FallbackFontsChange(Sender: TObject);
begin
  BuildFontDataArray;
  Invalidate;
end;

procedure TTextEditor.BalloonTimerTimer(Sender: TObject);
begin
  FBalloonTimer.Enabled := false;
  HideBalloon;
end;

procedure TTextEditor.UpdateSPI;
begin
  SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, FSPIScrollLines, 0);
end;

constructor TTextEditor.Create(AOwner: TComponent);
var
  item, item2: TMenuItem;
begin
  inherited;
  FPDict := TDictionary<string, TFormattingProcessor>.Create;
  FInsertionPoint := Point(-1, -1);
  FWantReturn := true;
  FWantTab := true;
  FAllowBitmapPaste := false;
  UpdateSPI;
  FMultipleCarets := false;
  SetLength(FCarets, 0);
  FCaretAfterEOL := true;
  FScrollBehaviour := sbDefault;
  FIndentSize := 2;
  FAutoIndent := true;
  FVisualUpdateLock := 0;
  FBitmapEffect := beNone;
  FDisabledEffect := beGrayscale;
  Width := 400;
  Height := 200;
  DoubleBuffered := true;
  FNoVerifyFont := false;
  FMultiCharReportView := false;
  FASHyphenAsteriskToggle := false;
  FMultiCharSelect := true;
  FCliMultiOutput := false;
  FValidPaintState := false;
  FCliHistoryIndex := -1;
  SetLength(FCliHistory, 0);
  FPreserveDesiredColumn := false;
  FScriptCounter := 0;
  FAbortScript := false;
  FScriptRunning := false;
  FNotifyMsgDuration := DEFAULT_NOTIFICATION_MSG_DURATION;
  SetLength(FNotifications, 0);
  FSelectionBarBehaviour := sbbAutoMixed;
  FTextFileOwner := tfoEditor;
  FBorderColor := clSilver;
  FRightLine := false;
  FRightLinePos := 580;
  FRightLineColor := clSilver;
  FPrintSettings := TPrintSettings.Create;
  FZoom := 100;
  FRulerColor := clDefault;
  FNoScrollToCaret := false;
  FRulerWidth := DEFAULT_RULER_WIDTH;
  FMarginLeft := DEFAULT_MARGIN_LEFT;
  FMarginRight := DEFAULT_MARGIN_RIGHT;
  FMarginTop := DEFAULT_MARGIN_TOP;
  FMarginBottom := DEFAULT_MARGIN_BOTTOM;
  FNextControlID := 0;
  FCachedHorizontalExtent := 0;
  FMultiSize := false;
  FAutoHeight := true;
  FLabelStyle := false;
  FLabelEllipsis := true;
  FTabLength := 2;
  FBalloonTimer := TTimer.Create(nil);
  FBalloonTimer.Enabled := false;
  FBalloonTimer.Interval := 10000;
  FBalloonTimer.OnTimer := BalloonTimerTimer;
  FNumbersOnly := false;
  FPasswordChar := #0;
  FGLYPHBM := TBitmap.Create;
  FFONTBM := TBitmap.Create;
  FUnicodeFallback := true;
  FFallbackFonts := TStringList.Create;
  FFallbackFonts.OnChange := FallbackFontsChange;
  FBorderType := btThemeBorder;
  FStartOver := true;
  FFndBkColor := clYellow;
  FFndFgColor := clBlack;
  FInputTransform := itNone;
  FMessageInterface := true;
  FAutoReplace := false;
  FScrollPos := Point(0, 0);
  FBracketHighlight := false;
  FShowHiddenCharacters := false;
  FLineHighlight := false;
  FLineHighlightColor := DEFAULT_LINE_HIGHLIGHT_COLOR;
  FBracketHighlightColor := DEFAULT_BRACKET_HIGHLIGHT_COLOR;
  FDoubleClicking := false;
  Cursor := crIBeam;
  FUseSystemColors := true;
  FBackgroundColor := clWhite;
  FForegroundColor := clBlack;
  FSelBackgroundColor := clBlack;
  FSelForegroundColor := clWhite;
  FLetterSpacing := 1;
  FLineSpacing := 1;
  FTextFile := TTextFile.Create;
  ConnectTextFileToEditor;
  FTextFile.AutoIndent := true;
  FFont := TFont.Create;
  VerifyFont;
  FFont.OnChange := FontChange;
  SetupColors;
  FHandleHotkeys := true;
  FHandleBookmarkHotkeys := true;
  FBeepOnInputError := true;
  FErrorMessageOnReadOnlyError := true;
  FOverwrite := false;
  FMatchBrackets := true;
  FRulerFont := TFont.Create;
  FRulerFont.OnChange := RulerFontChange;

  ControlStyle := ControlStyle + [csPannable];

  FTypeTimer := TTimer.Create(nil);
  FTypeTimer.Enabled := false;
  FTypeTimer.Interval := 2000;
  FTypeTimer.OnTimer := TypeTimerTimer;

  // Cursors
  Screen.Cursors[crBlock] := LoadImage(hInstance, 'SELHOVER', IMAGE_CURSOR,
    0, 0, LR_DEFAULTCOLOR);
  Screen.Cursors[crBlockCopy] := LoadImage(hInstance, 'SELCOPY', IMAGE_CURSOR,
    0, 0, LR_DEFAULTCOLOR);
  Screen.Cursors[crHand] := LoadImage(hInstance, 'HAND', IMAGE_CURSOR, 0, 0,
    LR_DEFAULTCOLOR);
  Screen.Cursors[crHandHold] := LoadImage(hInstance, 'HANDHOLD', IMAGE_CURSOR,
    0, 0, LR_DEFAULTCOLOR);
  Screen.Cursors[crLineSel] := LoadImage(hInstance, 'LINESEL', IMAGE_CURSOR,
    0, 0, LR_DEFAULTCOLOR);

  // Popup menu
  FPopupMenu := TPopupMenu.Create(nil);
  FPopupMenu.OnPopup := MenuPopup;

  item := TMenuItem.Create(FPopupMenu);
  item.Caption := SMenuOpenURL;
  item.Hint := SMenuOpenURLHint;
  item.OnClick := MenuItemMessage;
  item.Tag := EDITOR_COMMAND_OPEN_URL_AT_CARET;
  FPopupMenu.Items.Add(item);

  item := TMenuItem.Create(FPopupMenu);
  item.Caption := '-';
  item.Tag := 0;
  FPopupMenu.Items.Add(item);

  item := TMenuItem.Create(FPopupMenu);
  item.Caption := SMenuUndo;
  item.Hint := SMenuUndoHint;
  item.OnClick := MenuItemMessage;
  item.Tag := EDITOR_COMMAND_UNDO;
  FPopupMenu.Items.Add(item);

  item := TMenuItem.Create(FPopupMenu);
  item.Caption := SMenuRedo;
  item.Hint := SMenuRedoHint;
  item.OnClick := MenuItemMessage;
  item.Tag := EDITOR_COMMAND_REDO;
  FPopupMenu.Items.Add(item);

  item := TMenuItem.Create(FPopupMenu);
  item.Caption := '-';
  item.Tag := 0;
  FPopupMenu.Items.Add(item);

  item := TMenuItem.Create(FPopupMenu);
  item.Caption := SMenuCut;
  item.Hint := SMenuCutHint;
  item.OnClick := MenuItemMessage;
  item.Tag := EDITOR_COMMAND_CUT;
  FPopupMenu.Items.Add(item);

  item := TMenuItem.Create(FPopupMenu);
  item.Caption := SMenuCopy;
  item.Hint := SMenuCopyHint;
  item.OnClick := MenuItemMessage;
  item.Tag := EDITOR_COMMAND_COPY;
  FPopupMenu.Items.Add(item);

  item := TMenuItem.Create(FPopupMenu);
  item.Caption := SMenuPaste;
  item.Hint := SMenuPasteHint;
  item.OnClick := MenuItemMessage;
  item.Tag := EDITOR_COMMAND_PASTE;
  FPopupMenu.Items.Add(item);

  item := TMenuItem.Create(FPopupMenu);
  item.Caption := SMenuClear;
  item.Hint := SMenuClearHint;
  item.OnClick := MenuItemMessage;
  item.Tag := EDITOR_COMMAND_CLEAR_SELECTION;
  FPopupMenu.Items.Add(item);

  item := TMenuItem.Create(FPopupMenu);
  item.Caption := '-';
  item.Tag := 0;
  FPopupMenu.Items.Add(item);

  item := TMenuItem.Create(FPopupMenu);
  item.Caption := SMenuSelectAll;
  item.Hint := SMenuSelectAllHint;
  item.OnClick := MenuItemMessage;
  item.Tag := EDITOR_COMMAND_SELECT_ALL;
  FPopupMenu.Items.Add(item);

  item := TMenuItem.Create(FPopupMenu);
  item.Caption := '-';
  item.Tag := 0;
  FPopupMenu.Items.Add(item);

  item := TMenuItem.Create(FPopupMenu);
  item.Caption := SMenuSetBookmark;
  item.OnClick := MenuItemMessage;
  item.Tag := EDITOR_COMMAND_BOOKMARK_SET_MENU;
  FPopupMenu.Items.Add(item);

  item := TMenuItem.Create(FPopupMenu);
  item.Caption := SMenuGotoBookmark;
  item.OnClick := MenuItemMessage;
  item.Tag := EDITOR_COMMAND_BOOKMARK_GO_MENU;
  FPopupMenu.Items.Add(item);

  item := TMenuItem.Create(FPopupMenu);
  item.Caption := SMenuClearBookmark;
  item.OnClick := MenuItemMessage;
  item.Tag := EDITOR_COMMAND_BOOKMARK_CLEAR_MENU;
  FPopupMenu.Items.Add(item);

  item := TMenuItem.Create(FPopupMenu);
  item.Caption := '-';
  item.Tag := 0;
  FPopupMenu.Items.Add(item);

  item := TMenuItem.Create(FPopupMenu);
  item.Caption := SMenuClasses;
  item.OnClick := MenuItemMessage;
  item.Tag := EDITOR_COMMAND_CLASS_MENU;
  FPopupMenu.Items.Add(item);

  item := TMenuItem.Create(FPopupMenu);
  item.Caption := SMenuActivateControl;
  item.Hint := SMenuActivateControlHint;
  item.OnClick := MenuItemMessage;
  item.Tag := EDITOR_COMMAND_ACTIVATE_CONTROL;
  FPopupMenu.Items.Add(item);

  item := TMenuItem.Create(FPopupMenu);
  item.Caption := SMenuTransform;
  item.OnClick := MenuItemMessage;
  item.Tag := EDITOR_COMMAND_SEL_TRANSFORM_MENU;
  FPopupMenu.Items.Add(item);

  item2 := TMenuItem.Create(item);
  item2.Caption := SMenuTransformUpperCase;
  item2.Hint := SMenuTransformUpperCaseHint;
  item2.OnClick := MenuItemMessage;
  item2.Tag := EDITOR_COMMAND_SEL_UPPER_CASE;
  item.Add(item2);

  item2 := TMenuItem.Create(item);
  item2.Caption := SMenuTransformLowerCase;
  item2.Hint := SMenuTransformLowerCaseHint;
  item2.OnClick := MenuItemMessage;
  item2.Tag := EDITOR_COMMAND_SEL_LOWER_CASE;
  item.Add(item2);

  item2 := TMenuItem.Create(item);
  item2.Caption := SMenuTransformInvertCase;
  item2.Hint := SMenuTransformInvertCaseHint;
  item2.OnClick := MenuItemMessage;
  item2.Tag := EDITOR_COMMAND_SEL_INVERT_CASE;
  item.Add(item2);

  item2 := TMenuItem.Create(item);
  item2.Caption := SMenuTransformCamelCase;
  item2.Hint := SMenuTransformCamelCaseHint;
  item2.OnClick := MenuItemMessage;
  item2.Tag := EDITOR_COMMAND_SEL_CAMEL_CASE;
  item.Add(item2);

  item2 := TMenuItem.Create(item);
  item2.Caption := SMenuTransformSentenceCase;
  item2.Hint := SMenuTransformSentenceCaseHint;
  item2.OnClick := MenuItemMessage;
  item2.Tag := EDITOR_COMMAND_SEL_SENTENCE_CASE;
  item.Add(item2);

  item2 := TMenuItem.Create(item);
  item2.Caption := '-';
  item2.OnClick := MenuItemMessage;
  item2.Tag := 0;
  item.Add(item2);

  item2 := TMenuItem.Create(item);
  item2.Caption := SMenuTransformReverse;
  item2.Hint := SMenuTransformReverseHint;
  item2.OnClick := MenuItemMessage;
  item2.Tag := EDITOR_COMMAND_SEL_REVERSE;
  item.Add(item2);

  item2 := TMenuItem.Create(item);
  item2.Caption := SMenuTransformROT13;
  item2.Hint := SMenuTransformROT13Hint;
  item2.OnClick := MenuItemMessage;
  item2.Tag := EDITOR_COMMAND_SEL_ROT13;
  item.Add(item2);

  item2 := TMenuItem.Create(item);
  item2.Caption := SMenuTransformCaesar;
  item2.Hint := SMenuTransformCaesarHint;
  item2.OnClick := MenuItemMessage;
  item2.Tag := EDITOR_COMMAND_SEL_CAESAR;
  item.Add(item2);

  item2 := TMenuItem.Create(item);
  item2.Caption := SMenuTransformVigenere;
  item2.Hint := SMenuTransformVigenereHint;
  item2.OnClick := MenuItemMessage;
  item2.Tag := EDITOR_COMMAND_SEL_VIGENERE;
  item.Add(item2);

  item2 := TMenuItem.Create(item);
  item2.Caption := SMenuTransformVigenereInverse;
  item2.Hint := SMenuTransformVigenereInverseHint;
  item2.OnClick := MenuItemMessage;
  item2.Tag := EDITOR_COMMAND_SEL_VIGENERE or $00010000;
  item.Add(item2);

  // Image popup
  FImagePopup := TPopupMenu.Create(nil);

  item := TMenuItem.Create(FImagePopup);
  item.Caption := SMenuCopyImage;
  item.Hint := SMenuCopyImageHint;
  item.OnClick := ImageMenuCommand;
  item.Tag := IMAGE_COMMAND_COPY;
  FImagePopup.Items.Add(item);

  item := TMenuItem.Create(FImagePopup);
  item.Caption := SMenuDeleteImage;
  item.Hint := SMenuDeleteImageHint;
  item.OnClick := ImageMenuCommand;
  item.Tag := IMAGE_COMMAND_REMOVE;
  FImagePopup.Items.Add(item);

  item := TMenuItem.Create(FImagePopup);
  item.Caption := SMenuChangeImage;
  item.Hint := SMenuChangeImageHint;
  item.OnClick := ImageMenuCommand;
  item.Tag := IMAGE_COMMAND_CHANGE;
  FImagePopup.Items.Add(item);

  // Ruler menu
  FRulerMenu := TPopupMenu.Create(nil);

  item := TMenuItem.Create(FRulerMenu);
  item.Caption := SMenuRulerProperties;
  item.Hint := SMenuRulerPropertiesHint;
  item.OnClick := RulerMenuCommand;
  item.Tag := RULER_COMMAND_PROPERTIES;
  FRulerMenu.Items.Add(item);

  // Drop context menu
  FDropMenu := TPopupMenu.Create(nil);

  FDropMenuMove := TMenuItem.Create(FDropMenu);
  FDropMenuMove.Caption := SMoveHere;
  FDropMenu.Items.Add(FDropMenuMove);

  FDropMenuCopy := TMenuItem.Create(FDropMenu);
  FDropMenuCopy.Caption := SCopyHere;
  FDropMenu.Items.Add(FDropMenuCopy);

  item := TMenuItem.Create(FDropMenu);
  item.Caption := '-';
  FDropMenu.Items.Add(item);

  item := TMenuItem.Create(FDropMenu);
  item.Caption := SCancel;
  FDropMenu.Items.Add(item);

  UseDefaultFallbackFonts;
  BuildFontDataArray;

  FBlinkRemover := TTimer.Create(nil);
  FBlinkRemover.Enabled := false;
  FBlinkRemover.Interval := 500;
  FBlinkRemover.OnTimer := BlinkRemoverTimer;

  TabStop := true;

  OleInitialize(nil);

  CoCreateInstance(CLSID_DragDropHelper, nil, CLSCTX_INPROC_SERVER,
    IID_IDropTargetHelper, FDropTargetHelper);

  FXDRAG := Abs(GetSystemMetrics(SM_CXDRAG));
  FYDRAG := Abs(GetSystemMetrics(SM_CYDRAG));

end;

procedure TTextEditor.ImageMenuCommand(Sender: TObject);
var
  bm: TBitmap;
begin
  if not (FImagePopup.PopupComponent is TImage) then Exit;
  if not (Sender is TMenuItem) then Exit;

  case TMenuItem(Sender).Tag of
    IMAGE_COMMAND_COPY:
      if TImage(FImagePopup.PopupComponent).Picture.Graphic is TIcon then
      begin
        bm := TBitmap.Create;
        try
          bm.SetSize(TImage(FImagePopup.PopupComponent).Picture.Width,
            TImage(FImagePopup.PopupComponent).Picture.Height);
          bm.Canvas.Draw(0, 0, TImage(FImagePopup.PopupComponent).Picture.Graphic);
          Clipboard.Assign(bm);
        finally
          bm.Free;
        end;
      end
      else
        Clipboard.Assign(TImage(FImagePopup.PopupComponent).Picture);
    IMAGE_COMMAND_REMOVE:
      FTextFile.ClearLine(
        GetLineFromControlID(GetIDFromControl(TControl(FImagePopup.PopupComponent))));
    IMAGE_COMMAND_CHANGE:
      with TOpenDialog.Create(nil) do
        try
          Options := [ofPathMustExist, ofFileMustExist];
          Filter := SImageFilter;
          Title := SOpenImageDialogCaption;
          if Execute then
          begin
            TImage(FImagePopup.PopupComponent).Picture.LoadFromFile(FileName);
            TextFileLineClassChange(Sender,
              GetLineFromControlID(GetIDFromControl(TControl(FImagePopup.PopupComponent))));
          end;
        finally
          Free;
        end;
  end;
end;

procedure TTextEditor.RulerPropertiesApply(Sender: TObject);
begin
  if Sender is TRulerPropertiesFrm then
    with TRulerPropertiesFrm(Sender) do
    begin
      RulerFont.Assign(PrFont);
      RulerColor := PrColor;
      RulerWidth := PrWidth;
    end;
end;

procedure TTextEditor.RulerMenuCommand(Sender: TObject);
begin
  if not (Sender is TMenuItem) then Exit;

  case TMenuItem(Sender).Tag of
    RULER_COMMAND_PROPERTIES:
      with TRulerPropertiesFrm.Create(nil) do
        try
          PrFont.Assign(FRulerFont);
          PrColor := FRulerColor;
          PrWidth := FRulerWidth;
          OnApply := RulerPropertiesApply;
          ShowModal;
        finally
          Free;
        end;
  end;
end;

procedure TTextEditor.TextFileGetControlText(Sender: TObject; LineIndex: integer;
  var ControlText: string);
var
  ctl: TControl;
  len: integer;
begin
  ControlText := '';
  ctl := GetControlFromLine(LineIndex);
  if Assigned(ctl) then
  begin
    if ctl is TWinControl then
    begin
      SetLength(ControlText, 128);
      len := GetWindowText(TWinControl(ctl).Handle, PChar(ControlText),
        Length(ControlText));
      SetLength(ControlText, len);
    end
    else
      ControlText := SPicture;
  end;
end;

procedure TTextEditor.TextFileBookmarksMoved(Sender: TObject);
begin
  UpdateRuler;
  if Assigned(FOnBookmarksMoved) then
    FOnBookmarksMoved(Self);
end;

procedure TTextEditor.BlinkRemoverTimer(Sender: TObject);
begin
  FBlinkRemover.Enabled := false;
  if FMatchBrackets then
    HighlightCurrentBracket;
end;

function TTextEditor.BookmarkUsed(AIndex: integer): boolean;
begin
  result := not SamePoint(Bookmarks[AIndex], EMPTY_BOOKMARK);
end;

procedure TTextEditor.BuildFontDataArray;
var
  i: Integer;
begin
  FreeFontDataArray;
  SetLength(FGlyphSets, 1 + FFallbackFonts.Count);
  for i := low(FGlyphSets) to high(FGlyphSets) do
    FGlyphSets[i] := nil;
  if Screen.Fonts.IndexOf(FFont.Name) <> -1 then
    GetFontChrs(FFont.Name, FGlyphSets[0]);
  for i := 0 to FFallbackFonts.Count - 1 do
    if Screen.Fonts.IndexOf(FFallbackFonts[i]) <> -1 then
      GetFontChrs(FFallbackFonts[i], FGlyphSets[i + 1]);
end;

procedure TTextEditor.FreeFontDataArray;
var
  i: Integer;
begin
  for i := low(FGlyphSets) to high(FGlyphSets) do
    if FGlyphSets[i] <> nil then
      FreeMem(FGlyphSets[i]);
  SetLength(FGlyphSets, 0);
end;

procedure TTextEditor.TypeTimerEnd;
begin
  if FTypeTimer.Enabled then
    TypeTimerTimer(Self);
end;

procedure TTextEditor.TypeTimerTimer(Sender: TObject);
begin
  FTypeTimer.Enabled := false;
  AddUndoRecord(SUndoTyped, UID_TYPING);
end;

procedure TTextEditor.PostType;
begin
  FTypeTimer.Enabled := false;
  FTypeTimer.Enabled := true;
end;

procedure TTextEditor.Print(AFirstLine, ALastLine: integer);
begin
  Print(ExtractFileName(FTextFile.FileName), AFirstLine, ALastLine);
end;

procedure TTextEditor.PrintSelection(const ATitle: string);
var
  FirstPoint, SecondPoint: TPoint;
begin
  if not FTextFile.HasSelection then Exit;
  FTextFile.CaretPos.GetSelBdry(FirstPoint, SecondPoint);
  Print(ATitle, FirstPoint.Y, SecondPoint.Y);
end;

procedure TTextEditor.PrintSelection;
begin
  PrintSelection(ExtractFileName(FTextFile.FileName));
end;

procedure TTextEditor.PushEditorState;
begin
  TypeTimerEnd;
  if Assigned(FTextFile) then
  begin
    if Assigned(FFormattingProcessor) then
      FTextFile.EditorState.FormattingProcessor := FFormattingProcessor.ClassName
    else
      FTextFile.EditorState.FormattingProcessor := '';
    FTextFile.EditorState.ScrollPos := FScrollPos;
    FTextFile.EditorState.MultiSize := FMultiSize;
    FTextFile.EditorState.Overwrite := FOverwrite;
    FTextFile.EditorState.HiddenChrs := FShowHiddenCharacters;
    FTextFile.EditorState.RulerVisible := RulerVisible;
    FTextFile.EditorState.ZoomLevel := FZoom;
    if Assigned(FFormattingProcessor) then
      FTextFile.EditorState.FFPCacheLen := FFormattingProcessor.GetCache(FTextFile.EditorState.FFPCache)
    else
    begin
      FTextFile.EditorState.FFPCacheLen := 0;
      FTextFile.EditorState.FFPCache := nil;
    end;
    FTextFile.EditorState.Valid := true;
  end;
end;

function TTextEditor.QueryContinueDrag(fEscapePRessed: BOOL;
  grfKeyState: Longint): HRESULT;
begin
  if fEscapePressed then
    result := DRAGDROP_S_CANCEL
  else if (grfKeyState and FDragButtonOLE) = 0 then
    result := DRAGDROP_S_DROP
  else
    result := S_OK;
end;

procedure TTextEditor.Print(const ATitle: string;
  AFirstLine: integer = 0; ALastLine: integer = -1);
var
  x, y: integer;
  px, py: integer;
  fs: TSize;
  WrapList: TIntegerDynArray;
  LineLength, ChrsPerLine: integer;

  procedure CheckNewPage;
  begin
    if py + fs.cy > Printer.PageHeight - FPrintSettings.VerticalMargin then
    begin
      Printer.NewPage;
      py := FPrintSettings.VerticalMargin;
    end;
  end;

  function WrapNow: boolean;

    function ShouldWrapAt(X: integer): boolean;
    var
      i: Integer;
    begin
      result := false;
      for i := low(WrapList) to high(WrapList) do
        if WrapList[i] = X then
          Exit(true);
    end;

  begin
    result := FPrintSettings.WordWrap and
      (
          ((not FPrintSettings.NiceWordWrap) and (px > Printer.PageWidth - FPrintSettings.HorizontalMargin))
                                  or
          (FPrintSettings.NiceWordWrap and (ShouldWrapAt(x)))
      )
  end;

  function Scale(const ASize: TSize): TSize;
  begin
    result.cx := ASize.cx * 6; {TODO: determine actual res}
    result.cy := ASize.cy * 6;
  end;

var
  ctl: TControl;

begin

  if ALastLine = -1 then ALastLine := LineCount - 1;

  NotifyApp(EN_PRINTING);

  if Assigned(FOnPrintBegin) then
    FOnPrintBegin(Self, ALastLine - AFirstLine + 1);

  Enabled := false;

  try

    with Printer do
    begin
      BeginDoc;
      Title := ATitle;

      py := FPrintSettings.VerticalMargin;

      for y := AFirstLine to ALastLine do
      begin
        ApplyFont(LineClasses[y], Canvas);
        if LineIsControl(y) then
          fs := Scale(GetLineControlSize(y))
        else
          fs := Canvas.TextExtent('M');
        CheckNewPage;
        px := FPrintSettings.HorizontalMargin;
        LineLength := FTextFile.VirtualLineWidths[y];
        ChrsPerLine := (PageWidth - 2*FPrintSettings.HorizontalMargin) div fs.cx;
        if FPrintSettings.WordWrap then
        begin
          if FPrintSettings.NiceWordWrap then
            FTextFile.FindWhereToWrap(y, ChrsPerLine, WrapList)
          else
            SetLength(WrapList, 0);
        end;
        for x := 0 to LineLength - 1 do
        begin
          if WrapNow then
          begin
            inc(py, fs.cy + LineSpacing);
            CheckNewPage;
            px := FPrintSettings.HorizontalMargin;
            if FPrintSettings.ShowWordWrapIcon then
            begin
              Canvas.Font.Color := FPrintSettings.WordWrapIconColor;
              Canvas.Font.Style := [];
              Canvas.TextOut(px - 3 * fs.cx div 2, py, FPrintSettings.WordWrapIcon);
            end;
          end;
          if LineIsControl(y) then
          begin

            ctl := GetControlFromLine(y);
            if ctl is TWinControl then // Apparently (e.g.) edit controls do not like the WM_PAINT appraoch
              with TBitmap.Create do
                try
                  SetSize(ctl.Width, ctl.Height);
                  TWinControl(ctl).PaintTo(Canvas, 0, 0);
                  StretchBlt(Printer.Handle, px, py, fs.cx, fs.cy,
                    Canvas.Handle, 0, 0, Width, Height, SRCCOPY);
                finally
                  Free;
                end
            else if ctl is TGraphicControl then
              with TBitmap.Create do
                try
                  SetSize(ctl.Width, ctl.Height);
                  Canvas.Lock;
                  TGraphicControl(ctl).Perform(WM_PAINT, Canvas.Handle, 0);
                  Canvas.Unlock;
                  StretchBlt(Printer.Handle, px, py, fs.cx, fs.cy,
                    Canvas.Handle, 0, 0, Width, Height, SRCCOPY);
                finally
                  Free;
                end

          end
          else
          begin
            ReapplyFont(Canvas);
            ApplyInteractiveFormatting(x, y, Canvas);
            Canvas.TextOut(px, py, FTextFile.Character[y, x]);
          end;
          inc(px, fs.cx);
        end;
        inc(py, fs.cy);

        if Assigned(FOnPrintProgress) then
          if not FOnPrintProgress(Self, y - AFirstLine + 1, ALastLine - AFirstLine + 1) then
          begin
            Abort;
            SysUtils.Abort;
          end;
      end;

      EndDoc;

    end;

  finally

    Enabled := true;

    RemoveNotification(EN_PRINTING);

    if Assigned(FOnPrintEnd) then
      FOnPrintEnd(Self);

  end;

end;

procedure TTextEditor.MenuPopup(Sender: TObject);
var
  i, j: Integer;
  subitem: TMenuItem;
  URL: string;
begin
  for i := 0 to FPopupMenu.Items.Count - 1 do
  begin
    FPopupMenu.Items[i].Enabled := CommandEnabled(FPopupMenu.Items[i].Tag);
    FPopupMenu.Items[i].Visible := CommandVisible(FPopupMenu.Items[i].Tag);

    case FPopupMenu.Items[i].Tag of
      EDITOR_COMMAND_OPEN_URL_AT_CARET:
        if GetURLAtCaret(URL) then
          FPopupMenu.Items[i].Hint := Format(SMenuOpenURLHint, [URL]);
      EDITOR_COMMAND_SEL_TRANSFORM_MENU:
        for j := 0 to FPopupMenu.Items[i].Count - 1 do
        begin
          FPopupMenu.Items[i].Items[j].Enabled := CommandEnabled(FPopupMenu.Items[i].Items[j].Tag);
          FPopupMenu.Items[i].Items[j].Visible := CommandVisible(FPopupMenu.Items[i].Items[j].Tag);
        end;
      EDITOR_COMMAND_BOOKMARK_SET_MENU:
        begin
          FPopupMenu.Items[i].Clear;
          for j := 0 to BookmarkCount - 1 do
          begin
            subitem := TMenuItem.Create(FPopupMenu.Items[i]);
            subitem.Caption := GetBookmarkDescr(j);
            subitem.Hint := SMenuSetBookmarkItemHint;
            if InRange(j, 1, 9) then
              subitem.ShortCut := ShortCut(ord('0') + j, [ssShift, ssCtrl]);
            subitem.OnClick := MenuItemMessage;
            subitem.Tag := EDITOR_COMMAND_BOOKMARK_SET or (j shl 16);
            FPopupMenu.Items[i].Add(subitem);
          end;
        end;
      EDITOR_COMMAND_BOOKMARK_GO_MENU:
        begin
          FPopupMenu.Items[i].Clear;
          for j := 0 to BookmarkCount - 1 do
          begin
            if not BookmarkUsed(j) then Continue;
            subitem := TMenuItem.Create(FPopupMenu.Items[i]);
            subitem.Caption := GetBookmarkDescr(j);
            subitem.Hint := SMenuGotoBookmarkItemHint;
            if InRange(j, 1, 9) then
              subitem.ShortCut := ShortCut(ord('0') + j, [ssCtrl]);
            subitem.OnClick := MenuItemMessage;
            subitem.Tag := EDITOR_COMMAND_BOOKMARK_GO or (j shl 16);
            FPopupMenu.Items[i].Add(subitem);
          end;
          if UsedBookmarkCount = 0 then
          begin
            subitem := TMenuItem.Create(FPopupMenu.Items[i]);
            subitem.Caption := SMenuNoBookmarksSetParen;
            subitem.Enabled := false;
            FPopupMenu.Items[i].Add(subitem);
          end;
        end;
      EDITOR_COMMAND_BOOKMARK_CLEAR_MENU:
        begin
          FPopupMenu.Items[i].Clear;
          for j := 0 to BookmarkCount - 1 do
          begin
            if not BookmarkUsed(j) then Continue;
            subitem := TMenuItem.Create(FPopupMenu.Items[i]);
            subitem.Caption := GetBookmarkDescr(j);
            subitem.Hint := SMenuClearBookmarkItemHint;
            subitem.OnClick := MenuItemMessage;
            subitem.Tag := EDITOR_COMMAND_BOOKMARK_CLEAR or (j shl 16);
            FPopupMenu.Items[i].Add(subitem);
          end;
          if UsedBookmarkCount = 0 then
          begin
            subitem := TMenuItem.Create(FPopupMenu.Items[i]);
            subitem.Caption := SMenuNoBookmarksSetParen;
            subitem.Enabled := false;
            FPopupMenu.Items[i].Add(subitem);
          end
          else
          begin
            subitem := TMenUItem.Create(FPopupMenu.Items[i]);
            subitem.Caption := '-';
            FPopupMenu.Items[i].Add(subitem);

            subitem := TMenuItem.Create(FPopupMenu.Items[i]);
            subitem.Caption := SMenuClearAllBookmarks;
            subitem.Hint := SMenuClearAllBookmarksHint;
            subitem.OnClick := MenuItemMessage;
            subitem.Tag := EDITOR_COMMAND_BOOKMARK_CLEAR_ALL;
            FPopupMenu.Items[i].Add(subitem);
          end;
        end;
      EDITOR_COMMAND_CLASS_MENU:
        begin
          FPopupMenu.Items[i].Clear;
          subitem := TMenuItem.Create(FPopupMenu.Items[i]);
          subitem.Caption := SMenuUseNoClass;
          subitem.Hint := SMenuUseNoClassHint;
          subitem.Checked := Length(LineClasses[CaretPos.Y]) = 0;
          subitem.OnClick := MenuItemMessage;
          subitem.Tag := EDITOR_COMMAND_CLASS_REMOVE;
          FPopupMenu.Items[i].Add(subitem);
          subitem := TMenuItem.Create(FPopupMenu.Items[i]);
          subitem.Caption := '-';
          FPopupMenu.Items[i].Add(subitem);
          for j := 0 to ClassCount - 1 do
          begin
            subitem := TMenuItem.Create(FPopupMenu.Items[i]);
            subitem.Caption := Classes[j].Name;
            subitem.Hint := SMenuClassesItemHint;
            subitem.Checked := SameStr(LineClasses[CaretPos.Y], Classes[j].Name);
            subitem.OnClick := MenuItemMessage;
            subitem.Tag := EDITOR_COMMAND_CLASS_USE or (j shl 16);
            FPopupMenu.Items[i].Add(subitem);
          end;
        end;
    end;

  end;
end;

function TTextEditor.MakeLinesUnique: boolean;
begin
  TypeTimerEnd;
  result := FTextFile.MakeLinesUnique;
  if result then
    AddUndoRecord(SUndoMadeLinesUnique, UID_UNKNOWN);
end;

procedure TTextEditor.MakeUndoRoot;
begin
  FTypeTimer.Enabled := false;
  FTextFile.ClearUndoHistory;
  AddUndoRecord(SUndoInitialText, UID_UNKNOWN);
end;

function TTextEditor.MaxLineWidth: integer; // in px, ctrl aware
var
  i: Integer;
begin
  result := 0;
  for i := 0 to FTextFile.LineCount - 1 do
    if LineWidths(i) > result then
      result := LineWidths(i);
end;

procedure TTextEditor.MenuItemMessage(Sender: TObject);
begin
  if not Enabled then Exit;
  if Sender is TMenuItem then
    with Sender as TMenuItem do
      EditorCommand(Tag);
end;

procedure TTextEditor.CreateParams(var Params: TCreateParams);
begin
  inherited;
  Params.Style := Params.Style or WS_HSCROLL or WS_VSCROLL or 0*WS_CLIPCHILDREN;
  ControlStyle := ControlStyle - [csNeedsBorderPaint];
  case FBorderType of
    btNone: ;
    btWin32ThinLine:
      Params.Style := Params.Style or WS_BORDER;
    btWin32SunkenEdge:
      Params.ExStyle := Params.ExStyle or WS_EX_CLIENTEDGE;
    btThemeBorder:
      begin
        Params.ExStyle := Params.ExStyle or WS_EX_CLIENTEDGE;
        ControlStyle := ControlStyle + [csNeedsBorderPaint];
      end;
    btSimpleColor: ;
  end;
end;

procedure TTextEditor.CreateWnd;
begin

  inherited;

  FHintWindow := CreateWindowEx(0, TOOLTIPS_CLASS, nil, WS_POPUP or TTS_ALWAYSTIP
    or TTS_NOPREFIX or TTS_BALLOON, Integer(CW_USEDEFAULT), Integer(CW_USEDEFAULT),
    Integer(CW_USEDEFAULT), Integer(CW_USEDEFAULT), Handle, 0, HInstance, nil);

  if FHintWindow <> 0 then
  begin
    FToolInfo.cbSize := sizeof(FToolInfo);
    FToolInfo.uFlags := TTF_TRANSPARENT or TTF_CENTERTIP or TTF_IDISHWND or
      TTF_TRACK or TTF_ABSOLUTE;
    FToolInfo.hwnd := Handle;
    FToolInfo.uId := Handle;
    FToolInfo.hInst := 0;
    FToolInfo.lpszText := '';
    SendMessage(FHintWindow, TTM_ADDTOOL, 0, LParam(@FToolInfo));
  end;

  OleCheck(RegisterDragDrop(Handle, Self));

end;

function TTextEditor.GetTotalVerticalExtent: integer;
begin
  if FMultiSize then
    result := FAccumLineHeights[FTextFile.LineCount - 1] + FFontSizes[FTextFile.LineCount - 1].cy
  else
    result := FFontSize.cy * FTextFile.LineCount;
end;

function TTextEditor.GetURLAtCaret(out AURL: string): boolean;
begin
  result := FTextFile.GetURLAtCaret(AURL);
end;

function TTextEditor.GetUsedBookmarkCount: integer;
begin
  result := FTextFile.UsedBookmarkCount;
end;

function TTextEditor.GetTotalHorizontalExtent: integer;
begin
  if FMultiSize then
    result := FCachedHorizontalExtent
  else
    result := FFontSize.cx * FTextFile.MaxLineWidth
end;

function TTextEditor.IsCaretVisible: boolean;
begin
  result := FCaretVisible and PtInRect(TextContentRect, PhysicalPixelAtChar(GetCaretPos));
end;

{ BMSB, BMSCROLLBAR }
procedure TTextEditor.UpdateScrollBars;
var
  ScrollInfo: TScrollInfo;
  OldVScroll, NewVScroll, OldHScroll, NewHScroll, OldCaretVisible: boolean;
begin

  if SingleLine then
  begin
    ShowScrollBar(Handle, SB_BOTH, false);
    Exit;
  end;

  OldVScroll := GetWindowLong(Handle, GWL_STYLE) and WS_VSCROLL <> 0;
  OldHScroll := GetWindowLong(Handle, GWL_STYLE) and WS_HSCROLL <> 0;
  OldCaretVisible := IsCaretVisible;

  ScrollInfo.cbSize := sizeof(TScrollInfo);
  ScrollInfo.fMask := SIF_ALL;
  ScrollInfo.nMin := 0;
  ScrollInfo.nMax := max(GetTotalVerticalExtent, FScrollPos.Y + ClientHeight - FMarginTop - FMarginBottom - 1);
  ScrollInfo.nPage := ClientHeight - FMarginTop - FMarginBottom;
  ScrollInfo.nPos := FScrollPos.Y;
  SetScrollInfo(Handle, SB_VERT, ScrollInfo, true);

  ScrollInfo.cbSize := sizeof(TScrollInfo);
  ScrollInfo.fMask := SIF_ALL;
  ScrollInfo.nMin := 0;
  ScrollInfo.nMax := max(GetTotalHorizontalExtent, FScrollPos.X - FMarginLeft - FMarginRight + ClientWidth - 1);
  ScrollInfo.nPage := ClientWidth - FMarginLeft - FMarginRight;
  ScrollInfo.nPos := FScrollPos.X;
  SetScrollInfo(Handle, SB_HORZ, ScrollInfo, true);

  NewVScroll := GetWindowLong(Handle, GWL_STYLE) and WS_VSCROLL <> 0;
  NewHScroll := GetWindowLong(Handle, GWL_STYLE) and WS_HSCROLL <> 0;

  if OldCaretVisible and ((OldVScroll xor NewVScroll) or (OldHScroll xor NewHScroll)) then
    ScrollToCaret;
end;

procedure TTextEditor.UpdateScrollMode;
var
  OldScrollMode: boolean;
begin
  FScrollMode := GetScrollMode;
  OldScrollMode := HasNotificationMessage(EN_SCROLL_MODE);
  if FScrollMode and not OldScrollMode then
    NotifyApp(EN_SCROLL_MODE)
  else if OldScrollMode and not FScrollMode then
    RemoveNotification(EN_SCROLL_MODE);
end;

procedure TTextEditor.CutToClipboard;
begin
  TypeTimerEnd;
  FTextFile.CutToClipboard;
  AddUndoRecord(SUndoCutToClipboard, UID_CUT);
end;

function TTextEditor.FPFileChangeNotification(ChangeType: TChangeType;
  Data1, Data2, Data3, Data4: Integer): TChangeRecord;
var
  TC1, TC2: cardinal;
begin
  TC1 := GetTickCount;
  result := FFormattingProcessor.FileChangeNotification(ChangeType, Data1, Data2, Data3, Data4);
  TC2 := GetTickCount;
  if TC2 - TC1 > 5000 then
    if MessageBox(0, PChar(SFPSlowText), PChar(SFPSlowTitle), MB_ICONQUESTION or MB_YESNO) = ID_YES then
      FormattingProcessor := nil;
end;

function TTextEditor.FPFromString(
  const FPClassName: string): TFormattingProcessor;
begin
  if not FPDict.TryGetValue(FPClassName, result) then
    result := nil;
end;

procedure TTextEditor.TextFileChange(Sender: TObject; ChangeType: TChangeType;
  Data1, Data2, Data3, Data4: Integer);
var
  IFCR: TChangeRecord;
begin
  if Assigned(FFormattingProcessor) then
  begin
    IFCR := FPFileChangeNotification(ChangeType, Data1, Data2, Data3, Data4);
    with ChangeUnion(IFCR, MakeChangeRecord(ChangeType, Data1, Data2, Data3, Data4)) do
      VisualUpdate(ChangeType, Data1, Data2, Data3, Data4);
  end
  else
    VisualUpdate(ChangeType, Data1, Data2, Data3, Data4);

  if FMultiSize then
    RecomputeHorizontalExtent;
  UpdateScrollBars;
  if Assigned(FOnChange) then
    FOnChange(Self);
end;

procedure TTextEditor.TextFileInputError(Sender: TObject);
begin
  if FBeepOnInputError then
    beep;
  NotifyAppWithTimer(EN_INPUT_ERROR);
end;

procedure TTextEditor.TextFileReadOnlyError(Sender: TObject);
begin
  if FErrorMessageOnReadOnlyError then
    MessageBox(Handle, PChar(SReadOnlyErrorText),
      PChar(SReadOnlyErrorTitle), MB_ICONINFORMATION or MB_OK);
  NotifyAppWithTimer(EN_READ_ONLY_ERROR);
end;


procedure TTextEditor.TextFileModified(Sender: TObject);
begin
  if BalloonVisible and (ord(FBalloonPersistence) <= ord(bpModify)) then
    HideBalloon;
  if Assigned(FOnModified) then
    FOnModified(Self);
end;

function TTextEditor.TransformSelection(
  Transformation: TTextTransformFunc; const TransformName: string): boolean;
begin
  result := FTextFile.HasSelection and (SelectionType = stLineBased);
  if result then
  begin
    TypeTimerEnd;
    FTextFile.InsertText(Transformation(SelText));
    AddUndoRecord(Format(SUndoSelectionTransformed, [TransformName]), UID_UNKNOWN);
  end;
end;

procedure TTextEditor.TransformText(Transformation: TTextTransformFunc;
  const TransformName: string);
begin
  TypeTimerEnd;
  PlainText := Transformation(PlainText);
  AddUndoRecord(Format(SUndoTextTransformed, [TransformName]), UID_UNKNOWN);
end;

procedure TTextEditor.TrimRight;
begin
  TypeTimerEnd;
  FTextFile.TrimRight;
  AddUndoRecord(SUndoTrimRight, UID_UNKNOWN);
end;

procedure TTextEditor.TruncateAt(AIndex: integer; AChar: char;
  PreserveChar: boolean; AReverse: boolean);
begin
  TruncateAt(0, LineCount - 1, AIndex, AChar, PreserveChar, AReverse);
end;

procedure TTextEditor.TruncateAtInSelection(AIndex: integer; AChar: char;
  PreserveChar: boolean; AReverse: boolean);
var
  FirstLine, SecondLine: integer;
begin
  FirstLine := min(CaretPos.Y, SelEndPos.Y);
  SecondLine := max(CaretPos.Y, SelEndPos.Y);
  TruncateAt(FirstLine, SecondLine, AIndex, AChar, PreserveChar, AReverse);
end;

procedure TTextEditor.TruncateAt(AFirstLine, ALastLine, AIndex: integer;
  AChar: char; PreserveChar: boolean; AReverse: boolean);
begin
  TypeTimerEnd;
  FTextFile.TruncateAt(AFirstLine, ALastLine, AIndex, AChar, PreserveChar, AReverse);
  AddUndoRecord(SUndoLinesTruncated, UID_UNKNOWN);
end;

function TTextEditor.GetLineComparer: TLineComparer;
begin
  result := FTextFile.LineComparer;
end;

function TTextEditor.GetLineControlSize(LineIndex: integer): TSize;
var
  i: Integer;
  ID: integer;
begin
  result := FFontSize;
  if not (LineIsControl(LineIndex) and TryStrToInt(Copy(FTextFile.Lines[LineIndex],
    Length(LINE_CONTROL_PREFIX) + 1), ID)) then
    Exit;
  for i := 0 to high(FLineControls) do
    if (FLineControls[i].ID = ID) and Assigned(FLineControls[i].Control) then
    begin
      result.cx := FLineControls[i].Control.Width + 2*CARET_WIDTH;
      result.cy := FLineControls[i].Control.Height;
      break;
    end;
end;

function TTextEditor.GetLineCount: integer;
begin
  result := FTextFile.LineCount;
end;

function TTextEditor.GetLineFromControlID(ID: integer): integer;
var
  i: Integer;
  IDStr: string;
begin
  result := -1;
  IDStr := IntToStr(ID);
  for i := 0 to FTextFile.LineCount - 1 do
    if LineIsControl(i) and SameStr(Copy(FTextFile.Lines[i], Length(LINE_CONTROL_PREFIX) + 1), IDStr) then
      Exit(i);
end;

procedure TTextEditor.ReapplyFont(ATo: TCanvas);
begin
  if ATo = nil then ATo := Canvas;
  ATo.Font.Size := FCurrentFormat.Size;
  ATo.Font.Color := FCurrentFormat.Color;
  ATo.Font.Style := FCurrentFormat.Style;
end;

procedure TTextEditor.RebuildLineCache;
var
  nLines: integer;
  i: integer;
  ci: integer;
begin
  nLines := FTextFile.LineCount;
  SetLength(FFontSizes, nLines);
  for i := 0 to nLines - 1 do
  begin
    if SameStr(FTextFile.Classes[i], LINE_CONTROL_CLASS) then
      FFontSizes[i] := GetLineControlSize(i)
    else
    begin
      ci := GetClassIndex(FTextFile.Classes[i]);
      if ci <> -1 then
        FFontSizes[i] := FClassArray[ci].Format.BoxSize
      else
        FFontSizes[i] := FFontSize;
    end;
  end;
  SetLength(FAccumLineHeights, nLines);
  FAccumLineHeights[0] := 0;
  for i := 1 to nLines - 1 do
    FAccumLineHeights[i] := FAccumLineHeights[i - 1] + FFontSizes[i - 1].cy;
end;

procedure TTextEditor.RecomputeHorizontalExtent;
begin
  if FMultiSize then
    FCachedHorizontalExtent := MaxLineWidth
  else
    FCachedHorizontalExtent := FFontSize.cx * FTextFile.MaxLineWidth;
end;

procedure TTextEditor.TextFileLineChange(Sender: TObject;
  LineChangeType: TLineChangeType; From: integer);
begin
  if FMultiSize then
  begin
    RebuildLineCache;
    UpdateLineControls;
  end;
  if RulerVisible then
    if LineChangeType = lctAppend then
      UpdateRulerLine(LineCount - 1)
    else
      UpdateRuler;
end;

procedure TTextEditor.TextFileLineClassChange(Sender: TObject;
  LineIndex: integer);
begin
  if FMultiSize then
  begin
    RebuildLineCache;
    RecomputeHorizontalExtent;
    UpdateScrollBars;
    VisualUpdate(ctLineRange, LineIndex, FTextFile.LineCount - 1, 0, 0);
    VisualUpdate(ctPostFile, 0, 0, 0, 0);
    UpdateLineControls;
    UpdateRuler;
  end
  else
    VisualUpdate(ctLine, LineIndex, 0, 0, 0);

  if CaretPos.Y = LineIndex then
    UpdateCaret
  else
    DoSetCaretPos;
end;

procedure TTextEditor.TextFileControlRemoved(Sender: TObject;
  ControlID: integer);
var
  i: Integer;
  j: Integer;
begin
  Exit; // it is nice to be able to undo the removal
  for i := 0 to high(FLineControls) do
    if FLineControls[i].ID = ControlID then
    begin
      FLineControls[i].Control.Free;
      for j := i to high(FLineControls) - 1 do
        FLineControls[j] := FLineControls[j + 1];
      SetLength(FLineControls, Length(FLineControls) - 1);
      break;
    end;
end;

function TTextEditor.ClassExists(const AClassName: string): boolean;
begin
  result := GetClassIndex(AClassName) <> -1;
end;

function TTextEditor.ClassExists(const AClassName: string;
  out Index: integer): boolean;
begin
  Index := GetClassIndex(AClassName);
  result := Index <> -1;
end;

procedure TTextEditor.Clear;
begin
  Escape(true);
  TypeTimerEnd;
  FTextFile.Clear;
  FTextFile.ControlAware := false;
  AddUndoRecord(SUndoTextCleared, UID_UNKNOWN);
end;

procedure TTextEditor.ClearBookmarks;
begin
  TypeTimerEnd;
  FTextFile.ClearBookmarks;
  AddUndoRecord(SUndoBookmarksCleared, UID_UNKNOWN);
  UpdateRuler;
end;

procedure TTextEditor.ClearBracketHighlight;
begin
  if FBracketHighlight then
  begin
    FBracketHighlight := false;
    VisualUpdate(ctTwoChars, FBracketPos1.Y, FBracketPos1.X, FBracketPos2.Y,
      FBracketPos2.X);
  end;
end;

procedure TTextEditor.ClearClasses;
begin
  SetLength(FClassArray, 0);
end;

procedure TTextEditor.ClearControls;
var
  i: Integer;
begin
  for i := 0 to high(FLineControls) do
    FLineControls[i].Control.Free;
  SetLength(FLineControls, 0);
end;

procedure TTextEditor.SetBracketHighlight(const PointA, PointB: TPoint);
begin
  if FBracketHighlight then
    ClearBracketHighlight;
  FBracketHighlight := true;
  FBracketPos1 := PointA;
  FBracketPos2 := PointB;
  VisualUpdate(ctTwoChars, FBracketPos1.Y, FBracketPos1.X, FBracketPos2.Y,
    FBracketPos2.X);
end;

procedure TTextEditor.HighlightCurrentBracket;
var
  BracketPos: TPoint;
begin
  ClearBracketHighlight;
  BracketPos := FTextFile.MatchBracket(FTextFile.CaretPos.Data);
  if BracketPos.Y <> -1 then
    SetBracketHighlight(CaretPos, BracketPos);
end;

{ pre-BMCARET }
procedure TTextEditor.TextFileCaretPosChange(Sender: TObject);
begin
  if (not FPreserveDesiredColumn) and FMultiSize then
    FDesiredColumn :=  FTextFile.CaretPos.X * FFontSizes[FTextFile.CaretPos.Y].cx;
  if BalloonVisible and (ord(FBalloonPersistence) <= ord(bpCaretPos)) then
    HideBalloon;
  ChangeCursor;
  if FMultiSize and (FTextFile.CaretPos.Y <> FOldCaretPosY) then
    UpdateCaret;
  if FTextFile.CaretPos.Y <> FOldCaretPosY then
  begin
    UpdateRulerLine(FTextFile.CaretPos.Y);
    UpdateRulerLine(FOldCaretPosY);
  end;
  if (not ScrollToCaret) and Focused then
    DoSetCaretPos;
  if FLineHighlight and (FTextFile.CaretPos.Y <> FOldCaretPosY) then
  begin
    VisualUpdate(ctLine, FOldCaretPosY, 0, 0, 0);
    VisualUpdate(ctLine, FTextFile.CaretPos.Y, 0, 0, 0);
  end;
  if FMatchBrackets then
    HighlightCurrentBracket;
  if FListBoxMode and (FTextFile.CaretPos.Y <> FOldCaretPosY) then
    if Assigned(FOnListBoxChange) then
      FOnListBoxChange(Self);
  FOldCaretPosY := FTextFile.CaretPos.Y;
  if FMultiCharSelect and HasNotificationMessage(EN_MULTICHAR) then
    RemoveNotification(EN_MULTICHAR);
  if Assigned(FOnSelChange) then
    FOnSelChange(Self);
end;

function TTextEditor.ScrollToCaret: boolean;
var
  CaretPixel: TPoint;
  NewX, NewY: integer;
  _ScrollExtra: integer;
begin
  if FNoScrollToCaret then Exit(false);

  CaretPixel := VirtualPixelAtChar(FTextFile.CaretPos.Data);

  if (SCROLL_EXTRA < ClientWidth div 2) and not SingleLine then
    _ScrollExtra := SCROLL_EXTRA
  else
    _ScrollExtra := 0;

  if FScrollPos.X + ClientWidth - FMarginLeft - FMarginRight < CaretPixel.X + CARET_WIDTH then
    NewX := CaretPixel.X - ClientWidth + FMarginLeft + FMarginRight + CARET_WIDTH + _ScrollExtra
  else if FScrollPos.X > CaretPixel.X then
    NewX := Max(CaretPixel.X - _ScrollExtra, 0)
  else
    NewX := FScrollPos.X;

  if FScrollPos.Y + ClientHeight - FMarginTop - FMarginBottom < CaretPixel.Y + FFontSize.cy then
    NewY := CaretPixel.Y - ClientHeight + FMarginTop + FMarginBottom + FFontSize.cy
  else if FScrollPos.Y > CaretPixel.Y then
    NewY := CaretPixel.Y
  else
    NewY := FScrollPos.Y;

  result := (NewX <> FScrollPos.X) or (NewY <> FScrollPos.Y);

  if result then
    SetScrollPosXY(NewX, NewY);
end;

procedure TTextEditor.TextFileCaretPosSelChange(Sender: TObject;
  ChangeType: TChangeType; Data1, Data2, Data3, Data4: Integer);
begin
  VisualUpdate(ChangeType, Data1, Data2, Data3, Data4);
end;

procedure TTextEditor.WMContextMenu(var Message: TWMContextMenu);
var
  p: TPoint;
  PmMain: TPopupMenu;
  PmRuler: TPopupMenu;
  ctl: TControl;
begin

  if Assigned(PopupMenu) then
    PmMain := PopupMenu
  else
    PmMain := FPopupMenu;

  if Assigned(RulerPopupMenu) then
    PmRuler := RulerPopupMenu
  else
    PmRuler := FRulerMenu;

  if (Message.XPos = -1) and (Message.YPos = -1) then // menu key or Shift+F10
    if Windows.GetCaretPos(p) then
      with ClientToScreen(p) do
        if FMultiSize then
          PmMain.Popup(x, y + FFontSizes[CaretPos.Y].cy)
        else
          PmMain.Popup(x, y + FFontSize.cy)
    else
      with ClientToScreen(Point(ClientWidth div 2, ClientHeight div 2)) do
        PmMain.Popup(x, Y)
  else // RMB
  begin
    p := ScreenToClient(SmallPointToPoint(Message.Pos));
    if (p.X >= ClientWidth) or (p.Y >= ClientHeight) then
    begin
      inherited;
      Exit;
    end
    else if p.X >= GetFunctionalSelectionBarWidth then
    begin
      ctl := ControlAtPos(p, true);
      if ctl <> nil then
        Message.Result := ctl.Perform(WM_CONTEXTMENU, TMessage(Message).WParam, TMessage(Message).LParam);
      if Message.Result = 0 then
        PmMain.Popup(Message.XPos, Message.YPos)
    end
    else if p.X < FRulerWidth then
      PmRuler.Popup(Message.XPos, Message.YPos);
  end;

  Message.Result := 1;

end;

procedure TTextEditor.WMEnable(var Message: TWMEnable);
begin
  inherited;
  SetupColors;
  Invalidate;
end;

procedure TTextEditor.WMEraseBkgnd(var Message: TWMEraseBkgnd);
begin
  Message.Result := 1;
end;

procedure TTextEditor.WMGetDlgCode(var Message: TWMGetDlgCode);
begin
  inherited;
  Message.Result := Message.Result or DLGC_WANTCHARS or DLGC_WANTARROWS or
    IfThen(FWantTab, DLGC_WANTTAB) or IfThen(FWantReturn, DLGC_WANTALLKEYS);
end;

procedure TTextEditor.WMHScroll(var Message: TWMHScroll);
var
  ScrollInfo: TScrollInfo;
begin
  inherited;
  case Message.ScrollCode of
    SB_LEFT:
      SetScrollPosX(0);
    SB_RIGHT:
      SetScrollPosX(FFontSize.cx * FTextFile.MaxLineWidth);
    SB_LINELEFT:
      SetScrollPosX(FScrollPos.X - FFontSize.cx);
    SB_LINERIGHT:
      SetScrollPosX(FScrollPos.X + FFontSize.cx);
    SB_PAGELEFT:
      SetScrollPosX(FScrollPos.X - ClientWidth + FMarginLeft + FMarginRight);
    SB_PAGERIGHT:
      SetScrollPosX(FScrollPos.X + ClientWidth - FMarginLeft - FMarginRight);
    SB_THUMBTRACK:
      begin
        ScrollInfo.cbSize := sizeof(TScrollInfo);
        ScrollInfo.fMask := SIF_TRACKPOS;
        GetScrollInfo(Handle, SB_HORZ, ScrollInfo);
        SetScrollPosX(ScrollInfo.nTrackPos);
      end;
  end;
end;

{ BMSCROLL }
procedure TTextEditor.SetScrollPosX(Value: integer);
var
  OldScrollPos, diff: integer;
  TCR: TRect;
begin
  if Value < 0 then Value := 0;
  OldScrollPos := FScrollPos.X;
  FScrollPos.X := Value;
  diff := OldScrollPos - Value;
  if diff = 0 then Exit;
  UpdateScrollBars;
  TCR := TextContentRect;
  if abs(diff) < ClientWidth - FMarginLeft - FMarginRight then
    ScrollWindowEx(Handle, diff, 0, @TCR, @TCR, 0, nil, SW_INVALIDATE or SW_SCROLLCHILDREN)
  else
    Invalidate;
  DoSetCaretPos;
  if FTextFile.ControlAware then
    UpdateLineControls;
  MoveBalloonPostScroll;
end;

procedure TTextEditor.WMKillFocus(var Message: TWMKillFocus);
begin
  inherited;
  Escape(true);
  DestroyCaret;
  FCaretVisible := false;
  if SingleLine and FLabelStyle then Invalidate;
end;

procedure TTextEditor.WMMouseHWheel(var Message: TWMMouseWheel);
begin
  if (GetKeyState(VK_LMENU) and $8000) <> 0 then
  begin
    if Message.WheelDelta < 0 then
      FTextFile.Left
    else
      FTextFile.Right;
    Exit;
  end;

  if (Message.Keys and MK_SHIFT) <> 0 then
    SetScrollPosX(FScrollPos.X + Sign(Message.WheelDelta))
  else
    SetScrollPosX(FScrollPos.X + round(FFontSize.cx * Message.WheelDelta / WHEEL_DELTA));

  Message.Result := 0;
end;

procedure TTextEditor.WMMouseWheel(var Message: TWMMouseWheel);
var
  ShiftState: TShiftState;
begin

  ShiftState := [];
  if Message.Keys and MK_CONTROL <> 0 then
    Include(ShiftState, ssCtrl);
  if Message.Keys and MK_LBUTTON <> 0 then
    Include(ShiftState, ssLeft);
  if Message.Keys and MK_MBUTTON <> 0 then
    Include(ShiftState, ssMiddle);
  if Message.Keys and MK_RBUTTON <> 0 then
    Include(ShiftState, ssRight);
  if Message.Keys and MK_SHIFT <> 0 then
    Include(ShiftState, ssShift);
  if IsKeyDown(VK_LMENU) then
    Include(ShiftState, ssAlt);

  if ssCtrl in ShiftState then
  begin
    if Message.WheelDelta < 0 then
      ZoomOut
    else
      ZoomIn;
    Exit;
  end;

  if ssAlt in ShiftState then
  begin
    if Message.WheelDelta < 0 then
      FTextFile.Down
    else
      FTextFile.Up;
    Update;
    Exit;
  end;

  if (ssShift in ShiftState) or (FScrollBehaviour = sbPixel) then
    SetScrollPosY(FScrollPos.Y - Sign(Message.WheelDelta), true)
  else if FScrollBehaviour = sbLine then
    SetScrollPosY(FScrollPos.Y - round(FFontSize.cy * Message.WheelDelta / WHEEL_DELTA), true)
  else
    if FSPIScrollLines >= 0 then
      SetScrollPosY(FScrollPos.Y - round(FFontSize.cy * FSPIScrollLines * Message.WheelDelta / WHEEL_DELTA), true)
    else if FSPIScrollLines = -1 then
      SetScrollPosY(FScrollPos.Y - round(ClientHeight * Message.WheelDelta / WHEEL_DELTA), true);

  Message.Result := 0;

  with ScreenToClient(Message.Pos) do
    ChangeCursor(ShiftState, Y, X);

end;

procedure TTextEditor.WMNCActivate(var Message: TWMNCActivate);
begin
  inherited;
end;

procedure TTextEditor.WMNCCalcSize(var Message: TWMNCCalcSize);
var
  R: TRect;
begin
  DefaultHandler(Message);
  R := Message.CalcSize_Params.rgrc0;
  InflateRect(R, -BorderWidth, -BorderWidth);
  Message.CalcSize_Params.rgrc0 := R;
  Message.Result := 0;
end;

procedure TTextEditor.WMNCHitTest(var Message: TWMNCHitTest);
var
  pnt: TPoint;
  WS: integer;
  R: TRect;
  ClientWidth, ClientHeight: integer;
begin
  if FBorderType = btSimpleColor then
  begin
    Windows.GetClientRect(Handle, R);
    ClientWidth := R.Right - R.Left;
    ClientHeight := R.Bottom - R.Top;
    pnt := ScreenToClient(SmallPointToPoint(Message.Pos));
    WS := GetWindowLong(Handle, GWL_STYLE);
    if PtInRect(R, pnt) then
      Message.result := HTCLIENT
    else if ((WS and WS_VSCROLL) <> 0) and
      InRange(pnt.X, ClientWidth, ClientWidth + GetSystemMetrics(SM_CXVSCROLL)) and
      InRange(pnt.Y, 0, ClientHeight) then
      Message.Result := HTVSCROLL
    else if ((WS and WS_HSCROLL) <> 0) and
      InRange(pnt.Y, ClientHeight, ClientHeight + GetSystemMetrics(SM_CYHSCROLL)) and
      InRange(pnt.X, 0, ClientWidth) then
      Message.Result := HTHSCROLL
    else
      Message.Result := HTBORDER;
  end
  else
    inherited;
end;

procedure TTextEditor.WMNCPaint(var Message: TWMNCPaint);
var
  dc: HDC;
  WS: integer;
  OldColor: TColor;
  R: TRect;
  Width, Height: integer;
begin
  if FBorderType = btSimpleColor then
  begin
    DefaultHandler(Message);
    GetWindowRect(Handle, R);
    Width := R.Right - R.Left;
    Height := R.Bottom - R.Top; // Self.Width and Self.Height are not up to date at this time
    OldColor := Brush.Color;
    dc := GetWindowDC(Handle);
    try
      WS := GetWindowLong(Handle, GWL_STYLE);
      if ((WS and WS_VSCROLL) <> 0) and ((WS and WS_HSCROLL) <> 0) then
      begin
        Brush.Color := clBtnFace;
        FillRect(dc, Rect(Width - BorderWidth - GetSystemMetrics(SM_CXVSCROLL),
          Height - BorderWidth - GetSystemMetrics(SM_CXHSCROLL),
          Width - BorderWidth,
          Height - BorderWidth),
          Brush.Handle);
      end;
      ExcludeClipRect(dc, BorderWidth, BorderWidth,
        Width - BorderWidth, Height - BorderWidth);
      Brush.Color := FBorderColor;
      FillRect(dc, Rect(0, 0, Width, Height), Brush.Handle);
      Message.Result := 0;
    finally
      ReleaseDC(Handle, dc);
      Brush.Color := OldColor;
    end;
  end
  else
    inherited;
end;

procedure TTextEditor.WMPaint(var Message: TWMPaint);
begin
  if Assigned(FDropTargetHelper) and Assigned(FDragDataObj) then
    FDropTargetHelper.Show(false);
  inherited;
  if Assigned(FDropTargetHelper) and Assigned(FDragDataObj) then
    FDropTargetHelper.Show(true);
end;

procedure TTextEditor.WMSetFocus(var Message: TWMSetFocus);
begin
  inherited;
  NeedValidPaintState;
  UpdateCaret;
  UpdateScrollMode;
  if SingleLine and FLabelStyle then
    Invalidate;
end;

procedure TTextEditor.WMSize(var Message: TWMSize);
begin
  inherited;
  UpdateScrollBars;
  DoSetCaretPos;
end;

procedure TTextEditor.WMVScroll(var Message: TWMVScroll);
var
  ScrollInfo: TScrollInfo;
begin
  inherited;
  case Message.ScrollCode of
    SB_TOP:
      SetScrollPosY(0);
    SB_BOTTOM:
      SetScrollPosY(FFontSize.cy * FTextFile.LineCount);
    SB_LINEUP:
      SetScrollPosY(FScrollPos.Y - FFontSize.cy);
    SB_LINEDOWN:
      SetScrollPosY(FScrollPos.Y + FFontSize.cy);
    SB_PAGEUP:
      SetScrollPosY(FScrollPos.Y - ClientHeight + FMarginTop + FMarginBottom);
    SB_PAGEDOWN:
      SetScrollPosY(FScrollPos.Y + ClientHeight - FMarginTop - FMarginBottom);
    SB_THUMBTRACK:
      begin
        ScrollInfo.cbSize := sizeof(TScrollInfo);
        ScrollInfo.fMask := SIF_TRACKPOS;
        GetScrollInfo(Handle, SB_VERT, ScrollInfo);
        SetScrollPosY(ScrollInfo.nTrackPos);
      end;
  end;
end;

procedure TTextEditor.WndProc(var Message: TMessage);
  function REFontInc(Old, Delta: integer): integer;
    function rnd(x: real): integer;
    begin
      if Delta > 0 then
        result := ceil(x)
      else
        result := floor(x);
    end;
  begin
    result := Old + Delta;
    if result < 1 then
      result := 1
    else if result <= 28 then
      result := 2*rnd(result / 2)
    else if result <= 36 then
      result := 36
    else if result <= 48 then
      result := 48
    else if result <= 72 then
      result := 72
    else if result <= 80 then
      result := 80
    else
      result := 10*rnd(result / 10);
    if result > 1638 then
      result := 1638;
  end;
const
  ECM_FIRST = $1500;
  EM_SETCUEBANNER = ECM_FIRST + 1;
  EM_GETCUEBANNER = ECM_FIRST + 2;
  EM_SHOWBALLOONTIP = ECM_FIRST + 3;
  EM_HIDEBALLOONTIP = ECM_FIRST + 4;
  EM_GETSCROLLPOS = WM_USER + 221;
  EM_SETSCROLLPOS = WM_USER + 222;
  EM_GETTOUCHOPTIONS = WM_USER + 310;
  EM_GETZOOM = WM_USER + 224;
  EM_SETZOOM = WM_USER + 225;
  EM_SETFONTSIZE = WM_USER + 223;
type
  PCHARRANGE = ^CHARRANGE;
  PTEXTRANGE = ^TEXTRANGE;
var
  s: string;
  i: integer;
begin
  inherited;
  case Message.Msg of
    WM_TIMER:
      if Message.WParam and EDITOR_NOTIFY <> 0 then
      begin
        RemoveNotification(Message.WParam and $FFFF);
        if not KillTimer(Handle, Message.WParam) then
          RaiseLastOSError;
        Message.Result := 0;
        Exit;
      end;
  end;
  if not FMessageInterface then Exit;
  case Message.Msg of
    WM_UNDO:
      Message.Result := B(Undo);
    EM_UNDO:
      Message.Result := B(Undo);
    EM_REDO:
      Message.Result := B(Redo);
    EM_CANUNDO:
      Message.Result := B(CanUndo);
    EM_CANREDO:
      Message.Result := B(CanRedo);
    WM_CUT:
      CutToClipboard;
    WM_COPY:
      CopyToClipboard;
    WM_PASTE:
      PasteFromClipboard;
    WM_CLEAR:
      ClearSelection;
    EM_SETSEL:
      begin
        if Message.WParam = NativeUInt(-1) then
          SelectNone
        else if (Message.WParam = 0) and (Message.LParam = -1) then
          SelectAll
        else if (Integer(Message.WParam) >= 0) and (Message.LParam >= 0) then
        begin
          FTextFile.CaretPos.SetPoint(FTextFile.GetPointOfIndex(Message.WParam));
          FTextFile.CaretPos.SetPoint(FTextFile.GetPointOfIndex(Message.LParam), true);
        end;
      end;
    EM_CHARFROMPOS:
      Message.Result := FTextFile.GetIndexOfPoint(CaretPosAtPhysicalPixel(Point(PPOINTL(Message.LParam)^.x, PPOINTL(Message.LParam)^.y)));
    EM_EMPTYUNDOBUFFER:
      ClearUndoHistory;
    EM_GETCUEBANNER:
      Message.Result := 0;
    EM_GETFIRSTVISIBLELINE:
      Message.Result := FirstVisibleLine;
    EM_GETHANDLE:
      Message.Result := 0;
    EM_GETLINE:
      begin
        if InRange(Message.WParam, 0, FTextFile.LineCount - 1) then
        begin
          s := FTextFile.Lines[Message.WParam];
          i := Min(PWord(Message.LParam)^, Length(s));
          Move(s, PChar(Message.LParam)^, i * sizeof(Char));
          Message.Result := i;
        end
        else
          Message.Result := 0;
      end;
    EM_GETLINECOUNT:
      Message.Result := FTextFile.LineCount;
    EM_GETMARGINS:
      Message.Result := 0;
    EM_GETMODIFY:
      Message.Result := B(FTextFile.FileModified);
    EM_GETPASSWORDCHAR:
      Message.Result := ord(FPasswordChar);
    EM_GETSEL:
      begin
        if FTextFile.HasSelection then
        begin
          if Message.WParam <> 0 then
            PDWORD(Message.WParam)^ := FTextFile.GetIndexOfPoint(FTextFile.CaretPos.FirstPoint);
          if Message.LParam <> 0 then
            PDWORD(Message.LParam)^ := FTextFile.GetIndexOfPoint(FTextFile.CaretPos.LastPoint) + 1;
        end
        else
        begin
          if Message.WParam <> 0 then
            PDWORD(Message.WParam)^ := SelStart;
          if Message.LParam <> 0 then
            PDWORD(Message.LParam)^ := SelStart;
        end;
      end;
    EM_GETTHUMB:
      Message.Result := FScrollPos.Y;
    EM_GETWORDBREAKPROC:
      Message.Result := 0;
    EM_HIDEBALLOONTIP:
      begin
        HideBalloon;
        Message.Result := 1;
      end;
    EM_LINEFROMCHAR:
      if Message.WParam = NativeUInt(-1) then
        Message.Result := FTextFile.CaretPos.FirstPoint.Y
      else if InRange(Message.WParam, 0, FTextFile.NumCharacters - 1) then
        Message.Result := FTextFile.GetPointOfIndex(Message.WParam).Y
      else
        Message.Result := -1;
    EM_LINEINDEX:
      if Message.WParam = NativeUInt(-1) then
        Message.Result := FTextFile.GetIndexOfPoint(Point(0, FTextFile.CaretPos.FirstPoint.Y))
      else if InRange(Message.WParam, 0, FTextFile.LineCount - 1) then
        Message.Result := FTextFile.GetIndexOfPoint(Point(0, Message.WParam))
      else
        Message.Result := -1;
    EM_LINELENGTH:
      if Message.WParam = NativeUInt(-1) then
        with FTextFile.CaretPos.LastPoint do
          Message.Result := FTextFile.CaretPos.FirstPoint.X + (FTextFile.VirtualLineWidths[Y] - X)
      else if InRange(Message.WParam, 0, FTextFile.LineCount - 1) then
        Message.Result := FTextFile.VirtualLineWidths[Message.WParam]
      else
        Message.Result := 0;
    EM_LINESCROLL:
      begin
        SetScrollPosXY(FScrollPos.X + FFontSize.cx * NativeInt(Message.WParam), FScrollPos.Y + FFontSize.cy * Message.LParam);
        Message.Result := IfThen(SingleLine, 0, 1);
      end;
    EM_POSFROMCHAR:
      with VirtualPixelAtChar(FTextFile.GetPointOfIndex(Message.LParam)) do
      begin
        PPOINTL(Message.WParam)^.x := X;
        PPointL(Message.WParam)^.y := Y;
      end;
    EM_REPLACESEL:
      begin
        InsertText(PChar(Message.LParam)^);
        if Message.WParam = 0 then
          ClearUndoHistory;
      end;
    EM_SCROLL:
      begin
        if (Message.WParam  = SB_LINEDOWN) or (Message.WParam = SB_LINEUP) then
          Message.Result := (1 shr 16) or 1
        else if (Message.WParam = SB_PAGEDOWN) or (Message.WParam = SB_PAGEUP) then
          Message.Result := (1 shr 16) or (ClientHeight div FFontSize.cy)
        else
          Message.Result := 0;
        if Message.Result <> 0 then
          Perform(WM_VSCROLL, Message.wParam, 0);
      end;
    EM_SCROLLCARET:
      Message.Result := B(ScrollToCaret);
    EM_SETCUEBANNER:
      Message.Result := 0;
    EM_SETMODIFY:
      FTextFile.FileModified := Message.WParam <> 0;
    EM_SETPASSWORDCHAR:
      SetPasswordChar(chr(Message.WParam));
    EM_SETREADONLY:
      begin
        if Message.WParam <> 0 then
          EditMode := emReadOnly
        else if EditMode = emReadOnly then
          EditMode := emText;
        Message.Result := 1;
      end;
    EM_SETTABSTOPS:
      Message.Result := 0;
    EM_SHOWBALLOONTIP:
      with PEDITBALLOONTIP(Message.LParam)^ do
      begin
        if cbStruct = sizeof(TEditBalloonTip) then
          Message.Result := B(ShowBalloon(pszTitle, pszText,
            TBalloonIconKind(ttiIcon), bpRemain, CaretPos))
        else
          Message.Result := 0;
      end;
    EM_CANPASTE:
      if Message.WParam = 0 then
        Message.Result := B(Clipboard.HasFormat(CF_TEXT) or (Clipboard.HasFormat(CF_BITMAP) and not SingleLine))
      else
        Message.Result := B((Message.WParam = CF_TEXT) or (Message.WParam = CF_UNICODETEXT) or ((Message.WParam = CF_BITMAP) and not SingleLine));
    EM_EXGETSEL:
      begin
        if FTextFile.AllSelected then
        begin
          PCHARRANGE(Message.LParam)^.cpMin := 0;
          PCHARRANGE(Message.LParam)^.cpMax := -1;
        end
        else if FTextFile.HasSelection then
        begin
          PCHARRANGE(Message.LParam)^.cpMin := FTextFile.GetIndexOfPoint(FTextFile.CaretPos.FirstPoint);
          PCHARRANGE(Message.LParam)^.cpMin := FTextFile.GetIndexOfPoint(FTextFile.CaretPos.LastPoint) + 1
        end
        else
        begin
          PCHARRANGE(Message.LParam)^.cpMin := FTextFile.GetIndexOfPoint(FTextFile.CaretPos.FirstPoint);
          PCHARRANGE(Message.LParam)^.cpMin := PCHARRANGE(@Message.LParam)^.cpMin;
        end;
      end;
    EM_EXLINEFROMCHAR:
      Message.Result := FTextFile.GetPointOfIndex(Message.LParam).Y;
    EM_FINDTEXT:
      {TODO};
    EM_FINDTEXTEX:
      {TODO};
    EM_GETREDONAME:
      if CanRedo then
        Message.Result := FTextFile.HistoryManager.UndoData[FTextFile.HistoryManager.HistoryIndex + 1].UID
      else
        Message.Result := 0;
    EM_GETSCROLLPOS:
      begin
        PPOINT(Message.LParam)^ := FScrollPos;
        Message.Result := 1;
      end;
    EM_GETSELTEXT:
      begin
        s := SelText;
        i := Length(SelText);
        Move(s, PChar(Message.LParam), i * sizeof(Char));
        Message.Result := i;
      end;
    EM_GETTEXTLENGTHEX:
      if (PDWORD(Message.WParam)^ and GTL_USECRLF) <> 0 then
        Message.Result := FTextFile.VirtualTextLength
      else
        Message.Result := FTextFile.NumCharacters;
    EM_GETTEXTMODE:
      Message.Result := TM_PLAINTEXT or TM_MULTILEVELUNDO or TM_MULTICODEPAGE;
    EM_GETTEXTRANGE:
      with PTEXTRANGE(Message.LParam)^ do
      begin
        if (chrg.cpMin = 0) and (chrg.cpMax = -1) then
          s := PlainText + #0
        else
          s := Copy(PlainText, chrg.cpMin + 1, chrg.cpMax - chrg.cpMin) + #0;
          Move(s[1], lpstrText^, Length(s))
      end;
    EM_GETTOUCHOPTIONS:
      Message.Result := 0;
    EM_GETUNDONAME:
      if CanUndo then
        Message.Result := FTextFile.HistoryManager.UndoData[FTextFile.HistoryManager.HistoryIndex].UID
      else
        Message.Result := 0;
    EM_GETZOOM:
      begin
        Message.WParam := FZoom;
        Message.LParam := 100;
        Message.Result := 1;
      end;
    EM_HIDESELECTION:
      {TODO};
    EM_SELECTIONTYPE:
      if FTextFile.HasSelection then
        Message.Result := SEL_TEXT or IfThen(SelLength > 1, SEL_MULTICHAR)
      else
        Message.Result := SEL_EMPTY;
    EM_SETBKGNDCOLOR:
      begin
        Message.Result := FBkColor;
        SetUseSystemColors(Message.WParam <> 0);
        if not FUseSystemColors then
          SetBackgroundColor(Message.LParam);
      end;
    EM_SETCHARFORMAT:
      {TODO};
    EM_SETFONTSIZE:
      begin
        Message.Result := B(InRange(Message.WParam, -1637, 1638));
        if Message.Result <> 0 then
          FFont.Size := REFontInc(FFont.Size, Message.WParam);
      end;
    EM_SETSCROLLPOS:
      begin
        Message.Result := 1;
        with PPoint(Message.LParam)^ do
          SetScrollPosXY(X, Y);
      end;
    EM_SETZOOM:
      begin
        Message.Result := B((Message.WParam > 0) and (Message.LParam > 0));
        if Message.Result <> 0 then
          SetZoom(100 * NativeInt(Message.WParam) div Message.LParam);
      end;
    EM_STOPGROUPTYPING:
      if FTypeTimer.Enabled then
        TypeTimerTimer(Self);
  end;
end;

procedure TTextEditor.WordWrap(ALineLength: integer; ANice: boolean;
  AChr: char);
begin
  TypeTimerEnd;
  FTextFile.WordWrap(ALineLength, ANice, AChr);
  AddUndoRecord(SUndoWordWrap, UID_UNKNOWN);
end;

procedure TTextEditor.TextFileFindDataClear(Sender: TObject);
begin
  Invalidate;
  if Assigned(FOnFindDataClear) then
    FOnFindDataClear(Self);
end;

procedure TTextEditor.TextFileLockVisualUpdates(Sender: TObject);
begin
  inc(FVisualUpdateLock);
end;

procedure TTextEditor.BeginVisualUpdate;
begin
  inc(FVisualUpdateLock);
end;

procedure TTextEditor.TextFileUnlockVisualUpdates(Sender: TObject);
begin
  dec(FVisualUpdateLock);
  if FVisualUpdateLock = 0 then
    DoSetCaretPos;
  // But the client is responsible for what to redraw
end;

procedure TTextEditor.EndVisualUpdate(AUpdate: boolean = false);
begin
  dec(FVisualUpdateLock);
  if FVisualUpdateLock = 0 then
  begin
    Invalidate;
    DoSetCaretPos;
    if AUpdate then Update;
  end;
end;

procedure TTextEditor.ConnectTextFileToEditor;
begin
  FTextFile.OnChange := TextFileChange;
  FTextFile.OnCaretPosChange := TextFileCaretPosChange;
  FTextFile.OnCaretPosSelChange := TextFileCaretPosSelChange;
  FTextFile.OnInputError := TextFileInputError;
  FTextFile.OnReadOnlyError := TextFileReadOnlyError;
  FTextFile.OnFileModified := TextFileModified;
  FTextFile.OnLineChange := TextFileLineChange;
  FTextFile.OnLineClassChange := TextFileLineClassChange;
  FTextFile.OnControlRemoved := TextFileControlRemoved;
  FTextFile.OnGetControlText := TextFileGetControlText;
  FTextFile.OnBookmarksMoved := TextFileBookmarksMoved;
  FTextFile.OnFindDataClear := TextFileFindDataClear;
  FTextFile.OnLockVisualUpdates := TextFileLockVisualUpdates;
  FTextFile.OnUnlockVisualUpdates := TextFileUnlockVisualUpdates;
  FTextFile.IndentSize := Self.IndentSize;
  FTextFile.AutoIndent := Self.AutoIndent;
  FTextFile.CaretAfterEOL := FCaretAfterEOL {important!};
end;

procedure TTextEditor.DisconnectTextFileFromEditor;
begin
  FTextFile.OnChange := nil;
  FTextFile.OnCaretPosChange := nil;
  FTextFile.OnCaretPosSelChange := nil;
  FTextFile.OnInputError := nil;
  FTextFile.OnFileModified := nil;
  FTextFile.OnLineChange := nil;
  FTextFile.OnLineClassChange := nil;
  FTextFile.OnControlRemoved := nil;
  FTextFile.OnGetControlText := nil;
  FTextFile.OnBookmarksMoved := nil;
  FTextFile.OnLockVisualUpdates := nil;
  FTextFile.OnUnlockVisualUpdates := nil;
end;

procedure TTextEditor.ZoomIn;
begin
  Zoom := Zoom + 10;
end;

procedure TTextEditor.ZoomOut;
begin
  Zoom := Max(Zoom - 10, 10);
end;

{ BMSCROLL }
procedure TTextEditor.SetScrollPosY(Value: integer; Lim: boolean = false);
var
  OldScrollPos, diff: integer;
  TCR: TRect;
begin
  if (Value < 0) or SingleLine then Value := 0;
  if Lim then
  begin
    if GetLineBottomVirtual(LineCount - 1) < (ClientHeight - FMarginTop - FMarginBottom) then
      Value := 0
    else if Value > GetLineBottomVirtual(LineCount - 1) - (ClientHeight - FMarginTop - FMarginBottom) then
      Value := GetLineBottomVirtual(LineCount - 1) - (ClientHeight - FMarginTop - FMarginBottom);
  end;
  OldScrollPos := FScrollPos.Y;
  FScrollPos.Y := Value;
  diff := OldScrollPos - Value;
  if diff = 0 then Exit;
  UpdateScrollBars;
  TCR := TextContentRect;
  TCR.Left := 0; // ruler should scroll with the text (in pure vertical scrolling)
  if abs(diff) < ClientHeight - FMarginTop - FMarginBottom then
    ScrollWindowEx(Handle, 0, diff, @TCR, @TCR, 0, nil, SW_INVALIDATE or SW_SCROLLCHILDREN)
  else
    Invalidate;
  UpdateRuler;
  DoSetCaretPos;
  if FTextFile.ControlAware then
    UpdateLineControls;
  MoveBalloonPostScroll;
end;

{ BMSCROLL }
procedure TTextEditor.SetScrollPosXY(X, Y: integer; Lim: boolean = false);
var
  OldScrollPos: TPoint;
  diffX, diffY: integer;
  TCR: TRect;
begin

  if SingleLine then Y := 0;

  if X < 0 then X := 0;
  if Y < 0 then Y := 0;

  if Lim then
  begin
    if GetLineBottomVirtual(LineCount - 1) < (ClientHeight - FMarginTop - FMarginBottom) then
      Y := 0
    else if Y > GetLineBottomVirtual(LineCount - 1) - (ClientHeight - FMarginTop - FMarginBottom) then
      Y := GetLineBottomVirtual(LineCount - 1) - (ClientHeight - FMarginTop - FMarginBottom);
  end;

  // Special case: optimize?
  if (X = FScrollPos.X) then
  begin
    SetScrollPosY(Y);
    Exit;
  end
  else if (Y = FScrollPos.Y) then
  begin
    SetScrollPosX(X);
    Exit;
  end;

  OldScrollPos := FScrollPos;
  FScrollPos := Point(X, Y);
  diffX := OldScrollPos.X - X;
  diffY := OldScrollPos.Y - Y;
  UpdateScrollBars;
  TCR := TextContentRect;
  if (abs(diffY) < ClientHeight - FMarginTop - FMarginBottom) and (abs(diffX) < ClientWidth - FMarginLeft - FMarginRight) then
    ScrollWindowEx(Handle, diffX, diffY, @TCR, @TCR, 0, nil, SW_INVALIDATE or SW_SCROLLCHILDREN)
  else
    Invalidate;
  UpdateRuler;
  DoSetCaretPos;
  if FTextFile.ControlAware then
    UpdateLineControls;
  MoveBalloonPostScroll;

end;

procedure TTextEditor.UpdateLineControls;
var
  i: Integer;
  LineIndex: integer;
begin
  for i := 0 to high(FLineControls) do
  begin
    if not Assigned(FLineControls[i].Control) then Continue;
    LineIndex := GetLineFromControlID(FLineControls[i].ID);
    if LineIndex = -1 then
    begin
      FLineControls[i].Control.Visible := false;
      Continue;
    end;
    FLineControls[i].Control.Visible := true;
    FLineControls[i].Control.SetBounds(FMarginLeft - FScrollPos.X + CARET_WIDTH,
      FMarginTop + FAccumLineHeights[LineIndex] - FScrollPos.Y,
      FLineControls[i].Control.Width,
      FLineControls[i].Control.Height);
  end;
end;

procedure TTextEditor.FontChange(Sender: TObject);
begin
  VerifyFont;
  SetupFontMetrics;
  BuildFontDataArray;
  AdjustHeight;
  Invalidate;
end;

procedure TTextEditor.UpdateRuler;
begin
  InvalidateRect(Handle, RulerRect, false);
end;

procedure TTextEditor.RulerFontChange(Sender: TObject);
begin
  UpdateRuler;
end;

function TTextEditor.GetCaretAfterEOL: boolean;
begin
  FCaretAfterEOL := FTextFile.CaretAfterEOL;
  result := FCaretAfterEOL;
end;

function TTextEditor.GetCaretPos: TPoint;
begin
  result := FTextFile.CaretPos.Data;
end;

function TTextEditor.GetCharAtCaret: char;
begin
  if (SelLength = 1) and (SafeSelLength = 1) then
    result := SelText[1]
  else if FTextFile.CaretPos.X < FTextFile.VirtualLineWidths[FTextFile.CaretPos.Y] then
    result := FTextFile.Lines[FTextFile.CaretPos.Y][FTextFile.CaretPos.X + 1]
  else
    result := #0;
end;

function TTextEditor.GetCharBeforeCaret: char;
begin
  if (SelLength = 1) and (SafeSelLength = 1) then
    result := SelText[1]
  else if FTextFile.CharacterExists(FTextFile.CaretPos.Y, FTextFile.CaretPos.X - 1) then
    result := FTextFile.Lines[FTextFile.CaretPos.Y][FTextFile.CaretPos.X]
  else
    result := #0;
end;

function TTextEditor.GetClassRecord(Index: integer): TClassRecord;
begin
  result := FClassArray[Index];
end;

function TTextEditor.GetCliHistory(Index: integer): string;
begin
  result := FCliHistory[Index];
end;

function TTextEditor.GetCliHistoryCount: integer;
begin
  result := Length(FCliHistory);
end;

function TTextEditor.GetCliHistoryIndex: integer;
begin
  result := FCliHistoryIndex;
end;

function TTextEditor.GetControlFromID(ID: integer): TControl;
var
  i: Integer;
begin
  result := nil;
  for i := low(FLineControls) to high(FLineControls) do
    if FLineControls[i].ID = ID then
      Exit(FLineControls[i].Control);
end;

function TTextEditor.GetControlFromLine(LineIndex: integer): TControl;
begin
  result := GetControlFromID(GetControlIDFromLine(LineIndex));
end;

function TTextEditor.GetControlIDFromLine(LineIndex: integer): integer;
var
  tmp: integer;
begin
  result := -1;
  if LineIsControl(LineIndex) and TryStrToInt(Copy(FTextFile.Lines[LineIndex],
    Length(LINE_CONTROL_PREFIX) + 1), tmp) then result := tmp;
end;

function TTextEditor.GetEditMode: TEditMode;
begin
  result := FTextFile.EditMode;
end;

function TTextEditor.GetClass(Index: integer): string;
begin
  result := FTextFile.Classes[Index];
end;

function TTextEditor.GetClassFromName(const AClassName: string;
  out AClassRecord: TClassRecord): boolean;
var
  i: Integer;
begin
  for i := low(FClassArray) to high(FClassArray) do
    if SameStr(AClassName, FClassArray[i].Name) then
    begin
      AClassRecord := FClassArray[i];
      Exit(true);
    end;
  result := false;
end;

function TTextEditor.GetClassIndex(const AClassName: string): integer;
var
  i: Integer;
begin
  if not AClassName.IsEmpty then
    for i := low(FClassArray) to high(FClassArray) do
      if SameStr(AClassName, FClassArray[i].Name) then
        Exit(i);
  result := -1;
end;

function TTextEditor.GetFalse: boolean;
begin
  result := false;
end;

function TTextEditor.GetFontChrs(const AFontName: TFontName;
  out GlyphSet: PGlyphSet): boolean;
var
  size: integer;
begin
  FGLYPHBM.Canvas.Font.Name := AFontName;
  size := GetFontUnicodeRanges(FGLYPHBM.Canvas.Handle, nil);
  GetMem(GlyphSet, size);
  result := GetFontUnicodeRanges(FGLYPHBM.Canvas.Handle, GlyphSet) > 0;
end;

function TTextEditor.GetIDFromControl(AControl: TControl): integer;
var
  i: Integer;
begin
  for i := low(FLineControls) to high(FLineControls) do
    if FLineControls[i].Control = AControl then
      Exit(i);
  result := -1;
end;

function TTextEditor.GetNotification(AIndex: integer): integer;
begin
  result := FNotifications[AIndex];
end;

function TTextEditor.GetNotificationCount: integer;
begin
  result := Length(FNotifications);
end;

function TTextEditor.GetNotificationStr(MsgID: integer): string;
begin
  result := FNotificationStrs[MsgID];
end;

function TTextEditor.GetNumClasses: integer;
begin
  result := Length(FClassArray);
end;

function TTextEditor.GetRulerVisible: boolean;
begin
  result := FRulerWidth > 0;
end;

function TTextEditor.GetSelEndPos: TPoint;
begin
  result := FTextFile.CaretPos.SelEnd;
end;

function TTextEditor.GetSelLength: integer;
begin
  result := FTextFile.SelLength;
end;

function TTextEditor.GetSelStart: integer;
begin
  result := FTextFile.SelStart;
end;

function TTextEditor.GetSelText: string;
begin
  result := FTextFile.SelText;
end;

function TTextEditor.GetSelType: TSelectionType;
begin
  result := FTextFile.CaretPos.SelectionType;
end;

function TTextEditor.GetSingleLine: boolean;
begin
  result := FTextFile.SingleLine;
end;

function TTextEditor.GetSortReverseOrder: boolean;
begin
  result := FTextFile.SortReverseOrder;
end;

function TTextEditor.GetText: string;
begin
  result := FTextFile.PlainText;
end;

function TTextEditor.GetWord: string;
begin
  result := FTextFile.GetWord;
end;

function TTextEditor.GetWord(const Point: TPoint): string;
begin
  result := FTextFile.GetWord(Point);
end;

function TTextEditor.GetWordBoundary(out StartPos, EndPos: integer): boolean;
begin
  result := FTextFile.GetWordBoundary(StartPos, EndPos);
end;

function TTextEditor.GetWrapAt: string;
begin
  result := FTextFile.WrapAt;
end;

function TTextEditor.GiveFeedback(dwEffect: Longint): HRESULT;
begin
  result := DRAGDROP_S_USEDEFAULTCURSORS;
end;

function TTextEditor.GotoBookmark(AIndex: integer): boolean;
begin
  result := FTextFile.GotoBookmark(AIndex);
end;

function TTextEditor.GotoHistoryVersion(Index: integer): boolean;
begin
  if FTypeTimer.Enabled then
    TypeTimerTimer(Self);
  FNoScrollToCaret := true;
  try
    result := FTextFile.GotoHistoryVersion(Index);
    if FTextFile.ControlAware then
      FixRemovedLineControlLines;
  finally
    FNoScrollToCaret := false;
  end;
  if result then
    CenterOnSelection(true);
end;

procedure TTextEditor.GotoSamePixelAtNextLine(Shift: boolean);
var
  px: TPoint;
begin

  if FTextFile.AtLastLine then
  begin
    TextFileInputError(Self);
    Exit;
  end;

  if not FMultiSize then
  begin
    FTextFile.Down(Shift);
    Exit;
  end;

  px := VirtualPixelAtChar(CaretPos);
  if FDesiredColumn <> 0 then
    px.X := FDesiredColumn;
  inc(px.Y, FFontSizes[CaretPos.Y].cy + FFontSizes[CaretPos.Y + 1].cy div 2);

  FPreserveDesiredColumn := true;
  try
    FTextFile.CaretPos.SetPoint(CaretPosAtVirtualPixel(px), Shift);
  finally
    FPreserveDesiredColumn := false;
  end;

end;

procedure TTextEditor.GotoSamePixelAtPrevLine(Shift: boolean);
var
  px: TPoint;
begin

  if CaretPos.Y = 0 then
  begin
    TextFileInputError(Self);
    Exit;
  end;

  if not FMultiSize then
  begin
    FTextFile.Up(Shift);
    Exit;
  end;

  px := VirtualPixelAtChar(CaretPos);
  if FDesiredColumn <> 0 then
    px.X := FDesiredColumn;
  dec(px.Y, FFontSizes[CaretPos.Y - 1].cy div 2);

  FPreserveDesiredColumn := true;
  try
    FTextFile.CaretPos.SetPoint(CaretPosAtVirtualPixel(px), Shift);
  finally
    FPreserveDesiredColumn := false;
  end;

end;

function TTextEditor.HasNotificationMessage(MsgID: integer): boolean;
var
  i: Integer;
begin
  for i := low(FNotifications) to high(FNotifications) do
    if FNotifications[i] = MsgID then
      Exit(true);
  result := false;
end;

procedure TTextEditor.HideBalloon;
begin
  if FHintWindow <> 0 then
    SendMessage(FHintWindow, TTM_TRACKACTIVATE, 0, LParam(@FToolInfo));
end;

function TTextEditor.GetWordBoundary(const Point: TPoint; out StartPos,
  EndPos: integer): boolean;
begin
  result := FTextFile.GetWordBoundary(Point, StartPos, EndPos);
end;

function UnicodeSuperscript(const AChar: char): char;
begin
  result := AChar;
  case AChar of
    '0':
      result := '⁰';
    '1':
      result := '¹';
    '2':
      result := '²';
    '3':
      result := '³';
    '4':
      result := '⁴';
    '5':
      result := '⁵';
    '6':
      result := '⁶';
    '7':
      result := '⁷';
    '8':
      result := '⁸';
    '9':
      result := '⁹';
    '+':
      result := '⁺';
    '-', '−':
      result := '⁻';
    '=':
      result := '⁼';
    '(':
      result := '⁽';
    ')':
      result := '⁾';
    'n':
      result := 'ⁿ';
  end;
end;

function UnicodeSubscript(const AChar: char): char;
begin
  result := AChar;
  case AChar of
    '0':
      result := '₀';
    '1':
      result := '₁';
    '2':
      result := '₂';
    '3':
      result := '₃';
    '4':
      result := '₄';
    '5':
      result := '₅';
    '6':
      result := '₆';
    '7':
      result := '₇';
    '8':
      result := '₈';
    '9':
      result := '₉';
    '+':
      result := '₊';
    '-', '−':
      result := '₋';
    '=':
      result := '₌';
    '(':
      result := '₍';
    ')':
      result := '₎';
  end;
end;

function UnicodeCircled(const AChar: char): char;
begin
  result := AChar;
  if InRange(ord(AChar), ord('A'), ord('Z')) then
    result := chr($24B6 + ord(AChar) - ord('A'))
  else if InRange(ord(AChar), ord('a'), ord('z')) then
    result := chr($24D0 + ord(AChar) - ord('a'))
  else if InRange(ord(AChar), ord('1'), ord('9')) then
    result := chr($2460 + ord(AChar) - ord('1'))
  else if AChar = '0' then
    result := #$24EA;
end;

function UnicodeParenthesized(const AChar: char): char;
begin
  result := AChar;
  if InRange(ord(AChar), ord('a'), ord('z')) then
    result := chr($249C + ord(AChar) - ord('a'))
  else if InRange(ord(AChar), ord('1'), ord('9')) then
    result := chr($2474 + ord(AChar) - ord('1'))
end;

function UnicodeFullStop(const AChar: char): char;
begin
  result := AChar;
  if InRange(ord(AChar), ord('1'), ord('9')) then
    result := chr($2488 + ord(AChar) - ord('1'))
end;

function UnicodeDoublyCircled(const AChar: char): char;
begin
  result := AChar;
  if InRange(ord(AChar), ord('1'), ord('9')) then
    result := chr($24F5 + ord(AChar) - ord('1'))
end;

function TTextEditor.GetBalloonPosition: TPoint;
begin
  result := ClientToScreen(PhysicalPixelAtChar(FBalloonPoint));
  inc(result.Y, FFontSize.cy);
  inc(result.X, CARET_WIDTH div 2);
end;

function TTextEditor.GetBookmark(Index: integer): TPoint;
begin
  result := FTextFile.Bookmarks[Index];
end;

function TTextEditor.GetBookmarkCount: integer;
begin
  result := FTextFile.BookmarkCount;
end;

function TTextEditor.GetBookmarkDescr(BookmarkIndex: integer): string;
begin
  if not InRange(BookmarkIndex, 0, BookmarkCount - 1) then
    Exit(SBookmarkDescriptionInvalid);

  if BookmarkUsed(BookmarkIndex) then
    result := Format(SBookmarkDescription,
      [BookmarkIndex, Bookmarks[BookmarkIndex].Y + 1, Bookmarks[BookmarkIndex].X + 1])
  else
    result := Format(SBookmarkDescriptionEmpty, [BookmarkIndex]);
end;

procedure TTextEditor.InsertChar(const AChar: Char; AOverwrite: boolean = false);
var
  BChar: char;
begin

  if FTextFile.ControlAware and LineIsControl(FTextFile.CaretPos.Y) then
  begin
    TextFileInputError(Self);
    ShowBalloon(SControlLineInputTitle, SControlLineInputText, bikError, bpCaretPos,
      GetCaretPos);
    Exit;
  end;

  if FNumbersOnly and not AChar.IsDigit and (FHintWindow <> 0) then
  begin
    TextFileInputError(Self);
    ShowBalloon(SNumOnlyErrorTitle, SNumOnlyErrorText, bikError, bpCaretPos,
      GetCaretPos);
    Exit;
  end;

  BChar := AChar;
  case FInputTransform of
    itUpperCase:
      BChar := AnsiUpperCase(AChar)[1];
    itLowerCase:
      BChar := AnsiLowerCase(AChar)[1];
    itSuperscript:
      BChar := UnicodeSuperscript(AChar);
    itSubscript:
      BChar := UnicodeSubscript(AChar);
    itCircled:
      BChar := UnicodeCircled(AChar);
    itParenthesized:
      BChar := UnicodeParenthesized(AChar);
    itFullStop:
      BChar := UnicodeFullStop(AChar);
    itDoublyCircled:
      BChar := UnicodeDoublyCircled(AChar);
  end;

  if FMultipleCarets then
    FTextFile.MultiInsertChar(FCarets, BChar, Overwrite)
  else
    FTextFile.InsertChar(BChar, AOverwrite);

  PostType;

  if FMatchBrackets and ((BChar = '❩') or (BChar = ')') or (BChar = '}') or (BChar = ']')) then
    BlinkBracket;

end;

procedure TTextEditor.InsertGraphic(AGraphic: TGraphic; LineIndex: integer);
var
  img: TImage;
begin
  img := TImage.Create(Self);
  try
    img.AutoSize := true;
    img.Stretch := false;
    img.Proportional := true;
    img.Center := true;
    img.Cursor := crArrow;
    img.Picture.Graphic := AGraphic;
    img.PopupMenu := FImagePopup;
    InsertLineControl(img, LineIndex);
  except {read-only mode, for instance}
    img.Free;
    raise;
  end;
end;

procedure TTextEditor.InsertLine(const AText, AClassName: string;
  LineIndex: integer);
begin
  FTextFile.InsertLine(AText, AClassName, LineIndex);
end;

procedure TTextEditor.InsertLine(const AText: string; LineIndex: integer);
begin
  FTextFile.InsertLine(AText, LineIndex);
end;

procedure TTextEditor.InsertLine(LineIndex: integer);
begin
  FTextFile.InsertLine('', LineIndex);
end;

procedure TTextEditor.RemoveAllMargins;
begin
  if FMarginLeft + FMarginRight + FMarginTop + FMarginBottom > 0 then
  begin
    FMarginLeft := 0;
    FMarginRight := 0;
    FMarginTop := 0;
    FMarginBottom := 0;
    FRulerWidth := 0;
    UpdateScrollBars;
    Invalidate;
    DoSetCaretPos;
  end;
end;

procedure TTextEditor.InsertLineControl(AControl: TControl; LineIndex: integer);
begin
  RemoveAllMargins;

  MultiSize := true;
  FTextFile.ControlAware := true;

  SetLength(FLineControls, Length(FLineControls) + 1);
  FLineControls[high(FLineControls)].ID := FNextControlID;
  FLineControls[high(FLineControls)].Control := AControl;

  InsertLine(LINE_CONTROL_PREFIX + IntToStr(FNextControlID), LINE_CONTROL_CLASS, LineIndex);

  inc(FNextControlID);

  AControl.Parent := Self;
  AControl.Visible := true;
end;

procedure TTextEditor.BlinkBracket;
var
  start: TPoint;
  NewBracket: TPoint;
begin
  NewBracket := Point(FTextFile.CaretPos.X - 1, FTextFile.CaretPos.Y);
  start := FTextFile.MatchBracket(NewBracket);
  if start.Y <> -1 then
  begin
    SetBracketHighlight(NewBracket, start);
    FBlinkRemover.Enabled := false;
    FBlinkRemover.Enabled := true;
  end;
end;

procedure TTextEditor.InsertText(const AText: string);
begin
  TypeTimerEnd;
  FTextFile.InsertText(AText);
  AddUndoRecord(SUndoTextInserted, UID_UNKNOWN);
end;

procedure TTextEditor.InsertTextAsBlock(const AText: string);
begin
  TypeTimerEnd;
  FTextFile.InsertTextAsBlock(AText);
  AddUndoRecord(SUndoTextInserted, UID_UNKNOWN);
end;

function TTextEditor.Undo: boolean;
begin
  if FTypeTimer.Enabled then
    TypeTimerTimer(Self);
  FNoScrollToCaret := true;
  try
    result := FTextFile.Undo;
    if result and FTextFile.ControlAware then
      FixRemovedLineControlLines;
  finally
    FNoScrollToCaret := false;
  end;
  if result then
    CenterOnSelection(true);
end;

function TTextEditor.TextContentRect: TRect;
begin
  result := ClientRect;
  inc(result.Left, FMarginLeft);
  inc(result.Top, FMarginTop);
  dec(result.Right, FMarginRight);
  dec(result.Bottom, FMarginBottom);
end;

{ pre-BMCARET }
procedure TTextEditor.UpdateCaret;
begin
  if not Focused then Exit;
  if FListBoxMode then Exit;
  if FMultiSize then
    CreateCaret(Handle, IfThen(FTextFile.EditMode = emReadOnly, 1, 0), IfThen(FOverwrite, FFontSizes[FTextFile.CaretPos.Y].cx, CARET_WIDTH), FFontSizes[FTextFile.CaretPos.Y].cy)
  else
    CreateCaret(Handle, IfThen(FTextFile.EditMode = emReadOnly, 1, 0), IfThen(FOverwrite, FFontSize.cx, CARET_WIDTH), FFontSize.cy);
  ShowCaret(Handle);
  FCaretVisible := true;
  DoSetCaretPos;
end;

procedure TTextEditor.MultiCharSelectDlgResize(Sender: TObject);
begin
  if Sender is TForm then
    with TForm(Sender) do
      SetWindowPos(Flv, HWND_TOP, 0, 0, ClientWidth, ClientHeight,
        SWP_NOOWNERZORDER or SWP_NOZORDER or SWP_SHOWWINDOW);
end;

procedure TTextEditor.MultiCharSelectDlgWndProc(var Message: TMessage);
type
  LPNMLVKEYDOWN = ^NMLVKEYDOWN;
  NMLVKEYDOWN = packed record
    hdr: NMHDR;
    wVKey: WORD;
    flags: UINT;
  end;
var
  index: integer;
  buf: array[0..32] of char;
begin
  FMultiCharSelectDlgDefaultWndProc(Message);
  case Message.Msg of
    WM_NOTIFY:
      if PNMHDR(Message.LParam).hwndFrom = Flv then
        case PNMHDR(Message.LParam).code of
          LVN_KEYDOWN:
            if LPNMLVKEYDOWN(Message.LParam).wVKey = VK_ESCAPE then
              FMultiCharSelectDlgFrm.ModalResult := mrCancel;
          NM_RETURN, NM_DBLCLK:
            begin
              index := ListView_GetNextItem(Flv, -1, LVNI_SELECTED);
              if index <> -1 then
              begin
                ListView_GetItemText(Flv, index, 1, @buf[0], Length(buf));
                if (buf[0] = 'U') and (buf[1]= '+') then
                  FMultiCharSelectDlgFrm.Tag := StrToInt('$' + Copy(PChar(@buf[0]), 3));
              end;
              FMultiCharSelectDlgFrm.ModalResult := mrOk;
            end;
        end;
  end;
end;

procedure TTextEditor.MultiCharSelectDlgActivate(Sender: TObject);
begin
  Windows.SetFocus(Flv);
end;

procedure TTextEditor.DoMultiCharSelect(AChrs: array of char);
var
  frm: TForm;
  lv: HWND;
  c: char;
  tvi: TLVTileViewInfo;
  li: TLVItem;
  cl: TLVColumn;
  index: integer;
  il: TImageList;
  bm: TBitmap;
  i: integer;
  R: TRect;
  S: string;
const
  colinfo: array[0..1] of integer = (1, 2);
begin

  frm := TForm.Create(nil);
  lv := 0;
  try
    frm.Caption := SMultiSelectCaption;
    frm.BorderStyle := bsSizeToolWin;
    frm.Width := 600;
    frm.Height := 400;
    frm.OnActivate := MultiCharSelectDlgActivate;
    frm.OnResize := MultiCharSelectDlgResize;
    FMultiCharSelectDlgFrm := frm;
    FMultiCharSelectDlgDefaultWndProc := frm.WindowProc;
    frm.WindowProc := MultiCharSelectDlgWndProc;
    with ClientToScreen(Point(ClientWidth div 2 - frm.ClientWidth div 2,
      ClientHeight div 2 - frm.ClientHeight div 2)) do
    begin
      frm.Left := X;
      frm.Top := Y;
    end;

    // Image list
    il := TImageList.Create(frm);
    il.Width := 64;
    il.Height := 64;

    bm := TBitmap.Create;
    try
      bm.SetSize(64, 64);
      R := Rect(0, 0, bm.Width, bm.Height);
      bm.Canvas.Font.Assign(Self.Font);
      bm.Canvas.Font.Height := 64;
      bm.Canvas.Font.Color := clBlack;
      for c in AChrs do
      begin
        bm.Canvas.Brush.Color := clWhite;
        bm.Canvas.FillRect(R);
        S := c;
        bm.Canvas.TextRect(R, S, [tfSingleLine, tfCenter, tfVerticalCenter]);
        il.Add(bm, nil);
      end;
    finally
      bm.Free;
    end;

    lv := CreateWindowEx(0, WC_LISTVIEW, nil,
      WS_CHILD or WS_VISIBLE or LVS_REPORT or LVS_NOSORTHEADER or LVS_SINGLESEL or LVS_AUTOARRANGE,
      0, 0, frm.ClientWidth, frm.ClientHeight, frm.Handle, 0, HInstance, nil);

    Flv := lv;

    ListView_SetExtendedListViewStyle(lv, LVS_EX_AUTOSIZECOLUMNS or LVS_EX_FULLROWSELECT);
    ListView_SetImageList(lv, il.Handle, LVSIL_NORMAL);

    // Enter tile mode
    if not FMultiCharReportView then
    begin
      ListView_SetView(lv, LV_VIEW_TILE);

      tvi.cbSize := sizeof(tvi);
      tvi.dwMask := LVTVIM_COLUMNS;
      tvi.cLines := 2;
      ListView_SetTileViewInfo(lv, tvi);
    end;

    // Columns
    cl.mask := LVCF_SUBITEM or LVCF_TEXT or LVCF_ORDER or LVCF_WIDTH;
    cl.iSubItem := 0;
    cl.pszText := PChar(SMultiCharDlgLvColumnTitleDescription);
    cl.iOrder := 0;
    cl.cx := 200;
    ListView_InsertColumn(lv, 0, cl);

    cl.iSubItem := 1;
    cl.pszText := PChar(SMultiCharDlgLvColumnTitleCodepoint);
    cl.iOrder := 1;
    cl.cx := 75;
    ListView_InsertColumn(lv, 1, cl);

    cl.iSubItem := 2;
    cl.pszText := PChar(SMultiCharDlgLvColumnTitleBlock);
    cl.iOrder := 2;
    cl.cx := 200;
    ListView_InsertColumn(lv, 2, cl);

    // Items
    li.mask := LVIF_TEXT or LVIF_IMAGE or LVIF_STATE or LVIF_COLUMNS;
    li.stateMask := 0;
    li.iSubItem := 0;
    li.state := 0;
    li.iImage := 0;
    li.cColumns := 2;
    li.puColumns := PUINT(@colinfo[0]);

    ListView_SetItemCount(lv, Length(AChrs));
    i := 0;
    for c in AChrs do
    begin
      li.iImage := i;
      inc(i);
      li.pszText := PChar(UCD.GetChrName(c));
      index := ListView_InsertItem(lv, li);
      ListView_SetItemText(lv, index, 1, PChar(UCD.GetChrCodepointStr(c)));
      ListView_SetItemText(lv, index, 2, PChar(UCD.GetChrBlock(c)));
    end;

    frm.Tag := 0;
    if frm.ShowModal = mrOk then
      if frm.Tag <> 0 then
      begin
        Backspace;
        InsertChar(Chr(frm.Tag));
      end;

  finally
    if lv <> 0 then DestroyWindow(lv);
    frm.Free;
  end;

end;

{ effect-BMCARET }
procedure TTextEditor.DoSetCaretPos;
var
  pt: TPoint;
begin

  if FVisualUpdateLock > 0 then Exit;

  if FListBoxMode or not Focused then Exit;

  if FMultiSize then
    pt := Point(FMarginLeft +
      FTextFile.CaretPos.X * FFontSizes[FTextFile.CaretPos.Y].cx - FScrollPos.X,
      FMarginTop + FAccumLineHeights[FTextFile.CaretPos.Y] - FScrollPos.Y)
  else
    pt := Point(FMarginLeft + FTextFile.CaretPos.X * FFontSize.cx - FScrollPos.X,
      FMarginTop + FTextFile.CaretPos.Y * FFontSize.cy - FScrollPos.Y);

  if PtInRect(TextContentRect, pt) then
  begin
    BinaryShowCaret;
    Windows.SetCaretPos(pt.X, pt.Y)
  end
  else
    BinaryHideCaret;

end;

procedure TTextEditor.BinaryHideCaret;
begin
  if FCaretVisible then
  begin
    HideCaret(Handle);
    FCaretVisible := false;
  end;
end;

procedure TTextEditor.BinaryShowCaret;
begin
  if not FCaretVisible then
  begin
    ShowCaret(Handle);
    FCaretVisible := true;
  end;
end;

function TTextEditor.GetLineTop(LineIndex: integer): integer;
begin
  if FMultiSize then
    result := FMarginTop + FAccumLineHeights[LineIndex] - FScrollPos.Y
  else
    result := FMarginTop + FFontSize.cy * LineIndex - FScrollPos.Y;
end;

function TTextEditor.GetListBoxItemIndex: integer;
begin
  result := CaretPos.Y;
end;

function TTextEditor.GetLastMultiCaret: TPoint;
begin
  if Length(FCarets) > 0 then
    result := FCarets[high(FCarets)]
  else
    result := CaretPos;
end;

function TTextEditor.GetLine(Index: integer): string;
begin
  result := FTextFile.Lines[Index];
end;

function TTextEditor.GetLineBookmark(ALineIndex: integer): integer;
var
  i: Integer;
begin
  for i := 0 to FTextFile.BookmarkCount - 1 do
    if FTextFile.Bookmarks[i].Y = ALineIndex then
      Exit(i);
  result := -1;
end;

function TTextEditor.GetLineBottom(LineIndex: integer): integer;
begin
  if FMultiSize then
    result := FMarginTop + FAccumLineHeights[LineIndex] + FFontSizes[LineIndex].cy - FScrollPos.Y
  else
    result := FMarginTop + FFontSize.cy * (LineIndex + 1) - FScrollPos.Y;
end;

function TTextEditor.GetLineBottomVirtual(LineIndex: integer): integer;
begin
  if FMultiSize then
    result := FMarginTop + FAccumLineHeights[LineIndex] + FFontSizes[LineIndex].cy
  else
    result := FMarginTop + FFontSize.cy * (LineIndex + 1);
end;

function TTextEditor.GetCharLeft(LineIndex, ColIndex: integer): integer;
begin
  if FMultiSize then
    result := FMarginLeft + FFontSizes[LineIndex].cx * ColIndex - FScrollPos.X
  else
    result := FMarginLeft + FFontSize.cx * ColIndex - FScrollPos.X;
end;

function TTextEditor.GetCharRight(LineIndex, ColIndex: integer): integer;
begin
  if FMultiSize then
    result := FMarginLeft + FFontSizes[LineIndex].cx * (ColIndex + 1) - FScrollPos.X
  else
    result := FMarginLeft + FFontSize.cx * (ColIndex + 1) - FScrollPos.X;
end;

procedure TTextEditor.VisualUpdate(ChangeType: TChangeType; Data1, Data2, Data3,
  Data4: integer);
var
  i: Integer;
begin

  if (FVisualUpdateLock > 0) or not (FValidPaintState and Visible) then Exit;
  begin
    case ChangeType of
      ctFile:
        Invalidate;
      ctLineRange:
        InvalidateRect(Handle, Rect(0, GetLineTop(Data1), ClientWidth, GetLineBottom(Data2)), false);
      ctBlock:
        if FMultiSize then
          for i := max(FirstVisibleLine, Data1) to min(LastVisibleLine, Data2) do
            InvalidateRect(Handle, Rect(GetCharLeft(i, Data3), GetLineTop(i), GetCharRight(i, Data4), GetLineBottom(i)), false)
        else
          InvalidateRect(Handle, Rect(GetCharLeft(Data1, Data3), GetLineTop(Data1), GetCharRight(Data1, Data4), GetLineBottom(Data2)), false);
      ctLine:
        InvalidateRect(Handle, Rect(0, GetLineTop(Data1), ClientWidth, GetLineBottom(Data1)), false);
      ctLineFrom:
        InvalidateRect(Handle, Rect(GetCharLeft(Data1, Data2), GetLineTop(Data1), ClientWidth, GetLineBottom(Data1)), false);
      ctChar:
        InvalidateRect(Handle, Rect(GetCharLeft(Data1, Data2), GetLineTop(Data1), GetCharRight(Data1, Data2), GetLineBottom(Data1)), false);
      ctTwoChars:
        begin
          InvalidateRect(Handle, Rect(GetCharLeft(Data1, Data2), GetLineTop(Data1), GetCharRight(Data1, Data2), GetLineBottom(Data1)), false);
          InvalidateRect(Handle, Rect(GetCharLeft(Data3, Data4), GetLineTop(Data3), GetCharRight(Data3, Data4), GetLineBottom(Data3)), false);
        end;
      ctPostFile:
        InvalidateRect(Handle, Rect(0, GetLineBottom(FTextFile.LineCount - 1), ClientWidth, ClientHeight), false);
    end;
    Update;
  end

end;

function TTextEditor.EditorCommand(Command, Param1, Param2, Param3,
  Param4: integer): integer;
var
  S: string;
  i: integer;
begin
  result := 0;

  // Param in Command?
  if (Command and $FFFF0000) <> 0 then
  begin
    Param1 := Command shr 16;
    Command := Command and $0000FFFF;
  end;

  case Command of
    EDITOR_COMMAND_RIGHT:
      FTextFile.Right(Param1 <> 0, Param2 <> 0, Param3 <> 0);
    EDITOR_COMMAND_LEFT:
      FTextFile.Left(Param1 <> 0, Param2 <> 0, Param3 <> 0);
    EDITOR_COMMAND_DOWN:
      FTextFile.Down(Param1 <> 0, Param2 <> 0);
    EDITOR_COMMAND_UP:
      FTextFile.Up(Param1 <> 0, Param2 <> 0);
    EDITOR_COMMAND_HOME:
      FTextFile.Home(Param1 <> 0, Param2 <> 0);
    EDITOR_COMMAND_END:
      FTextFile.KEnd(Param1 <> 0, Param2 <> 0);
    EDITOR_COMMAND_PAGE_UP:
      PageUp(Param1 <> 0);
    EDITOR_COMMAND_PAGE_DOWN:
      PageDown(Param1 <> 0);
    EDITOR_COMMAND_BACKSPACE:
      Backspace(Param1 <> 0);
    EDITOR_COMMAND_DELETE:
      Delete(Param1 <> 0);
    EDITOR_COMMAND_CLEAR_SELECTION:
      ClearSelection;
    EDITOR_COMMAND_SELECT_ALL:
      SelectAll;
    EDITOR_COMMAND_SELECT_NONE:
      SelectNone;
    EDITOR_COMMAND_SELECT_ALL_NONE:
      SelectAllNone;
    EDITOR_COMMAND_SELECT_WORD:
      result := B(SelectWord);
    EDITOR_COMMAND_SELECT_LINE:
      SelectLine;
    EDITOR_COMMAND_CLEAR_LINE:
      ClearLine(Param1);
    EDITOR_COMMAND_CUT:
      CutToClipboard;
    EDITOR_COMMAND_COPY:
      CopyToClipboard;
    EDITOR_COMMAND_PASTE:
      PasteFromClipboard;
    EDITOR_COMMAND_UNDO:
      result := B(Undo);
    EDITOR_COMMAND_REDO:
      result := B(Redo);
    EDITOR_COMMAND_CLEAR_UNDO_BUFFER:
      ClearUndoHistory;
    EDITOR_COMMAND_GOTO_SOF:
      FTextFile.GotoSOF(Param1 <> 0);
    EDITOR_COMMAND_GOTO_EOF:
      FTextFile.GotoEOF(Param1 <> 0);
    EDITOR_COMMAND_RETURN:
      Return;
    EDITOR_COMMAND_CHAR:
      InsertChar(Char(Param1), Param2 <> 0);
    EDITOR_COMMAND_GET_AT_SOF:
      result := B(FTextFile.AtSOF);
    EDITOR_COMMAND_GET_AT_EOL:
      result := B(FTextFile.AtEOL);
    EDITOR_COMMAND_GET_BEYOND_EOL:
      result := B(FTextFile.AtOrBeyondEOL);
    EDITOR_COMMAND_GET_AT_EOF:
      result := B(FTextFile.AtEOF);
    EDITOR_COMMAND_GET_AT_LAST_LINE:
      result := B(FTextFile.AtLastLine);
    EDITOR_COMMAND_GET_HAS_SELECTION:
      result := B(FTextFile.HasSelection);
    EDITOR_COMMAND_GET_LINE_NUMBER_0:
      result := FTextFile.CaretPos.Y;
    EDITOR_COMMAND_GET_COL_NUMBER_0:
      result := FTextFile.CaretPos.X;
    EDITOR_COMMAND_GET_CHR_INDEX:
      result := SelStart;
    EDITOR_COMMAND_GOTO_POINT:
      FTextFile.CaretPos.SetPoint(Param1, Param2, Param3 <> 0);
    EDITOR_COMMAND_GOTO_INDEX:
      SelStart := Param1;
    EDITOR_COMMAND_GET_SEL_LENGTH:
      result := SelLength;
    EDITOR_COMMAND_SET_SEL_LENGTH:
      SelLength := Param1;
    EDITOR_COMMAND_GET_EDIT_MODE:
      result := ord(FTextFile.EditMode);
    EDITOR_COMMAND_SET_EDIT_MODE:
      SetEditMode(TEditMode(Param1));
    EDITOR_COMMAND_GET_SELECTION_MODE:
      result := ord(GetSelType);
    EDITOR_COMMAND_SET_SELECTION_MODE:
      SetSelType(TSelectionType(Param1));
    EDITOR_COMMAND_GET_OVERWRITE:
      result := B(FOverwrite);
    EDITOR_COMMAND_SET_OVERWRITE:
      SetOverwrite(Param1 <> 0);
    EDITOR_COMMAND_GET_AUTO_REPLACE:
      result := B(FAutoReplace);
    EDITOR_COMMAND_SET_AUTO_REPLACE:
      AutoReplace := Param1 <> 0;
    EDITOR_COMMAND_GET_CHAR:
      result := Integer(GetCharAtCaret);
    EDITOR_COMMAND_ADD_INDENT:
      AddIndent;
    EDITOR_COMMAND_REMOVE_INDENT:
      RemoveIndent;
    EDITOR_COMMAND_TRIM_INDENT:
      RemoveAllIndent;
    EDITOR_COMMAND_SWAP_UP:
      SwapLinesAbove;
    EDITOR_COMMAND_SWAP_DOWN:
      SwapLinesBelow;
    EDITOR_COMMAND_GET_AUTO_INDENT:
      result := B(FAutoIndent);
    EDITOR_COMMAND_SET_AUTO_INDENT:
      SetAutoIndent(Param1 <> 0);
    EDITOR_COMMAND_GET_CARET_BEYOND_EOL:
      result := B(FTextFile.CaretAfterEOL);
    EDITOR_COMMAND_SET_CARET_BEYOND_EOL:
      SetCaretAfterEOL(Param1 <> 0);
    EDITOR_COMMAND_GET_NUM_CHARACTERS:
      result := FTextFile.NumCharacters;
    EDITOR_COMMAND_GET_TEXT_SIZE:
      result := FTextFile.VirtualTextLength;
    EDITOR_COMMAND_GET_NUM_LINES:
      result := FTextFile.LineCount;
    EDITOR_COMMAND_GET_MAX_WIDTH:
      result := FTextFile.MaxLineWidth;
    EDITOR_COMMAND_SCROLL_TO_CARET:
      result := B(ScrollToCaret);
    EDITOR_COMMAND_CENTER_ON_SELECTION:
      CenterOnSelection(Param1 <> 0);
    EDITOR_COMMAND_REPLACE_TOKEN:
      DoAutoReplace;
    EDITOR_COMMAND_REPLACE_CODEPOINT:
      FTextFile.ReplaceCodepoint;
    EDITOR_COMMAND_UPDATE_SCROLLBARS:
      UpdateScrollBars;
    EDITOR_COMMAND_UPDATE_CARET:
      UpdateCaret;
    EDITOR_COMMAND_UPDATE_CURSOR:
      ChangeCursor;
    EDITOR_COMMAND_REDRAW:
      Invalidate;
    EDITOR_COMMAND_REDRAW_LINE:
      VisualUpdate(ctLine, Param1, 0, 0, 0);
    EDITOR_COMMAND_REDRAW_LINE_RANGE:
      VisualUpdate(ctLineRange, Param1, Param2, 0, 0);
    EDITOR_COMMAND_REDRAW_BLOCK:
      VisualUpdate(ctBlock, Param1, Param2, Param3, Param4);
    EDITOR_COMMAND_GET_MODIFIED:
      result := B(FTextFile.FileModified);
    EDITOR_COMMAND_SET_MODIFIED:
      FTextFile.FileModified := Param1 <> 0;
    EDITOR_COMMAND_NEW:
      NewFile;
    EDITOR_COMMAND_CLEAR:
      FTextFile.Clear;
    EDITOR_COMMAND_OPEN:
      LoadFromFile(PChar(Param1), TEncoding.UTF8);
    EDITOR_COMMAND_SAVE:
      SaveToFile(PChar(Param1));
    EDITOR_COMMAND_GET_HIDDEN:
      result := B(FShowHiddenCharacters);
    EDITOR_COMMAND_SET_HIDDEN:
      SetShowHiddenCharacters(Param1 <> 0);
    EDITOR_COMMAND_SET_SELECTION:
      begin
        FTextFile.CaretPos.SetPoint(Param1, Param2, false);
        FTextFile.CaretPos.SetPoint(Param3, Param4, true);
      end;
    EDITOR_COMMAND_GET_MATCH_BRACKETS:
      result := B(FMatchBrackets);
    EDITOR_COMMAND_SET_MATCH_BRACKETS:
      SetMatchBrackets(Param1 <> 0);
    EDITOR_COMMAND_GET_BRACKET_HIGHLIGHT:
      result := B(FBracketHighlight);
    EDITOR_COMMAND_GET_SCROLL_POS_X:
      result := FScrollPos.X;
    EDITOR_COMMAND_GET_SCROLL_POS_Y:
      result := FScrollPos.Y;
    EDITOR_COMMAND_SET_SCROLL_POS:
      SetScrollPosXY(Param1, Param2);
    EDITOR_COMMAND_REDRAW_CHAR:
      VisualUpdate(ctChar, Param1, Param2, 0, 0);
    EDITOR_COMMAND_REDRAW_CHARS:
      VisualUpdate(ctChar, Param1, Param2, Param3, Param4);
    EDITOR_COMMAND_GET_INDENT:
      result := FIndentSize;
    EDITOR_COMMAND_SET_INDENT:
      SetIndentSize(Param1);
    EDITOR_COMMAND_GET_TAB_LENGTH:
      result := FTabLength;
    EDITOR_COMMAND_SET_TAB_LENGTH:
      FTabLength := Param1;
    EDITOR_COMMAND_GET_SINGLE_LINE:
      result := B(SingleLine);
    EDITOR_COMMAND_SET_SINGLE_LINE:
      SetSingleLine(Param1 <> 0);
    EDITOR_COMMAND_GET_LABEL_MODE:
      result := B(FLabelStyle);
    EDITOR_COMMAND_SET_LABEL_MODE:
      SetLabelStyle(Param1 <> 0);
    EDITOR_COMMAND_GET_ELLIPSIS_MODE:
      result := B(FLabelEllipsis);
    EDITOR_COMMAND_SET_ELLIPSIS_MODE:
      SetLabelEllipsis(Param1 <> 0);
    EDITOR_COMMAND_GET_INPUT_TRANSFORM:
      result := ord(FInputTransform);
    EDITOR_COMMAND_SET_INPUT_TRANSFORM:
      FInputTransform := TInputTransform(Param1);
    EDITOR_COMMAND_GET_NUMBERS_ONLY:
      result := B(FNumbersOnly);
    EDITOR_COMMAND_SET_NUMBERS_ONLY:
      FNumbersOnly := Param1 <> 0;
    EDITOR_COMMAND_GET_PASSWORD_CHAR:
      result := ord(FPasswordChar);
    EDITOR_COMMAND_SET_PASSWORD_CHAR:
      SetPasswordChar(Chr(Param1));
    EDITOR_COMMAND_GET_UNICODE_FALLBACK:
      result := B(FUnicodeFallback);
    EDITOR_COMMAND_SET_UNICODE_FALLBACK:
      SetUnicodeFallback(Param1 <> 0);
    EDITOR_COMMAND_ESCAPE:
      Escape(Param1 <> 0);
    EDITOR_COMMAND_USE_DEFAULT_FALLBACK_FONTS:
      UseDefaultFallbackFonts;
    EDITOR_COMMAND_SHOW_BALLOON:
      result := B(ShowBalloon(PChar(Param1), PChar(Param2), TBalloonIconKind(Param3),
        TBalloonPersistence(Param4), CaretPos));
    EDITOR_COMMAND_HIDE_BALLOON:
      HideBalloon;
    EDITOR_COMMAND_SHOW_BALLOON_POS:
      result := B(ShowBalloon(PChar(Param1), PChar(Param2), TBalloonIconKind(Byte(Param3)),
        TBalloonPersistence(Byte(Param3 shr 8)), FTextFile.GetPointOfIndex(Param4)));
    EDITOR_COMMAND_IS_BALLOON_VISIBLE:
      result := B(BalloonVisible);
    EDITOR_COMMAND_ADJUST_HEIGHT:
      begin
        result := B(SingleLine);
        if result <> 0 then
          ClientHeight := FFontSize.cy + AUTO_HEIGHT_PADDING;
      end;
    EDITOR_COMMAND_GET_UNDO_LENGTH:
      result := FTextFile.HistoryManager.Count;
    EDITOR_COMMAND_GET_UNDO_SIZE:
      result := FTextFile.HistoryManager.Size;
    EDITOR_COMMAND_GET_UNDO_MAX_SIZE:
      result := FTextFile.HistoryManager.MaxSize;
    EDITOR_COMMAND_SET_UNDO_MAX_SIZE:
      FTextFile.HistoryManager.MaxSize := Param1;
    EDITOR_COMMAND_GET_UNDO_FIRST_INDEX:
      result := FTextFile.HistoryManager.FirstItem;
    EDITOR_COMMAND_GET_UNDO_LAST_INDEX:
      result := FTextFile.HistoryManager.LastItem;
    EDITOR_COMMAND_GET_UNDO_POSITION:
      result := FTextFile.HistoryManager.HistoryIndex;
    EDITOR_COMMAND_WINDOWS_MESSAGE:
      result := Perform(Param1, Param2, Param3);
    EDITOR_COMMAND_COPY_ALL:
      CopyAll;
    EDITOR_COMMAND_FIND:
      result := Find(MakeFindQuery(PChar(Param1), Param2 <> 0, Param3 <> 0,
        Param4 <> 0));
    EDITOR_COMMAND_GET_FIND_COUNT:
      result := FTextFile.FindCount;
    EDITOR_COMMAND_FIND_NEXT:
      result := FindNext;
    EDITOR_COMMAND_FIND_PREV:
      result := FindPrevious;
    EDITOR_COMMAND_FIND_FROM_TOP:
      result := FindFromTop;
    EDITOR_COMMAND_GET_START_OVER:
      result := B(FStartOver);
    EDITOR_COMMAND_SET_START_OVER:
      FStartOver := Param1 <> 0;
    EDITOR_COMMAND_REPLACE_ALL:
      result := ReplaceAll(MakeFindQuery(PChar(Param1), (Param3 and 1) <> 0,
        (Param3 and 2) <> 0, (Param3 and 4) <> 0), PChar(Param2));
    EDITOR_COMMAND_ADD_UNDO_RECORD:
      AddUndoRecord(PChar(Param1), UNDONAMEID(Param2));
    EDITOR_COMMAND_POSTTYPE:
      PostType;
    EDITOR_COMMAND_TYPE_TIMER_EMD:
      TypeTimerEnd;
    EDITOR_COMMAND_TYPE_TIMER_DISABLE:
      FTypeTimer.Enabled := false;
    EDITOR_COMMAND_TYPE_TIMER_DISCONNECT:
      FTypeTimer.OnTimer := nil;
    EDITOR_COMMAND_TYPE_TIMER_CONNECT:
      FTypeTimer.OnTimer := TypeTimerTimer;
    EDITOR_COMMAND_GET_ENABLED:
      result := B(Enabled);
    EDITOR_COMMAND_SET_ENABLED:
      Enabled := Param1 <> 0;
    EDITOR_COMMAND_IS_FOCUSED:
      result := B(Focused);
    EDITOR_COMMAND_TRY_FOCUS:
      begin
        result := B(CanFocus);
        if result <> 0 then
          SetFocus;
      end;
    EDITOR_COMMAND_GET_FIRST_VISIBLE_LINE:
      result := FirstVisibleLine(true);
    EDITOR_COMMAND_GET_LAST_VISIBLE_LINE:
      result := LastVisibleLine(true);
    EDITOR_COMMAND_RECOMPUTE_HOR_EXTENT:
      begin
        RecomputeHorizontalExtent;
        result := FCachedHorizontalExtent;
      end;
    EDITOR_COMMAND_ACTIVATE_CONTROL:
      result := ActivateControl;
    EDITOR_COMMAND_REMOVE_LINE_CONTROL:
      result := B(FTextFile.DeleteControlAtLine(Param1));
    EDITOR_COMMAND_ADD_LINE_CONTROL:
      AddLineControl(TControl(Param1));
    EDITOR_COMMAND_ADD_GRAPHICS:
      AddGraphic(TGraphic(Param1));
    EDITOR_COMMAND_INSERT_LINE_CONTROL:
      InsertLineControl(TControl(Param1), Param2);
    EDITOR_COMMAND_INSERT_GRAPHICS:
      InsertGraphic(TGraphic(Param1), Param2);
    EDITOR_COMMAND_TRIM_RIGHT:
      TrimRight;
    EDITOR_COMMAND_BOOKMARK_SET:
      AddBookmark(Param1);
    EDITOR_COMMAND_BOOKMARK_GO:
      GotoBookmark(Param1);
    EDITOR_COMMAND_BOOKMARK_CLEAR:
      AddBookmark(Param1, EMPTY_BOOKMARK);
    EDITOR_COMMAND_BOOKMARK_CLEAR_ALL:
      ClearBookmarks;
    EDITOR_COMMAND_CLASS_USE:
      SetClass(CaretPos.Y, Classes[Param1].Name);
    EDITOR_COMMAND_CLASS_REMOVE:
      SetClass(CaretPos.Y, '');
    EDITOR_COMMAND_SET_FP:
      SetFormattingProcessor(TFormattingProcessor(Param1));
    EDITOR_COMMAND_EXPORT_HTML:
      ExportToHTML(PChar(Param1));
    EDITOR_COMMAND_OPEN_URL_AT_CARET:
      OpenURLAtCaret;
    EDITOR_COMMAND_SELECT_LINE_INDEX:
      SelectLine(Param1);
    EDITOR_COMMAND_SELECT_LINE_RANGE:
      SelectLines(Param1, Param2);
    EDITOR_COMMAND_DISABLE_SCROLL_TO_CARET:
      FNoScrollToCaret := true;
    EDITOR_COMMAND_ENABLE_SCROLL_TO_CARET:
      FNoScrollToCaret := false;
    EDITOR_COMMAND_CREATE_SELECTION:
      FTextFile.CaretPos.CreateSelection(Point(Param1, Param2),
        Point(Param3, Param4), stLineBased);
    EDITOR_COMMAND_CREATE_BLOCK_SELECTION:
      FTextFile.CaretPos.CreateSelection(Point(Param1, Param2),
        Point(Param3, Param4), stBlock);
    EDITOR_COMMAND_GET_LINE_HIGHLIGHT:
      result := B(FLineHighlight);
    EDITOR_COMMAND_SET_LINE_HIGHLIGHT:
      SetLineHighlight(Param1 <> 0);
    EDITOR_COMMAND_REDRAW_RULER:
      UpdateRuler;
    EDITOR_COMMAND_REDRAW_RULER_LINE:
      UpdateRulerLine(Param1);
    EDITOR_COMMAND_PRINT:
      if Param1 <> 0 then
        Print(PChar(Param1), Param2, Param3)
      else
        Print(Param2, Param3);
    EDITOR_COMMAND_PRINT_SELECTION:
      if Param1 <> 0 then
        PrintSelection(PChar(Param1))
      else
        PrintSelection;
    EDITOR_COMMAND_SET_PRINT_MARGINS:
      begin
        FPrintSettings.HorizontalMargin := Param1;
        FPrintSettings.VerticalMargin := Param2;
      end;
    EDITOR_COMMAND_SET_PRINT_WW_OPTIONS:
      begin
        FPrintSettings.WordWrap := Param1 <> 0;
        FPrintSettings.NiceWordWrap := Param2 <> 0;
        if Param3 = 0 then
          FPrintSettings.ShowWordWrapIcon := false
        else
        begin
          FPrintSettings.ShowWordWrapIcon := true;
          FPrintSettings.WordWrapIcon := Char(Param3);
          FPrintSettings.WordWrapIconColor := TColor(Param4);
        end;
      end;
    EDITOR_COMMAND_PRINT_DIALOG:
      begin
        with TPrintDialog.Create(nil) do
          try
            if FTextFile.HasSelection then
              Options := [poSelection, poWarning]
            else
              Options := [poWarning];
            if Execute then
              if PrintRange = prSelection then
                PrintSelection(SDefaultPrintJobTitle)
              else
                Print(SDefaultPrintJobTitle);
          finally
            Free;
          end;
      end;
    EDITOR_COMMAND_GET_PRINT_VMARGIN:
      result := FPrintSettings.VerticalMargin;
    EDITOR_COMMAND_GET_PRINT_HMARGIN:
      result := FPrintSettings.HorizontalMargin;
    EDITOR_COMMAND_GET_PRINT_WW_OPTIONS:
      result := B(FPrintSettings.WordWrap) or
        (B(FPrintSettings.NiceWordWrap) shl 16);
    EDITOR_COMMAND_GET_PRINT_WW_CHAR:
      if FPrintSettings.ShowWordWrapIcon then
        result := ord(FPrintSettings.WordWrapIcon)
      else
        result := 0;
    EDITOR_COMMAND_GET_PRINT_WW_COLOR:
      result := FPrintSettings.WordWrapIconColor;
    EDITOR_COMMAND_WORDWRAP:
      WordWrap(Param1, Param2 <> 0, Char(Param3));
    EDITOR_COMMAND_UPPER_CASE:
      ChrTransformText(ChrUpperCase, STransformNameUpperCase);
    EDITOR_COMMAND_LOWER_CASE:
      ChrTransformText(ChrLowerCase, STransformNameLowerCase);
    EDITOR_COMMAND_INVERT_CASE:
      ChrTransformText(ChrInvertCase, STransformNameInvertCase);
    EDITOR_COMMAND_SEL_UPPER_CASE:
      ChrTransformSelection(ChrUpperCase, STransformNameUpperCase);
    EDITOR_COMMAND_SEL_LOWER_CASE:
      ChrTransformSelection(ChrLowerCase, STransformNameLowerCase);
    EDITOR_COMMAND_SEL_INVERT_CASE:
      ChrTransformSelection(ChrInvertCase, STransformNameInvertCase);
    EDITOR_COMMAND_CAMEL_CASE:
      TransformText(TxtCamelCase, STransformNameCamelCase);
    EDITOR_COMMAND_SENTENCE_CASE:
      TransformText(TxtSentenceCase, STransformNameSentenceCase);
    EDITOR_COMMAND_SEL_CAMEL_CASE:
      TransformSelection(TxtCamelCase, STransformNameCamelCase);
    EDITOR_COMMAND_SEL_SENTENCE_CASE:
      TransformSelection(TxtSentenceCase, STransformNameSentenceCase);
    EDITOR_COMMAND_SEL_REVERSE:
      TransformSelection(ReverseText, STransformNameReverse);
    EDITOR_COMMAND_ROT13:
      ChrTransformText(ChrRot13, STransformNameRot13);
    EDITOR_COMMAND_SEL_ROT13:
      ChrTransformSelection(ChrRot13, STransformNameRot13);
    EDITOR_COMMAND_CAESAR:
      if (Param1 <> 0) or TMultiInputBox.NumInputBox(GetParentForm(Self), SCaesarNTitle, SCaesarNText, Param1, ord('A') - ord('Z') - 1, ord('Z') - ord('A') + 1) then
        ChrTransformText(ChrCaesar(Param1), Format(STransformNameCaesarN, [Param1]));
    EDITOR_COMMAND_SEL_CAESAR:
      if (Param1 <> 0) or TMultiInputBox.NumInputBox(GetParentForm(Self), SCaesarNTitle, SCaesarNText, Param1, ord('A') - ord('Z') - 1, ord('Z') - ord('A') + 1) then
        ChrTransformSelection(ChrCaesar(Param1), Format(STransformNameCaesarN, [Param1]));
    EDITOR_COMMAND_VIGENERE:
      if Param2 <> 0 then
        TransformText(TxtVigenère(PChar(Param2), Param1 <> 0), STransformNameVigenere)
      else
        if TMultiInputBox.TextInputBox(GetParentForm(Self), SVigenereTitle, SVigenereText, S, ecUpperCase, false, [aoCapitalAZ]) then
          TransformText(TxtVigenère(S, Param1 <> 0), STransformNameVigenere);
    EDITOR_COMMAND_SEL_VIGENERE:
      if Param2 <> 0 then
        TransformSelection(TxtVigenère(PChar(Param2), Param1 <> 0), STransformNameVigenere)
      else
        if TMultiInputBox.TextInputBox(GetParentForm(Self), SVigenereTitle, SVigenereText, S, ecUpperCase, false, [aoCapitalAZ]) then
          TransformSelection(TxtVigenère(S, Param1 <> 0), STransformNameVigenere);
    EDITOR_COMMAND_UPDATE_SCROLL_MODE:
      UpdateScrollMode;
    EDITOR_COMMAND_GET_SCROLL_MODE:
      result := B(FScrollMode);
    EDITOR_COMMAND_SORT:
      result := B(Sort(Param1, Param2));
    EDITOR_COMMAND_SORT_ALL:
      result := B(Sort);
    EDITOR_COMMAND_SORT_SEL:
      result := B(SortSelection);
    EDITOR_COMMAND_SET_LINE_COMPARER:
      SetLineComparer(TLineComparer(Param1));
    EDITOR_COMMAND_GET_LINE_COMPARER:
      result := integer(@LineComparer);
    EDITOR_COMMAND_SET_SORT_REVERSE:
      SortReverseOrder := Param1 <> 0;
    EDITOR_COMMAND_GET_SORT_REVERSE:
      result := B(SortReverseOrder);
    EDITOR_COMMAND_MAKE_LINES_UNIQUE:
      result := B(MakeLinesUnique);
    EDITOR_COMMAND_CLI_NEW_PROMPT:
      CliNewPrompt;
    EDITOR_COMMAND_CLI_WRITELN:
      CliWriteLn(PChar(Param1), PChar(Param2));
    EDITOR_COMMAND_ABORT_SCRIPT:
      FAbortScript := true;
    EDITOR_COMMAND_WRITE_INT:
      SelText := IntToStr(Param1);
    EDITOR_COMMAND_ABORT_SCRIPT_IF_EOL:
      if FTextFile.AtOrBeyondEOL then
        FAbortScript := true;
    EDITOR_COMMAND_ABORT_SCRIPT_IF_LL:
      if FTextFile.AtLastLine then
        FAbortScript := true;
    EDITOR_COMMAND_ABORT_SCRIPT_IF_EOF:
      if FTextFile.AtOrBeyondEOF then
        FAbortScript := true;
    EDITOR_COMMAND_ABORT_SCRIPT_IF_SOF:
      if FTextFile.AtSOF then
        FAbortScript := true;
    EDITOR_COMMAND_SET_SCRIPT_COUNTER:
      FScriptCounter := Param1;
    EDITOR_COMMAND_GET_SCRIPT_COUNTER:
      result := FScriptCounter;
    EDITOR_COMMAND_GET_LINE_NUMBER_1:
      result := FTextFile.CaretPos.Y + 1;
    EDITOR_COMMAND_GET_COL_NUMBER_1:
      result := FTextFile.CaretPos.X + 1;
    EDITOR_COMMAND_WRITE_DATE:
      SelText := DateToStr(Date);
    EDITOR_COMMAND_WRITE_TIME:
      SelText := TimeToStr(Time);
    EDITOR_COMMAND_WRITE_DATETIME:
      SelText := DateTimeToStr(Now);
    EDITOR_COMMAND_GET_TICKCOUNT:
      result := GetTickCount;
    EDITOR_COMMAND_GET_RANDOM_INTEGER:
      if Param2 > Param1 then
        result := RandomRange(Param1, Param2)
      else
        result := RandomRange(0, MaxInt);
    EDITOR_COMMAND_FIX_REMOVED_LINE_CONTROLS:
      FixRemovedLineControlLines;
    EDITOR_COMMAND_CLI_HISTORY_UP:
      result := B(CliHistoryUp);
    EDITOR_COMMAND_CLI_HISTORY_DOWN:
      result := B(CliHistoryDown);
    EDITOR_COMMAND_CLI_HISTORY_CLEAR:
      CliClearHistory;
    EDITOR_COMMAND_CLI_HISTORY_ADD:
      CliAddHistory(PChar(Param1));
    EDITOR_COMMAND_CLI_GET_HISTORY_LENGTH:
      result := CliHistoryCount;
    EDITOR_COMMAND_CLI_GET_HISTORY_INDEX:
      result := CliHistoryIndex;
    EDITOR_COMMAND_CLI_HISTORY_RECALL:
      result := B(CliHistoryRecall(Param1));
    EDITOR_COMMAND_BEGIN_ADD_LINES:
      BeginAddLine;
    EDITOR_COMMAND_END_ADD_LINES:
      EndAddLine;
    EDITOR_COMMAND_GET_LISTBOX_MODE:
      result := B(ListBoxMode);
    EDITOR_COMMAND_SET_LISTBOX_MODE:
      ListBoxMode := Param1 <> 0;
    EDITOR_COMMAND_WRITE_STRING:
      SelText := PChar(Param1);
    EDITOR_COMMAND_WRITE_INPUT_DIALOG:
      begin
        S := PChar(Param3);
        if TMultiInputBox.TextInputBox(GetParentForm(Self), PChar(Param1), PChar(Param2), S) then
          SelText := S;
      end;
    EDITOR_COMMAND_SET_AS_HYPHEN_ASTERISK_TOGGLE:
      FASHyphenAsteriskToggle := Param1 <> 0;
    EDITOR_COMMAND_SET_MULTI_CHAR_SELECT:
      MultiCharSelect := Param1 <> 0;
    EDITOR_COMMAND_GET_MULTI_CHAR_SELECT:
      result := B(FMultiCharSelect);
    EDITOR_COMMAND_SET_MULTI_CHAR_REPORT_VIEW:
      MultiCharReportView := Param1 <> 0;
    EDITOR_COMMAND_GET_MULTI_CHAR_REPORT_VIEW:
      result := B(FMultiCharReportView);
    EDITOR_COMMAND_SET_NO_VERIFY_FONT:
      FNoVerifyFont := Param1 <> 0;
    EDITOR_COMMAND_SET_DOUBLE_BUFFERING:
      DoubleBuffered := Param1 <> 0;
    EDITOR_COMMAND_REPLACE_ALL_IN_SELECTION:
      result := ReplaceAll(MakeFindQuery(PChar(Param1), (Param3 and 1) <> 0,
        (Param3 and 2) <> 0, (Param3 and 4) <> 0), PChar(Param2), true);
    EDITOR_COMMAND_SET_BITMAP_EFFECT:
      if InRange(Param1, ord(low(TBitmapEffect)), ord(high(TBitmapEffect))) then
      begin
        result := 1;
        BitmapEffect := TBitmapEffect(Param1);
      end
      else
        result := 0;
    EDITOR_COMMAND_GET_BITMAP_EFFECT:
      result := ord(FBitmapEffect);
    EDITOR_COMMAND_SET_DISABLED_EFFECT:
      if InRange(Param1, ord(low(TBitmapEffect)), ord(high(TBitmapEffect))) then
      begin
        result := 1;
        DisabledEffect := TBitmapEffect(Param1);
      end
      else
        result := 0;
    EDITOR_COMMAND_GET_DISABLED_EFFECT:
      result := ord(FDisabledEffect);
    EDITOR_COMMAND_REPEAT:
      for i := 1 to trunc(Param1) do
        EditorCommand(Param2, Param3, Param4);
    EDITOR_COMMAND_REPEAT_EX_SET_NUM:
      FRepeatExNum := trunc(Param1);
    EDITOR_COMMAND_REPEAT_EX_SET_COMMAND:
      FRepeatExCommand := trunc(Param1);
    EDITOR_COMMAND_REPEAT_EX:
      for i := 1 to FRepeatExNum do
        EditorCommand(FRepeatExCommand, Param1, Param2, Param3, Param4);
    EDITOR_COMMAND_RESTORE_MARGINS:
      RestoreAllMargins;
    EDITOR_COMMAND_FILL_WITH_CHAR:
      result := B(FillWithChar(char(Param1)));
    EDITOR_COMMAND_PASTE_AS_BLOCK:
      result := B(PasteFromClipboardAsBlock);
    EDITOR_COMMAND_TRUNCATE_AT:
      TruncateAt(Param1, Param2, Param3, Char(Word(Param4)), Param4 and $FFFF0000 <> 0);
    EDITOR_COMMAND_TRUNCATE_AT_IN_FILE:
      TruncateAt(Param1, Char(Word(Param2)), Param3 <> 0);
    EDITOR_COMMAND_TRUNCATE_AT_IN_SELECTION:
      TruncateAtInSelection(Param1, Char(Word(Param2)), Param3 <> 0);
    EDITOR_COMMAND_GET_JUST_OPENED:
      result := B(FTextFile.RecentlyOpened);
    EDITOR_COMMAND_LOAD_DEFAULT_CLASSES:
      LoadDefaultClasses;
    EDITOR_COMMAND_BEGIN_VISUAL_UPDATE:
      BeginVisualUpdate;
    EDITOR_COMMAND_END_VISUAL_UPDATE:
      EndVisualUpdate(Param1 <> 0);
    EDITOR_COMMAND_SURROUND_SEL:
      SurroundText(PChar(Param1), PChar(Param2));
    EDITOR_COMMAND_FILTER_LINES:
      if Param1 = SizeOf(TFilterOptions) then
      begin
        Filter(PFilterOptions(Param2)^);
        result := 1;
      end
      else
        result := 0;
    EDITOR_COMMAND_UPDATE_SPI:
      UpdateSPI;
    EDITOR_COMMAND_SET_STRICT_READONLY:
      FTextFile.StrictReadOnly := Param1 <> 0;
    EDITOR_COMMAND_REMOVE_GHOST_BOOKMARKS:
      result := B(FTextFile.RemoveGhostBookmarks);
    EDITOR_COMMAND_CHARACTER_FIND:
      result := Find(MakeFindQuery(Param1));
  end;
end;

procedure TTextEditor.EndAddLine;
begin
  FTextFile.EndAddLine;
end;

procedure TTextEditor.AbortScript;
begin
  FAbortScript := true;
end;

function TTextEditor.ActivateControl: HWND;
var
  ctl: TControl;
begin
  result := 0;
  ctl := GetControlFromLine(CaretPos.Y);
  if Assigned(ctl) and (ctl is TWinControl) then
  begin
    TWinControl(ctl).SetFocus;
    result := TWinControl(ctl).Handle;
  end
  else if Assigned(ctl) and (ctl is TImage) and Assigned(TImage(ctl).PopupMenu) then
  begin
    TImage(ctl).PopupMenu.PopupComponent := TImage(ctl);
    with ClientToScreen(Point(0, GetLineTop(CaretPos.Y))) do
      TImage(ctl).PopupMenu.Popup(X, Y);
    result := Handle;
  end;
end;

procedure TTextEditor.Escape(AAll: boolean);
begin
  if FMultipleCarets then
  begin
    FMultipleCarets := false;
    SetLength(FCarets, 0);
    Invalidate;
    RemoveNotification(EN_MULTICARET);
    if not AAll then Exit;
  end;
  if BalloonVisible then
  begin
    HideBalloon;
    if not AAll then Exit;
  end;
  if (EditMode = emConsole) and FTextFile.AtLastLine and not AAll then
    ClearLine;
end;

procedure TTextEditor.ExportToHTML(const FileName: TFileName);

  function Q(const S: string): string;
  begin
    if Pos(#32, S) > 0 then
      result := '"' + S + '"'
    else
      result := S;
  end;

  function MakeClassName(const S: string): string;
  var
    i: Integer;
  begin
    result := '';
    for i := 1 to Length(S) do
      if S[i].IsLetterOrDigit then
        result := result + S[i];
  end;

  function HtmlEscape(const C: char): string;
  begin
    if C = '<' then
      result := '&lt;'
    else if C = '>' then
      result := '&gt;'
    else if C = '&' then
      result := '&amp;'
    else
      result := C;
  end;

var
  SL: TStringList;
  DocumentName, FPName: string;
  Indent: string;
  rules: TCSSRules;
  oldc, c: Integer;
  i: Integer;
  j: Integer;
  L: string;
begin

  Indent := DupeString(#32, FIndentSize);
  DocumentName := FTextFile.FileName;
  if DocumentName = '' then
    DocumentName := SDefaultFileName;
  if Assigned(FFormattingProcessor) then
    FPName := FFormattingProcessor.ClassName
  else
    FPName := SNoInteractiveFormattingParen;

  SL := TStringList.Create;
  try
    SL.Add('<!DOCTYPE html>');
    SL.Add('');
    SL.Add('<html xmlns="http://www.w3.org/1999/xhtml">');
    SL.Add('<head>');
    SL.Add('');
    SL.Add('<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />');
    SL.Add('<title>' + ExtractFileName(DocumentName) + '</title>');
    SL.Add('');
    SL.Add('<style>');
    SL.Add('main {');
    SL.Add(Indent + 'background-color: ' + CSSColor(FBackgroundColor) + ';');
    SL.Add(Indent + 'color: ' + CSSColor(FForegroundColor) + ';');
    SL.Add('}');
    SL.Add('pre {');
    SL.Add(Indent + 'font-family: ' + Q(FFont.Name) + ';');
    SL.Add(Indent + 'font-size: ' + IntToStr(FFont.Size) + 'pt;');
    SL.Add('}');

    if (Length(FClassArray) > 0) and FTextFile.UseLineClasses then
    begin
      SL.Add('');
      for i := 0 to high(FClassArray) do
      begin
        SL.Add('.' + MakeClassName(FClassArray[i].Name) + ' {');
        SL.Add(Indent + 'font-size: ' + IntToStr(FClassArray[i].Format.Size) + 'pt;');
        SL.Add(Indent + 'color: ' + CSSColor(FClassArray[i].Format.Color) + ';');
        if fsBold in FClassArray[i].Format.Style then
          SL.Add(Indent + 'font-weight: ' + 'bold;');
        if fsItalic in FClassArray[i].Format.Style then
          SL.Add(Indent + 'font-style: ' + 'italic;');
        if fsUnderline in FClassArray[i].Format.Style then
          SL.Add(Indent + 'text-decoration: ' + 'underline;');
        SL.Add('}');
      end;
    end;

    if Assigned(FFormattingProcessor) then
    begin
      SL.Add('');
      rules := FFormattingProcessor.GetCSSRules;
      for i := low(rules) to high(rules) do
      begin
        SL.Add('.' + rules[i].Selector + ' {');
        for j := low(rules[i].Declarations) to high(rules[i].Declarations) do
          SL.Add(Indent + rules[i].Declarations[j].CSSProperty + ': ' + rules[i].Declarations[j].Value + ';');
        SL.Add('}');
      end;
    end;

    SL.Add('</style>');
    SL.Add('');
    SL.Add('</head>');
    SL.Add('');
    SL.Add('<body>');
    SL.Add('');
    SL.Add('<header>');
    SL.Add('');
    SL.Add(Indent + '<h1>' + ExtractFileName(DocumentName) + '</h1>');
    SL.Add('');
    SL.Add(Indent + '<dl>');
    SL.Add(Indent + Indent + '<dt>' + SHTMLExportFileName + '</dt>');
    SL.Add(Indent + Indent + '<dd>' + DocumentName + '</dd>');
    SL.Add(Indent + Indent + '<dt>' + SHTMLExportDate + '</dt>');
    SL.Add(Indent + Indent + '<dd><time>' + FormatDateTime('yyyy"-"mm"-"dd', Date) + '</time></dd>');
    SL.Add(Indent + Indent + '<dt>' + SHTMLExportTime + '</dt>');
    SL.Add(Indent + Indent + '<dd><time>' + FormatDateTime('hh":"mm":"ss', Time) + '</time></dd>');
    SL.Add(Indent + Indent + '<dt>' + SHTMLExportFP + '</dt>');
    SL.Add(Indent + Indent + '<dd>' + FPName + '</dd>');
    SL.Add(Indent + '</dl>');
    SL.Add('');
    SL.Add('</header>');
    SL.Add('');
    SL.Add('<main>');
    SL.Add('');
    SL.Add(Indent + '<pre>');
    oldc := -1;
    c := -1;
    for i := 0 to FTextFile.LineCount - 1 do
    begin
      L := '';
      if LineClasses[i] <> '' then
        L := '<span class="' + MakeClassName(LineClasses[i]) + '">';
      if c <> -1 then
        L := L + '<span class="' + rules[c].Selector + '">';

      for j := 0 to FTextFile.PhysicalLineWidths[i] - 1 do
      begin
        if Assigned(FFormattingProcessor) then
          c := FFormattingProcessor.GetCharCSSClass(i, j, FTextFile.Character[i, j]);
        if c <> oldc then
        begin
          if oldc <> -1 then
            L := L + '</span>';
          if c shr 16 = 0 then
            L := L + '<span class="' + rules[c].Selector + '">'
          else
            L := L + '<span class="' + rules[c and $FFFF].Selector + ' ' + rules[c shr 16].Selector + '">'
        end;
        L := L + HtmlEscape(FTextFile.Character[i, j]);
        oldc := c;
      end;
      if c <> -1 then
        L := L + '</span>';
      if LineClasses[i] <> '' then
        L := L + '</span>';
      SL.Add(L);
    end;
    SL.Add('</pre>');
    SL.Add('');
    SL.Add('</main>');
    SL.Add('');
    SL.Add('</body>');
    SL.Add('</html>');
    SL.SaveToFile(FileName, TEncoding.UTF8);
  finally
    SL.Free;
  end;

end;

function TTextEditor.CharInSet(AChar: char; ASet: array of char): boolean;
var
  i: Integer;
begin
  result := false;
  for i := low(ASet) to high(ASet) do
    if AChar = ASet[i] then
      Exit(true);
end;

function TTextEditor.CharInAnyMultiCharSet(AChar: char): boolean;
begin
  result := CharInSet(AChar, MultiCharHyphen) or CharInSet(AChar, MultiCharAsterisk) or
    CharInSet(AChar, MultiCharDoubleQuote) or CharInSet(AChar, MultiCharSingleQuote);
end;

procedure TTextEditor.KeyDown(var Key: Word; Shift: TShiftState);
var
  c: char;
begin
  inherited;

  UpdateScrollMode;

  // Cursor
  if Key in [VK_SHIFT, VK_CONTROL, VK_MENU, VK_SCROLL] then
    ChangeCursor(Shift);

  if (EditMode = emConsole) and (Key = VK_F7) then
  begin
    CliHistoryDialogSelect;
    Exit;
  end;

  if (Key = VK_F9) and FMultiCharSelect then
  begin
    c := GetCharBeforeCaret;
    if CharInSet(c, MultiCharHyphen) then
      DoMultiCharSelect(MultiCharHyphen)
    else if CharInSet(c, MultiCharAsterisk) then
      DoMultiCharSelect(MultiCharAsterisk)
    else if CharInSet(c, MultiCharDoubleQuote) then
      DoMultiCharSelect(MultiCharDoubleQuote)
    else if CharInSet(c, MultiCharSingleQuote) then
      DoMultiCharSelect(MultiCharSingleQuote);
    Exit;
  end;

  // Special case: scroll mode
  if FScrollMode then
  begin
    case Key of
      VK_UP:
        SetScrollPosY(FScrollPos.Y - IfThen(ssCtrl in Shift, 1, FFontSize.cy));
      VK_DOWN:
        SetScrollPosY(FScrollPos.Y + IfThen(ssCtrl in Shift, 1, FFontSize.cy));
      VK_LEFT:
        SetScrollPosX(FScrollPos.X - IfThen(ssCtrl in Shift, 1, FFontSize.cx));
      VK_RIGHT:
        SetScrollPosX(FScrollPos.X + IfThen(ssCtrl in Shift, 1, FFontSize.cx));
      VK_PRIOR:
        SetScrollPosY(FScrollPos.Y - ClientHeight);
      VK_NEXT:
        SetScrollPosY(FScrollPos.Y + ClientHeight);
      VK_HOME:
        SetScrollPosY(0);
      VK_END:
        SetScrollPosY(FTextFile.LineCount * FFontSize.cy - ClientHeight);
    end;
    Exit;
  end;

  if [ssShift, ssCtrl] <= Shift then
    case Key of
      VK_UP:
        begin
          SwapLinesAbove;
          Exit;
        end;
      VK_DOWN:
        begin
          SwapLinesBelow;
          Exit;
        end;
      ord('1')..ord('9'):
        if FHandleBookmarkHotkeys then
        begin
          AddBookmark(Key - ord('0'));
          Exit;
        end;
    end;

  if (ssCtrl in Shift) and (FTextFile.EditMode = emText) then
    case Key of
      VK_UP:
        begin
          with GetLastMultiCaret do
            if Y > 0 then
              CreateNewCaretAt(Point(X, Y - 1));
          Exit;
        end;
      VK_DOWN:
        begin
          with GetLastMultiCaret do
            if Y < LineCount - 1 then
              CreateNewCaretAt(Point(X, Y + 1));
          Exit;
        end;
    end;

  if FHandleBookmarkHotkeys and (Shift = [ssCtrl]) and
    (Key in [ord('1')..ord('9')]) then
  begin
    GotoBookmark(ord(key) - ord('0'));
    Exit;
  end;

  case Key of
    VK_BACK:
      Backspace(ssCtrl in Shift);
    VK_DELETE:
      begin
        Delete(ssCtrl in Shift);
        if FMatchBrackets then
          TextFileCaretPosChange(Self);
      end;
    VK_LEFT:
      FTextFile.Left(ssCtrl in Shift, ssShift in Shift, ssAlt in Shift);
    VK_RIGHT:
      FTextFile.Right(ssCtrl in Shift, ssShift in Shift, ssAlt in Shift);
    VK_UP:
      if (FTextFile.EditMode = emConsole) and FTextFile.AtLastLine and not (ssCtrl in Shift) then
        CliHistoryUp
      else if FMultiSize and not (ssAlt in Shift) then
        GotoSamePixelAtPrevLine(ssShift in Shift)
      else
        FTextFile.Up(ssShift in Shift, ssAlt in Shift);
    VK_DOWN:
      if (FTextFile.EditMode = emConsole) and FTextFile.AtLastLine and not (ssCtrl in Shift) then
        CliHistoryDown
      else if FMultiSize and not (ssAlt in Shift) then
        GotoSamePixelAtNextLine(ssShift in Shift)
      else
        FTextFile.Down(ssShift in Shift, ssAlt in Shift);
    VK_RETURN:
      begin
        if FAutoReplace then DoAutoReplace;
        Return;
      end;
    VK_HOME:
      FTextFile.Home(ssCtrl in Shift, ssShift in Shift);
    VK_END:
      FTextFile.KEnd(ssCtrl in Shift, ssShift in Shift);
    VK_PRIOR:
      PageUp(ssShift in Shift);
    VK_NEXT:
      PageDown(ssShift in Shift);
    VK_INSERT:
        SetOverwrite(not FOverwrite);
    VK_ESCAPE:
      Escape;
    VK_PAUSE:
      case EditMode of
        emText:
          EditMode := emReadOnly;
        emConsole: ;
        emReadOnly:
          EditMode := emText;
      end;
  end;

  if FHandleHotkeys then
  begin
    if [ssShift, ssCtrl] <= Shift then
      case Key of
        ord('A'):
          begin
            if not FTextFile.SelectWord then
              TextFileInputError(Self);
            Exit;
          end;
      end;
    if Shift = [ssCtrl] then
      case Key of
        ord('Z'):
          Undo;
        ord('Y'):
          Redo;
        ord('X'):
          CutToClipboard;
        ord('C'):