//
// This unit is part of the GLScene Engine, http://glscene.org
//

unit GLS.Memo;

(* Memo for GLScene *)

interface

{$I GLScene.inc}

uses
  WinApi.Windows,
  WinApi.Messages,
  System.SysUtils,
  System.Classes,
  System.UITypes,
  VCL.Graphics,
  VCL.Controls, 
  VCL.Forms, 
  VCL.Dialogs, 
  VCL.ClipBrd,
  VCL.StdCtrls, 
  VCL.ExtCtrls;



type
  TBorderType = (btRaised, btLowered, btFlatRaised, btFlatLowered);
  TCommand = Integer;

  TCellSize = record
    W, H: integer;
  end;

  TCellPos = record
    X, Y: integer;
  end;

  TFullPos = record
    LineNo, Pos: integer;
  end;

  TLineProp = class
    FObject: TObject;
    FStyleNo: integer;
    FInComment: Boolean;
    FInBrackets: integer;
    FValidAttrs: Boolean;
    FCharAttrs: string;
  end;

  TCharStyle = class(TPersistent)
  private
    FTextColor, FBkColor: TColor;
    FStyle: TFontStyles;
  published

    property TextColor: TColor read FTextColor write FTextColor;
    property BkColor: TColor read FBkColor write FBkColor;
    property Style: TFontStyles read FStyle write FStyle;
  end;

  TStyleList = class(TList)
  private
    procedure CheckRange(Index: integer);
    function GetTextColor(Index: Integer): TColor;
    procedure SetTextColor(Index: Integer; Value: TColor);
    function GetBkColor(Index: Integer): TColor;
    procedure SetBkColor(Index: Integer; Value: TColor);
    function GetStyle(Index: Integer): TFontStyles;
    procedure SetStyle(Index: Integer; Value: TFontStyles);
  protected
    
    property TextColor[Index: Integer]: TColor read GetTextColor write
    SetTextColor;
    property BkColor[Index: Integer]: TColor read GetBkColor write SetBkColor;
    property Style[Index: Integer]: TFontStyles read GetStyle write SetStyle;
  public
    
    destructor Destroy; override;
    procedure Clear; override;
    procedure Delete(Index: Integer);
    function Add(ATextColor, ABkCOlor: TColor; AStyle: TFontStyles): Integer;
    procedure Change(Index: integer; ATextColor, ABkColor: TColor; AStyle:
      TFontStyles);
  end;

  TGLAbstractMemoObject = class(TObject)
  public
    function MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer):
      Boolean; virtual; abstract;
    function MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer):
      Boolean; virtual; abstract;
    function MouseMove(Shift: TShiftState; X, Y: Integer):
      Boolean; virtual; abstract;
  end;

  TGLSMemoScrollBar = class;

  TGLSMemoAbstractScrollableObject = class(TCustomControl)
  protected
    procedure DoScroll(Sender: TGLSMemoScrollBar; ByValue: integer);
      virtual; abstract;
    procedure DoScrollPage(Sender: TGLSMemoScrollBar; ByValue: integer);
      virtual; abstract;
  end;

  TGLSCustomMemo = class;

  TsbState =
    (
    sbsWait,
    sbsBack,
    sbsForward,
    sbsPageBack,
    sbsPageForward,
    sbsDragging
    );

  TGLSMemoScrollBar = class(TGLAbstractMemoObject)
  private
    FKind: TScrollBarKind;
    FParent: TGLSMemoAbstractScrollableObject;
    FLeft, FTop, FWidth, FHeight: integer;
    FTotal, FMaxPosition, FPosition: integer;
    FButtonLength: integer;
    FState: TsbState;
    FXOffset, FYOffset: integer;
    procedure SetParams(Index: integer; Value: integer);
    procedure SetState(Value: TsbState);
    function GetRect: TRect;
    function GetThumbRect: TRect;
    function GetBackRect: TRect;
    function GetMiddleRect: TRect;
    function GetForwardRect: TRect;
    function GetPgBackRect: TRect;
    function GetPgForwardRect: TRect;
  public
    constructor Create(AParent: TGLSMemoAbstractScrollableObject;
      AKind: TScrollBarKind);
    procedure PaintTo(ACanvas: TCanvas);
    function MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer):
      Boolean; override;
    function MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer):
      Boolean; override;
    function MouseMove(Shift: TShiftState; X, Y: Integer):
      Boolean; override;
    function MoveThumbTo(X, Y: Integer): integer;
    property Parent: TGLSMemoAbstractScrollableObject read FParent;
    property Kind: TScrollBarKind read FKind write FKind;
    property State: TsbState read FState write SetState;
    property Left: integer index 0 read FLeft write SetParams;
    property Top: integer index 1 read FTop write SetParams;
    property Width: integer index 2 read FWidth write SetParams;
    property Height: integer index 3 read FHeight write SetParams;
    property Total: integer index 4 read FTotal write SetParams;
    property MaxPosition: integer index 5 read FMaxPosition write SetParams;
    property Position: integer index 6 read FPosition write SetParams;
    property FullRect: TRect read GetRect;
    property ThumbRect: TRect read GetThumbRect;
    property BackRect: TRect read GetBackRect;
    property MiddleRect: TRect read GetMiddleRect;
    property ForwardRect: TRect read GetForwardRect;
    property PageForwardRect: TRect read GetPgForwardRect;
    property PageBackRect: TRect read GetPgBackRect;
  end;

  TGLSMemoStrings = class(TStringList)
  private
    FMemo: TGLSCustomMemo;
    FLockCount: integer;
    FDeleting: Boolean;
    procedure CheckRange(Index: integer);
    function GetLineProp(Index: integer): TLineProp;
    procedure SetLineStyle(Index: integer; Value: integer);
    function GetLineStyle(Index: integer): integer;
    function GetInComment(Index: Integer): Boolean;
    procedure SetInComment(Index: Integer; Value: Boolean);
    function GetInBrackets(Index: Integer): integer;
    procedure SetInBrackets(Index: Integer; Value: integer);
    function GetValidAttrs(Index: Integer): Boolean;
    procedure SetValidAttrs(Index: Integer; Value: Boolean);
    function GetCharAttrs(Index: Integer): string;
    procedure SetCharAttrs(Index: Integer; const Value: string);
  protected
    function GetObject(Index: Integer): TObject; override;
    procedure PutObject(Index: Integer; AObject: TObject); override;
    procedure SetUpdateState(Updating: Boolean); override;
    function CreateProp(Index: integer): TLineProp;
    property LineProp[Index: integer]: TLineProp read GetLineProp; //PALOFF
    property Style[Index: integer]: integer read GetLineStyle write
    SetLineStyle;
    property InComment[Index: integer]: Boolean read GetInComment write
    SetInComment;
    property InBrackets[Index: integer]: integer read GetInBrackets write
    SetInBrackets;
    property ValidAttrs[Index: integer]: Boolean read GetValidAttrs write
    SetValidAttrs;
    property CharAttrs[Index: integer]: string read GetCharAttrs write
    SetCharAttrs;
  public
    destructor Destroy; override;
    procedure Clear; override;
    function DoAdd(const S: string): Integer;
    function Add(const S: string): Integer; override;
    function AddObject(const S: string; AObject: TObject): Integer; override;
    procedure Assign(Source: TPersistent); override;
    procedure Insert(Index: Integer; const S: string); override;
    procedure DoInsert(Index: Integer; const S: string);
    procedure InsertObject(Index: Integer; const S: string; AObject: TObject);
      override;
    procedure Delete(Index: Integer); override;
    procedure LoadFromFile(const FileName: string); override;
  end;

  TGLSMemoGutter = class(TObject)
  private
    FMemo: TGLSCustomMemo;
    FLeft, FTop, FWidth, FHeight: integer;
    FColor: TColor;
    procedure SetParams(Index: integer; Value: integer);
    function GetRect: TRect;
  protected
    procedure PaintTo(ACanvas: TCanvas);
    procedure Invalidate;
  public
    property Left: integer index 0 read FLeft write SetParams;
    property Top: integer index 1 read FTop write SetParams;
    property Width: integer index 2 read FWidth write SetParams;
    property Height: integer index 3 read FHeight write SetParams;
    property FullRect: TRect read GetRect;
  end;

  TGLSMemoUndo = class
  private
    FMemo: TGLSCustomMemo;
    FUndoCurX0, FUndoCurY0: integer;
    FUndoCurX, FUndoCurY: integer;
    FUndoText: string;
  public
    constructor Create(ACurX0, ACurY0, ACurX, ACurY: integer; const AText: string);
    function Append(NewUndo: TGLSMemoUndo): Boolean; virtual;
    procedure Undo;
    procedure Redo;
    procedure PerformUndo; virtual; abstract;
    procedure PerformRedo; virtual; abstract;
    property UndoCurX0: integer read FUndoCurX0 write FUndoCurX0;
    property UndoCurY0: integer read FUndoCurY0 write FUndoCurY0;
    property UndoCurX: integer read FUndoCurX write FUndoCurX;
    property UndoCurY: integer read FUndoCurY write FUndoCurY;
  end;

  TGLSMemoInsCharUndo = class(TGLSMemoUndo)
  public
    function Append(NewUndo: TGLSMemoUndo): Boolean; override;
    procedure PerformUndo; override;
    procedure PerformRedo; override;
  end;

  TGLSMemoDelCharUndo = class(TGLSMemoUndo)
  private
    FIsBackspace: Boolean;
  public
    function Append(NewUndo: TGLSMemoUndo): Boolean; override;
    procedure PerformUndo; override;
    procedure PerformRedo; override;
    property IsBackspace: Boolean read FIsBackspace write FIsBackspace;
  end;

  TGLSMEmoDelLineUndo = class(TGLSMemoUndo)
  private
    FIndex: integer;
  public
    constructor Create(AIndex, ACurX0, ACurY0, ACurX, ACurY: integer; const AText:
      string);
    procedure PerformUndo; override;
    procedure PerformRedo; override;
  end;

  TGLSMemoSelUndo = class(TGLSMemoUndo)
  private
    FUndoSelStartX, FUndoSelStartY,
      FUndoSelEndX, FUndoSelEndY: integer;
  public
    property UndoSelStartX: integer read FUndoSelStartX write FUndoSelStartX;
    property UndoSelStartY: integer read FUndoSelStartY write FUndoSelStartY;
    property UndoSelEndX: integer read FUndoSelEndX write FUndoSelEndX;
    property UndoSelEndY: integer read FUndoSelEndY write FUndoSelEndY;
  end;

  TGLSMemoDeleteBufUndo = class(TGLSMemoSelUndo)
  public
    procedure PerformUndo; override;
    procedure PerformRedo; override;
  end;

  TGLSMemoPasteUndo = class(TGLSMemoUndo)
  public
    procedure PerformUndo; override;
    procedure PerformRedo; override;
  end;

  TGLSMemoUndoList = class(TList)
  private
    FPos: integer;
    FMemo: TGLSCustomMemo;
    FIsPerforming: Boolean;
    FLimit: integer;
  protected
    function Get(Index: Integer): TGLSMemoUndo;
    procedure SetLimit(Value: integer);
  public
    constructor Create;
    destructor Destroy; override;
    function Add(Item: Pointer): Integer;
    procedure Clear; override;
    procedure Delete(Index: Integer);
    procedure Undo;
    procedure Redo;
    property Items[Index: Integer]: TGLSMemoUndo read Get; default;
    property IsPerforming: Boolean read FIsPerforming write FIsPerforming;
    property Memo: TGLSCustomMemo read FMemo write FMemo;
    property Pos: integer read FPos write FPos;
    property Limit: integer read FLimit write SetLimit;
  end;

  //--------------------------------------------------------------

  TGutterClickEvent = procedure(Sender: TObject; LineNo: integer) of object;
  TGutterDrawEvent = procedure(Sender: TObject; ACanvas: TCanvas;
    LineNo: integer; rct: TRect) of object;
  TGetLineAttrsEvent = procedure(Sender: TObject; LineNo: integer;
    var Attrs: string) of object;
  TUndoChangeEvent = procedure(Sender: TObject;
    CanUndo, CanRedo: Boolean) of object;
  TScrollMode = (smAuto, smStrict);

  TGLSCustomMemo = class(TGLSMemoAbstractScrollableObject)
  private
    FAutoIndent: Boolean;
    FMargin: integer;
    FHiddenCaret, FCaretVisible: Boolean;
    FCellSize: TCellSize;
    FCurX, FCurY: integer;
    FLeftCol, FTopLine: integer;
    FTabSize: integer;
    FFont: TFont;
    FBkColor: TColor;
    FSelColor: TColor;
    FSelBkColor: TColor;
    FReadOnly: Boolean;
    FDelErase: Boolean;
    FLines: TStrings;
    FSelStartX, FSelStartY,
      FSelEndX, FSelEndY,
      FPrevSelX, FPrevSelY: integer;
    FScrollBars: System.UITypes.TScrollStyle;
    FScrollBarWidth: integer;
    FGutter: TGLSMemoGutter;
    FGutterWidth: integer;
    sbVert, sbHorz: TGLSMemoScrollBar;
    FStyles: TStyleList;
    FLineBitmap: TBitmap;
    FSelCharPos: TFullPos;
    FSelCharStyle: integer;
    FLeftButtonDown: Boolean;
    FScrollMode: TScrollMode;
    FUndoList: TGLSMemoUndoList;
    FFirstUndoList: TGLSMemoUndoList;
    FUndoLimit: integer;
    FLastMouseUpX,
      FLastMouseUpY: integer;
    FAfterDoubleClick: Boolean;
    // events
    FOnMoveCursor: TNotifyEvent;
    FOnChange: TNotifyEvent;
    FOnAttrChange: TNotifyEvent;
    FOnStatusChange: TNotifyEvent;
    FOnSelectionChange: TNotifyEvent;
    FOnGutterDraw: TGutterDrawEvent;
    FOnGutterClick: TGutterClickEvent;
    FOnGetLineAttrs: TGetLineAttrsEvent;
    FOnUndoChange: TUndoChangeEvent;
    FHideCursor: Boolean;
    procedure SetHiddenCaret(Value: Boolean);
    procedure SetScrollBars(Value: System.UITypes.TScrollStyle);
    procedure SetGutterWidth(Value: integer);
    procedure SetGutterColor(Value: TColor);
    function GetGutterColor: TColor;
    procedure SetCurX(Value: integer);
    procedure SetCurY(Value: integer);
    procedure SetFont(Value: TFont);
    procedure SetColor(Index: integer; Value: TColor);
    function GetSelStart: TPoint;
    function GetSelEnd: TPoint;
    procedure SetLines(ALines: TStrings);
    procedure SetLineStyle(Index: integer; Value: integer);
    function GetLineStyle(Index: integer): integer;
    function GetInComment(Index: integer): Boolean;
    procedure SetInComment(Index: integer; Value: Boolean);
    function GetInBrackets(Index: Integer): integer;
    procedure SetInBrackets(Index: Integer; Value: integer);
    function GetValidAttrs(Index: integer): Boolean;
    procedure SetValidAttrs(Index: integer; Value: Boolean);
    function GetCharAttrs(Index: integer): string;
    procedure SetCharAttrs(Index: integer; const Value: string);
    procedure ExpandSelection;
    function GetSelText: string;
    procedure SetSelText(const AValue: string);
    function GetSelLength: integer;
    procedure MovePage(dP: integer; Shift: TShiftState);
    procedure ShowCaret(State: Boolean);
    procedure MakeVisible;
    function GetVisible(Index: integer): integer;
    function MaxLength: integer;
    procedure WMSize(var Msg: TWMSize); message WM_SIZE;
    procedure WMGetDlgCode(var Msg: TWMGetDlgCode); message WM_GETDLGCODE;
    procedure WMEraseBkgnd(var Msg: TWmEraseBkgnd); message WM_ERASEBKGND;
    procedure WMSetCursor(var Msg: TWMSetCursor); message WM_SETCURSOR;
    procedure WMSetFocus(var Msg: TWMSetFocus); message WM_SETFOCUS;
    procedure WMKillFocus(var Msg: TWMSetFocus); message WM_KILLFOCUS;
    procedure WMMouseWheel(var Message: TMessage); message WM_MOUSEWHEEL;
    procedure MoveCursor(dX, dY: integer; Shift: TShiftState);
    procedure ResizeEditor;
    procedure ResizeScrollBars;
    procedure ResizeGutter;
    procedure DoCommand(cmd: TCommand; const AShift: TShiftState);
    procedure DrawLine(LineNo: integer);
    function IsLineVisible(LineNo: integer): Boolean;
    procedure FreshLineBitmap;
    procedure SetUndoLimit(Value: integer);
  protected
    procedure WndProc(var Message: TMessage); override;
    function EditorRect: TRect;
    function LineRangeRect(FromLine, ToLine: integer): TRect;
    function ColRangeRect(FromCol, ToCol: integer): TRect;
    procedure InvalidateLineRange(FromLine, ToLine: integer);
    function AddString(const S: string): integer;
    procedure InsertString(Index: integer; S: string);
    procedure GoHome(Shift: TShiftState);
    procedure GoEnd(Shift: TShiftState);
    procedure InsertChar(C: Char);
    procedure DeleteChar(OldX, OldY: integer);
    procedure DeleteLine(Index, OldX, OldY, NewX, NewY: integer; FixUndo: Boolean);
    procedure BackSpace;
    procedure BackSpaceWord;
    function IndentCurrLine: string;
    procedure NewLine;
    procedure CreateParams(var Params: TCreateParams); override;
    procedure Paint; override;
    procedure DrawMargin;
    procedure DrawGutter;
    procedure DrawScrollBars;
    procedure KeyDown(var Key: Word; Shift: TShiftState); override;
    procedure KeyPress(var Key: Char); override;
    procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
    procedure MouseMove(Shift: TShiftState; X, Y: Integer); override;
    procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
    procedure DblClick; override;
    procedure DoScroll(Sender: TGLSMemoScrollBar; ByValue: integer); override;
    procedure DoScrollPage(Sender: TGLSMemoScrollBar; ByValue: integer); override;
    property VisiblePosCount: integer index 0 read GetVisible;
    property VisibleLineCount: integer index 1 read GetVisible;
    property LastVisiblePos: integer index 2 read GetVisible;
    property LastVisibleLine: integer index 3 read GetVisible;
    procedure DeleteSelection(bRepaint: Boolean);
    procedure Changed(FromLine, ToLine: integer); virtual;
    procedure AttrChanged(LineNo: integer); virtual;
    procedure SelectionChanged; virtual;
    procedure StatusChanged; virtual;
    procedure ClearUndoList;
    procedure UndoChange;
    property AutoIndent: Boolean read FAutoIndent write FAutoIndent;
    property GutterWidth: integer read FGutterWidth write SetGutterWidth;
    property GutterColor: TColor read GetGutterColor write SetGutterColor;
    property ScrollBars: System.UITypes.TScrollStyle read FScrollBars write SetScrollBars default ssBoth;
    property Font: TFont read FFont write SetFont;
    property ReadOnly: Boolean read FReadOnly write FReadOnly;
    property Lines: TStrings read FLines write SetLines;
    property BkColor: TColor index 0 read FBkColor write SetColor;
    property SelColor: TColor index 1 read FSelColor write SetColor;
    property SelBkColor: TColor index 2 read FSelBkColor write SetColor;
    property HiddenCaret: Boolean read FHiddenCaret write SetHiddenCaret;
    property TabSize: integer read FTabSize write FTabSize;
    property ScrollMode: TScrollMode read FScrollMode write FScrollMode default smAuto;
    property UndoLimit: integer read FUndoLimit write SetUndoLimit;
    property HideCursor: Boolean read FHideCursor write FHideCursor;
    property InComment[Index: integer]: Boolean read GetInComment write SetInComment;
    property InBrackets[Index: integer]: integer read GetInBrackets write SetInBrackets;
    property ValidAttrs[Index: integer]: Boolean read GetValidAttrs write SetValidAttrs;
    property CharAttrs[Index: integer]: string read GetCharAttrs write SetCharAttrs;
    {events}
    property OnGutterClick: TGutterClickEvent read FOnGutterClick write FOnGutterClick;
    property OnGutterDraw: TGutterDrawEvent read FOnGutterDraw write FOnGutterDraw;
    property OnChange: TNotifyEvent read FOnChange write FOnChange;
    property OnMoveCursor: TNotifyEvent read FOnMoveCursor write FOnMoveCursor;
    property OnAttrChange: TNotifyEvent read FOnAttrChange write FOnAttrChange;
    property OnSelectionChange: TNotifyEvent read FOnSelectionChange write FOnSelectionChange;
    property OnStatusChange: TNotifyEvent read FOnStatusChange write FOnStatusChange;
    property OnGetLineAttrs: TGetLineAttrsEvent read FOnGetLineAttrs write FOnGetLineAttrs;
    property OnUndoChange: TUndoChangeEvent read FOnUndoChange write FOnUndoChange;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure CopyToClipBoard;
    procedure PasteFromClipBoard;
    procedure CutToClipBoard;
    procedure SelectLines(StartLine, EndLine: Integer);
    procedure SelectAll;
    property SelStart: TPoint read GetSelStart;
    property SelEnd: TPoint read GetSelEnd;
    property Selection: string read GetSelText write SetSelText;
    property SelLength: integer read GetSelLength;
    procedure ClearSelection;
    procedure Clear;
    procedure SetCursor(ACurX, ACurY: Integer);
    function SelectLine(LineNo, StyleNo: Integer): integer;
    procedure SelectChar(LineNo, Pos, StyleNo: Integer);
    function CellFromPos(X, Y: integer): TCellPos;
    function CharFromPos(X, Y: integer): TFullPos;
    function CellRect(ACol, ARow: integer): TRect;
    function LineRect(ARow: integer): TRect;
    function ColRect(ACol: integer): TRect;
    function CharStyleNo(LineNo, Pos: integer): integer;
    procedure InsertTemplate(AText: string);
    procedure UnSelectChar;
    procedure Undo;
    procedure Redo;
    function CanUndo: Boolean;
    function CanRedo: Boolean;
    function FindText(Text: string; Options: TFindOptions; Select: Boolean): Boolean;
    property CurX: integer read FCurX write SetCurX;
    property CurY: integer read FCurY write SetCurY;
    property DelErase: Boolean read FDelErase write FDelErase;
    property LineStyle[Index: integer]: integer read GetLineStyle write
    SetLineStyle;
    property Styles: TStyleList read FStyles;
    property UndoList: TGLSMemoUndoList read FUndoList write FUndoList;
  end;

  TGLSMemo = class(TGLSCustomMemo)
  published
    {TControl }
    property PopupMenu;
    {TCustomControl }
    property Align;
    property Enabled;
    property ShowHint;
    property TabOrder;
    property TabStop;
    property Visible;
    property ReadOnly;
    {TGLSCustomMemo }
    property AutoIndent;
    property GutterColor;
    property GutterWidth;
    property ScrollBars;
    property Font;
    property BkColor;
    property Selection;
    property SelColor;
    property SelBkColor;
    property Lines;
    property HiddenCaret;
    property TabSize;
    property ScrollMode;
    property UndoLimit;
    property DelErase;
    {Inherited events }
    property OnClick;
    property OnDblClick;
    property OnDragDrop;
    property OnDragOver;
    property OnEndDrag;
    property OnMouseDown;
    property OnMouseMove;
    property OnMouseUp;
    property OnStartDrag;
    property OnEnter;
    property OnExit;
    property OnKeyDown;
    property OnKeyPress;
    property OnKeyUp;
    {Events }
    property OnGutterDraw;
    property OnGutterClick;
    property OnChange;
    property OnMoveCursor;
    property OnAttrChange;
    property OnSelectionChange;
    property OnStatusChange;
    property OnGetLineAttrs;
    property OnUndoChange;
  end;

  TGLSMemoStringList = class(TStringList)
  private
    procedure ReadStrings(Reader: TReader);
    procedure WriteStrings(Writer: TWriter);
  protected
    procedure DefineProperties(Filer: TFiler); override;
  end;

  TDelimiters = TSysCharSet;
  TTokenType =
    (
    ttWord,
    ttBracket,
    ttSpecial,
    ttDelimiter,
    ttSpace,
    ttEOL,
    ttInteger,
    ttFloat,
    ttComment,
    ttOther,
    ttWrongNumber);

  //--------------------------------------------------------------
  //        SYNTAX MEMO - declaration
  //--------------------------------------------------------------
  TGLSSynHiMemo = class(TGLSCustomMemo)
  private

    FIsPainting: Boolean;
    FInComment: Boolean;

    FWordList: TGLSMemoStringList;
    FSpecialList: TGLSMemoStringList;
    FBracketList: TGLSMemoStringList;
    FDelimiters: TDelimiters;
    FInBrackets: integer;
    FLineComment: string;
    FMultiCommentLeft: string;
    FMultiCommentRight: string;
    FDelimiterStyle: TCharStyle;
    FCommentStyle: TCharStyle;
    FNumberStyle: TCharStyle;
    FDelimiterStyleNo,
      FCommentStyleNo,
      FNumberStyleNo: integer;
    FCaseSensitive: Boolean;
    function GetToken(const S: string; var From: integer;
      out TokenType: TTokenType; out StyleNo: integer): string;
    procedure SetWordList(Value: TGLSMemoStringList);
    procedure SetSpecialList(Value: TGLSMemoStringList);
    procedure SetBracketList(Value: TGLSMemoStringList);
    procedure FindLineAttrs(Sender: TObject; LineNo: integer; var Attrs:
      string);
    procedure SetStyle(Index: integer; Value: TCharStyle);
    procedure SetCaseSensitive(Value: Boolean);
  protected
    procedure Paint; override;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure AddWord(StyleNo: integer; const ArrS: array of string);
    procedure AddSpecial(StyleNo: integer; const ArrS: array of string);
    procedure AddBrackets(StyleNo: integer; const ArrS: array of string);
    property Delimiters: TDelimiters read FDelimiters write FDelimiters;
  published
    {TControl}
    property PopupMenu;
    {TCustomControl}
    property Align;
    property Enabled;
    property ShowHint;
    property TabOrder;
    property TabStop;
    property Visible;
    property ReadOnly;
    {TGLSCustomMemo}
    property AutoIndent;
    property GutterColor;
    property GutterWidth;
    property ScrollBars;
    property Font;
    property BkColor;
    property SelColor;
    property SelBkColor;
    property Lines;
    property HiddenCaret;
    property TabSize;
    property ScrollMode;
    property UndoLimit;
    property DelErase;
    {Inherited events }
    property OnClick;
    property OnDblClick;
    property OnDragDrop;
    property OnDragOver;
    property OnEndDrag;
    property OnMouseDown;
    property OnMouseMove;
    property OnMouseUp;
    property OnStartDrag;
    property OnEnter;
    property OnExit;
    property OnKeyDown;
    property OnKeyPress;
    property OnKeyUp;
    {Events }
    property OnGutterClick;
    property OnGutterDraw;
    property OnChange;
    property OnMoveCursor;
    property OnSelectionChange;
    property OnStatusChange;
    property OnUndoChange;
    {TGLSSyntaxMemo }
    property LineComment: string read FLineComment write FLineComment;
    property MultiCommentLeft: string read FMultiCommentLeft write FMultiCommentLeft;
    property MultiCommentRight: string read FMultiCommentRight write FMultiCommentRight;
    property WordList: TGLSMemoStringList read FWordList write SetWordList;
    property SpecialList: TGLSMemoStringList read FSpecialList write SetSpecialList;
    property BracketList: TGLSMemoStringList read FBracketList write SetBracketList;
    property DelimiterStyle: TCharStyle index 0 read FDelimiterStyle write SetStyle;
    property CommentStyle: TCharStyle index 1 read FCommentStyle write SetStyle;
    property NumberStyle: TCharStyle index 2 read FNumberStyle write SetStyle;
    property CaseSensitive: Boolean read FCaseSensitive write SetCaseSensitive;
  end;

procedure Border(Canvas: TCanvas; const rct: TRect; BorderType: TBorderType);

//==========================================================
implementation
//==========================================================

const
  cmDelete = VK_DELETE;
  cmBackSpace = VK_BACK;
  cmWordBackSpace = 127; // Ctrl-BackSpace
  cmNewLine = VK_RETURN;
  cmHome = VK_HOME;
  cmEnd = VK_END;
  cmPageUp = VK_PRIOR;
  cmPageDown = VK_NEXT;
  cmInsert = VK_INSERT;
  cmDelLine = 25; // Ctrl-Y
  cmCopy = 3; // Ctrl-C
  cmCut = 24; // Ctrl-X
  cmPaste = 22; // Ctrl-V

resourcestring
  SObjectsNotSupported = 'Linked object not supported';

var
  bmScrollBarFill: TBitmap;
  bmScrollBarUp: TBitmap;
  bmScrollBarDown: TBitmap;
  bmScrollBarLeft: TBitmap;
  bmScrollBarRight: TBitmap;

  fIntelliWheelSupport: Boolean; // True if IntelliMouse + wheel enabled
  fIntelliMessage: UINT; // message sent from mouse on wheel roll
  fIntelliScrollLines: Integer; // number of lines to scroll per wheel roll

// ---------------------Helper functions 

function PointInRect(const P: TPoint; const rct: TRect): Boolean; inline;
begin
  with rct do
    Result := (Left <= P.X) and (Top <= P.Y) and
      (Right >= P.X) and (Bottom >= P.Y);
end;

procedure Swap(var I1, I2: integer); inline;
var
  temp: integer;
begin
  temp := I1;
  I1 := I2;
  I2 := temp;
end;

procedure OrderPos(var StartX, StartY, EndX, EndY: integer); inline;
begin
  if (EndY < StartY) or
    ((EndY = StartY) and (EndX < StartX)) then
  begin
    Swap(StartX, EndX);
    Swap(StartY, EndY);
  end;
end;

function TotalRect(const rct1, rct2: TRect): TRect; inline;
begin
  Result := rct1;
  with Result do
  begin
    if rct2.Left < Left then
      Left := rct2.Left;
    if rct2.Top < Top then
      Top := rct2.Top;
    if rct2.Right > Right then
      Right := rct2.Right;
    if rct2.Bottom > Bottom then
      Bottom := rct2.Bottom;
  end;
end;



// ---------------------TGLSCustomMemo functions

procedure TGLSCustomMemo.WndProc(var Message: TMessage);
  function GetShiftState: Integer;
  begin
    Result := 0;
    if GetAsyncKeyState(vk_Shift) < 0 then
      Result := Result or mk_Shift;
    if GetAsyncKeyState(vk_Control) < 0 then
      Result := Result or mk_Control;
    if GetAsyncKeyState(vk_LButton) < 0 then
      Result := Result or mk_LButton;
    if GetAsyncKeyState(vk_RButton) < 0 then
      Result := Result or mk_RButton;
    if GetAsyncKeyState(vk_MButton) < 0 then
      Result := Result or mk_MButton;
  end;
  //---------------------------------------------------
begin
  if (Message.Msg = fIntelliMessage) and (fIntelliMessage <> wm_MouseWheel) then
  begin
    PostMessage(Handle, wm_MouseWheel, MakeLong(GetShiftState, Message.wParam),
      Message.lParam);
  end
  else
    inherited;
end;

//------------------------------------------------
//    INTELLIMOUSE INIT
//------------------------------------------------

procedure IntelliMouseInit;
var
  hWndMouse: hWnd;
  mQueryScrollLines: UINT;
  //--------------------------------------------
  function NativeMouseWheelSupport: Boolean;
  var
    ver: TOSVersionInfo;
  begin
    Result := False;
    ver.dwOSVersionInfoSize := sizeof(ver);
    // For Windows 98, assume dwMajorVersion = 5 (It's 4 for W95)
    // For NT, we need 4.0 or better.
    if GetVersionEx(ver) then
      case ver.dwPlatformID of
        ver_Platform_Win32_Windows: Result := ver.dwMajorVersion >= 5;
        ver_Platform_Win32_NT: Result := ver.dwMajorVersion >= 4;
      end;
    { Quick and dirty temporary hack for Windows 98 beta 3 }
    if (not Result) and (ver.szCSDVersion = ' Beta 3') then
      Result := True;
  end;
  //--------------------------------------------
begin
  if NativeMouseWheelSupport then
  begin
    fIntelliWheelSupport := Boolean(GetSystemMetrics(sm_MouseWheelPresent));
    SystemParametersInfo(spi_GetWheelScrollLines, 0, @fIntelliScrollLines, 0);
    fIntelliMessage := wm_MouseWheel;
  end
  else
  begin
    { Look for hidden mouse window }
    hWndMouse := FindWindow('MouseZ', 'Magellan MSWHEEL');
    if hWndMouse <> 0 then
    begin
      { We're in business - get the scroll line info }
      fIntelliWheelSupport := True;
      mQueryScrollLines := RegisterWindowMessage('MSH_SCROLL_LINES_MSG');
      fIntelliScrollLines := SendMessage(hWndMouse, mQueryScrollLines, 0, 0);
      { Finally, get the custom mouse message as well }
      fIntelliMessage := RegisterWindowMessage('MSWHEEL_ROLLMSG');
    end;
  end;
  if (fIntelliScrollLines < 0) or (fIntelliScrollLines > 100) then
    fIntelliScrollLines := 3;
end;

//------------------------------------------------
//    WM MOUSE WHEEL
//------------------------------------------------

procedure TGLSCustomMemo.WMMouseWheel(var Message: TMessage);
{$J+}
{$IFOPT R+} {$DEFINE StoreRangeCheck} {$ENDIF} {$R-}
const
  Delta: SmallInt = 0;
begin
  Delta := Delta + SmallInt(HiWord(Message.wParam));
  while Abs(Delta) >= 120 do
  begin
    if Delta < 0 then
    begin
      DoScroll(sbVert, fIntelliScrollLines);
      Delta := Delta + 120;
    end
    else
    begin
      DoScroll(sbVert, -fIntelliScrollLines);
      Delta := Delta - 120;
    end;
  end;
end;
{$J-}
{$IFDEF StoreRangeCheck} {$R+} {$UNDEF StoreRangeCheck} {$ENDIF}

//--------------------------------------------------------------
//        SET CURSOR
//--------------------------------------------------------------

procedure TGLSCustomMemo.SetCursor(ACurX, ACurY: Integer);
begin
  ClearSelection;
  CurX := 0;
  CurY := ACurY;
  CurX := ACurX;
end;

//--------------------------------------------------------------
//        SELECT LINE, CHAR
//--------------------------------------------------------------

function TGLSCustomMemo.SelectLine(LineNo, StyleNo: Integer): integer;
var
  rct: TRect;
begin
  Result := LineStyle[LineNo];
  LineStyle[LineNo] := StyleNo;
  rct := LineRect(LineNo);
  InvalidateRect(Handle, @rct, True);
end;

procedure TGLSCustomMemo.SelectLines(StartLine, EndLine: Integer);
var
  rct: TRect;
begin
  FSelStartX := 0;
  FSelStartY := StartLine;
  FSelEndX := Length(Lines[EndLine]);
  FSelEndY := EndLine;
  rct := LineRangeRect(FSelStartY, FSelEndY);
  SelectionChanged;
  InvalidateRect(Handle, @rct, true);
end;

procedure TGLSCustomMemo.SelectChar(LineNo, Pos, StyleNo: Integer);
var
  rct: TRect;
begin
  UnselectChar;
  FSelCharPos.LineNo := LineNo;
  FSelCharPos.Pos := Pos;
  FSelCharStyle := StyleNo;
  rct := LineRect(LineNo);
  InvalidateRect(Handle, @rct, True);
end;

procedure TGLSCustomMemo.UnSelectChar;
var
  rct: TRect;
begin
  with FSelCharPos do
  begin
    if LineNo < 0 then
      Exit;
    rct := LineRect(LineNo);
    LineNo := -1;
    Pos := -1;
  end;
  FSelCharStyle := -1;
  InvalidateRect(Handle, @rct, True);
end;

//--------------------------------------------------------------
//        CLEAR
//--------------------------------------------------------------

procedure TGLSCustomMemo.Clear;
begin
  CurY := 0;
  CurX := 0;
  FLeftCol := 0;
  FTopLine := 0;
  Lines.Clear;
  TGLSMemoStrings(Lines).DoAdd('');
  ClearUndoList;
  Invalidate;
end;

//--------------------------------------------------------------
//        SELECT ALL
//--------------------------------------------------------------

procedure TGLSCustomMemo.SelectAll;
begin
  FSelStartY := 0;
  FSelStartX := 0;
  FSelEndY := Lines.Count - 1;
  FSelEndX := Length(Lines[Lines.Count - 1]);
  Invalidate;
end;

//-----------------------------------------------------------
//   SET CLIPBOARD CODE PAGE
//-----------------------------------------------------------

procedure SetClipboardCodePage(const CodePage: longint);
var
  Data: THandle;
  DataPtr: Pointer;
begin
  // Define new code page for clipboard
  Data := GlobalAlloc(GMEM_MOVEABLE + GMEM_DDESHARE, 4);
  try
    DataPtr := GlobalLock(Data);
    try
      Move(CodePage, DataPtr^, 4);
      SetClipboardData(CF_LOCALE, Data);
    finally
      GlobalUnlock(Data);
    end;
  except
    GlobalFree(Data);
  end;
end;

//--------------------------------------------------------------
//        COPY TO CLIPBOARD
//--------------------------------------------------------------

procedure CopyStringToClipboard(const Value: string);
const
  RusLocale = (SUBLANG_DEFAULT shl $A) or LANG_RUSSIAN;
begin
  Clipboard.Open;
  SetClipboardCodePage(RusLocale);
  try
    Clipboard.AsText := Value;
  finally
    SetClipboardCodePage(RusLocale);
    Clipboard.Close;
  end;
end;

procedure TGLSCustomMemo.CopyToClipBoard;
begin
  CopyStringToClipboard(GetSelText);
end;
//--------------------------------------------------------------
//        PASTE FROM CLIPBOARD
//--------------------------------------------------------------

procedure TGLSCustomMemo.PasteFromClipBoard;
var
  H, len: integer;
  Buff: string;
begin
  H := ClipBoard.GetAsHandle(CF_TEXT);
  len := GlobalSize(H);
  if len = 0 then
    Exit;

  SetLength(Buff, len);
  SetLength(Buff, ClipBoard.GetTextBuf(PChar(Buff), len));
  AdjustLineBreaks(Buff);

  SetSelText(Buff);
end;

//--------------------------------------------------------------
//        DELETE SELECTION
//--------------------------------------------------------------

procedure TGLSCustomMemo.DeleteSelection(bRepaint: Boolean);
var
  xSelStartX, xSelStartY, xSelEndX, xSelEndY: integer;
  i, len: integer;
  OldX, OldY: integer;
  S1, S2, S, AddSpaces: string;
  Undo: TGLSMemoDeleteBufUndo;
begin
  if (FSelStartY = FSelEndY) and (FSelStartX = FSelEndX) then
    Exit;

  OldX := CurX;
  OldY := CurY;
  xSelStartX := FSelStartX;
  xSelStartY := FSelStartY;
  xSelEndX := FSelEndX;
  xSelEndY := FSelEndY;
  OrderPos(xSelStartX, xSelStartY, xSelEndX, xSelEndY);

  if xSelStartY = xSelEndY then
  begin
    S1 := Copy(Lines[xSelStartY], xSelStartX + 1, xSelEndX - xSelStartX);
    S2 := '';
    AddSpaces := '';
  end
  else
  begin
    len := Length(Lines[xSelStartY]);
    S1 := Copy(Lines[xSelStartY], xSelStartX + 1, len);
    AddSpaces := StringOfChar(' ', xSelStartX - len);
    S2 := Copy(Lines[xSelEndY], 1, xSelEndX);
  end;
  Lines[xSelStartY] := Copy(Lines[xSelStartY], 1, xSelStartX) + AddSpaces +
    Copy(Lines[xSelEndY], xSelEndX + 1, Length(Lines[xSelEndY]));
  S := S1;
  for i := xSelStartY + 1 to xSelEndY do
  begin
    S := S + #13#10;
    if i <> xSelEndY then
      S := S + Lines[xSelStartY + 1];
    DeleteLine(xSelStartY + 1, -1, -1, -1, -1, False);
  end;
  S := S + S2;

  CurY := xSelStartY;
  CurX := xSelStartX;
  ClearSelection;

  Changed(xSelStartY, -1);
  SelectionChanged;
  if bRepaint then
    Invalidate;

  Undo := TGLSMemoDeleteBufUndo.Create(OldX, OldY, CurX, CurY, S);
  Undo.UndoSelStartX := xSelStartX;
  Undo.UndoSelStartY := xSelStartY;
  Undo.UndoSelEndX := xSelEndX;
  Undo.UndoSelEndY := xSelEndY;
  if Assigned(FUndoList) then
    FUndoList.Add(Undo);
end;

//--------------------------------------------------------------
//        CUT TO CLIPBOARD
//--------------------------------------------------------------

procedure TGLSCustomMemo.CutToClipBoard;
begin
  ClipBoard.SetTextBuf(PChar(GetSelText));
  DeleteSelection(True);
end;

//--------------------------------------------------------------
//        GET SEL TEXT
//--------------------------------------------------------------

function TGLSCustomMemo.GetSelText: string;
var
  i: integer;
  xSelStartX, xSelStartY, xSelEndX, xSelEndY: integer;
begin
  Result := '';
  if (FSelStartY = FSelEndY) and (FSelStartX = FSelEndX) then
    Exit;

  xSelStartX := FSelStartX;
  xSelStartY := FSelStartY;
  xSelEndX := FSelEndX;
  xSelEndY := FSelEndY;
  OrderPos(xSelStartX, xSelStartY, xSelEndX, xSelEndY);

  if xSelStartY = xSelEndY then
    Result := Copy(Lines[xSelStartY], xSelStartX + 1, xSelEndX - xSelStartX)
  else
  begin
    Result := Copy(Lines[xSelStartY], xSelStartX + 1,
      Length(Lines[xSelStartY]));
    for i := xSelStartY + 1 to xSelEndY - 1 do
      Result := Result + #13#10 + Lines[i];
    Result := Result + #13#10 + Copy(Lines[xSelEndY], 1, xSelEndX);
  end;
end;

//--------------------------------------------------------------
//        GET SEL START
//--------------------------------------------------------------

function TGLSCustomMemo.GetSelStart: TPoint;
var
  xSelStartX, xSelStartY, xSelEndX, xSelEndY: integer;
begin
  xSelStartX := FSelStartX;
  xSelStartY := FSelStartY;
  xSelEndX := FSelEndX;
  xSelEndY := FSelEndY;
  OrderPos(xSelStartX, xSelStartY, xSelEndX, xSelEndY);
  Result := Point(xSelStartX, xSelStartY);
end;

//--------------------------------------------------------------
//        GET SEL END
//--------------------------------------------------------------

function TGLSCustomMemo.GetSelEnd: TPoint;
var
  xSelStartX, xSelStartY, xSelEndX, xSelEndY: integer;
begin
  xSelStartX := FSelStartX;
  xSelStartY := FSelStartY;
  xSelEndX := FSelEndX;
  xSelEndY := FSelEndY;
  OrderPos(xSelStartX, xSelStartY, xSelEndX, xSelEndY);
  Result := Point(xSelEndX, xSelEndY);
end;

//--------------------------------------------------------------
//        SET SEL TEXT
//--------------------------------------------------------------

procedure TGLSCustomMemo.SetSelText(const AValue: string);
var
  i, k: integer;
  xSelStartX, xSelStartY, xSelEndX, xSelEndY: integer;
  Buff, S: string;
  OldX, OldY: integer;
begin
  Buff := AValue;
  xSelStartX := FSelStartX;
  xSelStartY := FSelStartY;
  xSelEndX := FSelEndX;
  xSelEndY := FSelEndY;
  OrderPos(xSelStartX, xSelStartY, xSelEndX, xSelEndY);

  DeleteSelection(False);

  OldX := CurX;
  OldY := CurY;
  i := Pos(#13#10, Buff);
  S := Lines[xSelStartY];
  if i = 0 then
  begin
    Lines[xSelStartY] := Copy(S, 1, xSelStartX) + Buff
      + Copy(S, xSelStartX + 1, Length(S));
    CurX := xSelStartX;
    if Buff <> '' then
      CurX := CurX + Length(Buff);
  end
  else
  begin
    k := xSelStartY;
    Lines[k] := Copy(S, 1, xSelStartX) + Copy(Buff, 1, i - 1);
    TGLSMemoStrings(Lines).DoInsert(k + 1, Copy(S, xSelStartX + 1, Length(S)));
    while True do
    begin
      Buff := Copy(Buff, i + 2, Length(Buff));
      i := Pos(#13#10, Buff);
      k := k + 1;
      if i = 0 then
        break;
      TGLSMemoStrings(Lines).DoInsert(k, Copy(Buff, 1, i - 1));
    end;
    Lines[k] := Buff + Lines[k];
    CurY := k;
    CurX := Length(Buff);
  end;

  ClearSelection;
  Changed(xSelStartY, -1);
  if Assigned(FUndoList) then
    FUndoList.Add(TGLSMemoPasteUndo.Create(OldX, OldY, CurX, CurY, AValue));
  Invalidate;
end;

//--------------------------------------------------------------
//        GET SEL LENGTH
//--------------------------------------------------------------

function TGLSCustomMemo.GetSelLength: integer;
begin
  Result := Length(GetSelText);
end;

//--------------------------------------------------------------
//        CHANGED
//--------------------------------------------------------------

procedure TGLSCustomMemo.Changed(FromLine, ToLine: integer);
var
  i: integer;
begin
  if ToLine < FromLine then
    ToLine := Lines.Count - 1;
  for i := FromLine to ToLine do
    ValidAttrs[i] := False;
  InvalidateLineRange(FromLine, ToLine);
  if Assigned(FOnChange) then
    FOnChange(Self);
end;

//--------------------------------------------------------------
//        ATTR CHANGED
//--------------------------------------------------------------

procedure TGLSCustomMemo.AttrChanged(LineNo: integer);
begin
  ValidAttrs[LineNo] := False;
  InvalidateLineRange(LineNo, LineNo);
  if Assigned(FOnAttrChange) then
    FOnAttrChange(Self);
end;

//--------------------------------------------------------------
//        SELECTION CHANGED
//--------------------------------------------------------------

procedure TGLSCustomMemo.SelectionChanged;
begin
  if Assigned(FOnSelectionChange) then
    FOnSelectionChange(Self);
end;

//--------------------------------------------------------------
//        STATUS CHANGED
//--------------------------------------------------------------

procedure TGLSCustomMemo.StatusChanged;
begin
  if Assigned(FOnStatusChange) then
    FOnStatusChange(Self);
end;

//--------------------------------------------------------------
//        CLEAR SELECTION
//--------------------------------------------------------------

procedure TGLSCustomMemo.ClearSelection;
var
  rct: TRect;
  Changed: Boolean;
begin
  Changed := not ((FSelStartX = FSelEndX) and (FSelStartY = FSelEndY));
  rct := LineRangeRect(FSelStartY, FSelEndY);
  FSelStartX := CurX;
  FSelStartY := CurY;
  FSelEndX := CurX;
  FSelEndY := CurY;
  FPrevSelX := CurX;
  FPrevSelY := CurY;
  if Changed then
  begin
    SelectionChanged;
    InvalidateRect(Handle, @rct, true);
  end;
  if Assigned(FOnMoveCursor) then
    FOnMoveCursor(Self);
end;

//--------------------------------------------------------------
//        EXPAND SELECTION
//--------------------------------------------------------------

procedure TGLSCustomMemo.ExpandSelection;
var
  rct: TRect;
begin
  rct := LineRangeRect(FPrevSelY, CurY);
  FSelEndX := CurX;
  FSelEndY := CurY;
  FPrevSelX := CurX;
  FPrevSelY := CurY;
  SelectionChanged;
  InvalidateRect(Handle, @rct, true);
  if Assigned(FOnMoveCursor) then
    FOnMoveCursor(Self);
end;

//--------------------------------------------------------------
//        MAX LENGTH
//--------------------------------------------------------------

function TGLSCustomMemo.MaxLength: integer;
var
  i, len: integer;
begin
  Result := 0;
  for i := 0 to Lines.Count - 1 do
  begin
    len := Length(Lines[i]);
    if len > Result then
      Result := len;
  end;
end;

//--------------------------------------------------------------
//        DO SCROLL
//--------------------------------------------------------------

procedure TGLSCustomMemo.DoScroll(Sender: TGLSMemoScrollBar; ByValue: integer);
var
  eRect, scrRect, sbRect: TRect;
  Old: integer;
begin
  eRect := EditorRect;
  case Sender.Kind of
    sbVertical:
      begin
        Old := FTopLine;
        FTopLine := FTopLine + ByValue;
        if FTopLine > Sender.MaxPosition then
          FTopLine := Sender.MaxPosition;
        if FTopLine < 0 then
          FTopLine := 0;
        if Old <> FTopLine then
        begin
          ShowCaret(False);
          if CurY < FTopLine then
            CurY := FTopLine;
          if CurY > LastVisibleLine then
            CurY := LastVisibleLine;

          ScrollDC(Canvas.Handle, 0, (Old - FTopLine) * FCellSize.H,
            eRect, eRect, 0, @scrRect);
          InvalidateRect(Handle, @scrRect, True);
          sbRect := Sender.FullRect;
          InvalidateRect(Handle, @sbRect, True);
          FGutter.Invalidate;
          ShowCaret(True);
        end;
      end;
    sbHorizontal:
      begin
        Old := FLeftCol;
        FLeftCol := FLeftCol + ByValue;
        if FLeftCol > Sender.MaxPosition then
          FLeftCol := Sender.MaxPosition;
        if FLeftCol < 0 then
          FLeftCol := 0;
        if Old <> FLeftCol then
        begin
          ShowCaret(False);
          if CurX < FLeftCol then
            CurX := FLeftCol;
          if CurX > LastVisiblePos then
            CurX := LastVisiblePos;
          ScrollDC(Canvas.Handle, (Old - FLeftCol) * FCellSize.W, 0,
            eRect, eRect, 0, @scrRect);
          InvalidateRect(Handle, @scrRect, True);
          sbRect := Sender.FullRect;
          InvalidateRect(Handle, @sbRect, True);
          ShowCaret(True);
        end;
      end;
  end;
end;

//--------------------------------------------------------------
//        DO SCROLL PAGE
//--------------------------------------------------------------

procedure TGLSCustomMemo.DoScrollPage(Sender: TGLSMemoScrollBar; ByValue:
  integer);
begin
  case Sender.Kind of
    sbVertical: DoScroll(Sender, ByValue * VisibleLineCount);
    sbHorizontal: DoScroll(Sender, ByValue * VisiblePosCount);
  end;
end;

//--------------------------------------------------------------
//        SET LINES
//--------------------------------------------------------------

procedure TGLSCustomMemo.SetLines(ALines: TStrings);
begin
  if ALines <> nil then
  begin
    FLines.Assign(ALines);
    Changed(0, -1);
    SelectionChanged;
    Invalidate;
  end;
end;

//--------------------------------------------------------------
//        SET/GET LINE STYLE
//--------------------------------------------------------------

procedure TGLSCustomMemo.SetLineStyle(Index: integer; Value: integer);
begin
  TGLSMemoStrings(FLines).Style[Index] := Value;
  if IsLineVisible(Index) then
    AttrChanged(Index);
end;

function TGLSCustomMemo.GetLineStyle(Index: integer): integer;
begin
  Result := TGLSMemoStrings(FLines).Style[Index];
end;

//--------------------------------------------------------------
//        GET/SET IN COMMENT
//--------------------------------------------------------------

function TGLSCustomMemo.GetInComment(Index: integer): Boolean;
begin
  Result := TGLSMemoStrings(FLines).InComment[Index];
end;

procedure TGLSCustomMemo.SetInComment(Index: integer; Value: Boolean);
begin
  TGLSMemoStrings(FLines).InComment[Index] := Value;
end;

//--------------------------------------------------------------
//        GET/SET IN BRACKETS
//--------------------------------------------------------------

function TGLSCustomMemo.GetInBrackets(Index: integer): integer;
begin
  Result := TGLSMemoStrings(FLines).InBrackets[Index];
end;

procedure TGLSCustomMemo.SetInBrackets(Index: integer; Value: integer);
begin
  TGLSMemoStrings(FLines).InBrackets[Index] := Value;
end;

//--------------------------------------------------------------
//        GET/SET VALID ATTRS
//--------------------------------------------------------------

function TGLSCustomMemo.GetValidAttrs(Index: integer): Boolean;
begin
  Result := TGLSMemoStrings(FLines).ValidAttrs[Index];
end;

procedure TGLSCustomMemo.SetValidAttrs(Index: integer; Value: Boolean);
begin
  TGLSMemoStrings(FLines).ValidAttrs[Index] := Value;
end;

//--------------------------------------------------------------
//        GET/SET CHAR ATTRS
//--------------------------------------------------------------

function TGLSCustomMemo.GetCharAttrs(Index: integer): string;
begin
  Result := TGLSMemoStrings(FLines).CharAttrs[Index];
end;

procedure TGLSCustomMemo.SetCharAttrs(Index: integer; const Value: string);
begin
  TGLSMemoStrings(FLines).CharAttrs[Index] := Value;
  if IsLineVisible(Index) then
    AttrChanged(Index);
end;

//--------------------------------------------------------------
//        SET CUR X
//--------------------------------------------------------------

procedure TGLSCustomMemo.SetCurX(Value: integer);
var
  len: integer;
  WasVisible: Boolean;
begin
  if Value < 0 then
    if CurY = 0 then
      Value := 0
    else
    begin
      CurY := CurY - 1;
      Value := Length(Lines[CurY]);
    end;

  if (CurY >= 0) and (CurY < Lines.Count) then
  begin
    len := Length(Lines[CurY]);
    if Value > len then
    begin
      Lines[CurY] := Lines[CurY] + StringOfChar(' ', Value - len);
      // Value := len;
      ValidAttrs[CurY] := False;
      InvalidateLineRange(CurY, CurY);
    end;
  end;

  FCurX := Value;

  WasVisible := FCaretVisible;
  if WasVisible then
    ShowCaret(False);
  MakeVisible;
  ResizeScrollBars;
  StatusChanged;
  if WasVisible then
    ShowCaret(True);
end;

//--------------------------------------------------------------
//        SET CUR Y
//--------------------------------------------------------------

procedure TGLSCustomMemo.SetCurY(Value: integer);
var
  Old: integer;
  WasVisible: Boolean;
begin
  WasVisible := FCaretVisible;
  if WasVisible then
    ShowCaret(False);
  Old := CurY;

  if Value < 0 then
    Value := 0;
  if Value >= Lines.Count then
    Value := Lines.Count - 1;

  FCurY := Value;
  if (CurY <> Old) and (Old >= 0) and (Old < Lines.Count) then
    Lines[Old] := TrimRight(Lines[Old]);
  CurX := CurX;

  MakeVisible;
  ResizeScrollBars;
  StatusChanged;
  if WasVisible then
    ShowCaret(True);
end;

//--------------------------------------------------------------
//        MOVE CURSOR
//--------------------------------------------------------------

procedure TGLSCustomMemo.MoveCursor(dX, dY: integer; Shift: TShiftState);
var
  Selecting: Boolean;
  //------------------------------------------------------------
  function IsDelimiter(c: char): Boolean;
  begin
    Result := Pos(c, ' .,;:/?!@#$%^&*(){}[]<>-+=|\') > 0;
  end;
  //------------------------------------------------------------
  function IsStopChar(c, cThis: char): Boolean;
  begin
    Result := IsDelimiter(c) <> IsDelimiter(cThis);
  end;
  //------------------------------------------------------------
  procedure MoveWordLeft;
  var
    S: string;
  begin
    CurX := CurX - 1;
    S := TrimRight(Lines[CurY]);
    while CurX > 0 do
    begin
      if IsStopChar(S[CurX], S[CurX + 1]) then
        break;
      CurX := CurX - 1;
    end;
    if (CurX < 0) then
      if CurY > 0 then
      begin
        CurY := CurY - 1;
        CurX := Length(Lines[CurY]);
      end;
  end;
  //------------------------------------------------------------
  procedure MoveWordRight;
  var
    Len: integer;
    S: string;
  begin
    S := TrimRight(Lines[CurY]);
    Len := Length(S);
    CurX := CurX + 1;
    while CurX < Len do
    begin
      if IsStopChar(S[CurX + 1], S[CurX]) then
        break;
      CurX := CurX + 1;
    end;
    if CurX > Len then
      if CurY < Lines.Count - 1 then
      begin
        CurY := CurY + 1;
        CurX := 0;
      end;
  end;
  //------------------------------------------------------------
begin
  Selecting := (ssShift in Shift) and (CurX = FPrevSelX)
    and (CurY = FPrevSelY);
  if ssCtrl in Shift then
  begin
    if dX > 0 then
      MoveWordRight;
    if dX < 0 then
      MoveWordLeft;
  end
  else
  begin
    CurY := CurY + dY;
    CurX := CurX + dX;
  end;
  if Selecting then
    ExpandSelection
  else
    ClearSelection;
end;

//--------------------------------------------------------------
//        MOVE PAGE
//--------------------------------------------------------------

procedure TGLSCustomMemo.MovePage(dP: integer; Shift: TShiftState);
var
  eRect: TRect;
  LinesPerPage: integer;
  Selecting: Boolean;
begin
  if FCellSize.H = 0 then
    Exit;
  Selecting := (ssShift in Shift) and (CurX = FPrevSelX)
    and (CurY = FPrevSelY);
  eRect := EditorRect;
  LinesPerPage := (eRect.Bottom - eRect.Top) div FCellSize.H - 1;
  CurY := CurY + dP * LinesPerPage;
  if ssCtrl in Shift then
    if dP > 0 then
    begin
      CurY := Lines.Count - 1;
      CurX := Length(Lines[Lines.Count - 1]);
    end
    else
    begin
      CurY := 0;
      CurX := 0;
    end;
  if Selecting then
    ExpandSelection
  else
    ClearSelection;
end;

//--------------------------------------------------------------
//        GO HOME
//--------------------------------------------------------------

procedure TGLSCustomMemo.GoHome(Shift: TShiftState);
var
  Selecting: Boolean;
begin
  Selecting := (ssShift in Shift) and (CurX = FPrevSelX)
    and (CurY = FPrevSelY);
  CurX := 0;
  FLeftCol := 0;
  if Selecting then
    ExpandSelection
  else
    ClearSelection;
end;

//--------------------------------------------------------------
//        GO END
//--------------------------------------------------------------

procedure TGLSCustomMemo.GoEnd(Shift: TShiftState);
var
  Selecting: Boolean;
  S, S1: string;
begin
  Selecting := (ssShift in Shift) and (CurX = FPrevSelX)
    and (CurY = FPrevSelY);

  S := Lines[CurY];
  if not Selecting then
    S := TrimRight(S);
  S1 := TrimRight(Copy(S, CurX + 1, Length(S)));
  S := Copy(S, 1, CurX);
  Lines[CurY] := S + S1;

  CurX := Length(Lines[CurY]);
  if Selecting then
    ExpandSelection
  else
    ClearSelection;
end;

//--------------------------------------------------------------
//        INSERT CHAR
//--------------------------------------------------------------

procedure TGLSCustomMemo.InsertChar(C: Char);
var
  S, S1: string;
  NewPlace: integer;
  rct: TRect;
  CurX0, CurY0: integer;
begin
  CurX0 := CurX;
  CurY0 := CurY;
  S := Lines[CurY];
  NewPlace := CurX + 1;
  if C = #9 then
  begin
    while (NewPlace mod TabSize) <> 0 do
      Inc(NewPlace);
    S1 := StringOfChar(' ', NewPlace - CurX);
  end
  else
    S1 := C;
  Insert(S1, S, CurX + 1);
  Lines[CurY] := S;
  CurX := NewPlace;
  ClearSelection;
  rct := LineRect(CurY);
  Changed(CurY, CurY);

  if Assigned(FUndoList) then
    FUndoList.Add(TGLSMemoInsCharUndo.Create(CurX0, CurY0, CurX, CurY, S1));

  InvalidateRect(Handle, @rct, True);
end;

//--------------------------------------------------------------
//        INSERT TEMPLATE
//--------------------------------------------------------------

procedure TGLSCustomMemo.InsertTemplate(AText: string);
var
  i, NewCurX, NewCurY: integer;
  Indent: string;
  FoundCursor: Boolean;
begin
  Indent := IndentCurrLine;

  DeleteSelection(False);
  ClearSelection;

  NewCurX := CurX;
  NewCurY := CurY;
  FoundCursor := False;
  i := 1;
  while i <= Length(AText) do
  begin
    if AText[i] = #13 then
    begin
      if (i = Length(AText)) or (AText[i + 1] <> #10) then
        Insert(#10 + Indent, AText, i + 1);
      if not FoundCursor then
      begin
        Inc(NewCurY);
        NewCurX := Length(Indent);
      end;
      Inc(i, 1 + Length(Indent));
    end
    else if AText[i] = #7 then
    begin
      FoundCursor := True;
      Delete(AText, i, 1);
      Dec(i);
    end
    else if Ord(AText[i]) < Ord(' ') then
    begin
      Delete(AText, i, 1);
      Dec(i);
    end
    else if not FoundCursor then
      Inc(NewCurX);
    Inc(i);
  end;

  SetSelText(AText);
  SetCursor(NewCurX, NewCurY);
  ClearSelection;
  try
    SetFocus;
  except
  end;

end;

//--------------------------------------------------------------
//        DELETE CHAR
//--------------------------------------------------------------

procedure TGLSCustomMemo.DeleteChar(OldX, OldY: integer);
var
  S, S1: string;
  rct: TRect;
  C: char;
  Undo: TGLSMemoDelCharUndo;
  IsBackspace: Boolean;
begin
  if FReadOnly then
    Exit;
  if OldX < 0 then
  begin
    OldX := CurX;
    OldY := CurY;
    IsBackspace := False;
  end
  else
    IsBackspace := True;

  ClearSelection;

  S := Lines[CurY];
  S1 := Copy(S, CurX + 1, Length(S));
  if not IsBackspace then
    S1 := TrimRight(S1);
  S := Copy(S, 1, CurX);
  Lines[CurY] := S + S1;

  if CurX < Length(Lines[CurY]) then
  begin
    S := Lines[CurY];
    C := S[CurX + 1];
    Delete(S, CurX + 1, 1);
    Lines[CurY] := S;
    Changed(CurY, CurY);
    rct := LineRect(CurY);
    Undo := TGLSMemoDelCharUndo.Create(OldX, OldY, CurX, CurY, C);
    Undo.IsBackSpace := IsBackSpace;
    if Assigned(FUndoList) then
      FUndoList.Add(Undo);
  end
  else if CurY < Lines.Count - 1 then
  begin
    S := Lines[CurY] + Lines[CurY + 1];
    Lines[CurY] := S;
    DeleteLine(CurY + 1, OldX, OldY, CurX, CurY, False);
    Changed(CurY, -1);
    rct := EditorRect;
    Undo := TGLSMemoDelCharUndo.Create(OldX, OldY, CurX, CurY, #13);
    Undo.IsBackSpace := IsBackSpace;
    if Assigned(FUndoList) then
      FUndoList.Add(Undo);
  end;
  ClearSelection;
  InvalidateRect(Handle, @rct, True);
end;

//--------------------------------------------------------------
//        DELETE LINE
//--------------------------------------------------------------

procedure TGLSCustomMemo.DeleteLine(Index, OldX, OldY, NewX, NewY: integer;
  FixUndo: Boolean);
var
  rct: TRect;
  s: string;
begin
  if Index < 0 then
    Index := CurY;
  if OldX < 0 then
  begin
    OldX := CurX;
    OldY := CurY;
  end;

  s := Lines[Index];

  TGLSMemoStrings(Lines).FDeleting := True;
  if Lines.Count = 1 then
    TGLSMemoStrings(Lines)[0] := ''
  else
    Lines.Delete(Index);
  TGLSMemoStrings(Lines).FDeleting := False;

  ClearSelection;
  if Index >= Lines.Count then
    Changed(Index - 1, -1)
  else
    Changed(Index, -1);
  rct := EditorRect;
  InvalidateRect(Handle, @rct, True);

  if NewX < 0 then
  begin
    if Length(Lines[0]) < CurX then
      CurX := Length(Lines[0]);
    if Index >= Lines.Count then
      CurY := Index - 1
    else
      CurY := Index;
    NewX := CurX;
    NewY := CurY;
  end
  else
  begin
    CurX := NewX;
    CurY := NewY;
  end;
  if Assigned(FUndoList) and FixUndo then
    FUndoList.Add(TGLSMEmoDelLineUndo.Create(Index, OldX, OldY, NewX, NewY, s));
end;

//--------------------------------------------------------------
//        BACK SPACE
//--------------------------------------------------------------

procedure TGLSCustomMemo.BackSpace;
var
  OldX, OldY: integer;
begin
  OldX := CurX;
  OldY := CurY;
  MoveCursor(-1, 0, []);
  if (OldX = CurX) and (OldY = CurY) then
    Exit;
  DeleteChar(OldX, OldY);
end;

//--------------------------------------------------------------
//        BACK SPACE WORD
//--------------------------------------------------------------

procedure TGLSCustomMemo.BackSpaceWord;
begin
  ClearSelection;
  MoveCursor(-1, 0, [ssShift, ssCtrl]);
  DeleteSelection(True);
end;

//--------------------------------------------------------------
//        INDENT CURR LINE
//--------------------------------------------------------------

function TGLSCustomMemo.IndentCurrLine: string;
var
  Len, Count: integer;
  CurS: string;
begin
  Result := '';
  if not AutoIndent then
    Exit;
  CurS := Lines[CurY];
  Len := Length(CurS);
  Count := 0;
  while (Count < CurX) and (Count < Len) do
  begin
    if CurS[Count + 1] <> ' ' then
      break;
    Inc(Count);
  end;
  Result := StringOfChar(' ', Count);
end;

//--------------------------------------------------------------
//        NEW LINE
//--------------------------------------------------------------

procedure TGLSCustomMemo.NewLine;
var
  S, sIndent: string;
  OldX, OldY: integer;
begin
  OldX := CurX;
  OldY := CurY;
  S := Lines[CurY];
  sIndent := IndentCurrLine;
  Lines[CurY] := Copy(S, 1, CurX);

  S := TrimRight(Copy(S, CurX + 1, Length(S)));
  if AutoIndent then
    while (Length(S) > 0) and (S[1] = ' ') do
      Delete(S, 1, 1);

  TGLSMemoStrings(Lines).DoInsert(CurY + 1, sIndent + S);
  GoHome([]);
  MoveCursor(0, 1, []);
  CurX := Length(sIndent);
  ClearSelection;
  if Assigned(FUndoList) then
    FUndoList.Add(TGLSMemoInsCharUndo.Create(OldX, OldY, CurX, CurY, #13 +
      sIndent));
  Invalidate;
  Changed(CurY - 1, -1);
end;

//--------------------------------------------------------------
//        ADD STRING
//--------------------------------------------------------------

function TGLSCustomMemo.AddString(const S: string): integer;
begin
  if Lines.Count = 0 then
    TGLSMemoStrings(Lines).DoAdd('');
  MovePage(1, [ssCtrl]); // end of text
  if not ((Lines.Count = 1) and (Lines[0] = '')) then
  begin
    TGLSMemoStrings(Lines).DoAdd('');
    CurX := 0;
    CurY := Lines.Count;
    ClearSelection;
    // S := #13#10 + S;
  end;
  SetSelText(S);
  Result := Lines.Count - 1;
end;

//--------------------------------------------------------------
//        INSERT STRING
//--------------------------------------------------------------

procedure TGLSCustomMemo.InsertString(Index: integer; S: string);
begin
  CurY := Index;
  CurX := 0;
  ClearSelection;
  if not ((Lines.Count = 1) and (Lines[0] = '')) then
    S := S + #13#10;
  SetSelText(S);
end;

//--------------------------------------------------------------
//        DO COMMAND
//--------------------------------------------------------------

procedure TGLSCustomMemo.DoCommand(cmd: TCommand; const AShift: TShiftState);
begin
  case cmd of
    cmDelete: if not FReadOnly then
      begin
        if ssShift in AShift then
          CutToClipboard
        else if FDelErase and
          (not ((FSelStartX = FSelEndX) and (FSelStartY = FSelEndY))) then
          DeleteSelection(True)
        else
          DeleteChar(-1, -1);
      end;
    cmBackSpace: BackSpace;
    cmWordBackSpace: BackSpaceWord;
    cmNewLine: NewLine;
    cmDelLine: DeleteLine(-1, -1, -1, -1, -1, True);
    cmCopy: CopyToClipboard;
    cmCut: CutToClipboard;
    cmPaste: PasteFromClipboard;
    cmHome: GoHome(AShift);
    cmEnd: GoEnd(AShift);
    cmPageDown: MovePage(1, AShift);
    cmPageUp: MovePage(-1, AShift);
    cmInsert:
      begin
        if ssShift in AShift then
          PasteFromClipboard;
        if ssCtrl in AShift then
          CopyToClipboard;
      end;
  end;
end;

//--------------------------------------------------------------
//        KEY DOWN
//--------------------------------------------------------------

procedure TGLSCustomMemo.KeyDown(var Key: Word; Shift: TShiftState);
begin
  ShowCaret(False);
  inherited;
  case Key of
    VK_LEFT: MoveCursor(-1, 0, Shift);
    VK_RIGHT: MoveCursor(1, 0, Shift);
    VK_UP: MoveCursor(0, -1, Shift);
    VK_DOWN: MoveCursor(0, 1, Shift);
    VK_HOME, VK_END,
      VK_DELETE: DoCommand(Key, Shift);
    VK_PRIOR, VK_NEXT:
      DoCommand(Key, Shift);
    VK_INSERT: DoCommand(Key, Shift);
  end;
  ShowCaret(True);
end;

//--------------------------------------------------------------
//        KEY PRESS
//--------------------------------------------------------------

procedure TGLSCustomMemo.KeyPress(var Key: Char);
begin
  if FReadOnly then
    Exit;
  ShowCaret(False);
  inherited;
  if (ord(Key) in [9, 32..255]) and (ord(Key) <> 127) then
  begin
    if FDelErase and (not ((FSelStartX = FSelEndX) and (FSelStartY = FSelEndY)))
      then
      DeleteSelection(True);
    InsertChar(Key);
  end
  else
    DoCommand(Ord(Key), []);
  ShowCaret(True);
end;

//--------------------------------------------------------------
//        MOUSE DOWN
//--------------------------------------------------------------

procedure TGLSCustomMemo.MouseDown(Button: TMouseButton; Shift: TShiftState;
  X, Y: Integer);
var
  newPos: TCellPos;
  charPos: TFullPos;
  Selecting: Boolean;
begin
  inherited;
  if not Focused then
  begin
    SetFocus;
    //     Exit;
  end;

  if FAfterDoubleClick then
  begin
    FAfterDoubleClick := False;
    Exit;
  end;

  if Button <>mbLeft then
    Exit;


  if sbVert.MouseDown(Button, Shift, X, Y) then
    Exit;
  if sbHorz.MouseDown(Button, Shift, X, Y) then
    Exit;

  if PointInRect(Point(X, Y), EditorRect) then
  begin
    ShowCaret(False);
    newPos := CellFromPos(X, Y);
    CurY := newPos.Y + FTopLine;
    CurX := newPos.X + FLeftCol;
    if Assigned(FOnMoveCursor) then
      FOnMoveCursor(Self);

    Selecting := ssShift in Shift;
    if Button = mbLeft then
    begin
      if Selecting then
        ExpandSelection
      else
        ClearSelection;
      FLeftButtonDown := True;
    end
    else
      ShowCaret(True);
  end;

  if Assigned(FOnGutterClick) then
    if PointInRect(Point(X, Y), FGutter.FullRect) then
    begin
      charPos := CharFromPos(X, Y);
      if charPos.LineNo < Lines.Count then
        FOnGutterClick(Self, charPos.LineNo);
    end;
end;

//--------------------------------------------------------------
//        MOUSE MOVE
//--------------------------------------------------------------

procedure TGLSCustomMemo.MouseMove(Shift: TShiftState; X, Y: Integer);
var
  newPos: TCellPos;
begin
  inherited;
  if sbVert.MouseMove(Shift, X, Y) then
    Exit;
  if sbHorz.MouseMove(Shift, X, Y) then
    Exit;
  if PointInRect(Point(X, Y), EditorRect) then
  begin
    if (ssLeft in Shift) and FLeftButtonDown then
    begin
      newPos := CellFromPos(X, Y);
      CurY := newPos.Y + FTopLine;
      CurX := newPos.X + FLeftCol;
      ExpandSelection;
    end;
  end
end;

//--------------------------------------------------------------
//        MOUSE UP
//--------------------------------------------------------------

procedure TGLSCustomMemo.MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y:
  Integer);
begin
  inherited;
  if sbVert.MouseUp(Button, Shift, X, Y) then
    Exit;
  if sbHorz.MouseUp(Button, Shift, X, Y) then
    Exit;
  if Button = mbLeft then
    ShowCaret(True);
  FLeftButtonDown := False;
  FLastMouseUpX := X;
  FLastMouseUpY := Y;
end;

//--------------------------------------------------------------
//        DBL CLICK
//--------------------------------------------------------------

procedure TGLSCustomMemo.DblClick;
var
  clickPos: TCellPos;
  clickX, clickY: integer;
  //------------------------------------------------------------
  //   SELECT WORD
  //------------------------------------------------------------
  procedure SelectWord;
  const
    stopChars: TSysCharSet = [' ', ';', '.', ',', ':', '?', '!', '''', '"',
      '<', '>', '/', '*', '+', '-', '=', '(', ')',
      '[', ']', '{', '}', '@', '#', '$', '%', '^',
      '&', '|', '\'];
  var
    s: string;
    i: integer;
    rct: TRect;
  begin
    CurX := clickX;
    CurY := clickY;
    if (CurX = clickX) and (CurY = clickY) then
    begin
      s := Lines[clickY];
      if s[clickX + 1] = ' ' then
        Exit;

      i := clickX;
      while (i >= 0) and not CharInSet(s[i + 1], stopChars) do
        Dec(i);
      FSelStartY := clickY;
      FSelStartX := i + 1;

      i := clickX;
      while (i < Length(s)) and not CharInSet(s[i + 1], stopChars) do
        Inc(i);
      FSelEndY := clickY;
      FSelEndX := i;

      if FSelEndX <> FSelStartX then
      begin
        FAfterDoubleClick := True;
        rct := LineRangeRect(CurY, CurY);
        SelectionChanged;
        InvalidateRect(Handle, @rct, true);
      end;
    end;
  end;
  //------------------------------------------------------------
begin

  if PointInRect(Point(FLastMouseUpX, FLastMouseUpY), EditorRect) then
  begin
    clickPos := CellFromPos(FLastMouseUpX, FLastMouseUpY);
    clickX := clickPos.X + FLeftCol;
    clickY := clickPos.Y + FTopLine;
    SelectWord;
  end;
  inherited;
end;

//--------------------------------------------------------------
//        WM_GETDLGCODE
//--------------------------------------------------------------

procedure TGLSCustomMemo.WMGetDlgCode(var Msg: TWMGetDlgCode);
begin
  Msg.Result := DLGC_WANTARROWS or DLGC_WANTTAB;
end;

//--------------------------------------------------------------
//        WM_ERASEBKGND
//--------------------------------------------------------------

procedure TGLSCustomMemo.WMEraseBkgnd(var Msg: TWmEraseBkgnd);
begin
  Msg.Result := 1;
end;

//--------------------------------------------------------------
//        WM_SIZE
//--------------------------------------------------------------

procedure TGLSCustomMemo.WMSize(var Msg: TWMSize);
begin
  if not (csLoading in ComponentState) then
    try
      ResizeEditor;
    except
    end;
end;

//--------------------------------------------------------------
//        WM_SETCURSOR
//--------------------------------------------------------------

procedure TGLSCustomMemo.WMSetCursor(var Msg: TWMSetCursor);
var
  P: TPoint;
begin
  Msg.Result := 1;
  GetCursorPos(P);
  P := ScreenToClient(P);
  if PointInRect(P, EditorRect) then
    Winapi.Windows.SetCursor(Screen.Cursors[crIBeam])
  else
    Winapi.Windows.SetCursor(Screen.Cursors[crArrow]);
end;

//--------------------------------------------------------------
//        WM_SETFOCUS
//--------------------------------------------------------------

procedure TGLSCustomMemo.WMSetFocus(var Msg: TWMSetFocus);
begin
  if FCellSize.H = 0 then
    SetFont(FFont);
  CreateCaret(Handle, HBITMAP(0), 2, FCellSize.H - 2);
  ShowCaret(true);
end;

//--------------------------------------------------------------
//        WM_KILLFOCUS
//--------------------------------------------------------------

procedure TGLSCustomMemo.WMKillFocus(var Msg: TWMSetFocus);
begin
  DestroyCaret;
  FCaretVisible := False;
  inherited;
end;

//--------------------------------------------------------------
//        SHOW CARET
//--------------------------------------------------------------

procedure TGLSCustomMemo.ShowCaret(State: Boolean);
var
  rct: TRect;
begin
  FCaretVisible := False;
  if not State then
    HideCaret(Handle)
  else if Focused and not HiddenCaret then
  begin
    rct := CellRect(CurX - FLeftCol, CurY - FTopLine);
    SetCaretPos(rct.Left, rct.Top + 1);
    Winapi.Windows.ShowCaret(Handle);
    FCaretVisible := True;
  end;
end;

//--------------------------------------------------------------
//        CELL RECT
//--------------------------------------------------------------

function TGLSCustomMemo.CellRect(ACol, ARow: integer): TRect;
var
  rct: TRect;
begin
  rct := EditorRect;
  with FCellSize do
    Result := Rect(rct.Left + W * ACol, rct.Top + H * ARow,
      rct.Left + W * (ACol + 1), rct.Top + H * (ARow + 1));
end;

//--------------------------------------------------------------
//        LINE RECT
//--------------------------------------------------------------

function TGLSCustomMemo.LineRect(ARow: integer): TRect;
var
  rct: TRect;
begin
  rct := EditorRect;
  ARow := ARow - FTopLine;
  with FCellSize do
    Result := Rect(rct.Left, rct.Top + H * ARow, rct.Right, rct.Top + H * (ARow
      + 1));
end;

//--------------------------------------------------------------
//        COL RECT
//--------------------------------------------------------------

function TGLSCustomMemo.ColRect(ACol: integer): TRect;
var
  rct: TRect;
begin
  rct := EditorRect;
  ACol := ACol - FLeftCol;
  with FCellSize do
    Result := Rect(rct.Left + W * ACol, rct.Top, rct.Left + W * (ACol + 1),
      rct.Bottom);
end;

//--------------------------------------------------------------
//        LINE RANGE RECT
//--------------------------------------------------------------

function TGLSCustomMemo.LineRangeRect(FromLine, ToLine: integer): TRect;
var
  rct1, rct2: TRect;
begin
  rct1 := LineRect(FromLine);
  rct2 := LineRect(ToLine);
  Result := TotalRect(rct1, rct2);
end;

//--------------------------------------------------------------
//        INVALIDATE LINE RANGE
//--------------------------------------------------------------

procedure TGLSCustomMemo.InvalidateLineRange(FromLine, ToLine: integer);
var
  rct: TRect;
begin
  if ToLine < FromLine then
    ToLine := Lines.Count - 1;
  rct := LineRangeRect(FromLine, ToLine);
  if GutterWidth > 2 then
    rct.Left := FGutter.Left;
  InvalidateRect(Handle, @rct, True);
end;

//--------------------------------------------------------------
//        COL RANGE RECT
//--------------------------------------------------------------

function TGLSCustomMemo.ColRangeRect(FromCol, ToCol: integer): TRect;
var
  rct1, rct2: TRect;
begin
  rct1 := ColRect(FromCol);
  rct2 := ColRect(ToCol);
  Result := TotalRect(rct1, rct2);
end;

//--------------------------------------------------------------
//        CELL and CHAR FROM POS
//--------------------------------------------------------------

function TGLSCustomMemo.CellFromPos(X, Y: integer): TCellPos;
var
  rct: TRect;
begin
  rct := EditorRect;
  if (FCellSize.H = 0) and Assigned(FFont) then
    SetFont(FFont);
  if (FCellSize.W <> 0) and (FCellSize.H <> 0) then
  begin
    Result.X := (X - rct.Left) div FCellSize.W;
    Result.Y := (Y - rct.Top) div FCellSize.H;
  end
  else
  begin
    Result.X := 0;
    Result.Y := 0;
  end;
end;

function TGLSCustomMemo.CharFromPos(X, Y: integer): TFullPos;
var
  rct: TRect;
begin
  rct := EditorRect;
  if (FCellSize.H = 0) and Assigned(FFont) then
    SetFont(FFont);
  if (FCellSize.W <> 0) and (FCellSize.H <> 0) then
  begin
    Result.Pos := (X - rct.Left) div FCellSize.W + FLeftCol;
    Result.LineNo := (Y - rct.Top) div FCellSize.H + FTopLine;
  end
  else
  begin
    Result.Pos := 1;
    Result.LineNo := 1;
  end;
end;

//--------------------------------------------------------------
//        SET COLOR
//--------------------------------------------------------------

procedure TGLSCustomMemo.SetColor(Index: integer; Value: TColor);
var
  eRect: TRect;
  Changed: Boolean;
begin
  Changed := False;
  case Index of
    0: if FBkColor <> Value then
      begin
        FBkColor := Value;
        FStyles.BkColor[0] := Value;
        Changed := True;
      end;
    1: if FSelColor <> Value then
      begin
        FSelColor := Value;
        Changed := True;
      end;
    2: if FSelBkColor <> Value then
      begin
        FSelBkColor := Value;
        Changed := True;
      end;
  end;
  if Changed then
  begin
    eRect := EditorRect;
    InvalidateRect(Handle, @eRect, True);
  end;
end;

//--------------------------------------------------------------
//        SET FONT
//--------------------------------------------------------------

procedure TGLSCustomMemo.SetFont(Value: TFont);
var
  wW, wi: integer;
  OldFontName: string;
  eRect: TRect;
begin
  OldFontName := Canvas.Font.Name;
  Canvas.Font.Name := Value.Name;
  wW := Canvas.TextWidth('W');
  wi := Canvas.TextWidth('i');
  Canvas.Font.Name := OldFontName;

  if wW <> wi then
    raise EAbort.Create('Monospace font required');

  FFont.Assign(Value);
  Canvas.Font.Assign(Value);
  FCellSize.W := Canvas.TextWidth('W');
  FCellSize.H := Canvas.TextHeight('W') + 1;

  if FCaretVisible then
  begin
    ShowCaret(False);
    DestroyCaret;
    CreateCaret(Handle, HBITMAP(0), 2, FCellSize.H - 2);
    ShowCaret(true);
  end;

  FStyles.TextColor[0] := FFont.Color;
  FStyles.Style[0] := FFont.Style;

  eRect := EditorRect;
  InvalidateRect(Handle, @eRect, True);
end;

//--------------------------------------------------------------
//        SET GUTTER WIDTH
//--------------------------------------------------------------

procedure TGLSCustomMemo.SetGutterWidth(Value: integer);
begin
  FGutterWidth := Value;
  FGutter.FWidth := Value;
  if not (csLoading in ComponentState) then
    ResizeEditor;
end;

//--------------------------------------------------------------
//        SET GUTTER COLOR
//--------------------------------------------------------------

procedure TGLSCustomMemo.SetGutterColor(Value: TColor);
begin
  if FGutter.FColor <> Value then
  begin
    FGutter.FColor := Value;
    FGutter.Invalidate;
  end;
end;

//--------------------------------------------------------------
//        GET GUTTER COLOR
//--------------------------------------------------------------

function TGLSCustomMemo.GetGutterColor: TColor;
begin
  Result := FGutter.FColor;
end;

//--------------------------------------------------------------
//        CHAR STYLE NO
//--------------------------------------------------------------

function TGLSCustomMemo.CharStyleNo(LineNo, Pos: integer): integer;
var
  ChStyle: string;
begin
  Result := 0;
  if (LineNo < 0) or (LineNo >= Lines.Count) then
    Exit;

  ChStyle := CharAttrs[LineNo];
  if (Pos <= 0) or (Pos > Length(ChStyle)) then
    Exit;

  Result := integer(ChStyle[Pos]);
end;

//--------------------------------------------------------------
//        DRAW LINE
//--------------------------------------------------------------

procedure TGLSCustomMemo.DrawLine(LineNo: integer);
var
  eRect, rct0, rct1, rct, lineRct: TRect;
  LineSelStart, LineSelEnd, LineStyleNo, pos: integer;
  S, S1, S2, S3, ChStyle: string;
  //--------- FIND LINE SELECTION -------------
  procedure FindLineSelection;
  var
    len: integer;
    xSelStartX, xSelStartY, xSelEndX, xSelEndY: integer;
  begin
    xSelStartX := FSelStartX;
    xSelStartY := FSelStartY;
    xSelEndX := FSelEndX;
    xSelEndY := FSelEndY;
    OrderPos(xSelStartX, xSelStartY, xSelEndX, xSelEndY);
    len := Length(Lines[LineNo]);
    LineSelStart := 0;
    LineSelEnd := 0;
    if xSelStartY = Lineno then
    begin
      LineSelStart := xSelStartX - FLeftCol;
      LineSelEnd := len - FLeftCol;
    end
    else if (xSelStartY < LineNo) and (LineNo < xSelEndY) then
    begin
      LineSelStart := 0;
      LineSelEnd := len - FLeftCol;
    end;

    if xSelEndY = LineNo then
      LineSelEnd := xSelEndX - FLeftCol;

    if LineSelEnd < LineSelStart then
      Swap(LineSelEnd, LineSelStart);

    if LineSelStart < 0 then
      LineSelStart := 0;
    S := Copy(Lines[LineNo], FLeftCol + 1, len);
    S1 := Copy(S, 1, LineSelStart);
    S2 := Copy(S, LineSelStart + 1, LineSelEnd - LineSelStart);
    S3 := Copy(S, LineSelEnd + 1, len);
  end;
  //------------- DRAW PART ---------------------
  procedure DrawPart(const Part: string; PartStyle, StartPos: integer;
    var rct: TRect; IsSelection: Boolean);
  var
    len, w: integer;
    rctInternal: TRect;
  begin
    len := Length(Part);
    if len > 0 then
      with FLineBitmap.Canvas do
      begin
        w := FCellSize.W * len;
        Font.Style := FStyles.Style[PartStyle];
        if IsSelection then
        begin
          Font.Color := SelColor;
          Brush.Color := SelBkColor;
        end
        else
        begin
          if LineStyleNo = 0 then
          begin
            Font.Color := FStyles.TextColor[PartStyle];
            Brush.Color := FStyles.BkColor[PartStyle];
          end
          else
          begin
            if (LineNo = FSelCharPos.LineNo) and
              (StartPos = FSelCharPos.Pos + 1) and (Length(Part) = 1) then
            begin
              Font.Color := FStyles.TextColor[PartStyle];
              Brush.Color := FStyles.BkColor[PartStyle];
            end
            else
            begin
              Font.Color := FStyles.TextColor[LineStyleNo];
              Brush.Color := FStyles.BkColor[LineStyleNo];
              Font.Style := FStyles.Style[LineStyleNo];
            end;
          end;
        end;
        rct.Right := rct.Left + w;
        rctInternal := rct;
        rctInternal.Left := rctInternal.Left - eRect.Left;
        rctInternal.Right := rctInternal.Right - eRect.Left;
        rctInternal.Top := rctInternal.Top - rct.Top;
        rctInternal.Bottom := rctInternal.Bottom - rct.Top;
        FillRect(rctInternal);
        DrawText(Handle, PChar(Part), len, rctInternal, DT_LEFT
          or DT_SINGLELINE or DT_NOPREFIX);
        rct0.Left := rct.Left + w;
        rct := rct0;
      end;
  end;
  //------------- DRAW SEGMENTS ---------------------
  procedure DrawSegments(S: string; WorkPos: integer;
    var rct: TRect; IsSelection: Boolean);
  var
    i, len, ThisStyle: integer;
  begin
    while True do
    begin
      Len := Length(S);
      if Len = 0 then
        Exit;
      ThisStyle := Ord(ChStyle[WorkPos]);
      i := 1;
      while (i <= Len) and
        (ThisStyle = Ord(ChStyle[WorkPos + i - 1])) do
        Inc(i);
      DrawPart(Copy(S, 1, i - 1), ThisStyle, WorkPos, rct, IsSelection);
      Inc(WorkPos, i - 1);
      s := Copy(s, i, Len);
    end;
  end;
  //---------------------------------------------
begin
  eRect := EditorRect;
  rct := CellRect(0, LineNo - FTopLine);
  rct0 := Rect(eRect.Left, rct.Top, eRect.Right, rct.Bottom);
  lineRct := rct0;

  if LineNo < Lines.Count then
  begin

    rct := rct0;
    S := Lines[LineNo];
    LineStyleNo := LineStyle[LineNo];
    ChStyle := CharAttrs[LineNo];
    FindLineSelection;

    if not Assigned(FOnGetLineAttrs) then
      ChStyle := StringOfChar(#0, Length(Lines[LineNo]));

    if Length(S) > 0 then
      if (FSelCharStyle >= 0) and (LineNo = FSelCharPos.LineNo) then
        ChStyle[FSelCharPos.Pos + 1] := Char(FSelCharStyle);

    pos := FLeftCol + 1; // 1
    DrawSegments(S1, pos, rct, False);
    Inc(pos, Length(S1));
    DrawSegments(S2, pos, rct, True);
    Inc(pos, Length(S2));
    DrawSegments(S3, pos, rct, False);

    //       else begin
    //            DrawPart(S1,StyleNo,rct,False);
    //            DrawPart(S2,StyleNo,rct,True);
    //            DrawPart(S3,StyleNo,rct,False);
    //       end;

    rct1 := rct;
    rct1.Left := rct1.Left - eRect.Left;
    rct1.Right := rct1.Right - eRect.Left;
    rct1.Top := rct1.Top - rct.Top;
    rct1.Bottom := rct1.Bottom - rct.Top;
    with FLineBitmap.Canvas do
    begin
      Brush.Color := FStyles.BkColor[LineStyleNo];
      FillRect(rct1);
    end;

    with LineRct do
      BitBlt(Canvas.Handle, Left, Top, Right - Left, Bottom - Top,
        FLineBitmap.Canvas.Handle, 0, 0, SRCCOPY);
  end
  else
    with Canvas do
    begin
      Brush.Color := BkColor;
      FillRect(rct0);
    end;
end;

//--------------------------------------------------------------
//        SET HIDDEN CARET
//--------------------------------------------------------------

procedure TGLSCustomMemo.SetHiddenCaret(Value: Boolean);
begin
  if Value <> FHiddenCaret then
  begin
    FHiddenCaret := Value;
    if Focused then
      if FHiddenCaret = FCaretVisible then
        ShowCaret(not FHiddenCaret);
  end;
end;

//--------------------------------------------------------------
//        BORDER
//--------------------------------------------------------------

procedure Border(Canvas: TCanvas; const rct: TRect; BorderType: TBorderType);
const
  Colors: array[TBorderType] of array[1..4] of TColor
    = (($D0D0D0, clWhite, clGray, clBlack),
    (clGray, clBlack, $D0D0D0, clWhite),
    (clWhite, clWhite, clWhite, clGray),
    (clGray, clWhite, clWhite, clGray));
begin
  with Canvas do
  begin
    Pen.Color := Colors[BorderType][1];
    MoveTo(rct.Left, rct.Bottom - 1);
    LineTo(rct.Left, rct.Top);
    LineTo(rct.Right, rct.Top);
    if BorderType in [btRaised, btLowered] then
    begin
      Pen.Color := Colors[BorderType][2];
      MoveTo(rct.Left + 1, rct.Bottom);
      LineTo(rct.Left + 1, rct.Top + 1);
      LineTo(rct.Right, rct.Top + 1);
      Pen.Color := Colors[BorderType][3];
      MoveTo(rct.Left + 1, rct.Bottom - 2);
      LineTo(rct.Right - 2, rct.Bottom - 2);
      LineTo(rct.Right - 2, rct.Top + 1);
    end;
    Pen.Color := Colors[BorderType][4];
    MoveTo(rct.Left, rct.Bottom - 1);
    LineTo(rct.Right - 1, rct.Bottom - 1);
    LineTo(rct.Right - 1, rct.Top);
  end;
end;

//--------------------------------------------------------------
//        EDITOR RECT
//--------------------------------------------------------------

function TGLSCustomMemo.EditorRect: TRect;
var
  l, t, r, b: integer;
begin
  l := 2;
  r := Width - 2;
  t := 2;
  b := Height - 2;
  if GutterWidth > 2 then
    l := l + GutterWidth;
  if FScrollBars in [ssBoth, ssVertical] then
    r := r - FScrollBarWidth;
  if FScrollBars in [ssBoth, ssHorizontal] then
    b := b - FScrollBarWidth;
  Result := Rect(l + FMargin, t, r, b);
end;

//--------------------------------------------------------------
//        DRAW MARGIN
//--------------------------------------------------------------

procedure TGLSCustomMemo.DrawMargin;
var
  eRect: TRect;
  i: integer;
begin
  eRect := EditorRect;
  with Canvas do
  begin
    Pen.Color := clWhite;
    for i := 1 to FMargin do
    begin
      MoveTo(eRect.Left - i, eRect.Top);
      LineTo(eRect.Left - i, eRect.Bottom + 1);
    end;
  end;
end;

//--------------------------------------------------------------
//        DRAW GUTTER
//--------------------------------------------------------------

procedure TGLSCustomMemo.DrawGutter;
begin
  if GutterWidth < 2 then
    Exit;
  ResizeGutter;
  FGutter.PaintTo(Canvas);
end;

//--------------------------------------------------------------
//        DRAW SCROLLBARS
//--------------------------------------------------------------

procedure TGLSCustomMemo.DrawScrollBars;
begin
  ResizeScrollBars;
  if FScrollBars in [ssBoth, ssVertical] then
    sbVert.PaintTo(Canvas);
  if FScrollBars in [ssBoth, ssHorizontal] then
    sbHorz.PaintTo(Canvas);
  if FScrollBars = ssBoth then
    with Canvas do
    begin
      Brush.Color := clSilver;
      FillRect(Rect(sbVert.Left, sbHorz.Top + 1,
        sbVert.Left + sbVert.Width, sbHorz.Top + sbHorz.Height));
    end;
end;

//--------------------------------------------------------------
//        FRESH LINE BITMAP
//--------------------------------------------------------------

procedure TGLSCustomMemo.FreshLineBitmap;
var
  eRect: TRect;
begin
  eRect := EditorRect;
  with FLineBitmap do
  begin
    Width := eRect.Right - eRect.Left;
    Height := FCellSize.H;
    FLineBitmap.Canvas.Font.Assign(Self.Canvas.Font);
  end;
end;

//--------------------------------------------------------------
//        PAINT
//--------------------------------------------------------------

procedure TGLSCustomMemo.Paint;
var
  pTop, pBottom: TFullPos;
  rct, eRect: TRect;
  i: integer;
  clipRgn: HRGN;
  Attrs: string;
begin
  if TGLSMemoStrings(Lines).FLockCount > 0 then
    Exit;
  with Canvas do
  begin
    if FCellSize.H = 0 then
      SetFont(FFont);
    FreshLineBitmap;

    Border(Canvas, Rect(0, 0, Width, Height), btLowered);
    DrawMargin;
    DrawGutter;
    DrawScrollBars;

    eRect := EditorRect;
    clipRgn := CreateRectRgn(eRect.Left, eRect.Top, eRect.Right, eRect.Bottom);
    ExtSelectClipRgn(Canvas.Handle, clipRgn, RGN_AND);
    DeleteObject(clipRgn);

    rct := Canvas.ClipRect;
    pTop := CharFromPos(rct.Left, rct.Top);
    pBottom := CharFromPos(rct.Left, rct.Bottom);

    if Assigned(FOnGetLineAttrs) then
      for i := 0 to Lines.Count - 1 do
        if not ValidAttrs[i] then
        begin
          FOnGetLineAttrs(Self, i, Attrs);
          CharAttrs[i] := Attrs;
          ValidAttrs[i] := True;
        end;

    for i := pTop.LineNo to pBottom.LineNo do
      DrawLine(i);
  end;
end;

//--------------------------------------------------------------
//        GET VISIBLE
//--------------------------------------------------------------

function TGLSCustomMemo.GetVisible(Index: integer): integer;
var
  Coord: TFullPos;
  Cell: TCellPos;
  eRect: TRect;
begin
  eRect := EditorRect;
  Coord := CharFromPos(eRect.Right - 1, eRect.Bottom - 1);
  Cell := CellFromPos(eRect.Right - 1, eRect.Bottom - 1);
  case Index of
    0: Result := Cell.X;
    1: Result := Cell.Y;
    2: Result := Coord.Pos - 1;
    3: Result := Coord.LineNo - 1;
  else
    Result := 0;
  end;
end;

//--------------------------------------------------------------
//        IS LINE VISIBLE
//--------------------------------------------------------------

function TGLSCustomMemo.IsLineVisible(LineNo: integer): Boolean;
begin
  if FCellSize.H = 0 then
    SetFont(FFont);
  Result := (FTopLine <= LineNo) and (LineNo <= LastVisibleLine + 1);
end;

//--------------------------------------------------------------
//        MAKE VISIBLE
//--------------------------------------------------------------

procedure TGLSCustomMemo.MakeVisible;
var
  Modified: Boolean;
begin
  Modified := False;
  if CurX < FLeftCol then
  begin
    FLeftCol := CurX - 2;
    if FLeftCol < 0 then
      FLeftCol := 0;
    Modified := True;
  end;
  if CurX > LastVisiblePos then
  begin
    if (FScrollBars in [ssBoth, ssHorizontal]) or
      (ScrollMode = smAuto) then
    begin
      FLeftCol := FLeftCol + CurX - LastVisiblePos + 2;
    end
    else
      CurX := LastVisiblePos;
    Modified := True;
  end;
  if CurY < FTopLine then
  begin
    FTopLine := CurY;
    if FTopLine < 0 then
      FTopLine := 0;
    Modified := True;
  end;
  if CurY > LastVisibleLine then
  begin
    if (FScrollBars in [ssBoth, ssVertical]) or
      (ScrollMode = smAuto) then
    begin
      FTopLine := FTopLine + CurY - LastVisibleLine;
    end
    else
      CurY := LastVisibleLine;
    Modified := True;
  end;
  if Modified then
    Invalidate;
end;

//--------------------------------------------------------------
//        RESIZE EDITOR
//--------------------------------------------------------------

procedure TGLSCustomMemo.ResizeEditor;
begin
  ResizeScrollBars;
  ResizeGutter;
  MakeVisible;
  Invalidate;
end;

//--------------------------------------------------------------
//        FIND TEXT
//--------------------------------------------------------------

function TGLSCustomMemo.FindText(Text: string; Options: TFindOptions; Select:
  Boolean): Boolean;
var
  i, p: integer;
  s1, s0, s: string;
  //-----------------------------------------------------------
  function LastPos(const Substr, s: string): integer;
  var
    i, j, lenSub: integer;
  begin
    Result := 0;
    lenSub := Length(Substr);
    i := Length(s) - lenSub + 1;
    while i > 0 do
    begin
      if s[i] = Substr[1] then
      begin
        Result := i;
        for j := i + 1 to i + lenSub - 1 do
          if s[j] <> Substr[j - i + 1] then
          begin
            Result := 0;
            break;
          end;
      end;
      if Result <> 0 then
        break;
      Dec(i);
    end;
  end;
  //-----------------------------------------------------------
begin
  Result := False;
  if not (frMatchCase in Options) then
    Text := AnsiLowerCase(Text);

  if SelLength > 0 then
    ClearSelection;
  s := Lines[CurY];
  s0 := Copy(s, 1, CurX);
  s1 := Copy(s, CurX + 1, Length(s));
  i := CurY;

  while True do
  begin

    if not (frMatchCase in Options) then
    begin
      s0 := AnsiLowerCase(s0);
      s1 := AnsiLowerCase(s1);
    end;

    if frDown in Options then
      p := Pos(Text, s1)
    else
      p := LastPos(Text, s0);

    if p > 0 then
    begin
      Result := True;
      CurY := i;
      if frDown in Options then
        CurX := Length(s0) + p - 1
      else
        CurX := p - 1;
      if Select then
      begin
        if not (frDown in Options) then
          CurX := CurX + Length(Text);
        ClearSelection;
        if frDown in Options then
          CurX := CurX + Length(Text)
        else
          CurX := CurX - Length(Text);
        ExpandSelection;
      end;
      break;
    end;

    if frDown in Options then
      Inc(i)
    else
      Dec(i);
    if (i < 0) or (i > Lines.Count - 1) then
      break;
    if frDown in Options then
    begin
      s0 := '';
      s1 := Lines[i];
    end
    else
    begin
      s0 := Lines[i];
      s1 := '';
    end;

  end;

end;

//--------------------------------------------------------------
//        RESIZE SCROLLBARS
//--------------------------------------------------------------

procedure TGLSCustomMemo.ResizeScrollBars;
var
  eRect, sbRect: TRect;
  MaxLen, OldMax, NewTop, Margin: integer;
begin
  eRect := EditorRect;
  if FScrollBars in [ssBoth, ssVertical] then
  begin
    with sbVert do
    begin
      Width := 16;
      Height := eRect.Bottom - eRect.Top + 1;
      Left := eRect.Right;
      Top := eRect.Top;
      OldMax := MaxPosition;
      MaxPosition := (Lines.Count - 1) - (LastVisibleLine - FTopLine);
      NewTop := FTopLine;
      if (FTopLine > 0) and (LastVisibleLine > Lines.Count - 1) then
      begin
        Dec(NewTop, LastVisibleLine - (Lines.Count - 1));
        if NewTop < 0 then
          NewTop := 0;
        MaxPosition := NewTop;
      end;
      if MaxPosition < 0 then
        MaxPosition := 0;
      Position := NewTop;
      Total := Lines.Count;
      if OldMax <> MaxPosition then
      begin
        if NewTop <> FTopLine then
        begin
          DoScroll(sbVert, NewTop - FTopLine);
          FGutter.Invalidate;
        end;
        sbRect := sbVert.FullRect;
        InvalidateRect(Handle, @sbRect, True);
      end;
    end;
  end;
  if FScrollBars in [ssBoth, ssHorizontal] then
  begin
    MaxLen := MaxLength;
    with sbHorz do
    begin
      Width := Self.Width - 4;
      if FScrollBars = ssBoth then
        Width := Width - sbVert.Width;
      Height := 16;
      Left := 2;
      Top := eRect.Bottom;
      OldMax := MaxPosition;

      Margin := LastVisiblePos - MaxLen;
      if Margin < 2 then
        Margin := 2;
      MaxPosition := MaxLen - (LastVisiblePos - FLeftCol) + Margin;

      if MaxPosition < 0 then
        MaxPosition := 0;
      Position := FLeftCol;
      Total := MaxLen;
      if OldMax <> MaxPosition then
      begin
        if MaxPosition = 0 then
        begin
          FLeftCol := 0;
          InvalidateRect(Handle, @eRect, True);
          ;
          FGutter.Invalidate;
        end;
        sbRect := sbHorz.FullRect;
        InvalidateRect(Handle, @sbRect, True);
      end;
    end;
  end;
end;

//--------------------------------------------------------------
//        RESIZE GUTTER
//--------------------------------------------------------------

procedure TGLSCustomMemo.ResizeGutter;
var
  eRect: TRect;
begin
  eRect := EditorRect;
  with FGutter do
  begin
    Height := eRect.Bottom - eRect.Top;
  end;
end;

//--------------------------------------------------------------
//        CREATE PARAMS
//--------------------------------------------------------------

procedure TGLSCustomMemo.CreateParams(var Params: TCreateParams);
begin
  inherited;
end;

//--------------------------------------------------------------
//        UNDO, REDO
//--------------------------------------------------------------

procedure TGLSCustomMemo.Undo;
begin
  FUndoList.Undo;
end;

procedure TGLSCustomMemo.Redo;
begin
  FUndoList.Redo;
end;

//--------------------------------------------------------------
//        SET UNDO LIMIT
//--------------------------------------------------------------

procedure TGLSCustomMemo.SetUndoLimit(Value: integer);
begin
  if (FUndoLimit <> Value) then
  begin
    if Value <= 0 then
      Value := 1;
    if Value > 100 then
      Value := 100;
    FUndoLimit := Value;
    FUndoList.Limit := Value;
  end;
end;

//--------------------------------------------------------------
//        UNDO (REDO) CHANGE
//--------------------------------------------------------------

procedure TGLSCustomMemo.UndoChange;
begin
  if Assigned(FOnUndoChange) then
    FOnUndoChange(Self, FUndoList.Pos < FUndoList.Count,
      FUndoList.Pos > 0);
end;

//--------------------------------------------------------------
//        CAN UNDO
//--------------------------------------------------------------

function TGLSCustomMemo.CanUndo: boolean;
begin
  Result := FUndoList.FPos < FUndoList.Count;
end;

//--------------------------------------------------------------
//        CAN REDO
//--------------------------------------------------------------

function TGLSCustomMemo.CanRedo: Boolean;
begin
  Result := FUndoList.FPos > 0;
end;

//--------------------------------------------------------------
//        CLEAR UNDO LIST
//--------------------------------------------------------------

procedure TGLSCustomMemo.ClearUndoList;
begin
  if Assigned(FUndoList) then
    FUndoList.Clear;
end;

//--------------------------------------------------------------
//        SET SCROLL BARS
//--------------------------------------------------------------

procedure TGLSCustomMemo.SetScrollBars(Value: System.UITypes.TScrollStyle);
begin
  if FScrollBars <> Value then
  begin
    FScrollBars := Value;
    if not (csLoading in ComponentState) then
      ResizeEditor;
  end;
end;

//--------------------------------------------------------------
//        CREATE
//--------------------------------------------------------------

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

  ControlStyle := [csCaptureMouse, csClickEvents,
    csDoubleClicks, csReplicatable];
  Width := 100;
  Height := 40;
  TabStop := True;
  Cursor := crIBeam;

  FFont := TFont.Create;
  FFont.Name := 'Courier New';
  FFont.Size := 10;
  Canvas.Font.Assign(FFont);

  FHiddenCaret := False;
  FCaretVisible := False;

  FCurX := 0;
  FCurY := 0;
  FLeftCol := 0;
  FTopLine := 0;
  FTabSize := 4;
  FMargin := 2;
  FAutoIndent := True;
  FLines := TGLSMemoStrings.Create;
  TGLSMemoStrings(FLines).FMemo := Self;

  FScrollBars := ssBoth;
  FScrollBarWidth := 16;
  sbVert := TGLSMemoScrollBar.Create(Self, sbVertical);
  sbVert.Width := FScrollBarWidth;
  sbHorz := TGLSMemoScrollBar.Create(Self, sbHorizontal);
  sbHorz.Height := FScrollBarWidth;

  FGutter := TGLSMemoGutter.Create;
  with FGutter do
  begin
    FLeft := 2;
    FTop := 2;
    FWidth := 0;
    FHeight := 0;
    FColor := clBtnFace;
    FMemo := Self;
  end;

  FSelStartX := 0;
  FSelStartY := 0;
  FSelEndX := 0;
  FSelEndY := 0;

  FBkColor := clWhite;
  FSelColor := clWhite;
  FSelBkColor := clNavy;

  FStyles := TStyleList.Create;
  FStyles.Add(clBlack, clWhite, []);

  FSelCharPos.LineNo := -1;
  FSelCharPos.Pos := -1;
  FSelCharStyle := -1;

  FLineBitmap := TBitmap.Create;

  FLeftButtonDown := False;
  FScrollMode := smAuto;

  FUndoList := TGLSMemoUndoList.Create;
  FFirstUndoList := FUndoList;
  FUndoList.Memo := Self;

  FUndoLimit := 100;

  TGLSMemoStrings(FLines).DoAdd('');

  FAfterDoubleClick := False;

end;

//--------------------------------------------------------------
//        DESTROY
//--------------------------------------------------------------

destructor TGLSCustomMemo.Destroy;
begin
  FFont.Free;
  FLines.Free;
  FGutter.Free;
  sbVert.Free;
  sbHorz.Free;
  FStyles.Free;
  FLineBitmap.Free;
  FFirstUndoList.Free;
  inherited;
end;

 

// ---------------------TGLSMemoScrollBar functions 

procedure TGLSMemoScrollBar.SetParams(Index: integer; Value: integer);
begin
  case Index of
    0: if Left <> Value then
        FLeft := Value;
    1: if Top <> Value then
        FTop := Value;
    2: if Width <> Value then
        FWidth := Value;
    3: if Height <> Value then
        FHeight := Value;
    4: if Total <> Value then
        FTotal := Value;
    5: if MaxPosition <> Value then
        FMaxPosition := Value;
    6: if Position <> Value then
        FPosition := Value;
  end;
end;
//-------------------- CREATE ------------------------------

constructor TGLSMemoScrollBar.Create(AParent: TGLSMemoAbstractScrollableObject;
  AKind: TScrollBarKind);
begin
  FParent := AParent;
  FButtonLength := 16;
  FKind := AKind;
  FState := sbsWait;
end;
//-------------------- RECT -----------------------

function TGLSMemoScrollBar.GetRect: TRect;
begin
  Result := Rect(Left, Top, Left + Width, Top + Height);
end;
//-------------------- GET THUMB RECT -----------------------

function TGLSMemoScrollBar.GetThumbRect: TRect;
var
  TotalLen, FreeLen, ThumbLen, ThumbOffset, ThumbCoord: integer;
  K: double;
begin
  if MaxPosition <= 0 then
  begin
    Result := Rect(0, 0, 0, 0);
    Exit;
  end;
  if Kind = sbVertical then
    TotalLen := Height
  else
    TotalLen := Width;
  FreeLen := TotalLen - 2 * FButtonLength;

  K := (Total - MaxPosition) / MaxPosition;
  if K > 0 then
  begin
    ThumbLen := round(FreeLen * K / (1 + K));
    if ThumbLen < 8 then
      ThumbLen := 8;
  end
  else
    ThumbLen := 8;

  if ThumbLen >= FreeLen then
    Result := Rect(0, 0, 0, 0)
  else
  begin
    ThumbOffset := round((FreeLen - ThumbLen) * Position / MaxPosition);
    ThumbCoord := FButtonLength + ThumbOffset;
    if Kind = sbVertical then
      Result := Rect(Left + 1, Top + ThumbCoord, Left + Width, Top + ThumbCoord
        + ThumbLen)
    else
      Result := Rect(Left + ThumbCoord, Top + 1, Left + ThumbCoord + ThumbLen,
        Top + Height);
  end;
end;
//-------------------- GET Back RECT -----------------------

function TGLSMemoScrollBar.GetBackRect: TRect;
begin
  if Kind = sbVertical then
    Result := Rect(Left + 1, Top, Left + Width, Top + FButtonLength)
  else
    Result := Rect(Left, Top + 1, Left + FButtonLength, Top + Height);
end;
//-------------------- GET MIDDLE RECT -----------------------

function TGLSMemoScrollBar.GetMiddleRect: TRect;
var
  bRect, fRect: TRect;
begin
  bRect := BackRect;
  fRect := ForwardRect;
  if Kind = sbVertical then
    Result := Rect(Left + 1, bRect.Bottom, Left + Width, fRect.Top)
  else
    Result := Rect(bRect.Right, Top + 1, fRect.Left, Top + Height);
end;
//-------------------- GET Forward RECT -----------------------

function TGLSMemoScrollBar.GetForwardRect: TRect;
begin
  if Kind = sbVertical then
    Result := Rect(Left + 1, Top + Height - FButtonLength, Left + Width, Top +
      Height)
  else
    Result := Rect(Left + Width - FButtonLength, Top + 1, Left + Width, Top +
      Height);
end;
//-------------------- GET PAGE BACK RECT -----------------------

function TGLSMemoScrollBar.GetPgBackRect: TRect;
var
  thRect: TRect;
begin
  thRect := GetThumbRect;
  if thRect.Bottom = 0 then
  begin
    Result := Rect(0, 0, 0, 0);
    Exit;
  end;
  if Kind = sbVertical then
    Result := Rect(Left + 1, Top + FButtonLength, Left + Width, thRect.Top - 1)
  else
    Result := Rect(Left + FButtonLength, Top + 1, thRect.Left - 1, Top +
      Height);
end;
//-------------------- GET PG FORWARD RECT -----------------------

function TGLSMemoScrollBar.GetPgForwardRect: TRect;
var
  thRect: TRect;
begin
  thRect := GetThumbRect;
  if thRect.Bottom = 0 then
  begin
    Result := Rect(0, 0, 0, 0);
    Exit;
  end;
  if Kind = sbVertical then
    Result := Rect(Left + 1, thRect.Bottom, Left + Width, Top + Height -
      FButtonLength)
  else
    Result := Rect(thRect.Right, Top + 1, Left + Width - FButtonLength, Top +
      Height);
end;
//-------------------- PAINT TO -----------------------

procedure TGLSMemoScrollBar.PaintTo(ACanvas: TCanvas);
var
  sRect, mRect, gRect, thRect: TRect;
  iconX, iconY, shift: integer;
begin
  with ACanvas do
  begin
    if Kind = sbVertical then
    begin
      Pen.Color := clSilver;
      MoveTo(Left, Top);
      LineTo(Left, Top + Height);

      sRect := BackRect;
      Brush.Color := clSilver;
      FillRect(sRect);
      if State = sbsBack then
      begin
        shift := 1;
        Pen.Color := clGray;
        with sRect do
          Rectangle(Left, Top, Right, Bottom);
      end
      else
      begin
        shift := 0;
        Border(ACanvas, sRect, btFlatRaised);
      end;
      iconX := sRect.Left + (Width - 1 - 7) div 2;
      iconY := sRect.Top + (FButtonLength - 8) div 2;
      Draw(iconX + shift, iconY + shift, bmScrollBarUp);

      gRect := ForwardRect;
      Brush.Color := clSilver;
      FillRect(gRect);
      if State = sbsForward then
      begin
        shift := 1;
        Pen.Color := clGray;
        with gRect do
          Rectangle(Left, Top, Right, Bottom);
      end
      else
      begin
        shift := 0;
        Border(ACanvas, gRect, btFlatRaised);
      end;
      iconX := gRect.Left + (Width - 1 - 7) div 2;
      iconY := gRect.Top + (FButtonLength - 8) div 2;
      Draw(iconX + shift, iconY + shift, bmScrollBarDown);

      mRect := Rect(sRect.Left, sRect.Bottom, gRect.Right, gRect.Top);
    end
    else
    begin
      Pen.Color := clSilver;
      MoveTo(Left, Top);
      LineTo(Left + Width, Top);

      sRect := BackRect;
      Brush.Color := clSilver;
      FillRect(sRect);
      if State = sbsBack then
      begin
        shift := 1;
        Pen.Color := clGray;
        with sRect do
          Rectangle(Left, Top, Right, Bottom);
      end
      else
      begin
        shift := 0;
        Border(ACanvas, sRect, btFlatRaised);
      end;
      iconX := sRect.Left + shift + (FButtonLength - 8) div 2;
      iconY := sRect.Top + shift + (Height - 1 - 7) div 2;
      Draw(iconX + shift, iconY + shift, bmScrollBarLeft);

      gRect := ForwardRect;
      Brush.Color := clSilver;
      FillRect(gRect);
      if State = sbsForward then
      begin
        shift := 1;
        Pen.Color := clGray;
        with gRect do
          Rectangle(Left, Top, Right, Bottom);
      end
      else
      begin
        shift := 0;
        Border(ACanvas, gRect, btFlatRaised);
      end;
      iconX := gRect.Left + (FButtonLength - 8) div 2;
      iconY := gRect.Top + (Height - 1 - 7) div 2;
      Draw(iconX + shift, iconY + shift, bmScrollBarRight);

      mRect := Rect(sRect.Right, sRect.Top, gRect.Left, gRect.Bottom);
    end;

    Brush.Bitmap := bmScrollBarFill;
    FillRect(mRect);
    Brush.Bitmap := nil;
    if State = sbsPageBack then
    begin
      Brush.Color := clGray;
      FillRect(PageBackRect);
    end;
    if State = sbsPageForward then
    begin
      Brush.Color := clGray;
      FillRect(PageForwardRect);
    end;

    thRect := ThumbRect;
    Brush.Color := clSilver;
    FillRect(thRect);
    Border(ACanvas, thRect, btFlatRaised);
  end;
end;
//-------------------- SET STATE ----------

procedure TGLSMemoScrollBar.SetState(Value: TsbState);
begin
  if FState <> Value then
  begin
    FState := Value;
  end;
end;
//-------------------- MOUSE DOWN ------------

function TGLSMemoScrollBar.MouseDown(Button: TMouseButton; Shift: TShiftState;
  X,
  Y: Integer):
  Boolean;
var
  sRect, gRect, thRect, pbRect, pfRect: TRect;
begin
  Result := False;
  if (Width = 0) or (Height = 0) then
    Exit;
  sRect := BackRect;
  gRect := ForwardRect;
  pbRect := PageBackRect;
  pfRect := PageForwardRect;
  thRect := ThumbRect;
  if PointInRect(Point(X, Y), sRect) then
  begin
    State := sbsBack;
    InvalidateRect(Parent.Handle, @sRect, True);
    Result := True;
    Exit;
  end;
  if PointInRect(Point(X, Y), gRect) then
  begin
    State := sbsForward;
    InvalidateRect(Parent.Handle, @gRect, True);
    Result := True;
    Exit;
  end;
  if PointInRect(Point(X, Y), pbRect) then
  begin
    State := sbsPageBack;
    InvalidateRect(Parent.Handle, @pbRect, True);
    Result := True;
    Exit;
  end;
  if PointInRect(Point(X, Y), pfRect) then
  begin
    State := sbsPageForward;
    InvalidateRect(Parent.Handle, @pfRect, True);
    Result := True;
    Exit;
  end;
  if PointInRect(Point(X, Y), thRect) then
  begin
    State := sbsDragging;
    FXOffset := X - thRect.Left;
    FYOffset := Y - thRect.Top;
    Result := True;
    Exit;
  end;

end;
//-------------------- MOUSE UP ----------

function TGLSMemoScrollBar.MouseUp(Button: TMouseButton; Shift: TShiftState; X,
  Y:
  Integer):
  Boolean;
var
  sRect, gRect, thRect, pbRect, pfRect: TRect;
begin
  Result := False;
  if (Width = 0) or (Height = 0) then
    Exit;
  sRect := BackRect;
  gRect := ForwardRect;
  pbRect := PageBackRect;
  pfRect := PageForwardRect;
  thRect := ThumbRect;
  case State of
    sbsBack:
      begin
        State := sbsWait;
        InvalidateRect(Parent.Handle, @sRect, True);
        FParent.DoScroll(Self, -1);
        Result := True;
        Exit;
      end;
    sbsForward:
      begin
        State := sbsWait;
        InvalidateRect(Parent.Handle, @gRect, True);
        FParent.DoScroll(Self, 1);
        Result := True;
        Exit;
      end;
    sbsPageBack:
      begin
        State := sbsWait;
        InvalidateRect(Parent.Handle, @pbRect, True);
        FParent.DoScrollPage(Self, -1);
        Result := True;
        Exit;
      end;
    sbsPageForward:
      begin
        State := sbsWait;
        InvalidateRect(Parent.Handle, @pfRect, True);
        FParent.DoScrollPage(Self, 1);
        Result := True;
        Exit;
      end;
    sbsDragging:
      begin
        State := sbsWait;
        Result := True;
        Exit;
      end;
  end;
end;
//-------------------- MOUSE MOVE -----------

function TGLSMemoScrollBar.MouseMove(Shift: TShiftState; X, Y: Integer):
  Boolean;
var
  sRect, gRect, thRect, pbRect, pfRect: TRect;
begin
  Result := False;
  if (Width = 0) or (Height = 0) then
    Exit;
  sRect := BackRect;
  gRect := ForwardRect;
  pbRect := PageBackRect;
  pfRect := PageForwardRect;
  thRect := ThumbRect;
  case State of
    sbsBack:
      if not PointInRect(Point(X, Y), sRect) then
      begin
        State := sbsWait;
        InvalidateRect(Parent.Handle, @sRect, True);
        Result := True;
        Exit;
      end;
    sbsForward:
      if not PointInRect(Point(X, Y), gRect) then
      begin
        State := sbsWait;
        InvalidateRect(Parent.Handle, @gRect, True);
        Result := True;
        Exit;
      end;
    sbsPageBack:
      if not PointInRect(Point(X, Y), pbRect) then
      begin
        State := sbsWait;
        InvalidateRect(Parent.Handle, @pbRect, True);
        Result := True;
        Exit;
      end;
    sbsPageForward:
      if not PointInRect(Point(X, Y), pfRect) then
      begin
        State := sbsWait;
        InvalidateRect(Parent.Handle, @pfRect, True);
        Result := True;
        Exit;
      end;
    sbsDragging:
      begin
        MoveThumbTo(X, Y);
        Result := True;
        Exit;
      end;
  end;
end;
//-------------------- MOVE THUMB TO ------------

function TGLSMemoScrollBar.MoveThumbTo(X, Y: Integer): integer;
var
  thRect, mRect: TRect;
  FreeLen, ThumbLen, NewPosition, NewOffset: integer;
begin
  thRect := ThumbRect;
  mRect := MiddleRect;
  NewOffset := 0;
  FreeLen := 0;
  ThumbLen := 0;
  case Kind of
    sbVertical:
      begin
        FreeLen := mRect.Bottom - mRect.Top;
        ThumbLen := thRect.Bottom - thRect.Top;
        NewOffset := Y - FYOffset - (Top + FButtonLength);
      end;
    sbHorizontal:
      begin
        FreeLen := mRect.Right - mRect.Left;
        ThumbLen := thRect.Right - thRect.Left;
        NewOffset := X - FXOffset - (Left + FButtonLength);
      end
  end;
  NewPosition := round(NewOffset * MaxPosition / (FreeLen - ThumbLen));
  Result := NewPosition - Position;
  if NewPosition <> Position then
  begin
    Parent.DoScroll(Self, NewPosition - Position);
  end;
end;

//--------------------------------------------------------------
//        GUTTER
//--------------------------------------------------------------
//-------------------- SET PARAMS -----------------------

procedure TGLSMemoGutter.SetParams(Index: integer; Value: integer);
begin
  case Index of
    0: FLeft := Value;
    1: FTop := Value;
    2: FWidth := Value;
    3: FHeight := Value;
  end;
end;
//-------------------- PAINT TO -----------------------

procedure TGLSMemoGutter.PaintTo(ACanvas: TCanvas);
var
  LineNo, T, H: integer;
begin
  with ACanvas do
  begin
    Pen.Color := clGray;
    MoveTo(Left + Width - 1, Top);
    LineTo(Left + Width - 1, Top + Height);
    Pen.Color := clWhite;
    MoveTo(Left + Width - 2, Top);
    LineTo(Left + Width - 2, Top + Height);
    Brush.Color := Self.FColor;
    FillRect(Rect(Left, Top, Left + Width - 2, Top + Height));
    if Assigned(FMemo.OnGutterDraw) then
    begin
      T := Top;
      H := FMemo.FCellSize.H;
      LineNo := FMemo.FTopLine;
      while T < Top + Height do
      begin
        FMemo.OnGutterDraw(FMemo, ACanvas, LineNo,
          Rect(Left, T, Left + Width - 2, T + H));
        T := T + H;
        Inc(LineNo);
        if LineNo >= FMemo.Lines.Count then
          break;
      end;
    end;
  end;
end;

//-------------------- INVALIDATE -----------------------

procedure TGLSMemoGutter.Invalidate;
var
  gRect: TRect;
begin
  gRect := Rect(Left, Top, Left + Width, Top + Height);
  InvalidateRect(FMemo.Handle, @gRect, True);
end;

//-------------------- GET RECT -----------------------

function TGLSMemoGutter.GetRect: TRect;
begin
  Result := Rect(Left, Top, Left + Width, Top + Height);
end;

 

// ---------------------TStyleList 

procedure TStyleList.CheckRange(Index: integer);
begin
  if (Index < 0) or (Index >= Count) then
    raise EListError.Create('Incorrect list item index ' + IntToStr(Index));
end;
//-------------------- DESTROY ---------------------------

destructor TStyleList.Destroy;
begin
  Clear;
  inherited;
end;
//-------------------- CHANGE ---------------------------

procedure TStyleList.Change(Index: integer; ATextColor, ABkCOlor: TColor;
  AStyle: TFontStyles);
var
  P: TCharStyle;
begin
  CheckRange(Index);
  P := TCharStyle(Items[Index]);
  P.TextColor := ATextColor;
  P.BkColor := ABkColor;
  P.Style := AStyle;
end;
//-------------------- ADD ---------------------------

function TStyleList.Add(ATextColor, ABkColor: TColor; AStyle: TFontStyles):
  Integer;
var
  P: TCharStyle;
begin
  P := TCharStyle.Create;
  with P do
  begin
    TextColor := ATextColor;
    BkColor := ABkColor;
    Style := AStyle;
  end;
  Result := inherited Add(P);
end;
//-------------------- CLEAR ---------------------------

procedure TStyleList.Clear;
begin
  while Count > 0 do
    Delete(0);
end;
//-------------------- DELETE ---------------------------

procedure TStyleList.Delete(Index: Integer);
var
  P: TCharStyle;
begin
  CheckRange(Index);
  P := TCharStyle(Items[Index]);
  P.Free;
  inherited;
end;
//-------------------- GET/SET TEXT COLOR ---------------------------

function TStyleList.GetTextColor(Index: Integer): TColor;
begin
  CheckRange(Index);
  Result := TCharStyle(Items[Index]).TextColor;
end;

procedure TStyleList.SetTextColor(Index: Integer; Value: TColor);
begin
  CheckRange(Index);
  TCharStyle(Items[Index]).TextColor := Value;
end;
//-------------------- GET/SET BK COLOR ---------------------------

function TStyleList.GetBkColor(Index: Integer): TColor;
begin
  CheckRange(Index);
  Result := TCharStyle(Items[Index]).BkColor;
end;

procedure TStyleList.SetBkColor(Index: Integer; Value: TColor);
begin
  CheckRange(Index);
  TCharStyle(Items[Index]).BkColor := Value;
end;
//-------------------- GET/SET STYLE ---------------------------

function TStyleList.GetStyle(Index: Integer): TFontStyles;
begin
  CheckRange(Index);
  Result := TCharStyle(Items[Index]).Style;
end;

procedure TStyleList.SetStyle(Index: Integer; Value: TFontStyles);
begin
  CheckRange(Index);
  TCharStyle(Items[Index]).Style := Value;
end;

 

// ---------------------TGLSMemoStrings 

destructor TGLSMemoStrings.Destroy;
var
  P: TObject;
begin
  while Count > 0 do
  begin
    P := inherited GetObject(0);
    P.Free;
    inherited Delete(0);
  end;
  inherited;
end;
//-------------------- CLEAR ----------------------

procedure TGLSMemoStrings.Clear;
begin
  while Count > 0 do
  begin
    Delete(0);
    if (Count = 1) and (Strings[0] = '') then
      break;
  end;
end;

//-------------------- ASSIGN ----------------------

procedure TGLSMemoStrings.Assign(Source: TPersistent);
var
  P: TObject;
begin
  if Source is TStrings then
  begin
    BeginUpdate;
    try
      while Count > 0 do
      begin
        P := inherited GetObject(0);
        P.Free;
        inherited Delete(0);
      end;
      //      inherited Clear;
      AddStrings(TStrings(Source));
    finally
      EndUpdate;
    end;
    Exit;
  end;
  inherited Assign(Source);
end;

//-------------------- ADD ----------------------

function TGLSMemoStrings.DoAdd(const S: string): Integer;
begin
  Result := inherited AddObject(S, nil);
end;
//-------------------- ADD ----------------------

function TGLSMemoStrings.Add(const S: string): Integer;
begin
  if Assigned(FMemo.Parent) then
    Result := FMemo.AddString(S)
  else
    Result := DoAdd(S);
end;
//-------------------- OBJECT ----------------------

function TGLSMemoStrings.AddObject(const S: string; AObject: TObject): Integer;
begin
  if AObject <> nil then
    raise EInvalidOp.Create(SObjectsNotSupported);
  Result := DoAdd(S);
end;
//-------------------- INSERT ----------------------

procedure TGLSMemoStrings.InsertObject(Index: Integer;
  const S: string; AObject: TObject);
begin
  if AObject <> nil then
    raise EInvalidOp.Create(SObjectsNotSupported);
  DoInsert(Index, S);
end;
//-------------------- DO INSERT ----------------------

procedure TGLSMemoStrings.DoInsert(Index: Integer; const S: string);
begin
  InsertItem(Index, S, nil);
end;
//-------------------- INSERT ----------------------

procedure TGLSMemoStrings.Insert(Index: Integer; const S: string);
begin
  if Assigned(FMemo) then
    FMemo.InsertString(Index, S)
  else
    DoInsert(Index, S);
end;
//-------------------- DELETE ----------------------

procedure TGLSMemoStrings.Delete(Index: Integer);
var
  P: TObject;
begin
  if (Index < 0) or (Index > Count - 1) then
    Exit;
  if FDeleting or (not Assigned(FMemo)) then
  begin
    P := inherited GetObject(Index);
    P.Free;
    inherited;
  end
  else
  begin
    FMemo.DeleteLine(Index, -1, -1, -1, -1, True);
  end;
end;
//-------------------- LOAD FROM FILE ----------------------

procedure TGLSMemoStrings.LoadFromFile(const FileName: string);
begin
  with FMemo do
  begin
    ClearSelection;
    ClearUndoList;
    CurX := 0;
    CurY := 0;
  end;
  Clear;
  inherited;
  FMemo.Invalidate;
end;
//-------------------- SET UPDATE STATE ----------------------

procedure TGLSMemoStrings.SetUpdateState(Updating: Boolean);
begin
  if Updating then
    Inc(FLockCount)
  else if FLockCount > 0 then
    Dec(FLockCount);
end;
//-------------------- CHECK RANGE ---------------------------

procedure TGLSMemoStrings.CheckRange(Index: integer);
begin
  if (Index < 0) or (Index >= Count) then
    raise EListError('Incorrect index of list item ' + IntToStr(Index));
end;
//-------------------- GET OBJECT ---------------------------

function TGLSMemoStrings.GetObject(Index: Integer): TObject;
begin
  CheckRange(Index);
  Result := inherited GetObject(Index);
  if Assigned(Result) and (Result is TLineProp) then
    Result := TLineProp(Result).FObject;
end;
//-------------------- PUT OBJECT ---------------------------

procedure TGLSMemoStrings.PutObject(Index: Integer; AObject: TObject);
var
  P: TObject;
begin
  CheckRange(Index);
  P := Objects[Index];
  if Assigned(P) and (P is TLineProp) then
    TLineProp(P).FObject := AObject
  else
    inherited PutObject(Index, AObject);
end;
//-------------------- GET LINE PROP ---------------------------

function TGLSMemoStrings.GetLineProp(Index: integer): TLineProp;
var
  P: TObject;
begin
  CheckRange(Index);
  Result := nil;
  P := inherited GetObject(Index);
  if Assigned(P) and (P is TLineProp) then
    Result := TLineProp(P);
end;

//-------------------- CREATE PROP --------------------------

function TGLSMemoStrings.CreateProp(Index: integer): TLineProp;
begin
  Result := TLineProp.Create;
  with Result do
  begin
    FStyleNo := 0;
    FInComment := False;
    FInBrackets := -1;
    FValidAttrs := False;
    FCharAttrs := '';
    FObject := Objects[Index];
  end;
  inherited PutObject(Index, Result);
end;

//-------------------- GET LINE STYLE --------------------------

function TGLSMemoStrings.GetLineStyle(Index: integer): integer;
var
  P: TLineProp;
begin
  P := LineProp[Index];
  if P = nil then
    Result := 0
  else
    Result := P.FStyleNo;
end;

//-------------------- SET LINE STYLE --------------------------

procedure TGLSMemoStrings.SetLineStyle(Index: integer; Value: integer);
var
  P: TLineProp;
begin
  P := LineProp[Index];
  if P = nil then
    P := CreateProp(Index);
  P.FStyleNo := Value;
end;

//-------------------- GET/SET IN COMMENT ---------------------------

function TGLSMemoStrings.GetInComment(Index: Integer): Boolean;
var
  P: TLineProp;
begin
  P := LineProp[Index];
  if P = nil then
    Result := False
  else
    Result := P.FInComment;
end;

procedure TGLSMemoStrings.SetInComment(Index: Integer; Value: Boolean);
var
  P: TLineProp;
begin
  P := LineProp[Index];
  if P = nil then
    P := CreateProp(Index);
  P.FInComment := Value;
end;

//-------------------- GET/SET IN BRACKETS ---------------------------

function TGLSMemoStrings.GetInBrackets(Index: Integer): integer;
var
  P: TLineProp;
begin
  P := LineProp[Index];
  if P = nil then
    Result := -1
  else
    Result := P.FInBrackets;
end;

procedure TGLSMemoStrings.SetInBrackets(Index: Integer; Value: integer);
var
  P: TLineProp;
begin
  P := LineProp[Index];
  if P = nil then
    P := CreateProp(Index);
  P.FInBrackets := Value;
end;

//-------------------- GET/SET VALID ATTRS ---------------------------

function TGLSMemoStrings.GetValidAttrs(Index: Integer): Boolean;
var
  P: TLineProp;
begin
  P := LineProp[Index];
  if P = nil then
    Result := False
  else
    Result := P.FValidAttrs;
end;

procedure TGLSMemoStrings.SetValidAttrs(Index: Integer; Value: Boolean);
var
  P: TLineProp;
begin
  P := LineProp[Index];
  if P = nil then
    P := CreateProp(Index);
  P.FValidAttrs := Value;
end;
//-------------------- GET/SET CHAR ATTRS ---------------------------

function TGLSMemoStrings.GetCharAttrs(Index: Integer): string;
var
  P: TLineProp;
begin
  P := LineProp[Index];
  if P = nil then
    Result := ''
  else
    Result := P.FCharAttrs;
end;

procedure TGLSMemoStrings.SetCharAttrs(Index: Integer; const Value: string);
var
  P: TLineProp;
begin
  P := LineProp[Index];
  if P = nil then
    P := CreateProp(Index);
  P.FCharAttrs := Value;
end;

 

// ---------------------TGLSMemoUndo 

constructor TGLSMemoUndo.Create(ACurX0, ACurY0, ACurX, ACurY: integer; const AText:
  string);
begin
  inherited Create;
  FUndoCurX0 := ACurX0;
  FUndoCurY0 := ACurY0;
  FUndoCurX := ACurX;
  FUndoCurY := ACurY;
  FUndoText := AText;
end;

procedure TGLSMemoUndo.Undo;
begin
  if Assigned(FMemo) then
    with FMemo do
    begin
      CurY := FUndoCurY;
      CurX := FUndoCurX;
      PerformUndo;
      CurY := FUndoCurY0;
      CurX := FUndoCurX0;
    end;
end;

procedure TGLSMemoUndo.Redo;
begin
  if Assigned(FMemo) then
    with FMemo do
    begin
      CurY := FUndoCurY0;
      CurX := FUndoCurX0;
      PerformRedo;
      CurY := FUndoCurY;
      CurX := FUndoCurX;
    end;
end;

function TGLSMemoUndo.Append(NewUndo: TGLSMemoUndo): Boolean;
begin
  Result := False;
end;

//----------------  TINSERT CHAR UNDO --------------------------

procedure TGLSMemoInsCharUndo.PerformUndo;
var
  i: integer;
  CurrLine: string;
begin
  for i := Length(FUndoText) downto 1 do
  begin
    CurrLine := FMemo.Lines[FMemo.CurY];
    if ((FUndoText[i] = #13) and (FMemo.CurX = 0)) or
      (FUndoText[i] = CurrLine[FMemo.CurX]) then
      FMemo.BackSpace;
  end;
end;

procedure TGLSMemoInsCharUndo.PerformRedo;
var
  i: integer;
begin
  with FMemo do
    for i := 1 to Length(FUndoText) do
      if FUndoText[i] = #13 then
        NewLine
      else
        InsertChar(FUndoText[i]);
end;

function TGLSMemoInsCharUndo.Append(NewUndo: TGLSMemoUndo): Boolean;
begin
  Result := False;
  if not ((NewUndo is TGLSMemoInsCharUndo) and
    (NewUndo.UndoCurX0 = FUndoCurX) and
    (NewUndo.UndoCurY0 = FUndoCurY)) then
    Exit;
  FUndoText := FUndoText + NewUndo.FUndoText;
  FUndoCurX := NewUndo.UndoCurX;
  FUndoCurY := NewUndo.UndoCurY;
  Result := True;
end;

//----------------  TDELETE CHAR UNDO --------------------------

procedure TGLSMemoDelCharUndo.PerformUndo;
var
  i: integer;
begin
  with FMemo do
    for i := 1 to Length(FUndoText) do
    begin
      if not FIsBackspace then
      begin
        CurY := FUndoCurY0;
        CurX := FUndoCurX0;
      end;
      if FUndoText[i] = #13 then
        NewLine
      else
        InsertChar(FUndoText[i]);
    end;
end;

procedure TGLSMemoDelCharUndo.PerformRedo;
var
  i: integer;
begin
  with FMemo do
    for i := 1 to Length(FUndoText) do
      if FIsBackspace then
        BackSpace
      else
        DeleteChar(-1, -1);
end;

function TGLSMemoDelCharUndo.Append(NewUndo: TGLSMemoUndo): Boolean;
begin
  Result := False;
  if not ((NewUndo is TGLSMemoDelCharUndo) and
    (NewUndo.UndoCurX0 = FUndoCurX) and
    (NewUndo.UndoCurY0 = FUndoCurY)) then
    Exit;
  if TGLSMemoDelCharUndo(NewUndo).FIsBackspace <> FIsBackspace then
    Exit;
  FUndoText := NewUndo.FUndoText + FUndoText;
  FUndoCurX := NewUndo.UndoCurX;
  FUndoCurY := NewUndo.UndoCurY;
  Result := True;
end;

//----------------  TDELETE BUF, LINE UNDO --------------------------

constructor TGLSMemoDelLineUndo.Create(AIndex, ACurX0, ACurY0, ACurX, ACurY:
  integer; const AText: string);
begin
  inherited Create(ACurX0, ACurY0, ACurX, ACurY, AText);
  FIndex := AIndex;
end;

procedure TGLSMemoDelLineUndo.PerformUndo;
var
  SaveCurX: integer;
begin
  with FMemo do
  begin
    SaveCurX := CurX;
    CurX := 0;
    ClearSelection;
    SetSelText(PChar(FUndoText + #13#10));
    CurX := SaveCurX;
  end;
end;

procedure TGLSMemoDelLineUndo.PerformRedo;
begin
  FMemo.DeleteLine(FIndex, FUndoCurX0, FUndoCurY0, FUndoCurX, FUndoCurY, True);
end;

procedure TGLSMemoDeleteBufUndo.PerformUndo;
begin
  with FMemo do
  begin
    ClearSelection;
    SetSelText(PChar(FUndoText));
  end;
end;

procedure TGLSMemoDeleteBufUndo.PerformRedo;
begin
  with FMemo do
  begin
    FSelStartX := FUndoSelStartX;
    FSelStartY := FUndoSelStartY;
    FSelEndX := FUndoSelEndX;
    FSelEndY := FUndoSelEndY;
    DeleteSelection(True);
  end;
end;

//----------------  TPASTE UNDO --------------------------

procedure TGLSMemoPasteUndo.PerformUndo;
begin
  with FMemo do
  begin
    FSelStartX := FUndoCurX0;
    FSelStartY := FUndoCurY0;
    FSelEndX := FUndoCurX;
    FSelEndY := FUndoCurY;
    DeleteSelection(True);
  end;
end;

procedure TGLSMemoPasteUndo.PerformRedo;
begin
  with FMemo do
  begin
    ClearSelection;
    SetSelText(PChar(FUndoText));
  end;
end;

//----------------  TUNDO LIST --------------------------

constructor TGLSMemoUndoList.Create;
begin
  inherited;
  FPos := 0;
  FIsPerforming := False;
  FLimit := 100;
end;

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

function TGLSMemoUndoList.Get(Index: Integer): TGLSMemoUndo;
begin
  Result := TGLSMemoUndo(inherited Get(Index));
end;

function TGLSMemoUndoList.Add(Item: Pointer): Integer;
begin
  Result := -1;
  if FIsPerforming then
  begin
    TGLSMemoUndo(Item).Free;
    Exit;
  end;

  if (Count > 0) and
    Items[0].Append(TGLSMemoUndo(Item)) then
  begin
    TGLSMemoUndo(Item).Free;
    Exit;
  end;

  TGLSMemoUndo(Item).FMemo := Self.FMemo;
  if FPos > 0 then
    while FPos > 0 do
    begin
      Delete(0);
      Dec(FPos);
    end;
  Insert(0, Item);
  if Count > FLimit then
    Delete(Count - 1);
  Memo.UndoChange;
  Result := 0;
end;

procedure TGLSMemoUndoList.Clear;
begin
  while Count > 0 do
    Delete(0);
  FPos := 0;
  with Memo do
    if not (csDestroying in ComponentState) then
      UndoChange;
end;

procedure TGLSMemoUndoList.Delete(Index: Integer);
begin
  TGLSMemoUndo(Items[Index]).Free;
  inherited;
end;

procedure TGLSMemoUndoList.Undo;
var
  OldAutoIndent: Boolean;
begin
  if FPos < Count then
  begin
    OldAutoIndent := Memo.AutoIndent;
    Memo.AutoIndent := False;
    FIsPerforming := True;
    Items[FPos].Undo;
    Inc(FPos);
    FIsPerforming := False;
    Memo.AutoIndent := OldAutoIndent;
    Memo.UndoChange;
  end;
end;

procedure TGLSMemoUndoList.Redo;
var
  OldAutoIndent: Boolean;
begin
  if FPos > 0 then
  begin
    OldAutoIndent := Memo.AutoIndent;
    Memo.AutoIndent := False;
    FIsPerforming := True;
    Dec(FPos);
    Items[FPos].Redo;
    FIsPerforming := False;
    Memo.AutoIndent := OldAutoIndent;
    Memo.UndoChange;
  end;
end;

procedure TGLSMemoUndoList.SetLimit(Value: integer);
begin
  if FLimit <> Value then
  begin
    if Value <= 0 then
      Value := 10;
    if Value > 0 then
      Value := 100;
    FLimit := Value;
    Clear;
  end;
end;

procedure TGLSSynHiMemo.Paint;
begin
  FIsPainting := True;
  try
    DelimiterStyle := FDelimiterStyle;
    CommentStyle := FCommentStyle;
    NumberStyle := FNumberStyle;
    inherited;
  finally
    FIsPainting := False;
  end;
end;

 

// ---------------------TGLSSynHiMemo 

procedure TGLSSynHiMemo.SetStyle(Index: integer; Value: TCharStyle);
var
  No: integer;
  eRect: TRect;
begin
  No := -1;
  case Index of
    0: No := FDelimiterStyleNo;
    1: No := FCommentStyleNo;
    2: No := FNumberStyleNo;
  end;
  with Value do
    Styles.Change(No, TextColor, BkColor, Style);
  if not FIsPainting then
  begin
    eRect := EditorRect;
    InvalidateRect(Handle, @eRect, True);
  end;
end;

//--------------------------------------------------------------
//        SYNTAX MEMO - SET WORD LIST
//--------------------------------------------------------------

procedure TGLSSynHiMemo.SetWordList(Value: TGLSMemoStringList);
begin
  FWordList.Assign(Value);
end;

procedure TGLSSynHiMemo.SetSpecialList(Value: TGLSMemoStringList);
begin
  FSpecialList.Assign(Value);
end;

procedure TGLSSynHiMemo.SetBracketList(Value: TGLSMemoStringList);
begin
  FBracketList.Assign(Value);
end;

//--------------------------------------------------------------
//        SYNTAX MEMO - SET CASE SENSITIVE
//--------------------------------------------------------------

procedure TGLSSynHiMemo.SetCaseSensitive(Value: Boolean);
var
  LineNo: integer;
begin
  if Value <> FCaseSensitive then
  begin
    FCaseSensitive := Value;
    for LineNo := 0 to Lines.Count - 1 do
      ValidAttrs[LineNo] := False;
    Invalidate;
  end;
end;

//--------------------------------------------------------------
//        SYNTAX MEMO - GET TOKEN
//--------------------------------------------------------------

function TGLSSynHiMemo.GetToken(const S: string; var From: integer;
  out TokenType: TTokenType; out StyleNo: integer): string;
var
  i, toStart, toEnd, Len, LenSpec: integer;
  Done: Boolean;
  Brackets: string;
  IntPart: integer;
  WasPoint: Boolean;
  //-------------------------------------------------------------
  function StartsFrom(const S: string; Pos: integer; const S0: string): Boolean;
  begin
    Result := (StrLComp(PChar(S) + Pos - 1, PChar(S0), Length(S0)) = 0);
  end;
  //-------------------------------------------------------------
  function Equal(const s1, s2: string): Boolean;
  begin
    if FCaseSensitive then
      Result := s1 = s2
    else
      Result := AnsiLowerCase(s1) = AnsiLowerCase(s2);
  end;
begin
  toStart := From;
  toEnd := From;
  TokenType := ttOther;
  StyleNo := 0;
  Len := Length(S);
  // End of line
  if From > Len then
  begin
    From := -1;
    Result := '';
    TokenType := ttEOL;
    StyleNo := 0;
    Exit;
  end;
  // Begin of multiline comment
  if (MultiCommentLeft <> '') and (MultiCommentRight <> '') and
    StartsFrom(S, From, MultiCommentLeft) then
  begin
    Result := MultiCommentLeft;
    FInComment := True;
    TokenType := ttComment;
    StyleNo := FCommentStyleNo;
    Inc(From, Length(MultiCommentLeft));
    Exit;
  end;
  // Inside multiline comment
  if FInComment then
  begin
    toEnd := toStart;
    while (toEnd <= Len) and (not StartsFrom(S, toEnd, MultiCommentRight)) do
      Inc(toEnd);
    if toEnd > Len then
    begin
      Result := Copy(S, From, toEnd - From);
      From := toEnd;
    end
    else
    begin
      FInComment := False;
      toEnd := toEnd + Length(MultiCommentRight);
      Result := Copy(S, From, toEnd - From);
      From := toEnd;
    end;
    TokenType := ttComment;
    StyleNo := FCommentStyleNo;
    Exit;
  end;

  // Inside brikets
  if FInBrackets >= 0 then
  begin
    Brackets := FBracketList[FInBrackets];
    toEnd := toStart + 1;
    while (toEnd <= Len) and (S[toEnd] <> Brackets[2]) do
      Inc(toEnd);
    StyleNo := integer(FBracketList.Objects[FInBrackets]);
    if toEnd <= Len then
    begin
      FInBrackets := -1;
      From := toEnd + 1;
    end
    else
      From := toEnd;
    Result := Copy(S, toStart, toEnd - toStart + 1);
    TokenType := ttBracket;
    Exit;
  end;
  // Spaces
  while (toStart <= Len) and (S[toStart] = ' ') do
    Inc(toStart);
  if toStart > From then
  begin
    Result := Copy(S, From, toStart - From);
    From := toStart;
    TokenType := ttSpace;
    StyleNo := 0;
    Exit;
  end;
  // Comment
  if (FLineComment <> '') and StartsFrom(S, From, FLineComment) then
  begin
    Result := Copy(S, From, Len);
    From := Len + 1;
    TokenType := ttComment;
    StyleNo := FCommentStyleNo;
    Exit;
  end;

  // Special keyword
  Done := False;
  for i := 0 to FSpecialList.Count - 1 do
  begin
    LenSpec := Length(FSpecialList[i]);
    if StrLComp(PChar(S) + toStart - 1,
      PChar(FSpecialList[i]), LenSpec) = 0 then
    begin
      toEnd := toStart + LenSpec - 1;
      StyleNo := integer(FSpecialList.Objects[i]);
      TokenType := ttSpecial;
      From := toEnd + 1;
      Done := True;
      break;
    end;
  end;
  // Brickets
  if not Done then
  begin
    for i := 0 to FBracketList.Count - 1 do
    begin
      Brackets := FBracketList[i];
      if S[toStart] = Brackets[1] then
      begin
        FInBrackets := i;
        toEnd := toStart + 1;
        while (toEnd <= Len) and (S[toEnd] <> Brackets[2]) do
          Inc(toEnd);
        if toEnd <= Len then
          FInBrackets := -1
        else
          Dec(toEnd);
        StyleNo := integer(FBracketList.Objects[i]);
        TokenType := ttBracket;
        Done := True;
        break;
      end;
    end;
  end;
  // Delimeters
  if not Done and CharInSet(S[toStart], Delimiters) then
  begin
    toEnd := toStart;
    StyleNo := FDelimiterStyleNo;
    TokenType := ttDelimiter;
    Done := True;
  end;
  // --- Integer or float type
  if not Done and CharInSet(S[toStart], ['0'..'9', '.']) then
  begin
    IntPart := 0;
    WasPoint := False;
    toEnd := toStart;
    Done := True;
    TokenType := ttInteger;
    StyleNo := FNumberStyleNo;
    while (toEnd <= Len) and CharInSet(S[toEnd], ['0'..'9', '.']) do
    begin
      if S[toEnd] = '.' then
      begin
        if not WasPoint then
        begin
          WasPoint := True;
          TokenType := ttFloat;
        end
        else
        begin
          TokenType := ttWrongNumber;
          Color := clRed;
        end;
      end
      else if not WasPoint then
        try
          IntPart := IntPart * 10 + Ord(S[toEnd]) - Ord('0');
        except
          IntPart := MaxInt;
        end;
      Inc(toEnd);
    end;
    Dec(toEnd);
  end;
  // Select word
  if not Done then
  begin
    toEnd := toStart;
    while (toEnd <= Len) and not CharInSet(S[toEnd], Delimiters) do
      Inc(toEnd);
    Dec(toEnd);
  end;
  // Find in dictionary
  Result := Copy(S, toStart, toEnd - toStart + 1);
  for i := 0 to FWordList.Count - 1 do
    if Equal(Result, FWordList[i]) then
    begin
      StyleNo := integer(FWordList.Objects[i]);
      TokenType := ttWord;
      break;
    end;
  From := toEnd + 1;
end;

//--------------------------------------------------------------
//        SYNTAX MEMO - FIND LINE ATTRS
//--------------------------------------------------------------

procedure TGLSSynHiMemo.FindLineAttrs(Sender: TObject; LineNo: integer;
  var Attrs: string);
var
  i, From, TokenLen: integer;
  S, Token: string;
  TokenType: TTokenType;
  StyleNo, OldInBrackets: integer;
  OldInComment: Boolean;
begin
  S := Lines[LineNo];
  SetLength(Attrs, Length(S));
  FInComment := InComment[LineNo];
  FInBrackets := InBrackets[LineNo];
  From := 1;
  while True do
  begin
    Token := GetToken(S, From, TokenType, StyleNo);
    if TokenType = ttEOL then
      break;
    TokenLen := Length(Token);
    for i := From - TokenLen to From - 1 do
      Attrs[i] := Char(StyleNo);
  end;
  if LineNo < Lines.Count - 1 then
  begin
    OldInComment := InComment[LineNo + 1];
    OldInBrackets := InBrackets[LineNo + 1];
    if OldInComment <> FInComment then
    begin
      InComment[LineNo + 1] := FInComment;
      ValidAttrs[LineNo + 1] := False;
    end;
    if OldInBrackets <> FInBrackets then
    begin
      InBrackets[LineNo + 1] := FInBrackets;
      ValidAttrs[LineNo + 1] := False;
    end;
  end;
end;

//--------------------------------------------------------------
//        SYNTAX MEMO - ADD WORD
//--------------------------------------------------------------

procedure TGLSSynHiMemo.AddWord(StyleNo: integer; const ArrS: array of string);
var
  i: integer;
begin
  for i := Low(ArrS) to high(ArrS) do
    FWordList.AddObject(ArrS[i], TObject(StyleNo));
end;

//--------------------------------------------------------------
//        SYNTAX MEMO - ADD SPECIAL
//--------------------------------------------------------------

procedure TGLSSynHiMemo.AddSpecial(StyleNo: integer; const ArrS: array of string);
var
  i: integer;
begin
  for i := Low(ArrS) to high(ArrS) do
    FSpecialList.AddObject(ArrS[i], TObject(StyleNo));
end;

//--------------------------------------------------------------
//        SYNTAX MEMO - ADD BRACKETS
//--------------------------------------------------------------

procedure TGLSSynHiMemo.AddBrackets(StyleNo: integer; const ArrS: array of string);
var
  i: integer;
begin
  for i := Low(ArrS) to high(ArrS) do
    FBracketList.AddObject(ArrS[i], TObject(StyleNo));
end;

//--------------------------------------------------------------
//        SYNTAX MEMO - CREATE
//--------------------------------------------------------------

constructor TGLSSynHiMemo.Create(AOwner: TComponent);
begin
  inherited;
  FInBrackets := -1;
  FIsPainting := False;
  FInComment := False;
  FWordList := TGLSMemoStringList.Create;
  FSpecialList := TGLSMemoStringList.Create;
  FBracketList := TGLSMemoStringList.Create;

  FDelimiterStyle := TCharStyle.Create;
  with FDelimiterStyle do
  begin
    TextColor := clBlue;
    BkColor := clWhite;
    Style := [];
  end;

  FCommentStyle := TCharStyle.Create;
  with FCommentStyle do
  begin
    TextColor := clYellow;
    BkColor := clSkyBlue;
    Style := [fsItalic];
  end;

  FNumberStyle := TCharStyle.Create;
  with FNumberStyle do
  begin
    TextColor := clNavy;
    BkColor := clWhite;
    Style := [fsBold];
  end;

  FDelimiterStyleNo := Styles.Add(clBlue, clWhite, []);
  FCommentStyleNo := Styles.Add(clSilver, clWhite, [fsItalic]);
  FNumberStyleNo := Styles.Add(clNavy, clWhite, [fsBold]);
  OnGetLineAttrs := FindLineAttrs;
  Delimiters := [' ', ',', ';', ':', '.', '(', ')', '{', '}', '[', ']',
    '=', '+', '-', '*', '/', '^', '%', '<', '>',
    '"', '''', #13, #10];
end;

//--------------------------------------------------------------
//        SYNTAX MEMO - DESTROY
//--------------------------------------------------------------

destructor TGLSSynHiMemo.Destroy;
begin
  FWordList.Free;
  FSpecialList.Free;
  FBracketList.Free;
  FDelimiterStyle.Free;
  FCommentStyle.Free;
  FNumberStyle.Free;
  inherited;
end;

// ---------------------TGLSMemoStringList

procedure TGLSMemoStringList.ReadStrings(Reader: TReader);
var
  i: Integer;
begin
  try
    Reader.ReadListBegin;
    Clear;
    while not Reader.EndOfList do
    begin
      i := Add(Reader.ReadString);
      Objects[i] := TObject(Reader.ReadInteger);
    end;
    Reader.ReadListEnd;
  finally
  end;
end;

//--------------------------------------------------------------
//        STRING LIST - WRITE STRINGS
//--------------------------------------------------------------

procedure TGLSMemoStringList.WriteStrings(Writer: TWriter);
var
  i: Integer;
begin
  with Writer do
  begin
    WriteListBegin;
    for i := 0 to Count - 1 do
    begin
      WriteString(Strings[i]);
      WriteInteger(Integer(Objects[i]));
    end;
    WriteListEnd;
  end;
end;

//--------------------------------------------------------------
//        STRING LIST - DEFINE PROPERTIES
//--------------------------------------------------------------

procedure TGLSMemoStringList.DefineProperties(Filer: TFiler);
begin
  Filer.DefineProperty('Strings', ReadStrings, WriteStrings, Count > 0);
end;

 

// ---------------------ScrollBar bitmaps 

procedure CreateScrollBarBitmaps;
var
  i, j: integer;
begin
  bmScrollBarFill := TBitmap.Create;
  with bmScrollBarFill, Canvas do
  begin
    Width := 8;
    Height := 8;
    Transparent := False;
    for i := 0 to 7 do
      for j := 0 to 7 do
        if Odd(i + j) then
          Pixels[i, j] := clSilver;
  end;

  bmScrollBarUp := TBitmap.Create;
  with bmScrollBarUp, Canvas do
  begin
    Width := 7;
    Height := 8;
    Brush.Color := clSilver;
    FillRect(Rect(0, 0, Width, Height));
    Pixels[3, 2] := clBlack;
    MoveTo(2, 3);
    LineTo(5, 3);
    MoveTo(1, 4);
    LineTo(6, 4);
    MoveTo(0, 5);
    LineTo(7, 5);
  end;

  bmScrollBarDown := TBitmap.Create;
  with bmScrollBarDown, Canvas do
  begin
    Width := 7;
    Height := 8;
    Brush.Color := clSilver;
    FillRect(Rect(0, 0, Width, Height));
    MoveTo(0, 2);
    LineTo(7, 2);
    MoveTo(1, 3);
    LineTo(6, 3);
    MoveTo(2, 4);
    LineTo(5, 4);
    Pixels[3, 5] := clBlack;
  end;

  bmScrollBarLeft := TBitmap.Create;
  with bmScrollBarLeft, Canvas do
  begin
    Width := 8;
    Height := 7;
    Brush.Color := clSilver;
    FillRect(Rect(0, 0, Width, Height));
    Pixels[2, 3] := clBlack;
    MoveTo(3, 2);
    LineTo(3, 5);
    MoveTo(4, 1);
    LineTo(4, 6);
    MoveTo(5, 0);
    LineTo(5, 7);
  end;
  bmScrollBarRight := TBitmap.Create;
  with bmScrollBarRight, Canvas do
  begin
    Width := 8;
    Height := 7;
    Brush.Color := clSilver;
    FillRect(Rect(0, 0, Width, Height));
    MoveTo(2, 0);
    LineTo(2, 7);
    MoveTo(3, 1);
    LineTo(3, 6);
    MoveTo(4, 2);
    LineTo(4, 5);
    Pixels[5, 3] := clBlack;
  end;
end;

//------------------ FREE SCROLL BAR BITMAPs -------------------

procedure FreeScrollBarBitmaps;
begin
  bmScrollBarFill.Free;
  bmScrollBarUp.Free;
  bmScrollBarDown.Free;
  bmScrollBarLeft.Free;
  bmScrollBarRight.Free;
end;


//----------------------------------
initialization
//----------------------------------

  RegisterClasses([TGLSSynHiMemo]);
  CreateScrollBarBitmaps;
  IntelliMouseInit;

//----------------------------------
finalization
//----------------------------------

  FreeScrollBarBitmaps;

end.

