{-------------------------------------------------------------------------------
The contents of this file are subject to the Mozilla Public License
Version 1.1 (the "License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.mozilla.org/MPL/

Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
the specific language governing rights and limitations under the License.

The Original Code is: SynEditMiscClasses.pas, released 2000-04-07.
The Original Code is based on the mwSupportClasses.pas file from the
mwEdit component suite by Martin Waldenburg and other developers, the Initial
Author of this file is Michael Hieke.
All Rights Reserved.

Contributors to the SynEdit and mwEdit projects are listed in the
Contributors.txt file.

Alternatively, the contents of this file may be used under the terms of the
GNU General Public License Version 2 or later (the "GPL"), in which case
the provisions of the GPL are applicable instead of those above.
If you wish to allow use of your version of this file only under the terms
of the GPL and not to allow others to use your version of this file
under the MPL, indicate your decision by deleting the provisions above and
replace them with the notice and other provisions required by the GPL.
If you do not delete the provisions above, a recipient may use your version
of this file under either the MPL or the GPL.

$Id$

You may retrieve the latest version of this file at the SynEdit home page,
located at http://SynEdit.SourceForge.net

Known Issues:
-------------------------------------------------------------------------------}

unit SynEditMiscClasses;

{$I synedit.inc}
{$INLINE off}

interface

uses
  Classes, SysUtils,
  // LazUtils
  LazMethodList, LazUtilities, LazLoggerBase,
  // LCL
  LCLIntf, LCLType, Graphics, Controls, Clipbrd, ImgList,
  // SynEdit
  SynEditHighlighter, SynEditMiscProcs, SynEditTypes, LazSynEditText, SynEditPointClasses, SynEditMouseCmds,
  SynEditTextBase;

const
  SYNEDIT_DEFAULT_MOUSE_OPTIONS = [];

  // MouseAction related options MUST NOT be included here
  SYNEDIT_DEFAULT_OPTIONS = [
    eoAutoIndent,
    eoScrollPastEol,
    eoSmartTabs,
    eoTabsToSpaces,
    eoTrimTrailingSpaces,
    eoGroupUndo,
    eoBracketHighlight
  ];

  SYNEDIT_DEFAULT_OPTIONS2 = [
    eoFoldedCopyPaste,
    eoOverwriteBlock,
    eoAcceptDragDropEditing
  ];

  // Those will be prevented from being set => so evtl they may be removed
  SYNEDIT_UNIMPLEMENTED_OPTIONS = [
    eoAutoSizeMaxScrollWidth,  //TODO Automatically resizes the MaxScrollWidth property when inserting text
    eoDisableScrollArrows,     //TODO Disables the scroll bar arrow buttons when you can't scroll in that direction any more
    eoDropFiles,               //TODO Allows the editor accept file drops
    eoHideShowScrollbars,      //TODO if enabled, then the scrollbars will only show when necessary.  If you have ScrollPastEOL, then it the horizontal bar will always be there (it uses MaxLength instead)
    eoSmartTabDelete,          //TODO similar to Smart Tabs, but when you delete characters
    ////eoSpecialLineDefaultFg,    //TODO disables the foreground text color override when using the OnSpecialLineColor event
    eoAutoIndentOnPaste,       // Indent text inserted from clipboard
    eoSpacesToTabs             // Converts space characters to tabs and spaces
  ];

type

  TSynUndoRedoItemEvent = function (Caller: TObject; Item: TSynEditUndoItem): Boolean of object;

  { TSynWordBreaker }

  TSynWordBreaker = class
  private
    FIdentChars: TSynIdentChars;
    FWhiteChars: TSynIdentChars;
    FWordBreakChars: TSynIdentChars;
    FWordChars: TSynIdentChars;
    procedure SetIdentChars(const AValue: TSynIdentChars);
    procedure SetWhiteChars(const AValue: TSynIdentChars);
    procedure SetWordBreakChars(const AValue: TSynIdentChars);
  public
    constructor Create;
    procedure Reset;

    // aX is the position between the chars (as in CaretX)
    // 1 is in front of the first char
    function IsInWord     (aLine: String; aX: Integer
                           ): Boolean;  // Includes at word boundary
    function IsAtWordStart(aLine: String; aX: Integer): Boolean;
    function IsAtWordEnd  (aLine: String; aX: Integer): Boolean;
    function NextWordStart(aLine: String; aX: Integer;
                           aIncludeCurrent: Boolean = False): Integer;
    function NextWordEnd  (aLine: String; aX: Integer;
                           aIncludeCurrent: Boolean = False): Integer;
    function PrevWordStart(aLine: String; aX: Integer;
                           aIncludeCurrent: Boolean = False): Integer;
    function PrevWordEnd  (aLine: String; aX: Integer;
                           aIncludeCurrent: Boolean = False): Integer;

    function NextBoundary (aLine: String; aX: Integer;
                           aIncludeCurrent: Boolean = False): Integer;
    function PrevBoundary (aLine: String; aX: Integer;
                           aIncludeCurrent: Boolean = False): Integer;

    property IdentChars: TSynIdentChars read FIdentChars write SetIdentChars;
    property WordChars: TSynIdentChars read FWordChars;
    property WordBreakChars: TSynIdentChars read FWordBreakChars write SetWordBreakChars;
    property WhiteChars: TSynIdentChars read FWhiteChars write SetWhiteChars;
  end;

  TLazSynSurface = class;
  TSynSelectedColor = class;
  TSynBookMarkOpt = class;

  { TSynEditBase }

  TSynEditBase = class(TCustomControl)
  private
    FMouseOptions: TSynEditorMouseOptions;
    fReadOnly: Boolean;
    fHideSelection: boolean;
    fBookMarkOpt: TSynBookMarkOpt;
    fExtraCharSpacing: integer;
    fExtraLineSpacing: integer;
    procedure BookMarkOptionsChanged(Sender: TObject);
    procedure SetHideSelection(Value: boolean);
  protected
    FWordBreaker: TSynWordBreaker;
    FBlockSelection: TSynEditSelection;
    FScreenCaret: TSynEditScreenCaret;
    FOptions: TSynEditorOptions;
    FOptions2: TSynEditorOptions2;
    function GetMarkupMgr: TObject; virtual; abstract;
    function GetLines: TStrings; virtual; abstract;
    function GetCanRedo: boolean; virtual; abstract;
    function GetCanUndo: boolean; virtual; abstract;
    function GetCaretObj: TSynEditCaret; virtual; abstract;
    function GetModified: Boolean; virtual; abstract;
    function GetReadOnly: boolean; virtual;
    function GetIsBackwardSel: Boolean;
    function GetHighlighterObj: TObject; virtual; abstract;
    function GetMarksObj: TObject; virtual; abstract;
    function GetSelText: string;
    function GetSelAvail: Boolean;
    function GetSelectedColor: TSynSelectedColor; virtual; abstract;
    function GetTextViewsManager: TSynTextViewsManager; virtual; abstract;
    procedure SetLines(Value: TStrings); virtual; abstract;
    function GetViewedTextBuffer: TSynEditStringsLinked; virtual; abstract;
    function GetFoldedTextBuffer: TObject; virtual; abstract;
    function GetTextBuffer: TSynEditStrings; virtual; abstract;
    function GetPaintArea: TLazSynSurface; virtual; abstract; // TLazSynSurfaceManager
    procedure SetModified(Value: boolean); virtual; abstract;
    procedure SetMouseOptions(AValue: TSynEditorMouseOptions); virtual;
    procedure SetReadOnly(Value: boolean); virtual;
    procedure StatusChanged(AChanges: TSynStatusChanges); virtual; abstract;
    procedure SetOptions(AOptions: TSynEditorOptions); virtual; abstract;
    procedure SetOptions2(AOptions2: TSynEditorOptions2); virtual; abstract;
    procedure SetSelectedColor(const aSelectedColor: TSynSelectedColor); virtual; abstract;

    function GetCharsInWindow: Integer; virtual; abstract;
    function GetCharWidth: integer; virtual; abstract;
    function GetLeftChar: Integer; virtual; abstract;
    function GetLineHeight: integer; virtual; abstract;
    function GetLinesInWindow: Integer; virtual; abstract;
    function GetTopLine: Integer; virtual; abstract;
    procedure SetLeftChar(Value: Integer); virtual; abstract;
    procedure SetTopLine(Value: Integer); virtual; abstract;

    function GetBlockBegin: TPoint; virtual; abstract;
    function GetBlockEnd: TPoint; virtual; abstract;
    function GetSelEnd: Integer; virtual; abstract;
    function GetSelStart: Integer; virtual; abstract;
    procedure SetBlockBegin(Value: TPoint); virtual; abstract;
    procedure SetBlockEnd(Value: TPoint); virtual; abstract;
    procedure SetSelEnd(const Value: Integer); virtual; abstract;
    procedure SetSelStart(const Value: Integer); virtual; abstract;
    procedure SetSelTextExternal(const Value: string); virtual; abstract;

    function GetMouseActions: TSynEditMouseActions; virtual; abstract;
    function GetMouseSelActions: TSynEditMouseActions; virtual; abstract;
    function GetMouseTextActions: TSynEditMouseActions; virtual; abstract;
    procedure SetMouseActions(const AValue: TSynEditMouseActions); virtual; abstract;
    procedure SetMouseSelActions(const AValue: TSynEditMouseActions); virtual; abstract;
    procedure SetMouseTextActions(AValue: TSynEditMouseActions); virtual; abstract;

    procedure SetExtraCharSpacing(const AValue: integer); virtual;
    procedure SetExtraLineSpacing(const AValue: integer); virtual;

    function GetCaretX : Integer; virtual; abstract;
    function GetCaretY : Integer; virtual; abstract;
    function GetCaretXY: TPoint; virtual; abstract;
    procedure SetCaretX(const Value: Integer); virtual; abstract;
    procedure SetCaretY(const Value: Integer); virtual; abstract;
    procedure SetCaretXY(Value: TPoint); virtual; abstract;
    function GetLogicalCaretXY: TPoint; virtual; abstract;
    procedure SetLogicalCaretXY(const NewLogCaretXY: TPoint); virtual; abstract;

    property MarkupMgr: TObject read GetMarkupMgr;
    property FoldedTextBuffer: TObject read GetFoldedTextBuffer;                // TSynEditFoldedView
    property ViewedTextBuffer: TSynEditStringsLinked read GetViewedTextBuffer;        // As viewed internally (with uncommited spaces / TODO: expanded tabs, folds). This may change, use with care
    property TextBuffer: TSynEditStrings read GetTextBuffer;                    // (TSynEditStringList) No uncommited (trailing/trimmable) spaces
    property WordBreaker: TSynWordBreaker read FWordBreaker;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;

    function FindGutterFromGutterPartList(const APartList: TObject): TObject; virtual; abstract;
  public
    // Caret
    function CaretXPix: Integer; virtual; abstract;
    function CaretYPix: Integer; virtual; abstract;

    function ScreenRowToRow(ScreenRow: integer; LimitToLines: Boolean = True): integer; virtual; abstract; deprecated 'use ScreenXYToTextXY';
    function RowToScreenRow(PhysicalRow: integer): integer; virtual; abstract; deprecated 'use TextXYToScreenXY';
    (* ScreenXY:
       First visible (scrolled in) screen line is 1
       First column is 1 => column does not take scrolling into account
    *)
    function ScreenXYToTextXY(AScreenXY: TPhysPoint; LimitToLines: Boolean = True): TPhysPoint; virtual; abstract;
    function TextXYToScreenXY(APhysTextXY: TPhysPoint): TPhysPoint; virtual; abstract;

    procedure GetWordBoundsAtRowCol(const XY: TPoint; out StartX, EndX: integer); virtual; abstract;
    function GetWordAtRowCol(XY: TPoint): string; virtual; abstract;

    // Cursor
    procedure UpdateCursorOverride; virtual; abstract;
  public
    // Undo Redo
    procedure BeginUndoBlock{$IFDEF SynUndoDebugBeginEnd}(ACaller: String = ''){$ENDIF}; virtual; abstract;
    procedure BeginUpdate(WithUndoBlock: Boolean = True); virtual; abstract;
    procedure EndUndoBlock{$IFDEF SynUndoDebugBeginEnd}(ACaller: String = ''){$ENDIF}; virtual; abstract;
    procedure EndUpdate; virtual; abstract;

    procedure ClearUndo; virtual; abstract;
    procedure Redo; virtual; abstract;
    procedure Undo; virtual; abstract;
    property CanRedo: boolean read GetCanRedo;
    property CanUndo: boolean read GetCanUndo;
  public
    // matching brackets
    procedure FindMatchingBracket; virtual; abstract;
    function FindMatchingBracket(PhysStartBracket: TPoint;
                                 StartIncludeNeighborChars, MoveCaret,
                                 SelectBrackets, OnlyVisible: Boolean
                                ): TPoint; virtual; abstract; // Returns Physical
    function FindMatchingBracketLogical(LogicalStartBracket: TPoint;
                                        StartIncludeNeighborChars, MoveCaret,
                                        SelectBrackets, OnlyVisible: Boolean
                                       ): TPoint; virtual; abstract; // Returns Logical
  public
    // handlers
    procedure RegisterCommandHandler(AHandlerProc: THookedCommandEvent;
      AHandlerData: pointer; AFlags: THookedCommandFlags = [hcfPreExec, hcfPostExec]); virtual; abstract;
    procedure UnregisterCommandHandler(AHandlerProc: THookedCommandEvent); virtual; abstract;

    procedure RegisterMouseActionSearchHandler(AHandlerProc: TSynEditMouseActionSearchProc); virtual; abstract;
    procedure UnregisterMouseActionSearchHandler(AHandlerProc: TSynEditMouseActionSearchProc); virtual; abstract;
    procedure RegisterMouseActionExecHandler(AHandlerProc: TSynEditMouseActionExecProc); virtual; abstract;
    procedure UnregisterMouseActionExecHandler(AHandlerProc: TSynEditMouseActionExecProc); virtual; abstract;

    procedure RegisterKeyTranslationHandler(AHandlerProc: THookedKeyTranslationEvent); virtual; abstract;
    procedure UnRegisterKeyTranslationHandler(AHandlerProc: THookedKeyTranslationEvent); virtual; abstract;

    procedure RegisterUndoRedoItemHandler(AHandlerProc: TSynUndoRedoItemEvent); virtual; abstract;
    procedure UnRegisterUndoRedoItemHandler(AHandlerProc: TSynUndoRedoItemEvent); virtual; abstract;

    procedure RegisterStatusChangedHandler(AStatusChangeProc: TStatusChangeEvent; AChanges: TSynStatusChanges); virtual; abstract;
    procedure UnRegisterStatusChangedHandler(AStatusChangeProc: TStatusChangeEvent); virtual; abstract;

    procedure RegisterBeforeMouseDownHandler(AHandlerProc: TMouseEvent); virtual; abstract;
    procedure UnregisterBeforeMouseDownHandler(AHandlerProc: TMouseEvent); virtual; abstract;

    procedure RegisterQueryMouseCursorHandler(AHandlerProc: TSynQueryMouseCursorEvent); virtual; abstract;
    procedure UnregisterQueryMouseCursorHandler(AHandlerProc: TSynQueryMouseCursorEvent); virtual; abstract;

    procedure RegisterBeforeKeyDownHandler(AHandlerProc: TKeyEvent); virtual; abstract;
    procedure UnregisterBeforeKeyDownHandler(AHandlerProc: TKeyEvent); virtual; abstract;
    procedure RegisterBeforeKeyUpHandler(AHandlerProc: TKeyEvent); virtual; abstract;
    procedure UnregisterBeforeKeyUpHandler(AHandlerProc: TKeyEvent); virtual; abstract;
    procedure RegisterBeforeKeyPressHandler(AHandlerProc: TKeyPressEvent); virtual; abstract;
    procedure UnregisterBeforeKeyPressHandler(AHandlerProc: TKeyPressEvent); virtual; abstract;
    procedure RegisterBeforeUtf8KeyPressHandler(AHandlerProc: TUTF8KeyPressEvent); virtual; abstract;
    procedure UnregisterBeforeUtf8KeyPressHandler(AHandlerProc: TUTF8KeyPressEvent); virtual; abstract;

    procedure RegisterPaintEventHandler(APaintEventProc: TSynPaintEventProc; AnEvents: TSynPaintEvents); virtual; abstract;
    procedure UnRegisterPaintEventHandler(APaintEventProc: TSynPaintEventProc); virtual; abstract;
    procedure RegisterScrollEventHandler(AScrollEventProc: TSynScrollEventProc; AnEvents: TSynScrollEvents); virtual; abstract;
    procedure UnRegisterScrollEventHandler(AScrollEventProc: TSynScrollEventProc); virtual; abstract;

  public
    function IsLinkable(Y, X1, X2: Integer): Boolean; virtual; abstract;
    // invalidate lines
    procedure InvalidateGutter; virtual; abstract;
    procedure InvalidateLine(Line: integer); virtual; abstract;
    procedure InvalidateGutterLines(FirstLine, LastLine: integer); virtual; abstract; // Currently invalidates full line => that may change
    procedure InvalidateLines(FirstLine, LastLine: integer); virtual; abstract;

    // text / lines
    function GetLineState(ALine: Integer): TSynLineState; virtual; abstract;
  public
    // Byte to Char
    function LogicalToPhysicalPos(const p: TPoint): TPoint; virtual; abstract;
    function LogicalToPhysicalCol(const Line: String; Index, LogicalPos
                              : integer): integer; virtual; abstract;
    // Char to Byte
    function PhysicalToLogicalPos(const p: TPoint): TPoint; virtual; abstract;
    function PhysicalToLogicalCol(const Line: string;
                                  Index, PhysicalPos: integer): integer; virtual; abstract;
    function PhysicalLineLength(Line: String; Index: integer): integer; virtual; abstract;
  public
    property BookMarkOptions: TSynBookMarkOpt read fBookMarkOpt write fBookMarkOpt; // ToDo: check "write fBookMarkOpt"
    property ExtraCharSpacing: integer read fExtraCharSpacing write SetExtraCharSpacing default 0;
    property ExtraLineSpacing: integer read fExtraLineSpacing write SetExtraLineSpacing default 0;
    property Lines: TStrings read GetLines write SetLines;
    // See SYNEDIT_UNIMPLEMENTED_OPTIONS for deprecated Values
    property Options: TSynEditorOptions read FOptions write SetOptions default SYNEDIT_DEFAULT_OPTIONS;
    property Options2: TSynEditorOptions2 read FOptions2 write SetOptions2 default SYNEDIT_DEFAULT_OPTIONS2;
    property ReadOnly: Boolean read GetReadOnly write SetReadOnly default FALSE;
    property Modified: Boolean read GetModified write SetModified;

    property CaretX: Integer read GetCaretX write SetCaretX;
    property CaretY: Integer read GetCaretY write SetCaretY;
    property CaretXY: TPoint read GetCaretXY write SetCaretXY;// screen position
    property LogicalCaretXY: TPoint read GetLogicalCaretXY write SetLogicalCaretXY;

    property CharsInWindow: Integer read GetCharsInWindow;
    property CharWidth: integer read GetCharWidth;
    property LeftChar: Integer read GetLeftChar write SetLeftChar;
    property LineHeight: integer read GetLineHeight;
    property LinesInWindow: Integer read GetLinesInWindow;
    property TopLine: Integer read GetTopLine write SetTopLine;

    property BlockBegin: TPoint read GetBlockBegin write SetBlockBegin;         // Set Blockbegin. For none persistent also sets Blockend. Setting Caret may undo this and should be done before setting block
    property BlockEnd: TPoint read GetBlockEnd write SetBlockEnd;
    property SelStart: Integer read GetSelStart write SetSelStart;              // 1-based byte pos of first selected char
    property SelEnd: Integer read GetSelEnd write SetSelEnd;                    // 1-based byte pos of first char after selction end
    property IsBackwardSel: Boolean read GetIsBackwardSel;
    property SelText: string read GetSelText write SetSelTextExternal;

    property MouseActions: TSynEditMouseActions read GetMouseActions write SetMouseActions;
    // Mouseactions, if mouse is over selection => fallback to normal
    property MouseSelActions: TSynEditMouseActions read GetMouseSelActions write SetMouseSelActions;
    property MouseTextActions: TSynEditMouseActions read GetMouseTextActions write SetMouseTextActions;
    property MouseOptions: TSynEditorMouseOptions read FMouseOptions write SetMouseOptions
      default SYNEDIT_DEFAULT_MOUSE_OPTIONS;

    property TextViewsManager: TSynTextViewsManager read GetTextViewsManager; experimental; // Only use to Add/remove views

    property SelectedColor: TSynSelectedColor read GetSelectedColor write SetSelectedColor;
    property SelAvail: Boolean read GetSelAvail;
    property HideSelection: boolean read fHideSelection write SetHideSelection default false;

    property Highlighter: TObject read GetHighlighterObj;
    property Marks: TObject read GetMarksObj;
  end;

  { TSynEditFriend }
  // TODO: Redesign

  TSynEditFriend = class(TComponent)
  private
    FFriendEdit: TSynEditBase;
    function GetCaretObj: TSynEditCaret;
    function GetFoldedTextBuffer: TObject;
    function GetIsRedoing: Boolean;
    function GetIsUndoing: Boolean;
    function GetMarkupMgr: TObject;
    function GetPaintArea: TLazSynSurface; // TLazSynSurfaceManager
    function GetScreenCaret: TSynEditScreenCaret;
    function GetSelectionObj: TSynEditSelection;
    function GetTextBuffer: TSynEditStrings;
    function GetViewedTextBuffer: TSynEditStringsLinked;
    function GetWordBreaker: TSynWordBreaker;
  protected
    property FriendEdit: TSynEditBase read FFriendEdit write FFriendEdit;
    property FoldedTextBuffer: TObject read GetFoldedTextBuffer;                // TSynEditFoldedView
    property ViewedTextBuffer: TSynEditStringsLinked read GetViewedTextBuffer;        // As viewed internally (with uncommited spaces / TODO: expanded tabs, folds). This may change, use with care
    property TextBuffer: TSynEditStrings read GetTextBuffer;                    // (TSynEditStringList)
    property CaretObj: TSynEditCaret read GetCaretObj;
    property ScreenCaret: TSynEditScreenCaret read GetScreenCaret; // TODO: should not be exposed
    property SelectionObj: TSynEditSelection read GetSelectionObj;
    property PaintArea: TLazSynSurface read GetPaintArea; // TLazSynSurfaceManager
    property MarkupMgr: TObject read GetMarkupMgr;
    property IsUndoing: Boolean read GetIsUndoing;
    property IsRedoing: Boolean read GetIsRedoing;
    property WordBreaker: TSynWordBreaker read GetWordBreaker;
  end;


  TSynObjectListItem = class;

  { TSynObjectList }

  TSynObjectList = class(TComponent)
  private
    FList: TList;
    FOnChange: TNotifyEvent;
    FOwner: TComponent;
    FSorted: Boolean;
    function GetBasePart(Index: Integer): TSynObjectListItem;
    procedure PutBasePart(Index: Integer; const AValue: TSynObjectListItem);
    procedure SetSorted(const AValue: Boolean);
  protected
    function GetChildOwner: TComponent; override;
    procedure GetChildren(Proc: TGetChildProc; Root: TComponent); override;
    procedure SetChildOrder(Child: TComponent; Order: Integer); override;
    procedure RegisterItem(AnItem: TSynObjectListItem); virtual;
    procedure DoChange(Sender: TObject); virtual;
    property List: TList read FList;
  public
    constructor Create(AOwner: TComponent); override;
    destructor  Destroy; override;
    procedure Assign(Source: TPersistent); override;
    Function  Add(AnItem: TSynObjectListItem): Integer;
    Procedure Delete(Index: Integer);
    Procedure Clear;
    Function  Count: Integer;
    Function  IndexOf(AnItem: TSynObjectListItem): Integer;
    Procedure Move(AOld, ANew: Integer);
    procedure Sort;
    property Sorted: Boolean read FSorted write SetSorted;
    property Owner: TComponent read FOwner;
    property BaseItems[Index: Integer]: TSynObjectListItem
      read GetBasePart write PutBasePart; default;
    property OnChange: TNotifyEvent read FOnChange write FOnChange;
  end;

  { TSynObjectListItem }

  TSynObjectListItem = class(TSynEditFriend)
  private
    FOwner: TSynObjectList;
    function GetIndex: Integer;
    procedure SetIndex(AValue: Integer);
  protected
    function Compare(Other: TSynObjectListItem): Integer; virtual;
    function GetDisplayName: String; virtual;
    property Owner: TSynObjectList read FOwner;
    // Use Init to setup things that are needed before Owner.RegisterItem (bur require Owner to be set)
    procedure Init; virtual;
  public
    constructor Create(AOwner: TComponent); override;
    destructor  Destroy; override;
    property Index: Integer read GetIndex write SetIndex;
    property DisplayName: String read GetDisplayName;
    function GetParentComponent: TComponent; override; // for child order in stream reading
  end;

  TSynObjectListItemClass = class of TSynObjectListItem;

  TLazSynDisplayTokenBound = record
    Physical: Integer;      // 1 based - May be in middle of char
    Logical: Integer;       // 1 based
    Offset: Integer;        // default 0. MultiWidth (e.g. Tab), if token starts in the middle of char
  end;

  { TSynSelectedColor }

  TSynSelectedColor = class(TSynHighlighterAttributesModifier)
  private
    // 0 or -1 start/end before/after line // 1 first char
    FStartX, FEndX: TLazSynDisplayTokenBound;
  protected
    procedure DoClear; override;
    procedure AssignFrom(Src: TLazSynCustomTextAttributes); override;
    procedure Init; override;
  public
    // boundaries of the frame
    procedure SetFrameBoundsPhys(AStart, AEnd: Integer);
    procedure SetFrameBoundsLog(AStart, AEnd: Integer; AStartOffs: Integer = 0; AEndOffs: Integer = 0);
    property StartX: TLazSynDisplayTokenBound read FStartX write FStartX;
    property EndX: TLazSynDisplayTokenBound read FEndX write FEndX;
  public
    function GetModifiedStyle(aStyle: TFontStyles): TFontStyles; // deprecated;
    procedure ModifyColors(var AForeground, ABackground, AFrameColor: TColor;
      var AStyle: TFontStyles; var AFrameStyle: TSynLineStyle); deprecated;
  end;

  TSynSelectedColorAlphaEntry = record
    Color: TColor;
    Alpha: Integer;
    Priority: Integer
  end;

  TSynSelectedColorMergeInfo = record
    BaseColor: TColor;
    BasePriority: Integer;
    AlphaCount: Integer;
    AlphaStack: Array of TSynSelectedColorAlphaEntry;
  end;

  TSynSelectedColorEnum = (
    sscBack, sscFore, sscFrameLeft, sscFrameRight, sscFrameTop, sscFrameBottom
  );

  { TSynSelectedColorMergeResult }

  TSynSelectedColorMergeResult = class(TSynSelectedColor)
  private
    // TSynSelectedColor.Style and StyleMask describe how to modify a style,
    // but PaintLines creates an instance that contains an actual style (without mask)
    MergeFinalStyle: Boolean; // always true
    FMergeInfoInitialized: Boolean;

    FCurrentEndX: TLazSynDisplayTokenBound;
    FCurrentStartX: TLazSynDisplayTokenBound;
    FFrameSidesInitialized: Boolean;
    FFrameSideColors: array[TLazSynBorderSide] of TColor;
    FFrameSideStyles: array[TLazSynBorderSide] of TSynLineStyle;
    FFrameSidePriority: array[TLazSynBorderSide] of Integer;
    FFrameSideOrigin: array[TLazSynBorderSide] of TSynFrameEdges;

    FMergeInfos: array [TSynSelectedColorEnum] of TSynSelectedColorMergeInfo;

    function IsMatching(ABound1, ABound2: TLazSynDisplayTokenBound): Boolean;
    function GetFrameSideColors(Side: TLazSynBorderSide): TColor;
    function GetFrameSideOrigin(Side: TLazSynBorderSide): TSynFrameEdges;
    function GetFrameSidePriority(Side: TLazSynBorderSide): integer;
    function GetFrameSideStyles(Side: TLazSynBorderSide): TSynLineStyle;
    procedure SetCurrentEndX(AValue: TLazSynDisplayTokenBound);
    procedure SetCurrentStartX(AValue: TLazSynDisplayTokenBound);
  protected
    procedure AssignFrom(Src: TLazSynCustomTextAttributes); override;
    procedure DoClear; override;
    procedure Init; override;

    procedure MaybeInitFrameSides;
    procedure MergeToInfo(var AnInfo: TSynSelectedColorMergeInfo;
      AColor: TColor; APriority, AnAlpha: Integer);
    function  CalculateInfo(var AnInfo: TSynSelectedColorMergeInfo;
              ANoneColor: TColor; IsFrame: Boolean = False): TColor;
    property FrameSidePriority[Side: TLazSynBorderSide]: integer read GetFrameSidePriority;
    property FrameSideOrigin[Side: TLazSynBorderSide]: TSynFrameEdges read GetFrameSideOrigin;
  public
    destructor Destroy; override;

    property FrameSideColors[Side: TLazSynBorderSide]: TColor read GetFrameSideColors;
    property FrameSideStyles[Side: TLazSynBorderSide]: TSynLineStyle read GetFrameSideStyles;
    // boundaries for current paint
    property CurrentStartX: TLazSynDisplayTokenBound read FCurrentStartX write SetCurrentStartX;
    property CurrentEndX: TLazSynDisplayTokenBound read FCurrentEndX write SetCurrentEndX;
  public
    procedure InitMergeInfo;    // (called automatically) Set all MergeInfo to the start values. After this was called, ay Changes to the color properties are ignored
    procedure ProcessMergeInfo; // copy the merge result, to the actual color properties
    procedure CleanupMergeInfo; // free the alpha arrays
    procedure Merge(Other: TSynHighlighterAttributesModifier);
    procedure Merge(Other: TSynHighlighterAttributesModifier; LeftCol, RightCol: TLazSynDisplayTokenBound);
    procedure MergeFrames(Other: TSynHighlighterAttributesModifier; LeftCol, RightCol: TLazSynDisplayTokenBound);
  end;

  { TLazSynSurface }

  TLazSynSurface = class
  private
    FBounds: TRect;
    FBoundsChangeList: TMethodList;
    FDisplayView: TLazSynDisplayView;
    FOwner: TWinControl;
    function GetHandle: HWND;
    procedure SetDisplayView(AValue: TLazSynDisplayView);
  protected
    procedure BoundsChanged; virtual;
    procedure DoPaint(ACanvas: TCanvas; AClip: TRect); virtual; abstract;
    procedure DoDisplayViewChanged; virtual;
    property  Handle: HWND read GetHandle;
  public
    constructor Create(AOwner: TWinControl);
    destructor Destroy; override;
    procedure Assign(Src: TLazSynSurface); virtual;
    procedure AddBoundsChangeHandler(AHandler: TNotifyEvent);
    procedure RemoveBoundsChangeHandler(AHandler: TNotifyEvent);

    procedure Paint(ACanvas: TCanvas; AClip: TRect);
    procedure InvalidateLines(FirstTextLine, LastTextLine: TLineIdx; AScreenLineOffset: Integer = 0); virtual;
    procedure SetBounds(ATop, ALeft, ABottom, ARight: Integer);

    property Left: Integer   read FBounds.Left;
    property Top: Integer    read FBounds.Top;
    property Right:Integer   read FBounds.Right;
    property Bottom: integer read FBounds.Bottom;
    property Bounds: TRect read FBounds;

    property DisplayView:   TLazSynDisplayView    read FDisplayView   write SetDisplayView;
  end;

  { TSynBookMarkOpt }

  TSynBookMarkOpt = class(TPersistent)
  private
    fBookmarkImages: TCustomImageList;
    fDrawBookmarksFirst: boolean;                                               //mh 2000-10-12
    fEnableKeys: Boolean;
    fGlyphsVisible: Boolean;
    fLeftMargin: Integer;
    fOwner: TComponent;
    fXoffset: integer;
    fOnChange: TNotifyEvent;
    procedure SetBookmarkImages(const Value: TCustomImageList);
    procedure SetDrawBookmarksFirst(Value: boolean);                            //mh 2000-10-12
    procedure SetGlyphsVisible(Value: Boolean);
    procedure SetLeftMargin(Value: Integer);
    procedure SetXOffset(Value: integer);
  public
    constructor Create(AOwner: TComponent);
  published
    property BookmarkImages: TCustomImageList
      read fBookmarkImages write SetBookmarkImages;
    property DrawBookmarksFirst: boolean read fDrawBookmarksFirst               //mh 2000-10-12
      write SetDrawBookmarksFirst default True;
    property EnableKeys: Boolean
      read fEnableKeys write fEnableKeys default True;
    property GlyphsVisible: Boolean
      read fGlyphsVisible write SetGlyphsVisible default True;
    property LeftMargin: Integer read fLeftMargin write SetLeftMargin default 2;
    property Xoffset: integer read fXoffset write SetXOffset default 12;
    property OnChange: TNotifyEvent read fOnChange write fOnChange;
  end;

  { TSynInternalImage }

  TSynInternalImage = class(TObject)
  public
    constructor Create(const AName: string; Count: integer);
    destructor Destroy; override;
    procedure DrawMark(ACanvas: TCanvas; Number, X, Y, LineHeight: integer);
  end;


  { TSynEditSearchCustom }

  TSynEditSearchCustom = class(TComponent)
  protected
    function GetPattern: string; virtual; abstract;
    procedure SetPattern(const Value: string); virtual; abstract;
    function GetLength(aIndex: integer): integer; virtual; abstract;
    function GetResult(aIndex: integer): integer; virtual; abstract;
    function GetResultCount: integer; virtual; abstract;
    procedure SetOptions(const Value: TSynSearchOptions); virtual; abstract;
  public
    function FindAll(const NewText: string): integer; virtual; abstract;
    property Pattern: string read GetPattern write SetPattern;
    property ResultCount: integer read GetResultCount;
    property Results[aIndex: integer]: integer read GetResult;
    property Lengths[aIndex: integer]: integer read GetLength;
    property Options: TSynSearchOptions write SetOptions;
  end;

  {$IFDEF FPC_REQUIRES_PROPER_ALIGNMENT}
  TSynClipboardStreamTag = type integer;
  {$ELSE }
  TSynClipboardStreamTag = type word;
  {$ENDIF}

  { TSynClipboardStream }

  TSynClipboardStream = class
  private
    FMemStream: TMemoryStream;
    FText: String;
    FTextP: PChar;
    FIsPlainText: Boolean;
    FColumnModeFlag: Boolean;

    function GetMemory: Pointer;
    function GetSize: LongInt;
    function GetSelectionMode: TSynSelectionMode;
    procedure SetSelectionMode(const AValue: TSynSelectionMode);
    procedure SetInternalText(const AValue: String);
    procedure SetText(const AValue: String);
  public
    constructor Create;
    destructor Destroy; override;
    class function ClipboardFormatId: TClipboardFormat;
    class function ClipboardFormatMSDEVColumnSelect: TClipboardFormat;
    class function ClipboardFormatBorlandIDEBlockType: TClipboardFormat;

    function CanReadFromClipboard(AClipboard: TClipboard): Boolean;
    function ReadFromClipboard(AClipboard: TClipboard): Boolean;
    function WriteToClipboard(AClipboard: TClipboard): Boolean;

    procedure Clear;

    function HasTag(ATag: TSynClipboardStreamTag): Boolean;
    function GetTagPointer(ATag: TSynClipboardStreamTag): Pointer;
    function GetTagLen(ATag: TSynClipboardStreamTag): Integer;
    // No check for duplicates
    Procedure AddTag(ATag: TSynClipboardStreamTag; Location: Pointer; Len: Integer);
    property IsPlainText: Boolean read FIsPlainText;

    // Currently Each method (or each method of a pair) must be assigned only ONCE
    property TextP: PChar read FTextP;
    property Text: String write SetText;
    property InternalText: String write SetInternalText;

    property SelectionMode: TSynSelectionMode read GetSelectionMode write SetSelectionMode;

    property Memory: Pointer read GetMemory;
    property Size: LongInt read GetSize;
  end;

  { TSynMethodList }

  TSynMethodList = Class(TMethodList)
  private
    function IndexToObjectIndex(const AnObject: TObject; AnIndex: Integer): integer;
    function GetObjectItems(AnObject: TObject; Index: integer): TMethod;
    procedure SetObjectItems(AnObject: TObject; Index: integer; const AValue: TMethod);
  public
    function CountByObject(const AnObject: TObject): integer;
    procedure DeleteByObject(const AnObject: TObject; Index: integer);
    procedure AddCopyFrom(AList: TSynMethodList; AOwner: TObject = nil);
  public
    property ItemsByObject[AnObject: TObject; Index: integer]: TMethod
      read GetObjectItems write SetObjectItems; default;
  end;

  TSynFilteredMethodListEntry = record
    FHandler: TMethod;
    FFilter: LongInt;
  end;

  { TSynFilteredMethodList }

  TSynFilteredMethodList = Class
  private
    FCount: Integer;
  protected
    FItems: Array of TSynFilteredMethodListEntry;
    function IndexOf(AHandler: TMethod): Integer;
    function IndexOf(AHandler: TMethod; AFilter: LongInt): Integer;
    function NextDownIndex(var Index: integer): boolean;
    function NextDownIndexNumFilter(var Index: integer; AFilter: LongInt): boolean;
    function NextDownIndexBitFilter(var Index: integer; AFilter: LongInt): boolean;
    procedure Delete(AIndex: Integer);
  public
    constructor Create;
    procedure AddNumFilter(AHandler: TMethod; AFilter: LongInt);                         // Separate entries for same method with diff filter
    procedure AddBitFilter(AHandler: TMethod; AFilter: LongInt);                    // Filter is bitmask
    procedure Remove(AHandler: TMethod);
    procedure Remove(AHandler: TMethod; AFilter: LongInt);
    procedure CallNotifyEventsNumFilter(Sender: TObject; AFilter: LongInt);
    procedure CallNotifyEventsBitFilter(Sender: TObject; AFilter: LongInt);         // filter is Bitmask
    property Count: Integer read FCount;
  end;

const
  synClipTagText = TSynClipboardStreamTag(1);
  synClipTagExtText = TSynClipboardStreamTag(2);
  synClipTagMode = TSynClipboardStreamTag(3);
  synClipTagFold = TSynClipboardStreamTag(4);


type

  TReplacedChildSite = (rplcLeft, rplcRight);

  { TSynSizedDifferentialAVLNode }

  TSynSizedDifferentialAVLNode = Class
  private
    procedure SetLeftSizeSum(AValue: Integer);
  protected
    (* AVL Tree structure *)
    FParent, FLeft, FRight : TSynSizedDifferentialAVLNode;    (* AVL Links *)
    FBalance : shortint;                                    (* AVL Balance *)

    (* Position:  stores difference to parent value
    *)
    FPositionOffset: Integer;

    (* Size:  Each node can have a Size, or similar value.
              LeftSizeSum is the Sum of all sizes on the Left. This allows one to quickly
              calculate the sum of all preceding nodes together
    *)
    FSize: Integer;
    FLeftSizeSum: Integer;

    property LeftSizeSum: Integer read FLeftSizeSum write SetLeftSizeSum;
    {$IFDEF SynDebug}
    function Debug: String; virtual;
    {$ENDIF}
  public
    function TreeDepth: integer;           (* longest WAY down. Only one node => 1! *)

    procedure SetLeftChild(ANode : TSynSizedDifferentialAVLNode); overload; inline;
    procedure SetLeftChild(ANode : TSynSizedDifferentialAVLNode;
                           anAdjustChildPosOffset : Integer); overload; inline;
    procedure SetLeftChild(ANode : TSynSizedDifferentialAVLNode;
                           anAdjustChildPosOffset,
                           aLeftSizeSum : Integer); overload; inline;

    procedure SetRightChild(ANode : TSynSizedDifferentialAVLNode); overload; inline;
    procedure SetRightChild(ANode : TSynSizedDifferentialAVLNode;
                            anAdjustChildPosOffset : Integer); overload; inline;

    function ReplaceChild(OldNode, ANode : TSynSizedDifferentialAVLNode) : TReplacedChildSite; overload; inline;
    function ReplaceChild(OldNode, ANode : TSynSizedDifferentialAVLNode;
                          anAdjustChildPosOffset : Integer) : TReplacedChildSite; overload; inline;

    procedure AdjustLeftCount(AValue : Integer);
    procedure AdjustParentLeftCount(AValue : Integer);
    procedure AdjustPosition(AValue : Integer); // Must not change order with prev/next node

    function Precessor: TSynSizedDifferentialAVLNode;
    function Successor: TSynSizedDifferentialAVLNode;
    function Precessor(var aStartPosition, aSizesBeforeSum : Integer): TSynSizedDifferentialAVLNode;
    function Successor(var aStartPosition, aSizesBeforeSum : Integer): TSynSizedDifferentialAVLNode;

    function GetSizesBeforeSum: Integer;
    function GetPosition: Integer;
  end;

  TSynSizedDiffAVLFindMode = (afmNil, afmCreate, afmPrev, afmNext);

  { TSynSizedDifferentialAVLTree }

  TSynSizedDifferentialAVLTree = class
  protected
    FRoot: TSynSizedDifferentialAVLNode;
    FRootOffset : Integer; // Always 0, unless subclassed with nested trees

    // SetRoot, does not obbey fRootOffset => use SetRoot(node, -fRootOffset)
    procedure SetRoot(ANode : TSynSizedDifferentialAVLNode); virtual; overload;
    procedure SetRoot(ANode : TSynSizedDifferentialAVLNode; anAdjustChildPosOffset : Integer); virtual; overload;

    procedure DisposeNode(var ANode: TSynSizedDifferentialAVLNode); virtual;

    function  InsertNode(ANode : TSynSizedDifferentialAVLNode) : Integer; // returns FoldedBefore // ANode may not have children
    procedure RemoveNode(ANode: TSynSizedDifferentialAVLNode); // Does not Free
    procedure BalanceAfterInsert(ANode: TSynSizedDifferentialAVLNode);
    procedure BalanceAfterDelete(ANode: TSynSizedDifferentialAVLNode);

    function CreateNode(APosition: Integer): TSynSizedDifferentialAVLNode; virtual;
  public
    constructor Create;
    destructor  Destroy; override;
    {$IFDEF SynDebug}
    procedure   Debug;
    {$ENDIF}

    procedure Clear; virtual;
    function First: TSynSizedDifferentialAVLNode;
    function Last: TSynSizedDifferentialAVLNode;
    function First(out aStartPosition, aSizesBeforeSum : Integer): TSynSizedDifferentialAVLNode;
    function Last(out aStartPosition, aSizesBeforeSum : Integer): TSynSizedDifferentialAVLNode;

    function FindNodeAtLeftSize(ALeftSum: INteger;
                                out aStartPosition, aSizesBeforeSum : Integer): TSynSizedDifferentialAVLNode;
    function FindNodeAtPosition(APosition: INteger; AMode: TSynSizedDiffAVLFindMode;
                                out aStartPosition, aSizesBeforeSum : Integer): TSynSizedDifferentialAVLNode;
    procedure AdjustForLinesInserted(AStartLine, ALineCount : Integer);
    procedure AdjustForLinesDeleted(AStartLine, ALineCount : Integer);
  end;


implementation

{ TSynEditBase }

constructor TSynEditBase.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);

  FMouseOptions := SYNEDIT_DEFAULT_MOUSE_OPTIONS;
  fBookMarkOpt := TSynBookMarkOpt.Create(Self);
  fBookMarkOpt.OnChange := @BookMarkOptionsChanged;
end;

procedure TSynEditBase.BookMarkOptionsChanged(Sender: TObject);
begin
  InvalidateGutter;
end;

destructor TSynEditBase.Destroy;
begin
  FreeAndNil(fBookMarkOpt);

  inherited Destroy;
end;

function TSynEditBase.GetReadOnly: boolean;
begin
  Result := fReadOnly;
end;

function TSynEditBase.GetSelAvail: Boolean;
begin
  Result := FBlockSelection.SelAvail;
end;

function TSynEditBase.GetIsBackwardSel: Boolean;
begin
  Result := FBlockSelection.SelAvail and FBlockSelection.IsBackwardSel;
end;

function TSynEditBase.GetSelText: string;
begin
  Result := FBlockSelection.SelText;
end;

procedure TSynEditBase.SetExtraCharSpacing(const AValue: integer);
begin
  fExtraCharSpacing := AValue;
end;

procedure TSynEditBase.SetExtraLineSpacing(const AValue: integer);
begin
  fExtraLineSpacing := AValue;
end;

procedure TSynEditBase.SetHideSelection(Value: boolean);
begin
  if fHideSelection <> Value then begin
    FHideSelection := Value;
    Invalidate;
  end;
end;

procedure TSynEditBase.SetMouseOptions(AValue: TSynEditorMouseOptions);
begin
  if FMouseOptions = AValue then Exit;
  FMouseOptions := AValue;
end;

procedure TSynEditBase.SetReadOnly(Value: boolean);
begin
  if fReadOnly <> Value then begin
    fReadOnly := Value;
    StatusChanged([scReadOnly]);
  end;
end;

{ TSynEditFriend }

function TSynEditFriend.GetViewedTextBuffer: TSynEditStringsLinked;
begin
  Result := FFriendEdit.ViewedTextBuffer;
end;

function TSynEditFriend.GetWordBreaker: TSynWordBreaker;
begin
  Result := FFriendEdit.WordBreaker;
end;

function TSynEditFriend.GetMarkupMgr: TObject;
begin
  Result := FFriendEdit.MarkupMgr;
end;

function TSynEditFriend.GetPaintArea: TLazSynSurface;
begin
  Result := FFriendEdit.GetPaintArea;
end;

function TSynEditFriend.GetScreenCaret: TSynEditScreenCaret;
begin
  Result := FFriendEdit.FScreenCaret;
end;

function TSynEditFriend.GetSelectionObj: TSynEditSelection;
begin
  Result := FFriendEdit.FBlockSelection;
end;

function TSynEditFriend.GetTextBuffer: TSynEditStrings;
begin
  Result := FFriendEdit.TextBuffer;
end;

function TSynEditFriend.GetIsRedoing: Boolean;
begin
  Result := FFriendEdit.ViewedTextBuffer.IsRedoing;
end;

function TSynEditFriend.GetCaretObj: TSynEditCaret;
begin
  Result := FFriendEdit.GetCaretObj;
end;

function TSynEditFriend.GetFoldedTextBuffer: TObject;
begin
  Result := FFriendEdit.FoldedTextBuffer;
end;

function TSynEditFriend.GetIsUndoing: Boolean;
begin
  Result := FFriendEdit.ViewedTextBuffer.IsUndoing;
end;

{ TSynSelectedColorMergeResult }

function TSynSelectedColorMergeResult.IsMatching(ABound1,
  ABound2: TLazSynDisplayTokenBound): Boolean;
begin
  Result := ( (ABound1.Physical > 0) and
              (ABound1.Physical = ABound2.Physical)
            ) or
            ( (ABound1.Logical > 0) and
              (ABound1.Logical = ABound2.Logical) and (ABound1.Offset = ABound2.Offset)
            );
end;

function TSynSelectedColorMergeResult.GetFrameSideColors(Side: TLazSynBorderSide): TColor;
begin
  if FFrameSidesInitialized then begin
    Result := FFrameSideColors[Side];
    exit
  end;

  if (FCurrentStartX.Logical >= 0) or (FCurrentStartX.Physical >= 0) then
    case Side of
      bsLeft:  if not IsMatching(FCurrentStartX, FStartX) then exit(clNone);
      bsRight: if not IsMatching(FCurrentEndX,   FEndX)   then exit(clNone);
    end;

  if (Side in SynFrameEdgeToSides[FrameEdges])
  then Result := FrameColor
  else Result := clNone;
end;

function TSynSelectedColorMergeResult.GetFrameSideOrigin(Side: TLazSynBorderSide): TSynFrameEdges;
begin
  if FFrameSidesInitialized
  then Result := FFrameSideOrigin[Side]
  else if FrameColor = clNone
  then Result := sfeNone
  else Result := FrameEdges;
end;

function TSynSelectedColorMergeResult.GetFrameSidePriority(Side: TLazSynBorderSide): integer;
begin
  if FFrameSidesInitialized then begin
    Result := FFrameSidePriority[Side];
    exit
  end;

  if (FCurrentStartX.Logical >= 0) or (FCurrentStartX.Physical >= 0) then
    case Side of
      bsLeft:  if not IsMatching(FCurrentStartX, FStartX) then exit(0);
      bsRight: if not IsMatching(FCurrentEndX,   FEndX)   then exit(0);
    end;

  if (Side in SynFrameEdgeToSides[FrameEdges])
  then Result := FramePriority
  else Result := 0;
end;

function TSynSelectedColorMergeResult.GetFrameSideStyles(Side: TLazSynBorderSide): TSynLineStyle;
begin
  if FFrameSidesInitialized
  then Result := FFrameSideStyles[Side]
  else
  if Side in SynFrameEdgeToSides[FrameEdges]
  then Result := FrameStyle
  else Result := slsSolid;
end;

procedure TSynSelectedColorMergeResult.SetCurrentEndX(AValue: TLazSynDisplayTokenBound);
begin
  //if FCurrentEndX = AValue then Exit;
  FCurrentEndX := AValue;
  if not IsMatching(FCurrentEndX, FEndX) then begin
    FFrameSideColors[bsRight] := clNone;
    FMergeInfos[sscFrameRight].BaseColor := clNone;
    FMergeInfos[sscFrameRight].AlphaCount := 0;
  end;
end;

procedure TSynSelectedColorMergeResult.SetCurrentStartX(AValue: TLazSynDisplayTokenBound);
begin
  //if FCurrentStartX = AValue then Exit;
  FCurrentStartX := AValue;
  if not IsMatching(FCurrentStartX, FStartX) then begin
    FFrameSideColors[bsLeft] := clNone;
    FMergeInfos[sscFrameLeft].BaseColor := clNone;
    FMergeInfos[sscFrameLeft].AlphaCount := 0;
  end;
end;

procedure TSynSelectedColorMergeResult.AssignFrom(Src: TLazSynCustomTextAttributes);
var
  i: TLazSynBorderSide;
  j: TSynSelectedColorEnum;
  c: Integer;
begin
  //DoClear;
  FFrameSidesInitialized := False;
  FMergeInfoInitialized := False;
  for i := low(TLazSynBorderSide) to high(TLazSynBorderSide) do begin
    FFrameSideColors[i] := clNone;
    FFrameSideStyles[i] := slsSolid;
    FFrameSideOrigin[i] := sfeNone;
  end;
  FCurrentStartX.Physical := -1;
  FCurrentEndX.Physical   := -1;
  FCurrentStartX.Logical  := -1;
  FCurrentEndX.Logical    := -1;
  FCurrentStartX.Offset   := 0;
  FCurrentEndX.Offset     := 0;

  inherited AssignFrom(Src);

  if not (Src is TSynSelectedColorMergeResult) then
    exit;

  FCurrentStartX := TSynSelectedColorMergeResult(Src).FCurrentStartX;
  FCurrentEndX   := TSynSelectedColorMergeResult(Src).FCurrentEndX;
  FFrameSidesInitialized := TSynSelectedColorMergeResult(Src).FFrameSidesInitialized;

  for i := low(TLazSynBorderSide) to high(TLazSynBorderSide) do begin
    FFrameSideColors[i] := TSynSelectedColorMergeResult(Src).FFrameSideColors[i];
    FFrameSideStyles[i] := TSynSelectedColorMergeResult(Src).FFrameSideStyles[i];
    FFrameSideOrigin[i] := TSynSelectedColorMergeResult(Src).FFrameSideOrigin[i];
    FFrameSidePriority[i] := TSynSelectedColorMergeResult(Src).FFrameSidePriority[i];
  end;

  FMergeInfoInitialized := TSynSelectedColorMergeResult(Src).FMergeInfoInitialized;

  if FMergeInfoInitialized then begin
    for j := low(TSynSelectedColorEnum) to high(TSynSelectedColorEnum) do begin
      FMergeInfos[j].BaseColor    := TSynSelectedColorMergeResult(Src).FMergeInfos[j].BaseColor;
      FMergeInfos[j].BasePriority := TSynSelectedColorMergeResult(Src).FMergeInfos[j].BasePriority;
      c := TSynSelectedColorMergeResult(Src).FMergeInfos[j].AlphaCount;
      FMergeInfos[j].AlphaCount   := c;
      if Length(FMergeInfos[j].AlphaStack) < c then
        SetLength(FMergeInfos[j].AlphaStack, c + 3);
      if c > 0 then
        move(TSynSelectedColorMergeResult(Src).FMergeInfos[j].AlphaStack[0],
             FMergeInfos[j].AlphaStack[0],
             c * SizeOf(TSynSelectedColorAlphaEntry) );
    end;
  end;

  Changed; {TODO: only if really changed}
end;

procedure TSynSelectedColorMergeResult.DoClear;
var
  i: TLazSynBorderSide;
begin
  inherited;
  FFrameSidesInitialized := False;
  for i := low(TLazSynBorderSide) to high(TLazSynBorderSide) do begin
    FFrameSideColors[i] := clNone;
    FFrameSideStyles[i] := slsSolid;
    FFrameSideOrigin[i] := sfeNone;
  end;
  FCurrentStartX.Physical := -1;
  FCurrentEndX.Physical   := -1;
  FCurrentStartX.Logical  := -1;
  FCurrentEndX.Logical    := -1;
  FCurrentStartX.Offset   := 0;
  FCurrentEndX.Offset     := 0;
  CleanupMergeInfo;
end;

procedure TSynSelectedColorMergeResult.Init;
begin
  inherited Init;
  MergeFinalStyle := True;
  FMergeInfoInitialized := False;
end;

procedure TSynSelectedColorMergeResult.MaybeInitFrameSides;
var
  i: TLazSynBorderSide;
begin
  if FFrameSidesInitialized then
    exit;

  for i := low(TLazSynBorderSide) to high(TLazSynBorderSide) do begin
    FFrameSideColors[i]   := FrameSideColors[i];
    FFrameSideStyles[i]   := FrameSideStyles[i];
    FFrameSidePriority[i] := FrameSidePriority[i];
    FFrameSideOrigin[i]   := FrameSideOrigin[i];
  end;
  FFrameSidesInitialized := True;
end;

procedure TSynSelectedColorMergeResult.MergeToInfo(var AnInfo: TSynSelectedColorMergeInfo;
  AColor: TColor; APriority, AnAlpha: Integer);
begin
  if (APriority < AnInfo.BasePriority) or (AColor = clNone) then
    exit;

  if AnAlpha = 0 then begin // solid
    AnInfo.BaseColor := AColor;
    AnInfo.BasePriority := APriority;
  end
  else begin // remember alpha for later
    if Length(AnInfo.AlphaStack) <= AnInfo.AlphaCount then
      SetLength(AnInfo.AlphaStack, AnInfo.AlphaCount + 5);
    AnInfo.AlphaStack[AnInfo.AlphaCount].Color    := AColor;
    AnInfo.AlphaStack[AnInfo.AlphaCount].Alpha    := AnAlpha;
    AnInfo.AlphaStack[AnInfo.AlphaCount].Priority := APriority;
    inc(AnInfo.AlphaCount);
  end;
end;

function TSynSelectedColorMergeResult.CalculateInfo(var AnInfo: TSynSelectedColorMergeInfo;
  ANoneColor: TColor; IsFrame: Boolean): TColor;
var
  i, j, k, c, p, p2: Integer;
  tmp: TSynSelectedColorAlphaEntry;
  C1, C2, C3, M1, M2, M3, Alpha: Integer;
  Col: TColor;
begin
  p := AnInfo.BasePriority;

  c := AnInfo.AlphaCount - 1;
  j := -1;

  for i := 0 to c do begin
    p2 := AnInfo.AlphaStack[i].Priority;
    if (p2 < p) then
      continue;

    inc(j);
    k := j;
    while (k > 0) and (p2 < AnInfo.AlphaStack[k - 1].Priority) do
      dec(k);
    if k = i then
      continue;

    if k < j then begin
      tmp := AnInfo.AlphaStack[i];
      move(AnInfo.AlphaStack[k], AnInfo.AlphaStack[k + 1], (j-k) * sizeof(AnInfo.AlphaStack[0]));
      AnInfo.AlphaStack[k] := tmp;
    end
    else
      AnInfo.AlphaStack[k] := AnInfo.AlphaStack[i];
  end;
  c := j;
  AnInfo.AlphaCount := j;

  Result := AnInfo.BaseColor;
  // The highlighter may have merged, before defaults where set in
  // TLazSynPaintTokenBreaker.GetNextHighlighterTokenFromView / InitSynAttr
  if (Result = clNone) and (not IsFrame) then
    Result := ANoneColor;

  if (c >= 0) then begin
    if (Result = clNone) then
      Result := ANoneColor;
    Result := ColorToRGB(Result);  // no system color.
    C1 := Red(Result);
    C2 := Green(Result);
    C3 := Blue(Result);
    for i := 0 to c do begin
      Col := ColorToRGB(AnInfo.AlphaStack[i].Color);
      Alpha := AnInfo.AlphaStack[i].Alpha;
      M1 := Red(Col);
      M2 := Green(Col);
      M3 := Blue(Col);
      C1 := MinMax(C1 + (M1 - C1) * Alpha div 256, 0, 255);
      C2 := MinMax(C2 + (M2 - C2) * Alpha div 256, 0, 255);
      C3 := MinMax(C3 + (M3 - C3) * Alpha div 256, 0, 255);

    end;
    Result := RGBToColor(C1, C2, C3);
  end;
end;

destructor TSynSelectedColorMergeResult.Destroy;
begin
  CleanupMergeInfo;
  inherited Destroy;
end;

procedure TSynSelectedColorMergeResult.InitMergeInfo;
begin
  MaybeInitFrameSides;

  FMergeInfos[sscBack].AlphaCount   := 0;
  FMergeInfos[sscBack].BaseColor    := Background;
  FMergeInfos[sscBack].BasePriority := BackPriority;

  FMergeInfos[sscFore].AlphaCount   := 0;
  FMergeInfos[sscFore].BaseColor    := Foreground;
  FMergeInfos[sscFore].BasePriority := ForePriority;

  FMergeInfos[sscFrameLeft].AlphaCount   := 0;
  FMergeInfos[sscFrameLeft].BaseColor    := FrameSideColors[bsLeft];
  FMergeInfos[sscFrameLeft].BasePriority := FrameSidePriority[bsLeft];

  FMergeInfos[sscFrameRight].AlphaCount   := 0;
  FMergeInfos[sscFrameRight].BaseColor    := FrameSideColors[bsRight];
  FMergeInfos[sscFrameRight].BasePriority := FrameSidePriority[bsRight];

  FMergeInfos[sscFrameTop].AlphaCount   := 0;
  FMergeInfos[sscFrameTop].BaseColor    := FrameSideColors[bsTop];
  FMergeInfos[sscFrameTop].BasePriority := FrameSidePriority[bsTop];

  FMergeInfos[sscFrameBottom].AlphaCount   := 0;
  FMergeInfos[sscFrameBottom].BaseColor    := FrameSideColors[bsBottom];
  FMergeInfos[sscFrameBottom].BasePriority := FrameSidePriority[bsBottom];

  FMergeInfoInitialized := True;
end;

procedure TSynSelectedColorMergeResult.ProcessMergeInfo;
begin
  if not FMergeInfoInitialized then
    exit;
  BeginUpdate;
  Background := CalculateInfo(FMergeInfos[sscBack], Background);
  Foreground := CalculateInfo(FMergeInfos[sscFore], Foreground);
  // if the frame is clNone, and alpha is aplied, use the background as base
  FFrameSideColors[bsLeft]   := CalculateInfo(FMergeInfos[sscFrameLeft],   Background, True);
  FFrameSideColors[bsRight]  := CalculateInfo(FMergeInfos[sscFrameRight],  Background, True);
  FFrameSideColors[bsTop]    := CalculateInfo(FMergeInfos[sscFrameTop],    Background, True);
  FFrameSideColors[bsBottom] := CalculateInfo(FMergeInfos[sscFrameBottom], Background, True);
  EndUpdate;
  FMergeInfoInitialized := False;
end;

procedure TSynSelectedColorMergeResult.CleanupMergeInfo;
var
  i: TSynSelectedColorEnum;
begin
  for i := low(TSynSelectedColorEnum) to high(TSynSelectedColorEnum) do
    SetLength(FMergeInfos[i].AlphaStack, 0);
  FMergeInfoInitialized := False;
end;

procedure TSynSelectedColorMergeResult.Merge(Other: TSynHighlighterAttributesModifier);
begin
  Merge(Other, FStartX, FEndX); // always merge frame
end;

procedure TSynSelectedColorMergeResult.Merge(Other: TSynHighlighterAttributesModifier; LeftCol,
  RightCol: TLazSynDisplayTokenBound);
var
  sKeep, sSet, sClr, sInv, sInvInv: TFontStyles;
  j: TFontStyle;
begin
  BeginUpdate;
  if not FMergeInfoInitialized then
    InitMergeInfo;

  MergeToInfo(FMergeInfos[sscBack], Other.Background, Other.BackPriority, Other.BackAlpha);
  MergeToInfo(FMergeInfos[sscFore], Other.Foreground, Other.ForePriority, Other.ForeAlpha);

  MergeFrames(Other, LeftCol, RightCol);

  sKeep := [];
  for j := Low(TFontStyle) to High(TFontStyle) do
    if Other.StylePriority[j] < StylePriority[j]
     then sKeep := sKeep + [j];

  sSet := (Other.Style        * Other.StyleMask) - sKeep;
  sClr := (fsNot(Other.Style) * Other.StyleMask) - sKeep;
  sInv := (Other.Style        * fsNot(Other.StyleMask)) - sKeep;

  if MergeFinalStyle then begin
    Style := fsXor(Style, sInv) + sSet - sClr;
  end else begin
    sKeep := fsNot(Other.Style) * fsNot(Other.StyleMask);
    sInvInv := sInv * (Style * fsNot(StyleMask)); // invert * invert = not modified
    sInv    := sInv - sInvInv;
    sSet := sSet + sInv * (fsnot(Style) * StyleMask); // currently not set
    sClr := sClr + sInv * (Style        * StyleMask); // currently set
    sInv    := sInv - StyleMask; // now SInv only inverts currently "not modifying"

    Style     := (Style     * sKeep) + sSet - sClr - sInvInv + sInv;
    StyleMask := (StyleMask * sKeep) + sSet + sClr - sInvInv - sInv;
  end;


  //sMask := Other.StyleMask                            // Styles to be taken from Other
  //       + (fsNot(Other.StyleMask) * Other.Style);    // Styles to be inverted
  //Style     := (Style * fsNot(sMask))    // Styles that are neither taken, nor inverted
  //           + (Other.Style * sMask);    // Styles that are either inverted or set
  //StyleMask := (StyleMask * fsNot(sMask)) + (Other.StyleMask * sMask);

  EndUpdate;
end;

procedure TSynSelectedColorMergeResult.MergeFrames(Other: TSynHighlighterAttributesModifier; LeftCol,
  RightCol: TLazSynDisplayTokenBound);

  //procedure SetSide(ASide: TLazSynBorderSide; ASrc: TSynHighlighterAttributesModifier);
  //begin
  //(*
  //  if (FrameSideColors[ASide] <> clNone) and
  //     ( (ASrc.FramePriority < FrameSidePriority[ASide]) or
  //       ( (ASrc.FramePriority = FrameSidePriority[ASide]) and
  //         (SynFrameEdgePriorities[ASrc.FrameEdges] < SynFrameEdgePriorities[FrameSideOrigin[ASide]]) )
  //     )
  //
  //*)
  //  if (FrameSideColors[ASide] <> clNone) and
  //     ( (ASrc.FramePriority < FrameSidePriority[ASide]) or
  //       ( (ASrc.FramePriority = FrameSidePriority[ASide]) and
  //         (SynFrameEdgePriorities[ASrc.FrameEdges] < SynFrameEdgePriorities[FrameSideOrigin[ASide]]) )
  //     )
  //  then
  //    exit;
  //  FFrameSideColors[ASide] := ASrc.FrameColor;
  //  FFrameSideStyles[ASide] := ASrc.FrameStyle;
  //  FFrameSidePriority[ASide] := ASrc.FramePriority;
  //  FFrameSideOrigin[ASide]   := ASrc.FrameEdges;
  //  if ASide = bsLeft then
  //    FStartX := LeftCol; // LeftCol has Phys and log ; // ASrc.FStartX;
  //  if ASide = bsRight then
  //    FEndX := RightCol; // ASrc.FEndX;
  //end;

  procedure SetSide(AInfoSide: TSynSelectedColorEnum; ASide: TLazSynBorderSide;
    ASrc: TSynHighlighterAttributesModifier);
  begin
    if (FMergeInfos[AInfoSide].BaseColor <> clNone) and
       ( (ASrc.FramePriority < FMergeInfos[AInfoSide].BasePriority) or
         ( (ASrc.FramePriority = FMergeInfos[AInfoSide].BasePriority) and
           (SynFrameEdgePriorities[ASrc.FrameEdges] < SynFrameEdgePriorities[FrameSideOrigin[ASide]]) )
       )
    then
      exit;

    MergeToInfo(FMergeInfos[AInfoSide], ASrc.FrameColor, ASrc.FramePriority, ASrc.FrameAlpha);

    FFrameSidePriority[ASide] := ASrc.FramePriority; // used for style (style may be taken, from an alpha frame
    if ( (ASrc.FramePriority > FFrameSidePriority[ASide]) or
         ( (ASrc.FramePriority = FFrameSidePriority[ASide]) and
           (SynFrameEdgePriorities[ASrc.FrameEdges] >= SynFrameEdgePriorities[FrameSideOrigin[ASide]]) )
       )
    then
      FFrameSideStyles[ASide] := ASrc.FrameStyle;

    if ASrc.FrameAlpha = 0 then
      FFrameSideOrigin[ASide] := ASrc.FrameEdges;
  end;

begin
  if not FFrameSidesInitialized then
    MaybeInitFrameSides;

  If (Other = nil) or (Other.FrameColor = clNone) then
    exit;

  // Merge Values
  case Other.FrameEdges of
    sfeAround: begin
        // UpdateOnly, frame keeps behind individual sites
        if (not (Other is TSynSelectedColor)) or  // always merge, if it has no startx
           IsMatching(TSynSelectedColor(Other).StartX, LeftCol)
        then
          SetSide(sscFrameLeft, bsLeft, Other);
        if  (not (Other is TSynSelectedColor)) or
           IsMatching(TSynSelectedColor(Other).EndX, RightCol)
        then
          SetSide(sscFrameRight, bsRight, Other);
        SetSide(sscFrameBottom, bsBottom, Other);
        SetSide(sscFrameTop, bsTop, Other);
        //FrameColor := Other.FrameColor;
        //FrameStyle := Other.FrameStyle;
        //FrameEdges := Other.FrameEdges;
      end;
    sfeBottom: begin
        SetSide(sscFrameBottom, bsBottom, Other);
      end;
    sfeLeft: begin
       // startX ?
        SetSide(sscFrameLeft, bsLeft, Other);
      end;
  end;
end;

{ TSynSelectedColor }

function TSynSelectedColor.GetModifiedStyle(aStyle : TFontStyles) : TFontStyles;
begin
  Result := fsXor(aStyle, Style * fsNot(StyleMask)) // Invert Styles
            + (Style*StyleMask)                     // Set Styles
            - (fsNot(Style)*StyleMask);             // Remove Styles
end;

procedure TSynSelectedColor.ModifyColors(var AForeground, ABackground,
    AFrameColor: TColor; var AStyle: TFontStyles; var AFrameStyle: TSynLineStyle);
begin
  if Foreground <> clNone then AForeground := Foreground;
  if Background <> clNone then ABackground := Background;
  if FrameColor <> clNone then
  begin
    AFrameColor := FrameColor;
    AFrameStyle := FrameStyle;
  end;

  AStyle := GetModifiedStyle(AStyle);
end;

procedure TSynSelectedColor.AssignFrom(Src: TLazSynCustomTextAttributes);
begin
  inherited AssignFrom(Src);
  if not (Src is TSynSelectedColor) then exit;

  FStartX := TSynSelectedColor(Src).FStartX;
  FEndX   := TSynSelectedColor(Src).FEndX;

  Changed; {TODO: only if really changed}
end;

procedure TSynSelectedColor.Init;
begin
  inherited Init;
  Background := clHighLight;
  Foreground := clHighLightText;
  FrameColor := clNone;
  FrameStyle := slsSolid;
  FrameEdges := sfeAround;
  InternalSaveDefaultValues;
end;

procedure TSynSelectedColor.SetFrameBoundsPhys(AStart, AEnd: Integer);
begin
  FStartX.Physical := AStart;
  FEndX.Physical   := AEnd;
  FStartX.Logical  := -1;
  FEndX.Logical    := -1;
  FStartX.Offset   := 0;
  FEndX.Offset     := 0;
end;

procedure TSynSelectedColor.SetFrameBoundsLog(AStart, AEnd: Integer; AStartOffs: Integer;
  AEndOffs: Integer);
begin
  FStartX.Physical := -1;
  FEndX.Physical   := -1;
  FStartX.Logical  := AStart;
  FEndX.Logical    := AEnd;
  FStartX.Offset   := AStartOffs;
  FEndX.Offset     := AEndOffs;
end;

procedure TSynSelectedColor.DoClear;
begin
  inherited;
  FStartX.Physical := -1;
  FEndX.Physical   := -1;
  FStartX.Logical  := -1;
  FEndX.Logical    := -1;
  FStartX.Offset   := 0;
  FEndX.Offset     := 0;
end;

{ TLazSynSurface }

function TLazSynSurface.GetHandle: HWND;
begin
  Result := FOwner.Handle;
end;

procedure TLazSynSurface.SetDisplayView(AValue: TLazSynDisplayView);
begin
  if FDisplayView = AValue then Exit;
  FDisplayView := AValue;
  DoDisplayViewChanged;
end;

procedure TLazSynSurface.BoundsChanged;
begin
  //
end;

procedure TLazSynSurface.DoDisplayViewChanged;
begin
  //
end;

constructor TLazSynSurface.Create(AOwner: TWinControl);
begin
  FOwner := AOwner;
  FBoundsChangeList := TMethodList.Create;
end;

destructor TLazSynSurface.Destroy;
begin
  inherited Destroy;
  FreeAndNil(FBoundsChangeList);
end;

procedure TLazSynSurface.Assign(Src: TLazSynSurface);
begin
  // do not assign the bounds
  DisplayView := Src.DisplayView;
end;

procedure TLazSynSurface.AddBoundsChangeHandler(AHandler: TNotifyEvent);
begin
  FBoundsChangeList.Add(TMethod(AHandler));
end;

procedure TLazSynSurface.RemoveBoundsChangeHandler(AHandler: TNotifyEvent);
begin
  FBoundsChangeList.Remove(TMethod(AHandler));
end;

procedure TLazSynSurface.Paint(ACanvas: TCanvas; AClip: TRect);
begin
  if (AClip.Left   >= Bounds.Right) or
     (AClip.Right  <= Bounds.Left) or
     (AClip.Top    >= Bounds.Bottom) or
     (AClip.Bottom <= Bounds.Top)
  then
    exit;

  if (AClip.Left   < Bounds.Left)   then AClip.Left   := Bounds.Left;
  if (AClip.Right  > Bounds.Right)  then AClip.Right  := Bounds.Right;
  if (AClip.Top    < Bounds.Top)    then AClip.Top    := Bounds.Top;
  if (AClip.Bottom > Bounds.Bottom) then AClip.Bottom := Bounds.Bottom;

  DoPaint(ACanvas, AClip);
end;

procedure TLazSynSurface.InvalidateLines(FirstTextLine, LastTextLine: TLineIdx;
  AScreenLineOffset: Integer);
begin
  //
end;

procedure TLazSynSurface.SetBounds(ATop, ALeft, ABottom, ARight: Integer);
begin
  if (FBounds.Left = ALeft) and (FBounds.Top = ATop) and
     (FBounds.Right = ARight) and (FBounds.Bottom = ABottom)
  then exit;

  FBounds.Left := ALeft;
  FBounds.Top := ATop;
  FBounds.Right := ARight;
  FBounds.Bottom := ABottom;
  BoundsChanged;
  FBoundsChangeList.CallNotifyEvents(Self);
end;

{ TSynBookMarkOpt }

constructor TSynBookMarkOpt.Create(AOwner: TComponent);
begin
  inherited Create;
  fDrawBookmarksFirst := TRUE;                                                  //mh 2000-10-12
  fEnableKeys := True;
  fGlyphsVisible := True;
  fLeftMargin := 2;
  fOwner := AOwner;
  fXOffset := 12;
end;

procedure TSynBookMarkOpt.SetBookmarkImages(const Value: TCustomImageList);
begin
  if fBookmarkImages <> Value then begin
    if Assigned(fBookmarkImages) then fBookmarkImages.RemoveFreeNotification(fOwner);
    fBookmarkImages := Value;
    if Assigned(fBookmarkImages) then fBookmarkImages.FreeNotification(fOwner);
    if Assigned(fOnChange) then fOnChange(Self);
  end;
end;

{begin}                                                                         //mh 2000-10-12
procedure TSynBookMarkOpt.SetDrawBookmarksFirst(Value: boolean);
begin
  if Value <> fDrawBookmarksFirst then begin
    fDrawBookmarksFirst := Value;
    if Assigned(fOnChange) then fOnChange(Self);
  end;
end;
{end}                                                                           //mh 2000-10-12

procedure TSynBookMarkOpt.SetGlyphsVisible(Value: Boolean);
begin
  if fGlyphsVisible <> Value then begin
    fGlyphsVisible := Value;
    if Assigned(fOnChange) then fOnChange(Self);
  end;
end;

procedure TSynBookMarkOpt.SetLeftMargin(Value: Integer);
begin
  if fLeftMargin <> Value then begin
    fLeftMargin := Value;
    if Assigned(fOnChange) then fOnChange(Self);
  end;
end;

procedure TSynBookMarkOpt.SetXOffset(Value: integer);
begin
  if fXOffset <> Value then begin
    fXOffset := Value;
    if Assigned(fOnChange) then fOnChange(Self);
  end;
end;

var
  InternalImages: TBitmap;
  InternalImagesUsers: integer;
  IIWidth, IIHeight: integer;
  IICount: integer;

constructor TSynInternalImage.Create(const AName: string; Count: integer);
begin
  inherited Create;
  Inc(InternalImagesUsers);
  if InternalImagesUsers = 1 then begin
    InternalImages := TBitmap.Create;
    InternalImages.LoadFromResourceName(HInstance, AName);
    IIWidth := (InternalImages.Width + Count shr 1) div Count;
    IIHeight := InternalImages.Height;
    IICount := Count;
  end;
end;

destructor TSynInternalImage.Destroy;
begin
  Dec(InternalImagesUsers);
  if InternalImagesUsers = 0 then begin
    InternalImages.Free;
    InternalImages := nil;
  end;
  inherited Destroy;
end;

procedure TSynInternalImage.DrawMark(ACanvas: TCanvas;
  Number, X, Y, LineHeight: integer);
var
  rcSrc, rcDest: TRect;
begin
  if (Number >= 0) and (Number < IICount) then
  begin
    if LineHeight >= IIHeight then begin
      rcSrc := Rect(Number * IIWidth, 0, (Number + 1) * IIWidth, IIHeight);
      Inc(Y, (LineHeight - IIHeight) div 2);
      rcDest := Rect(X, Y, X + IIWidth, Y + IIHeight);
    end else begin
      rcDest := Rect(X, Y, X + IIWidth, Y + LineHeight);
      Y := (IIHeight - LineHeight) div 2;
      rcSrc := Rect(Number * IIWidth, Y, (Number + 1) * IIWidth, Y + LineHeight);
    end;
    ACanvas.CopyRect(rcDest, InternalImages.Canvas, rcSrc);
  end;
end;

{ TSynObjectList }

constructor TSynObjectList.Create(AOwner: TComponent);
begin
  Inherited Create(AOwner);
  SetAncestor(True);
  SetInline(True);
  FList := TList.Create;
  FOwner := AOwner;
end;

destructor TSynObjectList.Destroy;
begin
  Clear;
  FreeAndNil(FList);
  inherited Destroy;
end;

procedure TSynObjectList.Assign(Source: TPersistent);
begin
  FList.Assign(TSynObjectList(Source).FList);
  DoChange(self);
end;

function TSynObjectList.GetChildOwner: TComponent;
begin
  Result := self;
end;

procedure TSynObjectList.GetChildren(Proc: TGetChildProc; Root: TComponent);
var
  i: Integer;
begin
  if Root = self then
    for i:= 0 to Count -1 do
      Proc(BaseItems[i]);
end;

procedure TSynObjectList.SetChildOrder(Child: TComponent; Order: Integer);
begin
  (Child as TSynObjectListItem).Index := Order;
  DoChange(self);;
end;

procedure TSynObjectList.RegisterItem(AnItem: TSynObjectListItem);
begin
  Add(AnItem);
end;

function TSynObjectList.GetBasePart(Index: Integer): TSynObjectListItem;
begin
  Result := TSynObjectListItem(FList[Index]);
end;

procedure TSynObjectList.PutBasePart(Index: Integer; const AValue: TSynObjectListItem);
begin
  FList[Index] := Pointer(AValue);
  DoChange(self);
end;

procedure TSynObjectList.SetSorted(const AValue: Boolean);
begin
  if FSorted = AValue then exit;
  FSorted := AValue;
  Sort;
end;

procedure TSynObjectList.DoChange(Sender: TObject);
begin
  if Assigned(FOnChange) then
    FOnChange(Self);
end;

function CompareSynObjectListItems(Item1, Item2: Pointer): Integer;
begin
  Result := TSynObjectListItem(Item1).Compare(TSynObjectListItem(Item2));
end;

procedure TSynObjectList.Sort;
begin
  FList.Sort(@CompareSynObjectListItems);
end;

function TSynObjectList.Add(AnItem: TSynObjectListItem): Integer;
begin
  Result := FList.Add(Pointer(AnItem));
  if FSorted then Sort;
  DoChange(self);
end;

procedure TSynObjectList.Delete(Index: Integer);
begin
  FList.Delete(Index);
  DoChange(self);
end;

procedure TSynObjectList.Clear;
begin
  while FList.Count > 0 do
    BaseItems[0].Free;
  FList.Clear;
  DoChange(self);
end;

function TSynObjectList.Count: Integer;
begin
  Result := FList.Count;
end;

function TSynObjectList.IndexOf(AnItem: TSynObjectListItem): Integer;
begin
  Result := Flist.IndexOf(Pointer(AnItem));
end;

procedure TSynObjectList.Move(AOld, ANew: Integer);
begin
  if FSorted then raise Exception.Create('not allowed');
  FList.Move(AOld, ANew);
  DoChange(self);;
end;

{ TSynObjectListItem }

function TSynObjectListItem.GetIndex: Integer;
begin
  Result := Owner.IndexOf(self);
end;

function TSynObjectListItem.GetDisplayName: String;
begin
  Result := Name + ' (' + ClassName + ')';
end;

procedure TSynObjectListItem.Init;
begin
  //
end;

procedure TSynObjectListItem.SetIndex(AValue: Integer);
begin
  if AValue < 0 then AValue := 0;
  if AValue >= Owner.Count then AValue := Owner.Count - 1;
  Owner.Move(GetIndex, AValue);
end;

function TSynObjectListItem.Compare(Other: TSynObjectListItem): Integer;
begin
  Result := ComparePointers(Pointer(self), Pointer(Other));
end;

constructor TSynObjectListItem.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  SetAncestor(True);
  FOwner := AOwner as TSynObjectList;
  Init;
  FOwner.RegisterItem(self);
end;

destructor TSynObjectListItem.Destroy;
begin
  inherited Destroy;
  FOwner.Delete(FOwner.IndexOf(self));
end;

function TSynObjectListItem.GetParentComponent: TComponent;
begin
  Result := FOwner;
end;

{ TSynClipboardStream }

function TSynClipboardStream.GetMemory: Pointer;
begin
  Result := FMemStream.Memory;
end;

function TSynClipboardStream.GetSize: LongInt;
begin
  Result := FMemStream.Size;
end;

procedure TSynClipboardStream.SetInternalText(const AValue: String);
begin
  FIsPlainText := False;
  // Text, if we don't need CF_TEXT // Must include a zero byte
  AddTag(synClipTagText, @AValue[1], length(AValue) + 1);
end;

function TSynClipboardStream.GetSelectionMode: TSynSelectionMode;
var
  PasteMode: ^TSynSelectionMode;
begin
  PasteMode := GetTagPointer(synClipTagMode);
  if PasteMode = nil then
    if FColumnModeFlag then
      Result := smColumn
    else
      Result := smNormal
  else
    Result := PasteMode^;
end;

procedure TSynClipboardStream.SetSelectionMode(const AValue: TSynSelectionMode);
begin
  AddTag(synClipTagMode, @AValue, SizeOf(TSynSelectionMode));
  FColumnModeFlag := AValue = smColumn;
end;

procedure TSynClipboardStream.SetText(const AValue: String);
var
  SLen: Integer;
begin
  FIsPlainText := True;
  FText := AValue;
  SLen := length(FText);
  AddTag(synClipTagExtText, @SLen, SizeOf(Integer));
end;

constructor TSynClipboardStream.Create;
begin
  FMemStream := TMemoryStream.Create;
end;

destructor TSynClipboardStream.Destroy;
begin
  FreeAndNil(FMemStream);
  inherited Destroy;
end;

class function TSynClipboardStream.ClipboardFormatId: TClipboardFormat;
const
  SYNEDIT_CLIPBOARD_FORMAT_TAGGED = 'Application/X-Laz-SynEdit-Tagged';
  Format: TClipboardFormat = 0;
begin
  if Format = 0 then
    Format := ClipboardRegisterFormat(SYNEDIT_CLIPBOARD_FORMAT_TAGGED);
  Result := Format;
end;

class function TSynClipboardStream.ClipboardFormatMSDEVColumnSelect: TClipboardFormat;
const
  MSDEV_CLIPBOARD_FORMAT_TAGGED = 'MSDEVColumnSelect';
  Format: TClipboardFormat = 0;
begin
  if Format = 0 then
    Format := ClipboardRegisterFormat(MSDEV_CLIPBOARD_FORMAT_TAGGED);
  Result := Format;
end;

class function TSynClipboardStream.ClipboardFormatBorlandIDEBlockType: TClipboardFormat;
const
  BORLAND_CLIPBOARD_FORMAT_TAGGED = 'Borland IDE Block Type';
  Format: TClipboardFormat = 0;
begin
  if Format = 0 then
    Format := ClipboardRegisterFormat(BORLAND_CLIPBOARD_FORMAT_TAGGED);
  Result := Format;
end;

function TSynClipboardStream.CanReadFromClipboard(AClipboard: TClipboard): Boolean;
begin
  Result := AClipboard.HasFormat(ClipboardFormatId);
end;

function TSynClipboardStream.ReadFromClipboard(AClipboard: TClipboard): Boolean;
var
  ip: PInteger;
  len: LongInt;
  buf: TMemoryStream;
begin
  Result := false;
  Clear;
  FTextP := nil;
  // Check for embedded text
  if AClipboard.HasFormat(ClipboardFormatId) then begin
    Result := AClipboard.GetFormat(ClipboardFormatId, FMemStream);
    FTextP := GetTagPointer(synClipTagText);
    if FTextP <> nil then begin
      len := GetTagLen(synClipTagText);
      if len > 0 then
        (FTextP + len - 1)^ := #0
      else
        FTextP := nil;
    end;
  end;
  // Normal text
  if (FTextP = nil) then begin
    Result := true;
    FText := AClipboard.AsText;
    if FText <> '' then begin
      FTextP := @FText[1];
      ip := GetTagPointer(synClipTagExtText);
      if (length(FText) = 0) or (ip = nil) or (length(FText) <> ip^) then
        FIsPlainText := True;
    end;
    FColumnModeFlag := AClipboard.HasFormat(ClipboardFormatMSDEVColumnSelect);
    if (not FColumnModeFlag) and AClipboard.HasFormat(ClipboardFormatBorlandIDEBlockType) then begin
      buf := TMemoryStream.Create;
      try
      AClipboard.GetFormat(ClipboardFormatBorlandIDEBlockType, buf);
      except
        buf.Clear;
      end;
      if buf.Size = 1 then begin
        buf.Position := 0;
        FColumnModeFlag := buf.ReadByte = 2;
      end;
      buf.Free;
    end;
  end;
end;

function TSynClipboardStream.WriteToClipboard(AClipboard: TClipboard): Boolean;
const
  FormatBuf: array [0..0] of byte = (2);
begin
  AClipboard.Open;
  try
    if FIsPlainText and (FText <> '') then begin
      AClipboard.AsText:= FText;
    end;
    Result := AClipboard.AddFormat(ClipboardFormatId, FMemStream.Memory^, FMemStream.Size);
    if FColumnModeFlag then begin
      AClipboard.AddFormat(ClipboardFormatMSDEVColumnSelect, FormatBuf[0], 1);   // MSDEVColumnSelect needs some (arbitrary) data to be recognized. it is ignored when empty
      AClipboard.AddFormat(ClipboardFormatBorlandIDEBlockType, FormatBuf[0], 1); // BorlandIDEBlockType needs 2 to be passed into the data block
    end;
  finally
    AClipboard.Close;
  end;
  {$IFDEF SynClipboardExceptions}
  if not AClipboard.HasFormat(CF_TEXT) then
    raise ESynEditError.Create('Clipboard copy operation failed: HasFormat');
  {$ENDIF}
end;

procedure TSynClipboardStream.Clear;
begin
  FMemStream.Clear;
  FIsPlainText := False;
  FColumnModeFlag := False;
end;

function TSynClipboardStream.HasTag(ATag: TSynClipboardStreamTag): Boolean;
begin
  Result := GetTagPointer(ATag) <> nil;
end;

function TSynClipboardStream.GetTagPointer(ATag: TSynClipboardStreamTag): Pointer;
var
  ctag, mend: Pointer;
begin
  Result :=  nil;
  if FIsPlainText then
    exit;
  ctag := FMemStream.Memory;
  mend := ctag + FMemStream.Size;
  while (result = nil) and
        (ctag + SizeOf(TSynClipboardStreamTag) + SizeOf(Integer) <= mend) do
  begin
     if TSynClipboardStreamTag(ctag^) = ATag then begin
      Result := ctag + SizeOf(TSynClipboardStreamTag) + SizeOf(Integer)
    end else begin
      inc(ctag, SizeOf(TSynClipboardStreamTag));
      inc(ctag, PInteger(ctag)^);
      inc(ctag, SizeOf(Integer));
      {$IFDEF FPC_REQUIRES_PROPER_ALIGNMENT}
      ctag := Align(ctag, SizeOf(integer));
      {$ENDIF}
    end;
  end;
  if (Result <> nil) and
     (ctag + Integer((ctag + SizeOf(TSynClipboardStreamTag))^) > mend) then
  begin
    Result := nil;
    raise ESynEditError.Create('Clipboard read operation failed, data corrupt');
  end;
end;

function TSynClipboardStream.GetTagLen(ATag: TSynClipboardStreamTag): Integer;
var
  p: PInteger;
begin
  Result := 0;
  p := GetTagPointer(ATag);
  if p = nil then
    exit;
  dec(p, 1);
  Result := p^;
end;

procedure TSynClipboardStream.AddTag(ATag: TSynClipboardStreamTag; Location: Pointer;
  Len: Integer);
var
  msize: Int64;
  mpos: Pointer;
  LenBlock:PtrUInt;
begin
  msize := FMemStream.Size;
  LenBlock:= Len + SizeOf(TSynClipboardStreamTag) + SizeOf(Integer);
  {$IFDEF FPC_REQUIRES_PROPER_ALIGNMENT}
  LenBlock := Align(LenBlock, SizeOf(integer));
  {$ENDIF}
  FMemStream.Size := msize +LenBlock;
  mpos := FMemStream.Memory + msize;
  TSynClipboardStreamTag(mpos^) := ATag;
  inc(mpos, SizeOf(TSynClipboardStreamTag));
  Integer(mpos^) := Len;
  inc(mpos, SizeOf(Integer));
  System.Move(Location^, mpos^, Len);
end;

{ TSynWordBreaker }

procedure TSynWordBreaker.SetIdentChars(const AValue: TSynIdentChars);
begin
  if FIdentChars = AValue then exit;
  FIdentChars := AValue;
end;

procedure TSynWordBreaker.SetWhiteChars(const AValue: TSynIdentChars);
begin
  if FWhiteChars = AValue then exit;
  FWhiteChars := AValue;
  FWordChars := [#1..#255] - (FWordBreakChars + FWhiteChars);
end;

procedure TSynWordBreaker.SetWordBreakChars(const AValue: TSynIdentChars);
begin
  if FWordBreakChars = AValue then exit;
  FWordBreakChars := AValue;
  FWordChars := [#1..#255] - (FWordBreakChars + FWhiteChars);
end;

constructor TSynWordBreaker.Create;
begin
  inherited;
  Reset;
end;

procedure TSynWordBreaker.Reset;
begin
  FWhiteChars     := TSynWhiteChars;
  FWordBreakChars := TSynWordBreakChars;
  FIdentChars     := TSynValidStringChars - TSynSpecialChars;
  FWordChars      := [#1..#255] - (FWordBreakChars + FWhiteChars);
end;

function TSynWordBreaker.IsInWord(aLine: String; aX: Integer): Boolean;
var
  len: Integer;
begin
  len := Length(aLine);
  if (aX < 1) or (aX > len + 1) then exit(False);
  Result := ((ax <= len) and (aLine[aX] in FWordChars)) or
            ((aX > 1) and (aLine[aX - 1] in FWordChars));
end;

function TSynWordBreaker.IsAtWordStart(aLine: String; aX: Integer): Boolean;
var
  len: Integer;
begin
  len := Length(aLine);
  if (aX < 1) or (aX > len) then exit(False);
  Result := (aLine[aX] in FWordChars) and
            ((aX = 1) or not (aLine[aX - 1] in FWordChars));
end;

function TSynWordBreaker.IsAtWordEnd(aLine: String; aX: Integer): Boolean;
var
  len: Integer;
begin
  len := Length(aLine);
  if (aX <= 1) or (aX > len + 1) or (len = 0) then exit(False);
  Result := ((ax = len + 1) or not(aLine[aX] in FWordChars)) and
            (aLine[aX - 1] in FWordChars);
end;

function TSynWordBreaker.NextWordStart(aLine: String; aX: Integer;
  aIncludeCurrent: Boolean): Integer;
var
  len: Integer;
begin
  len := Length(aLine);
  if (aX < 1) then exit(-1);
  if not aIncludeCurrent then
    inc(aX);
  if (aX > len + 1) then exit(-1);
  if (aX > 1) and (aLine[aX - 1] in FWordChars) then
    while (aX <= len) and (aLine[aX] in FWordChars) do Inc(ax);
  while (aX <= len) and not(aLine[aX] in FWordChars) do Inc(ax);
  if aX > len then
    exit(-1);
  Result := aX;
end;

function TSynWordBreaker.NextWordEnd(aLine: String; aX: Integer;
  aIncludeCurrent: Boolean): Integer;
var
  len: Integer;
begin
  len := Length(aLine);
  if (aX < 1) then exit(-1);
  if not aIncludeCurrent then
    inc(aX);
  if (aX > len + 1) then exit(-1);
  if (aX = 1) or not(aLine[aX - 1] in FWordChars) then begin
    while (aX <= len) and not(aLine[aX] in FWordChars) do Inc(ax);
    if (aX >= len + 1) then exit(-1);
  end;
  while (aX <= len) and (aLine[aX] in FWordChars) do Inc(ax);
  Result := aX;
end;

function TSynWordBreaker.PrevWordStart(aLine: String; aX: Integer;
  aIncludeCurrent: Boolean): Integer;
var
  len: Integer;
begin
  len := Length(aLine);
  if (aX < 1) or (aX > len + 1) then exit(-1);
  if not aIncludeCurrent then
    dec(aX);
  while (aX >= 1) and ( (ax > len) or not(aLine[aX] in FWordChars) ) do Dec(ax);
  if aX = 0 then
    exit(-1);
  while (aX >= 1) and ( (ax > len) or (aLine[aX] in FWordChars) ) do Dec(ax);
  Result := aX  + 1;
end;

function TSynWordBreaker.PrevWordEnd(aLine: String; aX: Integer;
  aIncludeCurrent: Boolean): Integer;
var
  len: Integer;
begin
  len := Length(aLine);
  if (aX < 1) or (aX > len + 1) then exit(-1);
  if not aIncludeCurrent then
    dec(aX);
  if aX <= len then
    while (aX >= 1) and (aLine[aX] in FWordChars) do Dec(ax);
  while (aX >= 1) and ( (ax > len) or not(aLine[aX] in FWordChars) ) do Dec(ax);
  if aX = 0 then
    exit(-1);
  Result := aX + 1;
end;

function TSynWordBreaker.NextBoundary(aLine: String; aX: Integer;
  aIncludeCurrent: Boolean): Integer;
var
  len: Integer;
begin
  len := Length(aLine);
  if (aX < 1) then exit(-1);
  if aIncludeCurrent then dec(ax);
  if (ax > len) then exit(-1);

  if (aX > 0) and (aLine[aX] in FWordChars) then
    while (aX <= len) and (aLine[aX] in FWordChars) do Inc(ax)
  else
  if (aX > 0) and (aLine[aX] in FWordBreakChars) then
    while (aX <= len) and (aLine[aX] in FWordBreakChars) do Inc(ax)
  else
  begin
    while (aX <= len) and ((aX = 0) or (aLine[aX] in FWhiteChars)) do Inc(ax);
    if (ax > len) then exit(-1);
  end;
  Result := aX;
end;

function TSynWordBreaker.PrevBoundary(aLine: String; aX: Integer;
  aIncludeCurrent: Boolean): Integer;
var
  len: Integer;
begin
  len := Length(aLine);
  if (aX > len + 1) then exit(-1);
  if not aIncludeCurrent then dec(ax);
  if (aX < 1) then exit(-1);

  if (aX <= len) and (aLine[aX] in FWordChars) then
    while (aX >= 1) and (aLine[aX] in FWordChars) do dec(ax)
  else
  if (aX <= len) and (aLine[aX] in FWordBreakChars) then
    while (aX >= 1) and (aLine[aX] in FWordBreakChars) do dec(ax)
  else
  begin
    while (aX >= 1) and ((aX > len) or (aLine[aX] in FWhiteChars)) do dec(ax);
    if aX = 0 then exit(-1);
  end;
  Result := aX + 1;
end;

{ TSynMethodList }

function TSynMethodList.IndexToObjectIndex(const AnObject: TObject; AnIndex: Integer): integer;
var
  i, c: Integer;
begin
  Result := -1;
  if Self = nil then exit;
  i := 0;
  c := Count;
  while i < c do begin
    if TObject(Items[i].Data)=AnObject then begin
      if AnIndex = 0 then exit(i);
      dec(AnIndex);
    end;
    inc(i);
  end;
end;

function TSynMethodList.GetObjectItems(AnObject: TObject; Index: integer): TMethod;
begin
  Result := Items[IndexToObjectIndex(AnObject, Index)];
end;

procedure TSynMethodList.SetObjectItems(AnObject: TObject; Index: integer;
  const AValue: TMethod);
begin
  Items[IndexToObjectIndex(AnObject, Index)] := AValue;
end;

function TSynMethodList.CountByObject(const AnObject: TObject): integer;
var
  i: Integer;
begin
  Result := 0;
  if Self=nil then exit;
  i := Count-1;
  while i>=0 do begin
    if TObject(Items[i].Data)=AnObject then inc(Result);
    dec(i);
  end;
end;

procedure TSynMethodList.DeleteByObject(const AnObject: TObject; Index: integer);
begin
  Delete(IndexToObjectIndex(AnObject, Index));
end;

procedure TSynMethodList.AddCopyFrom(AList: TSynMethodList; AOwner: TObject = nil);
var
  i: Integer;
begin
  if AOwner = nil then begin
    for i := 0 to AList.Count - 1 do
      Add(AList.Items[i], True);
  end else begin
    for i := 0 to AList.CountByObject(AOwner) - 1 do
      Add(AList.ItemsByObject[AOwner, i], True);
  end;
end;

{ TSynFilteredMethodList }

function TSynFilteredMethodList.IndexOf(AHandler: TMethod): Integer;
begin
  Result := FCount - 1;
  while (Result >= 0) and
        ( (FItems[Result].FHandler.Code <> AHandler.Code) or
          (FItems[Result].FHandler.Data <> AHandler.Data) )
  do
    dec(Result);
end;

function TSynFilteredMethodList.IndexOf(AHandler: TMethod; AFilter: LongInt): Integer;
begin
  Result := FCount - 1;
  while (Result >= 0) and (
        (FItems[Result].FHandler.Code <> AHandler.Code) or
        (FItems[Result].FHandler.Data <> AHandler.Data) or
        (FItems[Result].FFilter <> AFilter) )
  do
    dec(Result);
end;

function TSynFilteredMethodList.NextDownIndex(var Index: integer): boolean;
begin
  if Self<>nil then begin
    dec(Index);
    if (Index>=FCount) then
      Index:=FCount-1;
  end else
    Index:=-1;
  Result:=(Index>=0);
end;

function TSynFilteredMethodList.NextDownIndexNumFilter(var Index: integer;
  AFilter: LongInt): boolean;
begin
  Repeat
    Result := NextDownIndex(Index);
  until (not Result) or (FItems[Index].FFilter = AFilter);
end;

function TSynFilteredMethodList.NextDownIndexBitFilter(var Index: integer;
  AFilter: LongInt): boolean;
begin
  Repeat
    Result := NextDownIndex(Index);
  until (not Result) or ((FItems[Index].FFilter and AFilter) <> 0);
end;

procedure TSynFilteredMethodList.Delete(AIndex: Integer);
begin
  if AIndex < 0 then exit;
  while AIndex < FCount - 1 do begin
    FItems[AIndex] := FItems[AIndex + 1];
    inc(AIndex);
  end;
  dec(FCount);
  if length(FItems) > FCount * 4 then
    SetLength(FItems, FCount * 2);
end;

constructor TSynFilteredMethodList.Create;
begin
  FCount := 0;
end;

procedure TSynFilteredMethodList.AddNumFilter(AHandler: TMethod; AFilter: LongInt);
var
  i: Integer;
begin
  i := IndexOf(AHandler, AFilter);
  if i >= 0 then
    raise Exception.Create('Duplicate');

  if FCount >= high(FItems) then
    SetLength(FItems, Max(8, FCount * 2));
  FItems[FCount].FHandler := AHandler;
  FItems[FCount].FFilter := AFilter;
  inc(FCount);
end;

procedure TSynFilteredMethodList.AddBitFilter(AHandler: TMethod; AFilter: LongInt);
var
  i: Integer;
begin
  i := IndexOf(AHandler);
  if i >= 0 then
    FItems[i].FFilter := FItems[i].FFilter or AFilter
  else begin
    if FCount >= high(FItems) then
      SetLength(FItems, Max(8, FCount * 2));
    FItems[FCount].FHandler := AHandler;
    FItems[FCount].FFilter := AFilter;
    inc(FCount);
  end;
end;

procedure TSynFilteredMethodList.Remove(AHandler: TMethod);
begin
  Delete(IndexOf(AHandler));
end;

procedure TSynFilteredMethodList.Remove(AHandler: TMethod; AFilter: LongInt);
begin
  Delete(IndexOf(AHandler, AFilter));
end;

procedure TSynFilteredMethodList.CallNotifyEventsNumFilter(Sender: TObject; AFilter: LongInt);
var
  i: Integer;
begin
  i:=Count;
  while NextDownIndexNumFilter(i, AFilter) do
    TNotifyEvent(FItems[i].FHandler)(Sender);
end;

procedure TSynFilteredMethodList.CallNotifyEventsBitFilter(Sender: TObject; AFilter: LongInt);
var
  i: Integer;
begin
  i:=Count;
  while NextDownIndexBitFilter(i, AFilter) do
    TNotifyEvent(FItems[i].FHandler)(Sender);
end;

{ TSynSizedDifferentialAVLNode }

procedure TSynSizedDifferentialAVLNode.SetLeftSizeSum(AValue: Integer);
begin
  if FLeftSizeSum = AValue then Exit;
  FLeftSizeSum := AValue;
  AdjustParentLeftCount(AValue - FLeftSizeSum);
end;

{$IFDEF SynDebug}
function TSynSizedDifferentialAVLNode.Debug: String;
begin
  Result := Format('Size=%3d (LeftSum=%3d)  Balance=%3d ',
                      [FSize,   FLeftSizeSum, FBalance]);
end;
{$ENDIF}

function TSynSizedDifferentialAVLNode.TreeDepth: integer;
var t: integer;
begin
  Result := 1;
  if FLeft <> nil  then Result := FLeft.TreeDepth+1;
  if FRight <> nil then t := FRight.TreeDepth+1 else t := 0;
  if t > Result then Result := t;
end;

procedure TSynSizedDifferentialAVLNode.SetLeftChild(ANode: TSynSizedDifferentialAVLNode);
begin
  FLeft := ANode;
  if ANode <> nil then ANode.FParent := self;
end;

procedure TSynSizedDifferentialAVLNode.SetLeftChild(ANode: TSynSizedDifferentialAVLNode;
  anAdjustChildPosOffset: Integer);
begin
  FLeft := ANode;
  if ANode <> nil then begin
    ANode.FParent := self;
    ANode.FPositionOffset := ANode.FPositionOffset + anAdjustChildPosOffset;
  end;
end;

procedure TSynSizedDifferentialAVLNode.SetLeftChild(ANode: TSynSizedDifferentialAVLNode;
  anAdjustChildPosOffset, aLeftSizeSum: Integer);
begin
  FLeft := ANode;
  FLeftSizeSum := aLeftSizeSum;
  if ANode <> nil then begin
    ANode.FParent := self;
    ANode.FPositionOffset := ANode.FPositionOffset + anAdjustChildPosOffset;
  end
end;

procedure TSynSizedDifferentialAVLNode.SetRightChild(ANode: TSynSizedDifferentialAVLNode);
begin
  FRight := ANode;
  if ANode <> nil then ANode.FParent := self;
end;

procedure TSynSizedDifferentialAVLNode.SetRightChild(ANode: TSynSizedDifferentialAVLNode;
  anAdjustChildPosOffset: Integer);
begin
  FRight := ANode;
  if ANode <> nil then begin
    ANode.FParent := self;
    ANode.FPositionOffset := ANode.FPositionOffset + anAdjustChildPosOffset;
  end;
end;

function TSynSizedDifferentialAVLNode.ReplaceChild(OldNode,
  ANode: TSynSizedDifferentialAVLNode): TReplacedChildSite;
begin
  if FLeft = OldNode then begin
    SetLeftChild(ANode);
    exit(rplcLeft);
  end;
  SetRightChild(ANode);
  result := rplcRight;
end;

function TSynSizedDifferentialAVLNode.ReplaceChild(OldNode,
  ANode: TSynSizedDifferentialAVLNode; anAdjustChildPosOffset: Integer): TReplacedChildSite;
begin
  if FLeft = OldNode then begin
    SetLeftChild(ANode, anAdjustChildPosOffset);
    exit(rplcLeft);
  end;
  SetRightChild(ANode, anAdjustChildPosOffset);
  result := rplcRight;
end;

procedure TSynSizedDifferentialAVLNode.AdjustLeftCount(AValue: Integer);
begin
  FLeftSizeSum := FLeftSizeSum + AValue;
  AdjustParentLeftCount(AValue);
end;

procedure TSynSizedDifferentialAVLNode.AdjustParentLeftCount(AValue: Integer);
var
  node, pnode : TSynSizedDifferentialAVLNode;
begin
  node := self;
  pnode := node.FParent;
  while pnode <> nil do begin
    if node = pnode.FLeft
    then pnode.FLeftSizeSum := pnode.FLeftSizeSum + AValue;
    node := pnode;
    pnode := node.FParent;
  end;
end;

procedure TSynSizedDifferentialAVLNode.AdjustPosition(AValue: Integer);
begin
  FPositionOffset := FPositionOffset + AValue;
  if FRight <> nil then
    FRight.FPositionOffset := FRight.FPositionOffset - AValue;;
  if FLeft <> nil then
    FLeft.FPositionOffset := FLeft.FPositionOffset - AValue;;
end;

function TSynSizedDifferentialAVLNode.GetSizesBeforeSum: Integer;
var
  n1, n2: TSynSizedDifferentialAVLNode;
begin
  Result := FLeftSizeSum;
  n1 := FParent;
  n2 := Self;
  while n1 <> nil do begin
    if n2 = n1.FRight then
      Result := Result + n1.FLeftSizeSum + n1.FSize;
    n2 := n1;
    n1 := n1.FParent;
  end;
end;

function TSynSizedDifferentialAVLNode.GetPosition: Integer;
var
  N: TSynSizedDifferentialAVLNode;
begin
  Result := FPositionOffset;
  N := FParent;
  while N <> nil do begin
    Result := Result + N.FPositionOffset;
    N := N.FParent;
  end;
end;

function TSynSizedDifferentialAVLNode.Precessor: TSynSizedDifferentialAVLNode;
begin
  Result := FLeft;
  if Result<>nil then begin
    while (Result.FRight<>nil) do Result := Result.FRight;
  end else begin
    Result := self;
    while (Result.FParent<>nil) and (Result.FParent.FLeft=Result) do
      Result := Result.FParent;
    Result := Result.FParent;
  end;
end;

function TSynSizedDifferentialAVLNode.Successor: TSynSizedDifferentialAVLNode;
begin
  Result := FRight;
  if Result<>nil then begin
    while (Result.FLeft<>nil) do Result := Result.FLeft;
  end else begin
    Result := self;
    while (Result.FParent<>nil) and (Result.FParent.FRight=Result) do
      Result := Result.FParent;
    Result := Result.FParent;
  end;
end;

function TSynSizedDifferentialAVLNode.Precessor(var aStartPosition,
  aSizesBeforeSum: Integer): TSynSizedDifferentialAVLNode;
begin
  Result := FLeft;
  if Result<>nil then begin
    aStartPosition := aStartPosition + Result.FPositionOffset;
    while (Result.FRight<>nil) do begin
      Result := Result.FRight;
      aStartPosition := aStartPosition + Result.FPositionOffset;
    end;
  end else begin
    Result := self;
    while (Result.FParent<>nil) and (Result.FParent.FLeft=Result) do begin
      aStartPosition := aStartPosition - Result.FPositionOffset;
      Result := Result.FParent;
    end;
    // result is now a FRight son
    aStartPosition := aStartPosition - Result.FPositionOffset;
    Result := Result.FParent;
  end;
  if result <> nil then
    aSizesBeforeSum := aSizesBeforeSum - Result.FSize
  else
    aSizesBeforeSum := 0;
end;

function TSynSizedDifferentialAVLNode.Successor(var aStartPosition,
  aSizesBeforeSum: Integer): TSynSizedDifferentialAVLNode;
begin
  aSizesBeforeSum := aSizesBeforeSum + FSize;
  Result := FRight;
  if Result<>nil then begin
    aStartPosition := aStartPosition + Result.FPositionOffset;
    while (Result.FLeft<>nil) do begin
      Result := Result.FLeft;
      aStartPosition := aStartPosition + Result.FPositionOffset;
    end;
  end else begin
    Result := self;
    while (Result.FParent<>nil) and (Result.FParent.FRight=Result) do begin
      aStartPosition := aStartPosition - Result.FPositionOffset;
      Result := Result.FParent;
    end;
    // Result is now a FLeft son; result has a negative FPositionOffset
    aStartPosition := aStartPosition - Result.FPositionOffset;
    Result := Result.FParent;
  end;
end;

{ TSynSizedDifferentialAVLTree }

procedure TSynSizedDifferentialAVLTree.SetRoot(ANode: TSynSizedDifferentialAVLNode);
begin
  fRoot := ANode;
  if ANode <> nil then ANode.FParent := nil;
end;

procedure TSynSizedDifferentialAVLTree.SetRoot(ANode: TSynSizedDifferentialAVLNode;
  anAdjustChildPosOffset: Integer);
begin
  fRoot := ANode;
  if ANode <> nil then begin
    ANode.FParent := nil;
    ANode.FPositionOffset := ANode.FPositionOffset + anAdjustChildPosOffset;
  end;
end;

procedure TSynSizedDifferentialAVLTree.DisposeNode(var ANode: TSynSizedDifferentialAVLNode);
begin
  FreeAndNil(ANode);
end;

function TSynSizedDifferentialAVLTree.InsertNode(ANode: TSynSizedDifferentialAVLNode): Integer;
var
  current: TSynSizedDifferentialAVLNode;
  rStartPosition, rSizesBeforeSum: Integer;
  ALine, ACount: Integer;
begin
  if fRoot = nil then begin
    SetRoot(ANode, -fRootOffset);
    Result := 0;
    exit;
  end;

  ALine := ANode.FPositionOffset;
  ACount := ANode.FSize;

  current := fRoot;
  rStartPosition := fRootOffset;
  rSizesBeforeSum := 0;

  while (current <> nil) do begin
    rStartPosition := rStartPosition + current.FPositionOffset;

    if ALine < rStartPosition then begin
      (* *** New block goes to the Fleft *** *)
      if current.FLeft <> nil Then begin
        current := current.FLeft;
        continue;
      end
      else begin // insert as FLeft
        current.AdjustParentLeftCount(ACount);
        current.SetLeftChild(ANode, -rStartPosition, ANode.FSize);
        BalanceAfterInsert(ANode);
        break;
      end;
    end;

    rSizesBeforeSum := rSizesBeforeSum + current.FLeftSizeSum;

    if ALine = rStartPosition then begin
      // Should not happen // did happen when nodes with 0 lines where re-inserrted, after editor-delete-lines
      debugln(['Droping Foldnode / Already exists. Startline=', rStartPosition,' LineCount=',ACount]);
      FreeAndNil(ANode);
      break;
    end

    else begin
      rSizesBeforeSum := rSizesBeforeSum + current.FSize;
      if current.FRight <> nil then begin
        current := current.FRight;
        continue;
      end
      else begin  // insert to the Fright - no nesting
        current.AdjustParentLeftCount(ACount);
        current.SetRightChild(ANode, -rStartPosition);
        BalanceAfterInsert(ANode);
        break;
      end;
    end;
  end; // while

  Result := rSizesBeforeSum;
end;

procedure TSynSizedDifferentialAVLTree.RemoveNode(ANode: TSynSizedDifferentialAVLNode);
var OldParent, Precessor, PrecOldParent, PrecOldLeft,
  OldSubTree: TSynSizedDifferentialAVLNode;
  OldBalance, PrecOffset, PrecLeftCount: integer;

begin
  if ((ANode.FLeft<>nil) and (ANode.FRight<>nil)) then begin
    PrecOffset := 0;
//    PrecOffset := ANode.FPositionOffset;
    Precessor := ANode.FLeft;
    while (Precessor.FRight<>nil) do begin
      PrecOffset := PrecOffset + Precessor.FPositionOffset;
      Precessor := Precessor.FRight;
    end;
(*                            *OR*
 PnL              PnL
   \               \
   Precessor       Anode
   /               /
  *               *                     PnL             PnL
 /               /                        \               \
AnL   AnR       AnL      AnR        Precessor   AnR       AnL      AnR
  \   /           \      /                  \   /           \      /
   Anode          Precessor()               Anode          Precessor()
*)
    OldBalance := ANode.FBalance;
    ANode.FBalance     := Precessor.FBalance;
    Precessor.FBalance := OldBalance;

    // Successor.FLeft = nil
    PrecOldLeft   := Precessor.FLeft;
    PrecOldParent := Precessor.FParent;

    if (ANode.FParent<>nil)
    then ANode.FParent.ReplaceChild(ANode, Precessor, PrecOffset + ANode.FPositionOffset)
    else SetRoot(Precessor, PrecOffset + ANode.FPositionOffset);

    Precessor.SetRightChild(ANode.FRight,
                           +ANode.FPositionOffset-Precessor.FPositionOffset);

    PrecLeftCount := Precessor.FLeftSizeSum;
    // ANode.FRight will be empty  // ANode.FLeft will be Succesor.FLeft
    if (PrecOldParent = ANode) then begin
      // Precessor is Fleft son of ANode
      // set ANode.FPositionOffset=0 => FPositionOffset for the Prec-Children is already correct;
      Precessor.SetLeftChild(ANode, -ANode.FPositionOffset,
                             PrecLeftCount + ANode.FSize);
      ANode.SetLeftChild(PrecOldLeft, 0, PrecLeftCount);
    end else begin
      // at least one node between ANode and Precessor ==> Precessor = PrecOldParent.FRight
      Precessor.SetLeftChild(ANode.FLeft, +ANode.FPositionOffset - Precessor.FPositionOffset,
                             ANode.FLeftSizeSum + ANode.FSize - Precessor.FSize);
      PrecOffset:=PrecOffset + ANode.FPositionOffset - Precessor.FPositionOffset;
      // Set Anode.FPositionOffset, so ANode movesinto position of Precessor;
      PrecOldParent.SetRightChild(ANode, - ANode.FPositionOffset -  PrecOffset);
      ANode.SetLeftChild(PrecOldLeft, 0, PrecLeftCount);
    end;

    ANode.FRight := nil;
  end;

  if (ANode.FRight<>nil) then begin
    OldSubTree := ANode.FRight;
    ANode.FRight := nil;
  end
  else if (ANode.FLeft<>nil) then begin
    OldSubTree := ANode.FLeft;
    ANode.FLeft := nil;
  end
  else OldSubTree := nil;

  OldParent := ANode.FParent;
  ANode.FParent := nil;
  ANode.FLeft := nil;
  ANode.FRight := nil;
  ANode.FBalance := 0;
  ANode.FLeftSizeSum := 0;
  // nested???

  if (OldParent<>nil) then begin      // Node has Fparent
    if OldParent.ReplaceChild(ANode, OldSubTree, ANode.FPositionOffset) = rplcLeft
    then begin
      Inc(OldParent.FBalance);
      OldParent.AdjustLeftCount(-ANode.FSize);
    end
    else begin
      Dec(OldParent.FBalance);
      OldParent.AdjustParentLeftCount(-ANode.FSize);
    end;
    BalanceAfterDelete(OldParent);
  end
  else SetRoot(OldSubTree, ANode.FPositionOffset);
end;

procedure TSynSizedDifferentialAVLTree.BalanceAfterInsert(ANode: TSynSizedDifferentialAVLNode);
var
  OldParent, OldParentParent, OldRight, OldRightLeft, OldRightRight, OldLeft,
  OldLeftLeft, OldLeftRight: TSynSizedDifferentialAVLNode;
  tmp : integer;
begin
  OldParent := ANode.FParent;
  if (OldParent=nil) then exit;

  if (OldParent.FLeft=ANode) then begin
    (* *** Node is left son *** *)
    dec(OldParent.FBalance);
    if (OldParent.FBalance=0) then exit;
    if (OldParent.FBalance=-1) then begin
      BalanceAfterInsert(OldParent);
      exit;
    end;

    // OldParent.FBalance=-2
    if (ANode.FBalance=-1) then begin
      (* ** single rotate ** *)
      (*  []
           \
           []  ORight                     []    ORight    []
            \   /                          \      \       /
            ANode(-1)  []        =>        []     OldParent(0)
               \       /                    \     /
               OldParent(-2)                 ANode(0)
      *)
      OldRight := ANode.FRight;
      OldParentParent := OldParent.FParent;
      (* ANode moves into position of OldParent *)
      if (OldParentParent<>nil)
      then OldParentParent.ReplaceChild(OldParent, ANode, OldParent.FPositionOffset)
      else SetRoot(ANode, OldParent.FPositionOffset);

      (* OldParent moves under ANode, replacing Anode.FRight, which moves under OldParent *)
      ANode.SetRightChild(OldParent, -ANode.FPositionOffset );
      OldParent.SetLeftChild(OldRight, -OldParent.FPositionOffset, OldParent.FLeftSizeSum - ANode.FSize - ANode.FLeftSizeSum);

      ANode.FBalance := 0;
      OldParent.FBalance := 0;
      (* ** END single rotate ** *)
    end
    else begin  // ANode.FBalance = +1
      (* ** double rotate ** *)
      OldParentParent := OldParent.FParent;
      OldRight := ANode.FRight;
      OldRightLeft := OldRight.FLeft;
      OldRightRight := OldRight.FRight;

      (* OldRight moves into position of OldParent *)
      if (OldParentParent<>nil)
      then OldParentParent.ReplaceChild(OldParent, OldRight, OldParent.FPositionOffset + ANode.FPositionOffset)
      else SetRoot(OldRight, OldParent.FPositionOffset + ANode.FPositionOffset);        // OldParent was root node. new root node

      OldRight.SetRightChild(OldParent, -OldRight.FPositionOffset);
      OldRight.SetLeftChild(ANode, OldParent.FPositionOffset, OldRight.FLeftSizeSum + ANode.FLeftSizeSum + ANode.FSize);
      ANode.SetRightChild(OldRightLeft, -ANode.FPositionOffset);
      OldParent.SetLeftChild(OldRightRight, -OldParent.FPositionOffset, OldParent.FLeftSizeSum - OldRight.FLeftSizeSum - OldRight.FSize);

      // balance
      if (OldRight.FBalance<=0)
      then ANode.FBalance := 0
      else ANode.FBalance := -1;
      if (OldRight.FBalance=-1)
      then OldParent.FBalance := 1
      else OldParent.FBalance := 0;
      OldRight.FBalance := 0;
      (* ** END double rotate ** *)
    end;
    (* *** END Node is left son *** *)
  end
  else begin
    (* *** Node is right son *** *)
    Inc(OldParent.FBalance);
    if (OldParent.FBalance=0) then exit;
    if (OldParent.FBalance=+1) then begin
      BalanceAfterInsert(OldParent);
      exit;
    end;

    // OldParent.FBalance = +2
    if(ANode.FBalance=+1) then begin
      (* ** single rotate ** *)
      OldLeft := ANode.FLeft;
      OldParentParent := OldParent.FParent;

      if (OldParentParent<>nil)
      then  OldParentParent.ReplaceChild(OldParent, ANode, OldParent.FPositionOffset)
      else SetRoot(ANode, OldParent.FPositionOffset);

      (* OldParent moves under ANode, replacing Anode.FLeft, which moves under OldParent *)
      ANode.SetLeftChild(OldParent, -ANode.FPositionOffset, ANode.FLeftSizeSum + OldParent.FSize + OldParent.FLeftSizeSum);
      OldParent.SetRightChild(OldLeft, -OldParent.FPositionOffset);

      ANode.FBalance := 0;
      OldParent.FBalance := 0;
      (* ** END single rotate ** *)
    end
    else begin  // Node.Balance = -1
      (* ** double rotate ** *)
      OldLeft := ANode.FLeft;
      OldParentParent := OldParent.FParent;
      OldLeftLeft := OldLeft.FLeft;
      OldLeftRight := OldLeft.FRight;

      (* OldLeft moves into position of OldParent *)
      if (OldParentParent<>nil)
      then  OldParentParent.ReplaceChild(OldParent, OldLeft, OldParent.FPositionOffset + ANode.FPositionOffset)
      else SetRoot(OldLeft, OldParent.FPositionOffset + ANode.FPositionOffset);

      tmp := OldLeft.FLeftSizeSum;
      OldLeft.SetLeftChild (OldParent, -OldLeft.FPositionOffset, tmp + OldParent.FLeftSizeSum + OldParent.FSize);
      OldLeft.SetRightChild(ANode, OldParent.FPositionOffset);

      OldParent.SetRightChild(OldLeftLeft, -OldParent.FPositionOffset);
      ANode.SetLeftChild(OldLeftRight, -ANode.FPositionOffset, ANode.FLeftSizeSum - tmp - OldLeft.FSize);

      // Balance
      if (OldLeft.FBalance>=0)
      then ANode.FBalance := 0
      else ANode.FBalance := +1;
      if (OldLeft.FBalance=+1)
      then OldParent.FBalance := -1
      else OldParent.FBalance := 0;
      OldLeft.FBalance := 0;
      (* ** END double rotate ** *)
    end;
  end;
end;

procedure TSynSizedDifferentialAVLTree.BalanceAfterDelete(ANode: TSynSizedDifferentialAVLNode);
var
  OldParent, OldRight, OldRightLeft, OldLeft, OldLeftRight,
  OldRightLeftLeft, OldRightLeftRight, OldLeftRightLeft, OldLeftRightRight: TSynSizedDifferentialAVLNode;
  tmp: integer;
begin
  if (ANode=nil) then exit;
  if ((ANode.FBalance=+1) or (ANode.FBalance=-1)) then exit;
  OldParent := ANode.FParent;
  if (ANode.FBalance=0) then begin
    // Treeheight has decreased by one
    if (OldParent<>nil) then begin
      if(OldParent.FLeft=ANode) then
        Inc(OldParent.FBalance)
      else
        Dec(OldParent.FBalance);
      BalanceAfterDelete(OldParent);
    end;
    exit;
  end;

  if (ANode.FBalance=-2) then begin
    // Node.Balance=-2
    // Node is overweighted to the left
    (*
          OLftRight
           /
        OLeft(<=0)
           \
             ANode(-2)
    *)
    OldLeft := ANode.FLeft;
    if (OldLeft.FBalance<=0) then begin
      // single rotate left
      OldLeftRight := OldLeft.FRight;

      if (OldParent<>nil)
      then OldParent.ReplaceChild(ANode, OldLeft, ANode.FPositionOffset)
      else SetRoot(OldLeft, ANode.FPositionOffset);

      OldLeft.SetRightChild(ANode, -OldLeft.FPositionOffset);
      ANode.SetLeftChild(OldLeftRight, -ANode.FPositionOffset, ANode.FLeftSizeSum - OldLeft.FSize - OldLeft.FLeftSizeSum);

      ANode.FBalance := (-1-OldLeft.FBalance);
      Inc(OldLeft.FBalance);

      BalanceAfterDelete(OldLeft);
    end else begin
      // OldLeft.FBalance = 1
      // double rotate left left
      OldLeftRight := OldLeft.FRight;
      OldLeftRightLeft := OldLeftRight.FLeft;
      OldLeftRightRight := OldLeftRight.FRight;

(*
 OLR-Left   OLR-Right
      \     /
      OldLeftRight          OLR-Left    OLR-Right
       /                       /            \
   OldLeft                 OldLeft         ANode
      \                         \           /
     ANode                       OldLeftRight
       |                            |
     OldParent                   OldParent  (or root)
*)
      if (OldParent<>nil)
      then OldParent.ReplaceChild(ANode, OldLeftRight, ANode.FPositionOffset + OldLeft.FPositionOffset)
      else SetRoot(OldLeftRight, ANode.FPositionOffset + OldLeft.FPositionOffset);

      OldLeftRight.SetRightChild(ANode, -OldLeftRight.FPositionOffset);
      OldLeftRight.SetLeftChild(OldLeft, ANode.FPositionOffset, OldLeftRight.FLeftSizeSum + OldLeft.FLeftSizeSum + OldLeft.FSize);
      OldLeft.SetRightChild(OldLeftRightLeft, -OldLeft.FPositionOffset);
      ANode.SetLeftChild(OldLeftRightRight,  -ANode.FPositionOffset, ANode.FLeftSizeSum - OldLeftRight.FLeftSizeSum - OldLeftRight.FSize);

      if (OldLeftRight.FBalance<=0)
      then OldLeft.FBalance := 0
      else OldLeft.FBalance := -1;
      if (OldLeftRight.FBalance>=0)
      then ANode.FBalance := 0
      else ANode.FBalance := +1;
      OldLeftRight.FBalance := 0;

      BalanceAfterDelete(OldLeftRight);
    end;
  end else begin
    // Node is overweighted to the right
    OldRight := ANode.FRight;
    if (OldRight.FBalance>=0) then begin
      // OldRight.FBalance=={0 or -1}
      // single rotate right
      OldRightLeft := OldRight.FLeft;

      if (OldParent<>nil)
      then OldParent.ReplaceChild(ANode, OldRight, ANode.FPositionOffset)
      else SetRoot(OldRight, ANode.FPositionOffset);

      OldRight.SetLeftChild(ANode, -OldRight.FPositionOffset, OldRight.FLeftSizeSum + ANode.FSize + ANode.FLeftSizeSum);
      ANode.SetRightChild(OldRightLeft, -ANode.FPositionOffset);

      ANode.FBalance := (1-OldRight.FBalance);
      Dec(OldRight.FBalance);

      BalanceAfterDelete(OldRight);
    end else begin
      // OldRight.FBalance=-1
      // double rotate right left
      OldRightLeft := OldRight.FLeft;
      OldRightLeftLeft := OldRightLeft.FLeft;
      OldRightLeftRight := OldRightLeft.FRight;
      if (OldParent<>nil)
      then OldParent.ReplaceChild(ANode, OldRightLeft, ANode.FPositionOffset + OldRight.FPositionOffset)
      else SetRoot(OldRightLeft, ANode.FPositionOffset + OldRight.FPositionOffset);

      tmp := OldRightLeft.FLeftSizeSum;
      OldRightLeft.SetLeftChild(ANode, -OldRightLeft.FPositionOffset, tmp + ANode.FLeftSizeSum + ANode.FSize);
      OldRightLeft.SetRightChild(OldRight, ANode.FPositionOffset);

      ANode.SetRightChild(OldRightLeftLeft, -ANode.FPositionOffset);
      OldRight.SetLeftChild(OldRightLeftRight, -OldRight.FPositionOffset, OldRight.FLeftSizeSum - tmp - OldRightLeft.FSize);

      if (OldRightLeft.FBalance<=0)
      then ANode.FBalance := 0
      else ANode.FBalance := -1;
      if (OldRightLeft.FBalance>=0)
      then OldRight.FBalance := 0
      else OldRight.FBalance := +1;
      OldRightLeft.FBalance := 0;
      BalanceAfterDelete(OldRightLeft);
    end;
  end;
end;

function TSynSizedDifferentialAVLTree.CreateNode(APosition: Integer): TSynSizedDifferentialAVLNode;
begin
  Result := TSynSizedDifferentialAVLNode.Create;
end;

constructor TSynSizedDifferentialAVLTree.Create;
begin
  inherited;
  fRoot := nil;
  fRootOffset := 0;
end;

destructor TSynSizedDifferentialAVLTree.Destroy;
begin
  Clear;
  inherited Destroy;
end;

{$IFDEF SynDebug}
procedure TSynSizedDifferentialAVLTree.Debug;
  function debug2(ind, typ : String; ANode, AParent : TSynSizedDifferentialAVLNode; offset : integer) :integer;
  begin
    result := 0;
    if ANode = nil then exit;
    with ANode do
      DebugLn([Format('%-14s - Pos=%3d (offs=%3d)  %s',
                      [ind + typ,
                       offset + ANode.FPositionOffset,   ANode.FPositionOffset,
                       ANode.Debug])
              ]);
    if ANode.FParent <> AParent then DebugLn([ind,'* Bad parent']);

    Result := debug2(ind+'  ', 'L', ANode.FLeft, ANode, offset+ANode.FPositionOffset);
    If Result <> ANode.FLeftSizeSum then  debugln([ind,'   ***** Leftcount was ',Result, ' but should be ', ANode.FLeftSizeSum]);
    Result := Result + debug2(ind+'  ', 'R', ANode.FRight, ANode, offset+ANode.FPositionOffset);
    Result := Result + ANode.FSize;
  end;
begin
  debug2('', '**', fRoot, nil, 0);
end;
{$ENDIF}

procedure TSynSizedDifferentialAVLTree.Clear;
  procedure DeleteNode(var ANode: TSynSizedDifferentialAVLNode);
  begin
    if ANode.FLeft  <> nil then DeleteNode(ANode.FLeft);
    if ANode.FRight <> nil then DeleteNode(ANode.FRight);
    DisposeNode(ANode);
  end;
begin
  if FRoot <> nil then DeleteNode(FRoot);
  SetRoot(nil);
end;

function TSynSizedDifferentialAVLTree.First: TSynSizedDifferentialAVLNode;
begin
  Result := FRoot;
  if Result = nil then
    exit;
  while Result.FLeft <> nil do
    Result := Result.FLeft;
end;

function TSynSizedDifferentialAVLTree.Last: TSynSizedDifferentialAVLNode;
begin
  Result := FRoot;
  if Result = nil then
    exit;
  while Result.FRight <> nil do
    Result := Result.FRight;
end;

function TSynSizedDifferentialAVLTree.First(out aStartPosition,
  aSizesBeforeSum: Integer): TSynSizedDifferentialAVLNode;
begin
  Result := FRoot;
  aStartPosition := FRootOffset;
  aSizesBeforeSum := 0;
  if Result = nil then
    exit;

  aStartPosition := aStartPosition + Result.FPositionOffset;
  while Result.FLeft <> nil do begin
    Result := Result.FLeft;
    aStartPosition := aStartPosition + Result.FPositionOffset;
  end;
end;

function TSynSizedDifferentialAVLTree.Last(out aStartPosition,
  aSizesBeforeSum: Integer): TSynSizedDifferentialAVLNode;
begin
  Result := FRoot;
  aStartPosition := FRootOffset;
  aSizesBeforeSum := 0;
  if Result = nil then
    exit;

  aStartPosition := aStartPosition + Result.FPositionOffset;
  aSizesBeforeSum := aSizesBeforeSum + Result.FLeftSizeSum;
  while Result.FRight <> nil do begin
    aSizesBeforeSum := aSizesBeforeSum + Result.FSize;
    Result := Result.FRight;
    aStartPosition := aStartPosition + Result.FPositionOffset;
    aSizesBeforeSum := aSizesBeforeSum + Result.FLeftSizeSum;
  end;
end;

function TSynSizedDifferentialAVLTree.FindNodeAtLeftSize(ALeftSum: INteger; out
  aStartPosition, aSizesBeforeSum: Integer): TSynSizedDifferentialAVLNode;
begin
  Result := FRoot;
  aStartPosition := FRootOffset;
  aSizesBeforeSum := 0;
  if Result = nil then
    exit;

  aStartPosition := aStartPosition + Result.FPositionOffset;
  while Result <> nil do begin
    if ALeftSum < Result.FLeftSizeSum then begin
      Result := Result.FLeft;
      if Result <> nil then
        aStartPosition := aStartPosition + Result.FPositionOffset;
      continue;
    end;

    ALeftSum := ALeftSum - Result.FLeftSizeSum;
    aSizesBeforeSum := aSizesBeforeSum + Result.FLeftSizeSum;
    if ALeftSum < Result.FSize then begin
      break;
    end
    else begin
      ALeftSum := ALeftSum - Result.FSize;
      aSizesBeforeSum := aSizesBeforeSum + Result.FSize;
      Result := Result.FRight;
      if Result <> nil then
        aStartPosition := aStartPosition + Result.FPositionOffset;
      continue;
    end;
  end;
end;

function TSynSizedDifferentialAVLTree.FindNodeAtPosition(APosition: INteger;
  AMode: TSynSizedDiffAVLFindMode; out aStartPosition,
  aSizesBeforeSum: Integer): TSynSizedDifferentialAVLNode;
var
  NxtPrv: TSynSizedDifferentialAVLNode;
  NxtPrvBefore, NxtPrvPos: Integer;

  procedure Store(N: TSynSizedDifferentialAVLNode); inline;
  begin
    NxtPrv := N;
    NxtPrvBefore := aSizesBeforeSum;
    NxtPrvPos    := aStartPosition;
  end;

  function Restore: TSynSizedDifferentialAVLNode; inline;
  begin
    Result := NxtPrv;
    aSizesBeforeSum := NxtPrvBefore;
    aStartPosition  := NxtPrvPos;
  end;

  function CreateRoot: TSynSizedDifferentialAVLNode; inline;
  begin
    Result := CreateNode(APosition);
    if Result <> nil then
      Result.FPositionOffset := APosition;
    SetRoot(Result);
  end;

  function CreateLeft(N: TSynSizedDifferentialAVLNode; ACurOffs: Integer): TSynSizedDifferentialAVLNode; inline;
  begin
    Result := CreateNode(APosition);
    Result.FPositionOffset := APosition;
    N.SetLeftChild(Result, -ACurOffs);
    BalanceAfterInsert(Result);
    aStartPosition := APosition;
    aSizesBeforeSum := Result.GetSizesBeforeSum;
  end;

  function CreateRight(N: TSynSizedDifferentialAVLNode; ACurOffs: Integer): TSynSizedDifferentialAVLNode; inline;
  begin
    Result := CreateNode(APosition);
    Result.FPositionOffset := APosition;
    N.SetRightChild(Result, -ACurOffs);
    BalanceAfterInsert(Result);
    aStartPosition := APosition;
    aSizesBeforeSum := Result.GetSizesBeforeSum;
  end;

begin
  aSizesBeforeSum := 0;
  aStartPosition := 0;
  Store(nil);
  aStartPosition := fRootOffset;
  Result := FRoot;
  if (Result = nil) then begin
    if (AMode = afmCreate) then begin
      Result := CreateRoot;
      if Result <> nil then
        aStartPosition := aStartPosition + Result.FPositionOffset;
    end;
    exit;
  end;

  while (Result <> nil) do begin
    aStartPosition := aStartPosition + Result.FPositionOffset;

    if aStartPosition > APosition then begin
      if (Result.FLeft = nil) then begin
        case AMode of
          afmCreate: Result := CreateLeft(Result, aStartPosition);
          afmNil:    Result := nil;
          afmPrev:   Result := Restore; // Precessor
          //afmNext:   Result := ; //already contains next node
        end;
        break;
      end;
      if AMode = afmNext then
        Store(Result); // Successor
      Result := Result.FLeft;
    end

    else
    if APosition = aStartPosition then begin
      aSizesBeforeSum := aSizesBeforeSum + Result.FLeftSizeSum;
      break;
    end

    else
    if aStartPosition < APosition then begin
      aSizesBeforeSum := aSizesBeforeSum + Result.FLeftSizeSum;
      if (Result.FRight = nil) then begin
        case AMode of
          afmCreate: Result := CreateRight(Result, aStartPosition);
          afmNil:    Result := nil;
          afmNext:   Result := Restore; // Successor
          //afmPrev :  Result := ; //already contains prev node
        end;
        break;
      end;
      if AMode = afmPrev then
        Store(Result); // Precessor
      aSizesBeforeSum := aSizesBeforeSum + Result.FSize;
      Result := Result.FRight;
    end;
  end; // while
end;

procedure TSynSizedDifferentialAVLTree.AdjustForLinesInserted(AStartLine, ALineCount: Integer);
var
  Current: TSynSizedDifferentialAVLNode;
  CurrentLine: Integer;
begin
  Current := TSynSizedDifferentialAVLNode(fRoot);
  CurrentLine := FRootOffset;
  while (Current <> nil) do begin
    CurrentLine := CurrentLine + Current.FPositionOffset;

    if AStartLine <= CurrentLine then begin
      // move current node
      Current.FPositionOffset := Current.FPositionOffset + ALineCount;
      CurrentLine := CurrentLine + ALineCount;
      if Current.FLeft <> nil then
        Current.FLeft.FPositionOffset := Current.FLeft.FPositionOffset - ALineCount;
      Current := Current.FLeft;
    end
    else if AStartLine > CurrentLine then begin
      // The new lines are entirly behind the current node
      Current := Current.FRight;
    end
  end;
end;

procedure TSynSizedDifferentialAVLTree.AdjustForLinesDeleted(AStartLine, ALineCount: Integer);
var
  Current : TSynSizedDifferentialAVLNode;
  CurrentLine: Integer;
begin
  Current := TSynSizedDifferentialAVLNode(fRoot);
  CurrentLine := FRootOffset;;
//  LastLineToDelete := AStartLine + ALineCount - 1; // only valid for delete; ALineCount < 0

  while (Current <> nil) do begin
    CurrentLine := CurrentLine + Current.FPositionOffset;

    if (AStartLine = CurrentLine) then begin
      Current := Current.FRight;
      if Current = nil then
        break;
      assert((Current.FPositionOffset > ALineCount), 'TSynSizedDifferentialAVLTree.AdjustForLinesDeleted: (Current=nil) or (Current.FPositionOffset > ALineCount)');
      Current.FPositionOffset := Current.FPositionOffset - ALineCount;
      break;
      // ((AStartLine < CurrentLine) and (LastLineToDelete >= CurrentLine)) then begin
      //{ $IFDEF AssertSynMemIndex}
      //raise Exception.Create('TSynEditMarkLineList.AdjustForLinesDeleted node to remove');
      //{ $ENDIF}
    end

    else if AStartLine < CurrentLine then begin
      // move current node (includes Fright subtree / Fleft subtree needs eval)
      Current.FPositionOffset := Current.FPositionOffset - ALineCount;
      CurrentLine := CurrentLine - ALineCount;

      Current := Current.FLeft;
      if Current <> nil then
        Current.FPositionOffset := Current.FPositionOffset + ALineCount;
    end

    else if AStartLine > CurrentLine then begin
      // The deleted lines are entirly behind the current node
      Current := Current.FRight;
    end;
  end;
end;

end.

