<commit_before>(*
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.

Copyright (c) <NAME>
*)
unit FormFrame;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, Graphics, Forms, Controls, Dialogs,
  ExtCtrls, Menus, StdCtrls, StrUtils, ComCtrls, Clipbrd,
  LCLIntf, LCLProc, LCLType, LazUTF8, LazFileUtils, FileUtil,
  LConvEncoding,
  GraphUtil,
  ATTabs,
  ATGroups,
  ATSynEdit,
  ATSynEdit_Finder,
  ATSynEdit_Keymap_Init,
  ATSynEdit_Adapters,
  ATSynEdit_Adapter_EControl,
  ATSynEdit_Adapter_LiteLexer,
  ATSynEdit_Carets,
  ATSynEdit_Gaps,
  ATSynEdit_Markers,
  ATSynEdit_CanvasProc,
  ATSynEdit_Commands,
  ATSynEdit_Bookmarks,
  ATStrings,
  ATStringProc,
  ATStringProc_HtmlColor,
  ATFileNotif,
  ATButtons,
  ATPanelSimple,
  ATBinHex,
  ATStreamSearch,
  ATImageBox,
  proc_globdata,
  proc_editor,
  proc_cmd,
  proc_colors,
  proc_files,
  proc_msg,
  proc_str,
  proc_py,
  proc_py_const,
  proc_miscutils,
  ec_SyntAnal,
  ec_proc_lexer,
  formlexerstylemap,
  at__jsonconf,
  math,
  LazUTF8Classes;

type
  TEditorFramePyEvent = function(AEd: TATSynEdit; AEvent: TAppPyEvent; const AParams: array of string): string of object;
  TEditorFrameStringEvent = procedure(Sender: TObject; const S: string) of object;

type
  TAppOpenMode = (
    cOpenModeNone,
    cOpenModeEditor,
    cOpenModeViewText,
    cOpenModeViewBinary,
    cOpenModeViewHex,
    cOpenModeViewUnicode
    );

type
  { TEditorFrame }

  TEditorFrame = class(TFrame)
    MenuItem1: TMenuItem;
    MenuItem2: TMenuItem;
    Splitter: TSplitter;
    TimerChange: TTimer;
    procedure TimerChangeTimer(Sender: TObject);
  private
    { private declarations }
    Adapter1: TATAdapterEControl;
    Adapter2: TATAdapterEControl;
    FTabCaption: string;
    FTabCaptionUntitled: string;
    FTabCaptionFromApi: boolean;
    FTabImageIndex: integer;
    FTabId: integer;
    FFileName: string;
    FFileName2: string;
    FFileWasBig: boolean;
    FNotif: TATFileNotif;
    FTextCharsTyped: integer;
    FActivationTime: Int64;
    FCodetreeFilter: string;
    FCodetreeFilterHistory: TStringList;
    FEnabledCodeTree: array[0..1] of boolean;
    FOnChangeCaption: TNotifyEvent;
    FOnProgress: TATFinderProgress;
    FOnUpdateStatus: TNotifyEvent;
    FOnEditorClickMoveCaret: TATSynEditClickMoveCaretEvent;
    FOnEditorClickEndSelect: TATSynEditClickMoveCaretEvent;
    FOnFocusEditor: TNotifyEvent;
    FOnEditorCommand: TATSynEditCommandEvent;
    FOnEditorChangeCaretPos: TNotifyEvent;
    FOnSaveFile: TNotifyEvent;
    FOnAddRecent: TEditorFrameStringEvent;
    FOnPyEvent: TEditorFramePyEvent;
    FOnInitAdapter: TNotifyEvent;
    FSplitPos: double;
    FSplitHorz: boolean;
    FActiveSecondaryEd: boolean;
    FLocked: boolean;
    FTabColor: TColor;
    FTabSizeChanged: boolean;
    FFoldTodo: string;
    FTopLineTodo: integer;
    FTabKeyCollectMarkers: boolean;
    FTagString: string;
    FNotInRecents: boolean;
    FMacroRecord: boolean;
    FMacroString: string;
    FImageBox: TATImageBox;
    FBin: TATBinHex;
    FBinStream: TFileStreamUTF8;
    FCheckFilenameOpened: TStrFunction;
    FOnMsgStatus: TStrEvent;
    FSaveDialog: TSaveDialog;
    FReadOnlyFromFile: boolean;
    FWasVisible: boolean;
    FInitialLexer: TecSyntAnalyzer;
    FSaveHistory: boolean;
    FEditorsLinked: boolean;
    FCachedTreeview: array[0..1] of TTreeView;

    procedure BinaryOnKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
    procedure BinaryOnScroll(Sender: TObject);
    procedure BinaryOnProgress(const ACurrentPos, AMaximalPos: Int64;
      var AContinueSearching: Boolean);
    procedure DoDeactivatePictureMode;
    procedure DoDeactivateViewerMode;
    procedure DoFileOpen_AsBinary(const AFileName: string; AMode: TATBinHexMode);
    procedure DoFileOpen_AsPicture(const AFileName: string);
    procedure DoFileOpen_Ex(Ed: TATSynEdit; const AFileName: string;
      AAllowLoadHistory, AAllowErrorMsgBox: boolean; AOpenMode: TAppOpenMode);
    procedure DoImageboxScroll(Sender: TObject);
    procedure DoOnChangeCaption;
    procedure DoOnUpdateStatus;
    procedure EditorClickEndSelect(Sender: TObject; APrevPnt, ANewPnt: TPoint);
    procedure EditorClickMoveCaret(Sender: TObject; APrevPnt, ANewPnt: TPoint);
    procedure EditorDrawMicromap(Sender: TObject; C: TCanvas; const ARect: TRect);
    procedure EditorOnChange(Sender: TObject);
    procedure EditorOnChangeModified(Sender: TObject);
    procedure EditorOnChangeState(Sender: TObject);
    procedure EditorOnClick(Sender: TObject);
    procedure EditorOnClickGap(Sender: TObject; AGapItem: TATGapItem; APos: TPoint);
    procedure EditorOnClickGutter(Sender: TObject; ABand, ALine: integer);
    procedure EditorOnClickDouble(Sender: TObject; var AHandled: boolean);
    procedure EditorOnClickMicroMap(Sender: TObject; AX, AY: integer);
    procedure EditorOnClickMiddle(Sender: TObject; var AHandled: boolean);
    procedure EditorOnCommand(Sender: TObject; ACmd: integer; const AText: string; var AHandled: boolean);
    procedure EditorOnCommandAfter(Sender: TObject; ACommand: integer; const AText: string);
    procedure EditorOnDrawBookmarkIcon(Sender: TObject; C: TCanvas; ALineNum: integer; const ARect: TRect);
    procedure EditorOnEnter(Sender: TObject);
    procedure EditorOnDrawLine(Sender: TObject; C: TCanvas; AX, AY: integer;
      const AStr: atString; ACharSize: TPoint; const AExtent: TATIntArray);
    procedure EditorOnCalcBookmarkColor(Sender: TObject; ABookmarkKind: integer; var AColor: TColor);
    procedure EditorOnChangeCaretPos(Sender: TObject);
    procedure EditorOnHotspotEnter(Sender: TObject; AHotspotIndex: integer);
    procedure EditorOnHotspotExit(Sender: TObject; AHotspotIndex: integer);
    procedure EditorOnKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
    procedure EditorOnPaste(Sender: TObject; var AHandled: boolean; AKeepCaret,
      ASelectThen: boolean);
    procedure EditorOnScroll(Sender: TObject);
    function GetAdapter(Ed: TATSynEdit): TATAdapterEControl;
    function GetCachedTreeview(Ed: TATSynEdit): TTreeView;
    function GetCommentString(Ed: TATSynEdit): string;
    function GetEnabledCodeTree(Ed: TATSynEdit): boolean;
    function GetEnabledFolding: boolean;
    function GetLineEnds(Ed: TATSynEdit): TATLineEnds;
    function GetNotifEnabled: boolean;
    function GetNotifTime: integer;
    function GetPictureScale: integer;
    function GetReadOnly(Ed: TATSynEdit): boolean;
    function GetSplitPosCurrent: double;
    function GetSplitted: boolean;
    function GetTabKeyCollectMarkers: boolean;
    function GetUnprintedEnds: boolean;
    function GetUnprintedEndsDetails: boolean;
    function GetUnprintedShow: boolean;
    function GetUnprintedSpaces: boolean;
    procedure InitEditor(var ed: TATSynEdit);
    function IsCaretInsideCommentOrString(Ed: TATSynEdit; AX, AY: integer): boolean;
    procedure NotifChanged(Sender: TObject);
    procedure SetEnabledCodeTree(Ed: TATSynEdit; AValue: boolean);
    procedure SetEnabledFolding(AValue: boolean);
    procedure SetFileName(const AValue: string);
    procedure SetFileName2(AValue: string);
    procedure SetFileWasBig(AValue: boolean);
    procedure SetLocked(AValue: boolean);
    procedure SetNotifEnabled(AValue: boolean);
    procedure SetNotifTime(AValue: integer);
    procedure SetPictureScale(AValue: integer);
    procedure SetReadOnly(Ed: TATSynEdit; AValue: boolean);
    procedure SetTabColor(AColor: TColor);
    procedure SetTabImageIndex(AValue: integer);
    procedure SetUnprintedEnds(AValue: boolean);
    procedure SetUnprintedEndsDetails(AValue: boolean);
    procedure SetUnprintedShow(AValue: boolean);
    procedure SetSplitHorz(AValue: boolean);
    procedure SetSplitPos(AValue: double);
    procedure SetSplitted(AValue: boolean);
    procedure SetTabCaption(const AValue: string);
    procedure SetLineEnds(Ed: TATSynEdit; AValue: TATLineEnds);
    procedure SetUnprintedSpaces(AValue: boolean);
    procedure SetEditorsLinked(AValue: boolean);
    procedure SplitterMoved(Sender: TObject);
    procedure UpdateEds(AUpdateWrapInfo: boolean=false);
    procedure UpdateCaptionFromFilename;
    function GetLexer(Ed: TATSynEdit): TecSyntAnalyzer;
    function GetLexerLite(Ed: TATSynEdit): TATLiteLexer;
    function GetLexerName(Ed: TATSynEdit): string;
    procedure SetLexer(Ed: TATSynEdit; an: TecSyntAnalyzer);
    procedure SetLexerLite(Ed: TATSynEdit; an: TATLiteLexer);
    procedure SetLexerName(Ed: TATSynEdit; const AValue: string);
    procedure UpdateTabTooltip;
  protected
    procedure DoOnResize; override;
  public
    { public declarations }
    Ed1: TATSynEdit;
    Ed2: TATSynEdit;
    Groups: TATGroups;

    constructor Create(AOwner: TComponent; AApplyCentering: boolean); reintroduce;
    destructor Destroy; override;
    function Editor: TATSynEdit;
    function EditorBro: TATSynEdit;
    property Adapter[Ed: TATSynEdit]: TATAdapterEControl read GetAdapter;
    procedure EditorOnKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
    procedure DoShow;
    property ReadOnly[Ed: TATSynEdit]: boolean read GetReadOnly write SetReadOnly;
    property ReadOnlyFromFile: boolean read FReadOnlyFromFile write FReadOnlyFromFile;
    property TabCaption: string read FTabCaption write SetTabCaption;
    property TabCaptionUntitled: string read FTabCaptionUntitled write FTabCaptionUntitled;
    property TabImageIndex: integer read FTabImageIndex write SetTabImageIndex;
    property TabCaptionFromApi: boolean read FTabCaptionFromApi write FTabCaptionFromApi;
    property TabId: integer read FTabId;
    property CachedTreeView[Ed: TATSynEdit]: TTreeView read GetCachedTreeview;
    property SaveHistory: boolean read FSaveHistory write FSaveHistory;
    procedure UpdateModified(Ed: TATSynEdit; AWithEvent: boolean= true);
    procedure UpdateReadOnlyFromFile(Ed: TATSynEdit);
    property NotifEnabled: boolean read GetNotifEnabled write SetNotifEnabled;
    property NotifTime: integer read GetNotifTime write SetNotifTime;

    property FileName: string read FFileName write SetFileName;
    property FileWasBig: boolean read FFileWasBig write SetFileWasBig;
    function GetFileName(Ed: TATSynEdit): string;
    procedure SetFileName(Ed: TATSynEdit; const AFileName: string);

    property Lexer[Ed: TATSynEdit]: TecSyntAnalyzer read GetLexer write SetLexer;
    property LexerLite[Ed: TATSynEdit]: TATLiteLexer read GetLexerLite write SetLexerLite;
    property LexerName[Ed: TATSynEdit]: string read GetLexerName write SetLexerName;
    function LexerNameAtPos(Ed: TATSynEdit; APos: TPoint): string;

    property Locked: boolean read FLocked write SetLocked;
    property CommentString[Ed: TATSynEdit]: string read GetCommentString;
    property TabColor: TColor read FTabColor write SetTabColor;
    property TabSizeChanged: boolean read FTabSizeChanged write FTabSizeChanged;
    property TabKeyCollectMarkers: boolean read GetTabKeyCollectMarkers write FTabKeyCollectMarkers;
    property TagString: string read FTagString write FTagString;
    property NotInRecents: boolean read FNotInRecents write FNotInRecents;
    property TopLineTodo: integer read FTopLineTodo write FTopLineTodo; //always use it instead of Ed.LineTop
    property TextCharsTyped: integer read FTextCharsTyped write FTextCharsTyped;
    property EnabledCodeTree[Ed: TATSynEdit]: boolean read GetEnabledCodeTree write SetEnabledCodeTree;
    property CodetreeFilter: string read FCodetreeFilter write FCodetreeFilter;
    property CodetreeFilterHistory: TStringList read FCodetreeFilterHistory;
    property ActivationTime: Int64 read FActivationTime write FActivationTime;
    function IsEmpty: boolean;
    function IsPreview: boolean;
    procedure ApplyTheme;
    procedure SetFocus; reintroduce;
    function IsText: boolean;
    function IsPicture: boolean;
    function IsBinary: boolean;
    function PictureSizes: TPoint;
    property PictureScale: integer read GetPictureScale write SetPictureScale;
    property Binary: TATBinHex read FBin;
    function BinaryFindFirst(AFinder: TATEditorFinder; AShowAll: boolean): boolean;
    function BinaryFindNext(ABack: boolean): boolean;
    //
    property LineEnds[Ed: TATSynEdit]: TATLineEnds read GetLineEnds write SetLineEnds;
    property UnprintedShow: boolean read GetUnprintedShow write SetUnprintedShow;
    property UnprintedSpaces: boolean read GetUnprintedSpaces write SetUnprintedSpaces;
    property UnprintedEnds: boolean read GetUnprintedEnds write SetUnprintedEnds;
    property UnprintedEndsDetails: boolean read GetUnprintedEndsDetails write SetUnprintedEndsDetails;
    property Splitted: boolean read GetSplitted write SetSplitted;
    property SplitHorz: boolean read FSplitHorz write SetSplitHorz;
    property SplitPos: double read FSplitPos write SetSplitPos;
    property EditorsLinked: boolean read FEditorsLinked write SetEditorsLinked;
    property EnabledFolding: boolean read GetEnabledFolding write SetEnabledFolding;
    property SaveDialog: TSaveDialog read FSaveDialog write FSaveDialog;
    //file
    procedure DoFileClose;
    procedure DoFileOpen(const AFileName, AFileName2: string; AAllowLoadHistory,
      AAllowErrorMsgBox: boolean; AOpenMode: TAppOpenMode);
    function DoFileSave(ASaveAs, AAllEditors: boolean): boolean;
    function DoFileSave_Ex(Ed: TATSynEdit; ASaveAs: boolean): boolean;
    procedure DoFileReload_DisableDetectEncoding;
    procedure DoFileReload;
    procedure DoLexerFromFilename(Ed: TATSynEdit; const AFileName: string);
    procedure DoSaveHistory(Ed: TATSynEdit);
    procedure DoSaveHistoryEx(Ed: TATSynEdit; c: TJsonConfig; const path: UnicodeString);
    procedure DoSaveUndo(Ed: TATSynEdit; const AFileName: string);
    procedure DoLoadHistory(Ed: TATSynEdit);
    procedure DoLoadHistoryEx(Ed: TATSynEdit; const AFileName: string; c: TJsonConfig; const path: UnicodeString);
    procedure DoLoadUndo(Ed: TATSynEdit);
    //misc
    function DoPyEvent(AEd: TATSynEdit; AEvent: TAppPyEvent; const AParams: array of string): string;
    procedure DoGotoPos(Ed: TATSynEdit; APosX, APosY: integer);
    procedure DoRestoreFolding;
    procedure DoRemovePreviewStyle;
    procedure DoToggleFocusSplitEditors;
    //macro
    procedure DoMacroStart;
    procedure DoMacroStop(ACancel: boolean);
    property MacroRecord: boolean read FMacroRecord;
    property MacroString: string read FMacroString write FMacroString;

    //events
    property OnProgress: TATFinderProgress read FOnProgress write FOnProgress;
    property OnCheckFilenameOpened: TStrFunction read FCheckFilenameOpened write FCheckFilenameOpened;
    property OnMsgStatus: TStrEvent read FOnMsgStatus write FOnMsgStatus;
    property OnFocusEditor: TNotifyEvent read FOnFocusEditor write FOnFocusEditor;
    property OnChangeCaption: TNotifyEvent read FOnChangeCaption write FOnChangeCaption;
    property OnUpdateStatus: TNotifyEvent read FOnUpdateStatus write FOnUpdateStatus;
    property OnEditorClickMoveCaret: TATSynEditClickMoveCaretEvent read FOnEditorClickMoveCaret write FOnEditorClickMoveCaret;
    property OnEditorClickEndSelect: TATSynEditClickMoveCaretEvent read FOnEditorClickEndSelect write FOnEditorClickEndSelect;
    property OnEditorCommand: TATSynEditCommandEvent read FOnEditorCommand write FOnEditorCommand;
    property OnEditorChangeCaretPos: TNotifyEvent read FOnEditorChangeCaretPos write FOnEditorChangeCaretPos;
    property OnSaveFile: TNotifyEvent read FOnSaveFile write FOnSaveFile;
    property OnAddRecent: TEditorFrameStringEvent read FOnAddRecent write FOnAddRecent;
    property OnPyEvent: TEditorFramePyEvent read FOnPyEvent write FOnPyEvent;
    property OnInitAdapter: TNotifyEvent read FOnInitAdapter write FOnInitAdapter;
  end;

procedure GetFrameLocation(Frame: TEditorFrame;
  out AGroups: TATGroups; out APages: TATPages;
  out ALocalGroupIndex, AGlobalGroupIndex, ATabIndex: integer);

var
  //must be set in FormMain.OnShow
  AllowFrameParsing: boolean = false;


implementation

{$R *.lfm}

const
  cHistory_Lexer       = '/lexer';
  cHistory_Enc         = '/enc';
  cHistory_Top         = '/top';
  cHistory_Wrap        = '/wrap_mode';
  cHistory_RO          = '/ro';
  cHistory_Ruler       = '/ruler';
  cHistory_Minimap     = '/minimap';
  cHistory_Micromap    = '/micromap';
  cHistory_TabSize     = '/tab_size';
  cHistory_TabSpace    = '/tab_spaces';
  cHistory_Nums        = '/nums';
  cHistory_Unpri        = '/unprinted_show';
  cHistory_Unpri_Spaces = '/unprinted_spaces';
  cHistory_Unpri_Ends   = '/unprinted_ends';
  cHistory_Unpri_Detail = '/unprinted_end_details';
  cHistory_Caret       = '/crt';
  cHistory_TabColor    = '/color';
  cHistory_Bookmark    = '/bm';
  cHistory_BookmarkKind = '/bm_kind';
  cHistory_Fold        = '/folded';
  cHistory_CodeTreeFilter = '/codetree_filter';
  cHistory_CodeTreeFilters = '/codetree_filters';

var
  FLastTabId: integer = 0;


procedure GetFrameLocation(Frame: TEditorFrame;
  out AGroups: TATGroups; out APages: TATPages;
  out ALocalGroupIndex, AGlobalGroupIndex, ATabIndex: integer);
var
  C: TWinControl;
begin
  APages:= Frame.Parent as TATPages;

  C:= APages;
  repeat
    C:= C.Parent;
  until C is TATGroups;

  AGroups:= C as TATGroups;
  AGroups.PagesAndTabIndexOfControl(Frame, ALocalGroupIndex, ATabIndex);

  AGlobalGroupIndex:= ALocalGroupIndex;
  if AGroups.Tag<>0 then
    Inc(AGlobalGroupIndex, High(TATGroupsNums) + AGroups.Tag);
end;


{ TEditorFrame }

procedure TEditorFrame.SetTabCaption(const AValue: string);
var
  Upd: boolean;
begin
  if AValue='?' then Exit;
  Upd:= FTabCaption<>AValue;

  FTabCaption:= AValue; //don't check Upd here (for Win32)

  if Upd then
    DoPyEvent(Editor, cEventOnState, [IntToStr(EDSTATE_TAB_TITLE)]);
  DoOnChangeCaption;
end;

procedure TEditorFrame.UpdateCaptionFromFilename;
var
  Name1, Name2: string;
begin
  if EditorsLinked then
  begin
    if FFileName='' then
      Name1:= FTabCaptionUntitled
    else
      Name1:= ExtractFileName_Fixed(FFileName);
    Name1:= msgModified[Ed1.Modified]+Name1;

    TabCaption:= Name1;
  end
  else
  begin
    if (FFileName='') and (FFileName2='') then
      TabCaption:= FTabCaptionUntitled
    else
    begin
      Name1:= ExtractFileName_Fixed(FFileName);
      if Name1='' then Name1:= msgUntitledTab;
      Name1:= msgModified[Ed1.Modified]+Name1;

      Name2:= ExtractFileName_Fixed(FFileName2);
      if Name2='' then Name2:= msgUntitledTab;
      Name2:= msgModified[Ed2.Modified]+Name2;

      TabCaption:= Name1+' | '+Name2;
    end;
  end;

  UpdateTabTooltip;
end;

procedure TEditorFrame.EditorOnClick(Sender: TObject);
var
  Ed: TATSynEdit;
  StateString: string;
begin
  Ed:= Sender as TATSynEdit;

  StateString:= ConvertShiftStateToString(KeyboardStateToShiftState);
  FTextCharsTyped:= 0; //reset count for option "autocomplete_autoshow_chars"

  if UiOps.MouseGotoDefinition<>'' then
    if StateString=UiOps.MouseGotoDefinition then
    begin
      DoPyEvent(Ed, cEventOnGotoDef, []);
      exit;
    end;

  DoPyEvent(Ed, cEventOnClick, ['"'+StateString+'"']);
end;

function TEditorFrame.GetSplitPosCurrent: double;
begin
  if not Splitted then
    Result:= 0
  else
  if FSplitHorz then
    Result:= Ed2.Height/Max(Height, 1)
  else
    Result:= Ed2.Width/Max(Width, 1);
end;

function TEditorFrame.GetSplitted: boolean;
begin
  Result:= Ed2.Visible;
end;

procedure TEditorFrame.EditorOnKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  //res=False: block key
  if DoPyEvent(Sender as TATSynEdit,
    cEventOnKey,
    [
      IntToStr(Key),
      '"'+ConvertShiftStateToString(Shift)+'"'
    ]) = cPyFalse then
    begin
      Key:= 0;
      Exit
    end;
end;

procedure TEditorFrame.EditorOnPaste(Sender: TObject; var AHandled: boolean;
  AKeepCaret, ASelectThen: boolean);
const
  cBool: array[boolean] of string = (cPyFalse, cPyTrue);
begin
  if DoPyEvent(Sender as TATSynEdit,
    cEventOnPaste,
    [
      cBool[AKeepCaret],
      cBool[ASelectThen]
    ]) = cPyFalse then
    AHandled:= true;
end;

procedure TEditorFrame.EditorOnScroll(Sender: TObject);
begin
  DoPyEvent(Sender as TATSynEdit, cEventOnScroll, []);
end;

procedure TEditorFrame.EditorOnKeyUp(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  //keyup: only for Ctrl/Shift/Alt
  //no res.
  case Key of
    VK_CONTROL,
    VK_MENU,
    VK_SHIFT,
    VK_RSHIFT:
      DoPyEvent(Sender as TATSynEdit,
        cEventOnKeyUp,
        [
          IntToStr(Key),
          '"'+ConvertShiftStateToString(Shift)+'"'
        ]);
  end;
end;

procedure TEditorFrame.DoShow;
begin
  //analize file, when frame is shown for the 1st time
  if AllowFrameParsing and not FWasVisible then
  begin
    FWasVisible:= true;
    //ShowMessage('show frame: '+FileName); ////debug

    if Assigned(FInitialLexer) then
    begin
      Lexer[Ed1]:= FInitialLexer;
      FInitialLexer:= nil;
    end;
  end;
end;

procedure TEditorFrame.TimerChangeTimer(Sender: TObject);
begin
  TimerChange.Enabled:= false;
  DoPyEvent(Editor, cEventOnChangeSlow, []);
end;

procedure TEditorFrame.EditorOnCalcBookmarkColor(Sender: TObject;
  ABookmarkKind: integer; var AColor: TColor);
begin
  if ABookmarkKind>1 then
    AColor:= AppBookmarkSetup[ABookmarkKind].Color;
end;

procedure TEditorFrame.EditorOnChangeCaretPos(Sender: TObject);
const
  cMaxSelectedLinesForAutoCopy = 200;
var
  Ed: TATSynEdit;
  NFrom, NTo: integer;
begin
  if Assigned(FOnEditorChangeCaretPos) then
    FOnEditorChangeCaretPos(Sender);

  DoOnUpdateStatus;

  Ed:= Sender as TATSynEdit;

  //support Primary Selection on Linux
  {$ifdef linux}
  if Ed.Carets.Count=1 then
  begin
    Ed.Carets[0].GetSelLines(NFrom, NTo, false);
    if (NTo-NFrom)<=cMaxSelectedLinesForAutoCopy then
      SClipboardCopy(Ed.TextSelected, PrimarySelection);
  end;
  {$endif}

  DoPyEvent(Ed, cEventOnCaret, []);
end;

procedure TEditorFrame.EditorOnHotspotEnter(Sender: TObject; AHotspotIndex: integer);
begin
  DoPyEvent(Sender as TATSynEdit, cEventOnHotspot, [
    cPyTrue, //hotspot enter
    IntToStr(AHotspotIndex)
    ]);
end;

procedure TEditorFrame.EditorOnHotspotExit(Sender: TObject; AHotspotIndex: integer);
begin
  DoPyEvent(Sender as TATSynEdit, cEventOnHotspot, [
    cPyFalse, //hotspot exit
    IntToStr(AHotspotIndex)
    ]);
end;

procedure TEditorFrame.EditorOnDrawLine(Sender: TObject; C: TCanvas; AX,
  AY: integer; const AStr: atString; ACharSize: TPoint;
  const AExtent: TATIntArray);
const
  cRegexRGB = 'rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(,\s*[\.\d]+\s*)?\)';
  cRegexHSL = 'hsla?\(\s*(\d{1,3})\s*,\s*(\d{1,3})%\s*,\s*(\d{1,3})%\s*(,\s*[\.\d%]+\s*)?\)';
var
  X1, X2, Y, NLen: integer;
  NColor: TColor;
  Parts: TRegexParts;
  Ch: atChar;
  ValueR, ValueG, ValueB: byte;
  ValueH, ValueS, ValueL: word;
  i: integer;
begin
  if AStr='' then Exit;
  if not IsFilenameListedInExtensionList(FileName, EditorOps.OpUnderlineColorFiles)
    then exit;

  for i:= 1 to Length(AStr)-3 do
  begin
    Ch:= AStr[i];

    //find #rgb, #rrggbb
    if Ch='#' then
    begin
      NColor:= SHtmlColorToColor(Copy(AStr, i+1, 7), NLen, clNone);
      if NColor=clNone then Continue;

      if i-2>=0 then
        X1:= AX+AExtent[i-2]
      else
        X1:= AX;
      X2:= AX+AExtent[i-1+NLen];
      Y:= AY+ACharSize.Y;

      C.Brush.Color:= NColor;
      C.FillRect(X1, Y-EditorOps.OpUnderlineColorSize, X2, Y);
    end
    else
    //find rgb(...), rgba(...)
    if (Ch='r') and
      (i+6<=Length(AStr)) and
      (AStr[i+1]='g') and
      (AStr[i+2]='b') and
      ((AStr[i+3]='(') or ((AStr[i+3]='a') and (AStr[i+4]='('))) and
      ((i=1) or not IsCharWord(AStr[i-1], '')) //word boundary
    then
    begin
      if SRegexFindParts(cRegexRGB, Copy(AStr, i, MaxInt), Parts) then
        if Parts[0].Pos=1 then //need at i-th char
        begin
          ValueR:= Min(255, StrToIntDef(Parts[1].Str, 0));
          ValueG:= Min(255, StrToIntDef(Parts[2].Str, 0));
          ValueB:= Min(255, StrToIntDef(Parts[3].Str, 0));

          NColor:= RGB(ValueR, ValueG, ValueB);
          NLen:= Parts[0].Len;

          if i-2>=0 then
            X1:= AX+AExtent[i-2]
          else
            X1:= AX;
          X2:= AX+AExtent[i-2+NLen];
          Y:= AY+ACharSize.Y;

          C.Brush.Color:= NColor;
          C.FillRect(X1, Y-EditorOps.OpUnderlineColorSize, X2, Y);
        end;
    end
    else
    //find hsl(...), hsla(...)
    if (Ch='h') and
      (i+6<=Length(AStr)) and
      (AStr[i+1]='s') and
      (AStr[i+2]='l') and
      ((AStr[i+3]='(') or ((AStr[i+3]='a') and (AStr[i+4]='('))) and
      ((i=1) or not IsCharWord(AStr[i-1], '')) //word boundary
      then
      begin
        if SRegexFindParts(cRegexHSL, Copy(AStr, i, MaxInt), Parts) then
          if Parts[0].Pos=1 then //need at i-th char
          begin
            ValueH:= StrToIntDef(Parts[1].Str, 0) * 255 div 360; //degrees -> 0..255
            ValueS:= StrToIntDef(Parts[2].Str, 0) * 255 div 100; //percents -> 0..255
            ValueL:= StrToIntDef(Parts[3].Str, 0) * 255 div 100; //percents -> 0..255

            NColor:= HLStoColor(ValueH, ValueL, ValueS);
            NLen:= Parts[0].Len;

            if i-2>=0 then
              X1:= AX+AExtent[i-2]
            else
              X1:= AX;
            X2:= AX+AExtent[i-2+NLen];
            Y:= AY+ACharSize.Y;

            C.Brush.Color:= NColor;
            C.FillRect(X1, Y-EditorOps.OpUnderlineColorSize, X2, Y);
          end;
      end;
  end;
end;


function TEditorFrame.GetLineEnds(Ed: TATSynEdit): TATLineEnds;
begin
  Result:= Ed.Strings.Endings;
end;

function TEditorFrame.GetNotifEnabled: boolean;
begin
  Result:= FNotif.Timer.Enabled;
end;

function TEditorFrame.GetNotifTime: integer;
begin
  Result:= FNotif.Timer.Interval;
end;

function TEditorFrame.GetPictureScale: integer;
begin
  if Assigned(FImageBox) then
    Result:= FImageBox.ImageZoom
  else
    Result:= 100;
end;

function TEditorFrame.GetReadOnly(Ed: TATSynEdit): boolean;
begin
  Result:= Ed.ModeReadOnly;
end;

function TEditorFrame.GetTabKeyCollectMarkers: boolean;
begin
  Result:= FTabKeyCollectMarkers and (Editor.Markers.Count>0);
end;

function TEditorFrame.GetUnprintedEnds: boolean;
begin
  Result:= Ed1.OptUnprintedEnds;
end;

function TEditorFrame.GetUnprintedEndsDetails: boolean;
begin
  Result:= Ed1.OptUnprintedEndsDetails;
end;

function TEditorFrame.GetUnprintedShow: boolean;
begin
  Result:= Ed1.OptUnprintedVisible;
end;

function TEditorFrame.GetUnprintedSpaces: boolean;
begin
  Result:= Ed1.OptUnprintedSpaces;
end;

procedure TEditorFrame.SetFileName(const AValue: string);
begin
  if SameFileName(FFileName, AValue) then Exit;
  FFileName:= AValue;

  //update Notif obj
  NotifEnabled:= NotifEnabled;
end;

procedure TEditorFrame.UpdateTabTooltip;
var
  Gr: TATGroups;
  Pages: TATPages;
  NLocalGroups, NGlobalGroup, NTab: integer;
  D: TATTabData;
  SHint: string;
begin
  GetFrameLocation(Self, Gr, Pages, NLocalGroups, NGlobalGroup, NTab);
  D:= Pages.Tabs.GetTabData(NTab);
  if Assigned(D) then
  begin
    SHint:= '';
    if EditorsLinked then
    begin
      if FFileName<>'' then
        SHint:= SCollapseHomeDirInFilename(FFileName);
    end
    else
    begin
      if (FFileName<>'') or (FFileName2<>'') then
        SHint:= SCollapseHomeDirInFilename(FFileName) + #10 +
                SCollapseHomeDirInFilename(FFileName2);
    end;

    D.TabHint:= SHint;
    Pages.Tabs.Invalidate;
  end;
end;

procedure TEditorFrame.SetFileName2(AValue: string);
begin
  if SameFileName(FFileName2, AValue) then Exit;
  FFileName2:= AValue;
end;

procedure TEditorFrame.SetFileWasBig(AValue: boolean);
begin
  FFileWasBig:= AValue;
  if AValue then
  begin
    Ed1.OptWrapMode:= cWrapOff;
    Ed2.OptWrapMode:= cWrapOff;
    Ed1.OptMicromapVisible:= false;
    Ed2.OptMicromapVisible:= false;
    Ed1.OptMinimapVisible:= false;
    Ed2.OptMinimapVisible:= false;
  end;
end;

procedure TEditorFrame.SetLocked(AValue: boolean);
begin
  if AValue=FLocked then exit;
  FLocked:= AValue;

  if FLocked then
  begin
    Ed1.BeginUpdate;
    Ed2.BeginUpdate;
  end
  else
  begin
    Ed1.EndUpdate;
    Ed2.EndUpdate;
  end;
end;

procedure TEditorFrame.SetNotifEnabled(AValue: boolean);
begin
  FNotif.Timer.Enabled:= false;
  FNotif.FileName:= '';

  if AValue and IsText and FileExistsUTF8(FileName) then
  begin
    FNotif.FileName:= FileName;
    FNotif.Timer.Enabled:= true;
  end;
end;

procedure TEditorFrame.SetNotifTime(AValue: integer);
begin
  FNotif.Timer.Interval:= AValue;
end;

procedure TEditorFrame.SetPictureScale(AValue: integer);
begin
  if Assigned(FImageBox) then
  begin
    if AValue>0 then
      FImageBox.ImageZoom:= AValue
    else
    if AValue=-1 then
      FImageBox.OptFitToWindow:= true;
  end;
end;

procedure TEditorFrame.SetReadOnly(Ed: TATSynEdit; AValue: boolean);
begin
  Ed.ModeReadOnly:= AValue;
  if (Ed=Ed1) and EditorsLinked then
    Ed2.ModeReadOnly:= AValue;
end;

procedure TEditorFrame.UpdateEds(AUpdateWrapInfo: boolean = false);
begin
  Ed2.OptUnprintedVisible:= Ed1.OptUnprintedVisible;
  Ed2.OptUnprintedSpaces:= Ed1.OptUnprintedSpaces;
  Ed2.OptUnprintedEnds:= Ed1.OptUnprintedEnds;
  Ed2.OptUnprintedEndsDetails:= Ed1.OptUnprintedEndsDetails;

  Ed1.Update(AUpdateWrapInfo);
  Ed2.Update(AUpdateWrapInfo);
end;

function TEditorFrame.GetLexer(Ed: TATSynEdit): TecSyntAnalyzer;
begin
  if Assigned(FInitialLexer) then
    Result:= FInitialLexer
  else
  if Ed.AdapterForHilite is TATAdapterEControl then
    Result:= TATAdapterEControl(Ed.AdapterForHilite).Lexer
  else
    Result:= nil;
end;

function TEditorFrame.GetLexerLite(Ed: TATSynEdit): TATLiteLexer;
begin
  if Ed.AdapterForHilite is TATLiteLexer then
    Result:= TATLiteLexer(Ed.AdapterForHilite)
  else
    Result:= nil;
end;

function TEditorFrame.GetLexerName(Ed: TATSynEdit): string;
var
  CurAdapter: TATAdapterHilite;
  an: TecSyntAnalyzer;
begin
  Result:= '';

  if Assigned(FInitialLexer) then
    exit(FInitialLexer.LexerName);

  CurAdapter:= Ed.AdapterForHilite;
  if CurAdapter=nil then exit;

  if CurAdapter is TATAdapterEControl then
  begin
    an:= TATAdapterEControl(CurAdapter).Lexer;
    if Assigned(an) then
      Result:= an.LexerName;
  end
  else
  if CurAdapter is TATLiteLexer then
  begin
    Result:= TATLiteLexer(CurAdapter).LexerName+msgLiteLexerSuffix;
  end;
end;


procedure TEditorFrame.SetLexerName(Ed: TATSynEdit; const AValue: string);
var
  SName: string;
  anLite: TATLiteLexer;
begin
  if AValue='' then
  begin
    if EditorsLinked then
    begin
      Adapter1.Lexer:= nil;
      Ed1.AdapterForHilite:= nil;
      Ed2.AdapterForHilite:= nil;
      UpdateEds;
    end
    else
    begin
      Lexer[Ed]:= nil;
      Ed.AdapterForHilite:= nil;
      Ed.Update;
    end;
    exit;
  end;

  if SEndsWith(AValue, msgLiteLexerSuffix) then
  begin
    SName:= Copy(AValue, 1, Length(AValue)-Length(msgLiteLexerSuffix));
    anLite:= AppManagerLite.FindLexerByName(SName);
    if Assigned(anLite) then
      LexerLite[Ed]:= anLite
    else
      Lexer[Ed]:= nil;
  end
  else
  begin
    Lexer[Ed]:= AppManager.FindLexerByName(AValue);
  end;
end;


function TEditorFrame.LexerNameAtPos(Ed: TATSynEdit; APos: TPoint): string;
var
  CurAdapter: TATAdapterHilite;
  an: TecSyntAnalyzer;
begin
  Result:= '';
  CurAdapter:= Ed.AdapterForHilite;
  if CurAdapter=nil then exit;

  if CurAdapter is TATAdapterEControl then
  begin
    an:= TATAdapterEControl(CurAdapter).LexerAtPos(APos);
    if Assigned(an) then
      Result:= an.LexerName;
  end
  else
  if CurAdapter is TATLiteLexer then
    Result:= TATLiteLexer(CurAdapter).LexerName+msgLiteLexerSuffix;
end;

procedure TEditorFrame.SetSplitHorz(AValue: boolean);
var
  al: TAlign;
begin
  FSplitHorz:= AValue;
  if not IsText then exit;

  if FSplitHorz then
    al:= alBottom
  else
    al:= alRight;
  Splitter.Align:= al;
  Ed2.Align:= al;

  //restore relative splitter ratio (e.g. 50%)
  SplitPos:= SplitPos;
end;

procedure TEditorFrame.SetSplitPos(AValue: double);
const
  Delta = 70;
var
  N: integer;
begin
  if not IsText then exit;
  if not Splitted then exit;

  AValue:= Max(0.0, Min(1.0, AValue));
  FSplitPos:= AValue;

  if FSplitHorz then
  begin
    N:= Round(AValue*Height);
    Ed2.Height:= Max(Delta, Min(Height-Delta, N));
    Splitter.Top:= 0;
  end
  else
  begin
    N:= Round(AValue*Width);
    Ed2.Width:= Max(Delta, Min(Width-Delta, N));
    Splitter.Left:= 0;
  end;
end;

procedure TEditorFrame.SetSplitted(AValue: boolean);
begin
  if not IsText then exit;
  if GetSplitted=AValue then exit;

  if not AValue and Ed2.Focused then
    if Ed1.CanFocus then
      Ed1.SetFocus;

  Ed2.Visible:= AValue;
  Splitter.Visible:= AValue;

  if AValue then
  begin
    SplitPos:= 0.5;
    //enable linking
    if FEditorsLinked then
      Ed2.Strings:= Ed1.Strings;
  end
  else
  begin
    //disable linking
    Ed2.Strings:= nil;
  end;

  Ed2.Update(true);
end;

procedure TEditorFrame.EditorOnChange(Sender: TObject);
var
  Ed: TATSynEdit;
begin
  Ed:= Sender as TATSynEdit;

  if (Ed=Ed1) and Splitted and EditorsLinked then
  begin
    Ed2.UpdateIncorrectCaretPositions;
    Ed2.Update(true);
  end;

  DoPyEvent(Ed, cEventOnChange, []);

  TimerChange.Enabled:= false;
  TimerChange.Interval:= UiOps.PyChangeSlow;
  TimerChange.Enabled:= true;
end;

procedure TEditorFrame.EditorOnChangeModified(Sender: TObject);
begin
  UpdateModified(Sender as TATSynEdit);
end;

procedure TEditorFrame.EditorOnChangeState(Sender: TObject);
begin
  //
end;

procedure TEditorFrame.UpdateModified(Ed: TATSynEdit; AWithEvent: boolean);
begin
  //when modified, remove "Preview tab" style (italic caption)
  if (Ed=Ed1) and Ed.Modified then
    DoRemovePreviewStyle;

  UpdateCaptionFromFilename;
  DoOnChangeCaption;

  if AWithEvent then
    DoPyEvent(Ed, cEventOnState, [IntToStr(EDSTATE_MODIFIED)]);

  DoOnUpdateStatus;
end;

procedure TEditorFrame.EditorOnEnter(Sender: TObject);
var
  IsEd2: boolean;
begin
  IsEd2:= Sender=Ed2;
  if IsEd2<>FActiveSecondaryEd then
  begin
    FActiveSecondaryEd:= IsEd2;
    DoOnUpdateStatus;
  end;

  if Assigned(FOnFocusEditor) then
    FOnFocusEditor(Sender);

  DoPyEvent(Sender as TATSynEdit, cEventOnFocus, []);

  FActivationTime:= GetTickCount64;
end;

procedure TEditorFrame.EditorOnCommand(Sender: TObject; ACmd: integer;
  const AText: string; var AHandled: boolean);
var
  Ed: TATSynEdit;
  Caret: TATCaretItem;
  Str: atString;
  ch: char;
begin
  Ed:= Sender as TATSynEdit;
  if Ed.Carets.Count=0 then exit;
  Caret:= Ed.Carets[0];

  case ACmd of
    cCommand_TextInsert:
      begin
        //improve auto-closing brackets, avoid duplicate ')' after '('
        if Ed.Strings.IsIndexValid(Caret.PosY) then
          if Length(AText)=1 then
          begin
            ch:= EditorGetPairForCloseBracket(AText[1]);
            if (ch<>#0) and (Pos(ch, UiOps.AutoCloseBrackets)>0) then
            begin
              Str:= Ed.Strings.Lines[Caret.PosY];
              if (Caret.PosX>0) and (Caret.PosX<Length(Str)) then
                if Copy(Str, Caret.PosX, 2) = ch+AText then
                begin
                  Ed.DoCommand(cCommand_KeyRight);
                  AHandled:= true;
                  exit;
                end;
            end;
          end;
      end;

    cCommand_KeyTab,
    cCommand_KeyEnter,
    cCommand_TextDeleteLine,
    cCommand_TextDeleteToLineBegin,
    cCommand_KeyUp,
    cCommand_KeyUp_Sel,
    cCommand_KeyDown,
    cCommand_KeyDown_Sel,
    cCommand_KeyLeft,
    cCommand_KeyLeft_Sel,
    cCommand_KeyRight,
    cCommand_KeyRight_Sel,
    cCommand_KeyHome,
    cCommand_KeyHome_Sel,
    cCommand_KeyEnd,
    cCommand_KeyEnd_Sel,
    cCommand_TextDeleteWordNext,
    cCommand_TextDeleteWordPrev:
      begin
        FTextCharsTyped:= 0;
      end;

    cCommand_ClipboardPaste,
    cCommand_ClipboardPaste_Select,
    cCommand_ClipboardPaste_KeepCaret,
    cCommand_ClipboardPaste_Column,
    cCommand_ClipboardPaste_ColumnKeepCaret:
      begin
        Adapter[Ed].StopTreeUpdate;
        Adapter[Ed].Stop;
      end;
  end;

  if Assigned(FOnEditorCommand) then
    FOnEditorCommand(Sender, ACmd, AText, AHandled);
end;

procedure TEditorFrame.EditorOnCommandAfter(Sender: TObject; ACommand: integer;
  const AText: string);
var
  Ed: TATSynEdit;
  Caret: TATCaretItem;
  SLexerName: string;
  bWordChar, bIdentChar: boolean;
begin
  Ed:= Sender as TATSynEdit;
  if Ed.Carets.Count<>1 then exit;
  Caret:= Ed.Carets[0];
  if not Ed.Strings.IsIndexValid(Caret.PosY) then exit;

// try
  //some commands affect FTextCharsTyped
  if (ACommand=cCommand_KeyBackspace) then
  begin
    if FTextCharsTyped>0 then
      Dec(FTextCharsTyped);
    exit;
  end;

  //autoshow autocompletion
  if (ACommand=cCommand_TextInsert) and
    (Length(AText)=1) then
  begin
    //autoshow by trigger chars
    if (UiOps.AutocompleteTriggerChars<>'') and
      (Pos(AText[1], UiOps.AutocompleteTriggerChars)>0) then
    begin
      //check that we are not inside comment/string
      if IsCaretInsideCommentOrString(Ed, Caret.PosX, Caret.PosY) then exit;

      FTextCharsTyped:= 0;
      Ed.DoCommand(cmd_AutoComplete);
      exit;
    end;

    //other conditions need word-char
    bWordChar:= IsCharWordInIdentifier(AText[1]);
    if not bWordChar then
    begin
      FTextCharsTyped:= 0;
      exit;
    end;

    SLexerName:= LexerNameAtPos(Ed, Point(Caret.PosX, Caret.PosY));

    //autoshow for HTML
    if UiOps.AutocompleteHtml and (Pos('HTML', SLexerName)>0) then
    begin
      if Ed.Strings.LineSub(Caret.PosY, Caret.PosX-1, 1)='<' then
        Ed.DoCommand(cmd_AutoComplete);
      exit;
    end;

    //autoshow for CSS
    if UiOps.AutocompleteCss and (SLexerName='CSS') then
    begin
      if EditorIsAutocompleteCssPosition(Ed, Caret.PosX-1, Caret.PosY) then
        Ed.DoCommand(cmd_AutoComplete);
      exit;
    end;

    //autoshow for others, when typed N chars
    if (UiOps.AutocompleteAutoshowCharCount>0) then
    begin
      //ignore if number typed
      bIdentChar:= bWordChar and not IsCharDigit(AText[1]);
      if (FTextCharsTyped=0) and (not bIdentChar) then exit;

      //check that we are not inside comment/string
      if IsCaretInsideCommentOrString(Ed, Caret.PosX, Caret.PosY) then exit;

      Inc(FTextCharsTyped);
      if FTextCharsTyped=UiOps.AutocompleteAutoshowCharCount then
      begin
        FTextCharsTyped:= 0;
        Ed.DoCommand(cmd_AutoComplete);
        exit;
      end;
    end
    else
      FTextCharsTyped:= 0;
  end;
 //finally
 // Application.MainForm.Caption:=
 //   'char: '+IfThen(AText<>'', AText[1])+
 //   ', count: '+Inttostr(FTextCharsTyped);
 //end;

  if Ed.LastCommandChangedLines>0 then
    if Assigned(FOnMsgStatus) then
      FOnMsgStatus(Self, Format(msgStatusChangedLinesCount, [Ed.LastCommandChangedLines]));
end;

procedure TEditorFrame.EditorOnClickDouble(Sender: TObject; var AHandled: boolean);
var
  Str: string;<commit_msg>fixing #1945, css autocompletion<commit_after>(*
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.

Copyright (c) <NAME>
*)
unit FormFrame;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, Graphics, Forms, Controls, Dialogs,
  ExtCtrls, Menus, StdCtrls, StrUtils, ComCtrls, Clipbrd,
  LCLIntf, LCLProc, LCLType, LazUTF8, LazFileUtils, FileUtil,
  LConvEncoding,
  GraphUtil,
  ATTabs,
  ATGroups,
  ATSynEdit,
  ATSynEdit_Finder,
  ATSynEdit_Keymap_Init,
  ATSynEdit_Adapters,
  ATSynEdit_Adapter_EControl,
  ATSynEdit_Adapter_LiteLexer,
  ATSynEdit_Carets,
  ATSynEdit_Gaps,
  ATSynEdit_Markers,
  ATSynEdit_CanvasProc,
  ATSynEdit_Commands,
  ATSynEdit_Bookmarks,
  ATStrings,
  ATStringProc,
  ATStringProc_HtmlColor,
  ATFileNotif,
  ATButtons,
  ATPanelSimple,
  ATBinHex,
  ATStreamSearch,
  ATImageBox,
  proc_globdata,
  proc_editor,
  proc_cmd,
  proc_colors,
  proc_files,
  proc_msg,
  proc_str,
  proc_py,
  proc_py_const,
  proc_miscutils,
  ec_SyntAnal,
  ec_proc_lexer,
  formlexerstylemap,
  at__jsonconf,
  math,
  LazUTF8Classes;

type
  TEditorFramePyEvent = function(AEd: TATSynEdit; AEvent: TAppPyEvent; const AParams: array of string): string of object;
  TEditorFrameStringEvent = procedure(Sender: TObject; const S: string) of object;

type
  TAppOpenMode = (
    cOpenModeNone,
    cOpenModeEditor,
    cOpenModeViewText,
    cOpenModeViewBinary,
    cOpenModeViewHex,
    cOpenModeViewUnicode
    );

type
  { TEditorFrame }

  TEditorFrame = class(TFrame)
    MenuItem1: TMenuItem;
    MenuItem2: TMenuItem;
    Splitter: TSplitter;
    TimerChange: TTimer;
    procedure TimerChangeTimer(Sender: TObject);
  private
    { private declarations }
    Adapter1: TATAdapterEControl;
    Adapter2: TATAdapterEControl;
    FTabCaption: string;
    FTabCaptionUntitled: string;
    FTabCaptionFromApi: boolean;
    FTabImageIndex: integer;
    FTabId: integer;
    FFileName: string;
    FFileName2: string;
    FFileWasBig: boolean;
    FNotif: TATFileNotif;
    FTextCharsTyped: integer;
    FActivationTime: Int64;
    FCodetreeFilter: string;
    FCodetreeFilterHistory: TStringList;
    FEnabledCodeTree: array[0..1] of boolean;
    FOnChangeCaption: TNotifyEvent;
    FOnProgress: TATFinderProgress;
    FOnUpdateStatus: TNotifyEvent;
    FOnEditorClickMoveCaret: TATSynEditClickMoveCaretEvent;
    FOnEditorClickEndSelect: TATSynEditClickMoveCaretEvent;
    FOnFocusEditor: TNotifyEvent;
    FOnEditorCommand: TATSynEditCommandEvent;
    FOnEditorChangeCaretPos: TNotifyEvent;
    FOnSaveFile: TNotifyEvent;
    FOnAddRecent: TEditorFrameStringEvent;
    FOnPyEvent: TEditorFramePyEvent;
    FOnInitAdapter: TNotifyEvent;
    FSplitPos: double;
    FSplitHorz: boolean;
    FActiveSecondaryEd: boolean;
    FLocked: boolean;
    FTabColor: TColor;
    FTabSizeChanged: boolean;
    FFoldTodo: string;
    FTopLineTodo: integer;
    FTabKeyCollectMarkers: boolean;
    FTagString: string;
    FNotInRecents: boolean;
    FMacroRecord: boolean;
    FMacroString: string;
    FImageBox: TATImageBox;
    FBin: TATBinHex;
    FBinStream: TFileStreamUTF8;
    FCheckFilenameOpened: TStrFunction;
    FOnMsgStatus: TStrEvent;
    FSaveDialog: TSaveDialog;
    FReadOnlyFromFile: boolean;
    FWasVisible: boolean;
    FInitialLexer: TecSyntAnalyzer;
    FSaveHistory: boolean;
    FEditorsLinked: boolean;
    FCachedTreeview: array[0..1] of TTreeView;

    procedure BinaryOnKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
    procedure BinaryOnScroll(Sender: TObject);
    procedure BinaryOnProgress(const ACurrentPos, AMaximalPos: Int64;
      var AContinueSearching: Boolean);
    procedure DoDeactivatePictureMode;
    procedure DoDeactivateViewerMode;
    procedure DoFileOpen_AsBinary(const AFileName: string; AMode: TATBinHexMode);
    procedure DoFileOpen_AsPicture(const AFileName: string);
    procedure DoFileOpen_Ex(Ed: TATSynEdit; const AFileName: string;
      AAllowLoadHistory, AAllowErrorMsgBox: boolean; AOpenMode: TAppOpenMode);
    procedure DoImageboxScroll(Sender: TObject);
    procedure DoOnChangeCaption;
    procedure DoOnUpdateStatus;
    procedure EditorClickEndSelect(Sender: TObject; APrevPnt, ANewPnt: TPoint);
    procedure EditorClickMoveCaret(Sender: TObject; APrevPnt, ANewPnt: TPoint);
    procedure EditorDrawMicromap(Sender: TObject; C: TCanvas; const ARect: TRect);
    procedure EditorOnChange(Sender: TObject);
    procedure EditorOnChangeModified(Sender: TObject);
    procedure EditorOnChangeState(Sender: TObject);
    procedure EditorOnClick(Sender: TObject);
    procedure EditorOnClickGap(Sender: TObject; AGapItem: TATGapItem; APos: TPoint);
    procedure EditorOnClickGutter(Sender: TObject; ABand, ALine: integer);
    procedure EditorOnClickDouble(Sender: TObject; var AHandled: boolean);
    procedure EditorOnClickMicroMap(Sender: TObject; AX, AY: integer);
    procedure EditorOnClickMiddle(Sender: TObject; var AHandled: boolean);
    procedure EditorOnCommand(Sender: TObject; ACmd: integer; const AText: string; var AHandled: boolean);
    procedure EditorOnCommandAfter(Sender: TObject; ACommand: integer; const AText: string);
    procedure EditorOnDrawBookmarkIcon(Sender: TObject; C: TCanvas; ALineNum: integer; const ARect: TRect);
    procedure EditorOnEnter(Sender: TObject);
    procedure EditorOnDrawLine(Sender: TObject; C: TCanvas; AX, AY: integer;
      const AStr: atString; ACharSize: TPoint; const AExtent: TATIntArray);
    procedure EditorOnCalcBookmarkColor(Sender: TObject; ABookmarkKind: integer; var AColor: TColor);
    procedure EditorOnChangeCaretPos(Sender: TObject);
    procedure EditorOnHotspotEnter(Sender: TObject; AHotspotIndex: integer);
    procedure EditorOnHotspotExit(Sender: TObject; AHotspotIndex: integer);
    procedure EditorOnKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
    procedure EditorOnPaste(Sender: TObject; var AHandled: boolean; AKeepCaret,
      ASelectThen: boolean);
    procedure EditorOnScroll(Sender: TObject);
    function GetAdapter(Ed: TATSynEdit): TATAdapterEControl;
    function GetCachedTreeview(Ed: TATSynEdit): TTreeView;
    function GetCommentString(Ed: TATSynEdit): string;
    function GetEnabledCodeTree(Ed: TATSynEdit): boolean;
    function GetEnabledFolding: boolean;
    function GetLineEnds(Ed: TATSynEdit): TATLineEnds;
    function GetNotifEnabled: boolean;
    function GetNotifTime: integer;
    function GetPictureScale: integer;
    function GetReadOnly(Ed: TATSynEdit): boolean;
    function GetSplitPosCurrent: double;
    function GetSplitted: boolean;
    function GetTabKeyCollectMarkers: boolean;
    function GetUnprintedEnds: boolean;
    function GetUnprintedEndsDetails: boolean;
    function GetUnprintedShow: boolean;
    function GetUnprintedSpaces: boolean;
    procedure InitEditor(var ed: TATSynEdit);
    function IsCaretInsideCommentOrString(Ed: TATSynEdit; AX, AY: integer): boolean;
    procedure NotifChanged(Sender: TObject);
    procedure SetEnabledCodeTree(Ed: TATSynEdit; AValue: boolean);
    procedure SetEnabledFolding(AValue: boolean);
    procedure SetFileName(const AValue: string);
    procedure SetFileName2(AValue: string);
    procedure SetFileWasBig(AValue: boolean);
    procedure SetLocked(AValue: boolean);
    procedure SetNotifEnabled(AValue: boolean);
    procedure SetNotifTime(AValue: integer);
    procedure SetPictureScale(AValue: integer);
    procedure SetReadOnly(Ed: TATSynEdit; AValue: boolean);
    procedure SetTabColor(AColor: TColor);
    procedure SetTabImageIndex(AValue: integer);
    procedure SetUnprintedEnds(AValue: boolean);
    procedure SetUnprintedEndsDetails(AValue: boolean);
    procedure SetUnprintedShow(AValue: boolean);
    procedure SetSplitHorz(AValue: boolean);
    procedure SetSplitPos(AValue: double);
    procedure SetSplitted(AValue: boolean);
    procedure SetTabCaption(const AValue: string);
    procedure SetLineEnds(Ed: TATSynEdit; AValue: TATLineEnds);
    procedure SetUnprintedSpaces(AValue: boolean);
    procedure SetEditorsLinked(AValue: boolean);
    procedure SplitterMoved(Sender: TObject);
    procedure UpdateEds(AUpdateWrapInfo: boolean=false);
    procedure UpdateCaptionFromFilename;
    function GetLexer(Ed: TATSynEdit): TecSyntAnalyzer;
    function GetLexerLite(Ed: TATSynEdit): TATLiteLexer;
    function GetLexerName(Ed: TATSynEdit): string;
    procedure SetLexer(Ed: TATSynEdit; an: TecSyntAnalyzer);
    procedure SetLexerLite(Ed: TATSynEdit; an: TATLiteLexer);
    procedure SetLexerName(Ed: TATSynEdit; const AValue: string);
    procedure UpdateTabTooltip;
  protected
    procedure DoOnResize; override;
  public
    { public declarations }
    Ed1: TATSynEdit;
    Ed2: TATSynEdit;
    Groups: TATGroups;

    constructor Create(AOwner: TComponent; AApplyCentering: boolean); reintroduce;
    destructor Destroy; override;
    function Editor: TATSynEdit;
    function EditorBro: TATSynEdit;
    property Adapter[Ed: TATSynEdit]: TATAdapterEControl read GetAdapter;
    procedure EditorOnKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
    procedure DoShow;
    property ReadOnly[Ed: TATSynEdit]: boolean read GetReadOnly write SetReadOnly;
    property ReadOnlyFromFile: boolean read FReadOnlyFromFile write FReadOnlyFromFile;
    property TabCaption: string read FTabCaption write SetTabCaption;
    property TabCaptionUntitled: string read FTabCaptionUntitled write FTabCaptionUntitled;
    property TabImageIndex: integer read FTabImageIndex write SetTabImageIndex;
    property TabCaptionFromApi: boolean read FTabCaptionFromApi write FTabCaptionFromApi;
    property TabId: integer read FTabId;
    property CachedTreeView[Ed: TATSynEdit]: TTreeView read GetCachedTreeview;
    property SaveHistory: boolean read FSaveHistory write FSaveHistory;
    procedure UpdateModified(Ed: TATSynEdit; AWithEvent: boolean= true);
    procedure UpdateReadOnlyFromFile(Ed: TATSynEdit);
    property NotifEnabled: boolean read GetNotifEnabled write SetNotifEnabled;
    property NotifTime: integer read GetNotifTime write SetNotifTime;

    property FileName: string read FFileName write SetFileName;
    property FileWasBig: boolean read FFileWasBig write SetFileWasBig;
    function GetFileName(Ed: TATSynEdit): string;
    procedure SetFileName(Ed: TATSynEdit; const AFileName: string);

    property Lexer[Ed: TATSynEdit]: TecSyntAnalyzer read GetLexer write SetLexer;
    property LexerLite[Ed: TATSynEdit]: TATLiteLexer read GetLexerLite write SetLexerLite;
    property LexerName[Ed: TATSynEdit]: string read GetLexerName write SetLexerName;
    function LexerNameAtPos(Ed: TATSynEdit; APos: TPoint): string;

    property Locked: boolean read FLocked write SetLocked;
    property CommentString[Ed: TATSynEdit]: string read GetCommentString;
    property TabColor: TColor read FTabColor write SetTabColor;
    property TabSizeChanged: boolean read FTabSizeChanged write FTabSizeChanged;
    property TabKeyCollectMarkers: boolean read GetTabKeyCollectMarkers write FTabKeyCollectMarkers;
    property TagString: string read FTagString write FTagString;
    property NotInRecents: boolean read FNotInRecents write FNotInRecents;
    property TopLineTodo: integer read FTopLineTodo write FTopLineTodo; //always use it instead of Ed.LineTop
    property TextCharsTyped: integer read FTextCharsTyped write FTextCharsTyped;
    property EnabledCodeTree[Ed: TATSynEdit]: boolean read GetEnabledCodeTree write SetEnabledCodeTree;
    property CodetreeFilter: string read FCodetreeFilter write FCodetreeFilter;
    property CodetreeFilterHistory: TStringList read FCodetreeFilterHistory;
    property ActivationTime: Int64 read FActivationTime write FActivationTime;
    function IsEmpty: boolean;
    function IsPreview: boolean;
    procedure ApplyTheme;
    procedure SetFocus; reintroduce;
    function IsText: boolean;
    function IsPicture: boolean;
    function IsBinary: boolean;
    function PictureSizes: TPoint;
    property PictureScale: integer read GetPictureScale write SetPictureScale;
    property Binary: TATBinHex read FBin;
    function BinaryFindFirst(AFinder: TATEditorFinder; AShowAll: boolean): boolean;
    function BinaryFindNext(ABack: boolean): boolean;
    //
    property LineEnds[Ed: TATSynEdit]: TATLineEnds read GetLineEnds write SetLineEnds;
    property UnprintedShow: boolean read GetUnprintedShow write SetUnprintedShow;
    property UnprintedSpaces: boolean read GetUnprintedSpaces write SetUnprintedSpaces;
    property UnprintedEnds: boolean read GetUnprintedEnds write SetUnprintedEnds;
    property UnprintedEndsDetails: boolean read GetUnprintedEndsDetails write SetUnprintedEndsDetails;
    property Splitted: boolean read GetSplitted write SetSplitted;
    property SplitHorz: boolean read FSplitHorz write SetSplitHorz;
    property SplitPos: double read FSplitPos write SetSplitPos;
    property EditorsLinked: boolean read FEditorsLinked write SetEditorsLinked;
    property EnabledFolding: boolean read GetEnabledFolding write SetEnabledFolding;
    property SaveDialog: TSaveDialog read FSaveDialog write FSaveDialog;
    //file
    procedure DoFileClose;
    procedure DoFileOpen(const AFileName, AFileName2: string; AAllowLoadHistory,
      AAllowErrorMsgBox: boolean; AOpenMode: TAppOpenMode);
    function DoFileSave(ASaveAs, AAllEditors: boolean): boolean;
    function DoFileSave_Ex(Ed: TATSynEdit; ASaveAs: boolean): boolean;
    procedure DoFileReload_DisableDetectEncoding;
    procedure DoFileReload;
    procedure DoLexerFromFilename(Ed: TATSynEdit; const AFileName: string);
    procedure DoSaveHistory(Ed: TATSynEdit);
    procedure DoSaveHistoryEx(Ed: TATSynEdit; c: TJsonConfig; const path: UnicodeString);
    procedure DoSaveUndo(Ed: TATSynEdit; const AFileName: string);
    procedure DoLoadHistory(Ed: TATSynEdit);
    procedure DoLoadHistoryEx(Ed: TATSynEdit; const AFileName: string; c: TJsonConfig; const path: UnicodeString);
    procedure DoLoadUndo(Ed: TATSynEdit);
    //misc
    function DoPyEvent(AEd: TATSynEdit; AEvent: TAppPyEvent; const AParams: array of string): string;
    procedure DoGotoPos(Ed: TATSynEdit; APosX, APosY: integer);
    procedure DoRestoreFolding;
    procedure DoRemovePreviewStyle;
    procedure DoToggleFocusSplitEditors;
    //macro
    procedure DoMacroStart;
    procedure DoMacroStop(ACancel: boolean);
    property MacroRecord: boolean read FMacroRecord;
    property MacroString: string read FMacroString write FMacroString;

    //events
    property OnProgress: TATFinderProgress read FOnProgress write FOnProgress;
    property OnCheckFilenameOpened: TStrFunction read FCheckFilenameOpened write FCheckFilenameOpened;
    property OnMsgStatus: TStrEvent read FOnMsgStatus write FOnMsgStatus;
    property OnFocusEditor: TNotifyEvent read FOnFocusEditor write FOnFocusEditor;
    property OnChangeCaption: TNotifyEvent read FOnChangeCaption write FOnChangeCaption;
    property OnUpdateStatus: TNotifyEvent read FOnUpdateStatus write FOnUpdateStatus;
    property OnEditorClickMoveCaret: TATSynEditClickMoveCaretEvent read FOnEditorClickMoveCaret write FOnEditorClickMoveCaret;
    property OnEditorClickEndSelect: TATSynEditClickMoveCaretEvent read FOnEditorClickEndSelect write FOnEditorClickEndSelect;
    property OnEditorCommand: TATSynEditCommandEvent read FOnEditorCommand write FOnEditorCommand;
    property OnEditorChangeCaretPos: TNotifyEvent read FOnEditorChangeCaretPos write FOnEditorChangeCaretPos;
    property OnSaveFile: TNotifyEvent read FOnSaveFile write FOnSaveFile;
    property OnAddRecent: TEditorFrameStringEvent read FOnAddRecent write FOnAddRecent;
    property OnPyEvent: TEditorFramePyEvent read FOnPyEvent write FOnPyEvent;
    property OnInitAdapter: TNotifyEvent read FOnInitAdapter write FOnInitAdapter;
  end;

procedure GetFrameLocation(Frame: TEditorFrame;
  out AGroups: TATGroups; out APages: TATPages;
  out ALocalGroupIndex, AGlobalGroupIndex, ATabIndex: integer);

var
  //must be set in FormMain.OnShow
  AllowFrameParsing: boolean = false;


implementation

{$R *.lfm}

const
  cHistory_Lexer       = '/lexer';
  cHistory_Enc         = '/enc';
  cHistory_Top         = '/top';
  cHistory_Wrap        = '/wrap_mode';
  cHistory_RO          = '/ro';
  cHistory_Ruler       = '/ruler';
  cHistory_Minimap     = '/minimap';
  cHistory_Micromap    = '/micromap';
  cHistory_TabSize     = '/tab_size';
  cHistory_TabSpace    = '/tab_spaces';
  cHistory_Nums        = '/nums';
  cHistory_Unpri        = '/unprinted_show';
  cHistory_Unpri_Spaces = '/unprinted_spaces';
  cHistory_Unpri_Ends   = '/unprinted_ends';
  cHistory_Unpri_Detail = '/unprinted_end_details';
  cHistory_Caret       = '/crt';
  cHistory_TabColor    = '/color';
  cHistory_Bookmark    = '/bm';
  cHistory_BookmarkKind = '/bm_kind';
  cHistory_Fold        = '/folded';
  cHistory_CodeTreeFilter = '/codetree_filter';
  cHistory_CodeTreeFilters = '/codetree_filters';

var
  FLastTabId: integer = 0;


procedure GetFrameLocation(Frame: TEditorFrame;
  out AGroups: TATGroups; out APages: TATPages;
  out ALocalGroupIndex, AGlobalGroupIndex, ATabIndex: integer);
var
  C: TWinControl;
begin
  APages:= Frame.Parent as TATPages;

  C:= APages;
  repeat
    C:= C.Parent;
  until C is TATGroups;

  AGroups:= C as TATGroups;
  AGroups.PagesAndTabIndexOfControl(Frame, ALocalGroupIndex, ATabIndex);

  AGlobalGroupIndex:= ALocalGroupIndex;
  if AGroups.Tag<>0 then
    Inc(AGlobalGroupIndex, High(TATGroupsNums) + AGroups.Tag);
end;


{ TEditorFrame }

procedure TEditorFrame.SetTabCaption(const AValue: string);
var
  Upd: boolean;
begin
  if AValue='?' then Exit;
  Upd:= FTabCaption<>AValue;

  FTabCaption:= AValue; //don't check Upd here (for Win32)

  if Upd then
    DoPyEvent(Editor, cEventOnState, [IntToStr(EDSTATE_TAB_TITLE)]);
  DoOnChangeCaption;
end;

procedure TEditorFrame.UpdateCaptionFromFilename;
var
  Name1, Name2: string;
begin
  if EditorsLinked then
  begin
    if FFileName='' then
      Name1:= FTabCaptionUntitled
    else
      Name1:= ExtractFileName_Fixed(FFileName);
    Name1:= msgModified[Ed1.Modified]+Name1;

    TabCaption:= Name1;
  end
  else
  begin
    if (FFileName='') and (FFileName2='') then
      TabCaption:= FTabCaptionUntitled
    else
    begin
      Name1:= ExtractFileName_Fixed(FFileName);
      if Name1='' then Name1:= msgUntitledTab;
      Name1:= msgModified[Ed1.Modified]+Name1;

      Name2:= ExtractFileName_Fixed(FFileName2);
      if Name2='' then Name2:= msgUntitledTab;
      Name2:= msgModified[Ed2.Modified]+Name2;

      TabCaption:= Name1+' | '+Name2;
    end;
  end;

  UpdateTabTooltip;
end;

procedure TEditorFrame.EditorOnClick(Sender: TObject);
var
  Ed: TATSynEdit;
  StateString: string;
begin
  Ed:= Sender as TATSynEdit;

  StateString:= ConvertShiftStateToString(KeyboardStateToShiftState);
  FTextCharsTyped:= 0; //reset count for option "autocomplete_autoshow_chars"

  if UiOps.MouseGotoDefinition<>'' then
    if StateString=UiOps.MouseGotoDefinition then
    begin
      DoPyEvent(Ed, cEventOnGotoDef, []);
      exit;
    end;

  DoPyEvent(Ed, cEventOnClick, ['"'+StateString+'"']);
end;

function TEditorFrame.GetSplitPosCurrent: double;
begin
  if not Splitted then
    Result:= 0
  else
  if FSplitHorz then
    Result:= Ed2.Height/Max(Height, 1)
  else
    Result:= Ed2.Width/Max(Width, 1);
end;

function TEditorFrame.GetSplitted: boolean;
begin
  Result:= Ed2.Visible;
end;

procedure TEditorFrame.EditorOnKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  //res=False: block key
  if DoPyEvent(Sender as TATSynEdit,
    cEventOnKey,
    [
      IntToStr(Key),
      '"'+ConvertShiftStateToString(Shift)+'"'
    ]) = cPyFalse then
    begin
      Key:= 0;
      Exit
    end;
end;

procedure TEditorFrame.EditorOnPaste(Sender: TObject; var AHandled: boolean;
  AKeepCaret, ASelectThen: boolean);
const
  cBool: array[boolean] of string = (cPyFalse, cPyTrue);
begin
  if DoPyEvent(Sender as TATSynEdit,
    cEventOnPaste,
    [
      cBool[AKeepCaret],
      cBool[ASelectThen]
    ]) = cPyFalse then
    AHandled:= true;
end;

procedure TEditorFrame.EditorOnScroll(Sender: TObject);
begin
  DoPyEvent(Sender as TATSynEdit, cEventOnScroll, []);
end;

procedure TEditorFrame.EditorOnKeyUp(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  //keyup: only for Ctrl/Shift/Alt
  //no res.
  case Key of
    VK_CONTROL,
    VK_MENU,
    VK_SHIFT,
    VK_RSHIFT:
      DoPyEvent(Sender as TATSynEdit,
        cEventOnKeyUp,
        [
          IntToStr(Key),
          '"'+ConvertShiftStateToString(Shift)+'"'
        ]);
  end;
end;

procedure TEditorFrame.DoShow;
begin
  //analize file, when frame is shown for the 1st time
  if AllowFrameParsing and not FWasVisible then
  begin
    FWasVisible:= true;
    //ShowMessage('show frame: '+FileName); ////debug

    if Assigned(FInitialLexer) then
    begin
      Lexer[Ed1]:= FInitialLexer;
      FInitialLexer:= nil;
    end;
  end;
end;

procedure TEditorFrame.TimerChangeTimer(Sender: TObject);
begin
  TimerChange.Enabled:= false;
  DoPyEvent(Editor, cEventOnChangeSlow, []);
end;

procedure TEditorFrame.EditorOnCalcBookmarkColor(Sender: TObject;
  ABookmarkKind: integer; var AColor: TColor);
begin
  if ABookmarkKind>1 then
    AColor:= AppBookmarkSetup[ABookmarkKind].Color;
end;

procedure TEditorFrame.EditorOnChangeCaretPos(Sender: TObject);
const
  cMaxSelectedLinesForAutoCopy = 200;
var
  Ed: TATSynEdit;
  NFrom, NTo: integer;
begin
  if Assigned(FOnEditorChangeCaretPos) then
    FOnEditorChangeCaretPos(Sender);

  DoOnUpdateStatus;

  Ed:= Sender as TATSynEdit;

  //support Primary Selection on Linux
  {$ifdef linux}
  if Ed.Carets.Count=1 then
  begin
    Ed.Carets[0].GetSelLines(NFrom, NTo, false);
    if (NTo-NFrom)<=cMaxSelectedLinesForAutoCopy then
      SClipboardCopy(Ed.TextSelected, PrimarySelection);
  end;
  {$endif}

  DoPyEvent(Ed, cEventOnCaret, []);
end;

procedure TEditorFrame.EditorOnHotspotEnter(Sender: TObject; AHotspotIndex: integer);
begin
  DoPyEvent(Sender as TATSynEdit, cEventOnHotspot, [
    cPyTrue, //hotspot enter
    IntToStr(AHotspotIndex)
    ]);
end;

procedure TEditorFrame.EditorOnHotspotExit(Sender: TObject; AHotspotIndex: integer);
begin
  DoPyEvent(Sender as TATSynEdit, cEventOnHotspot, [
    cPyFalse, //hotspot exit
    IntToStr(AHotspotIndex)
    ]);
end;

procedure TEditorFrame.EditorOnDrawLine(Sender: TObject; C: TCanvas; AX,
  AY: integer; const AStr: atString; ACharSize: TPoint;
  const AExtent: TATIntArray);
const
  cRegexRGB = 'rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(,\s*[\.\d]+\s*)?\)';
  cRegexHSL = 'hsla?\(\s*(\d{1,3})\s*,\s*(\d{1,3})%\s*,\s*(\d{1,3})%\s*(,\s*[\.\d%]+\s*)?\)';
var
  X1, X2, Y, NLen: integer;
  NColor: TColor;
  Parts: TRegexParts;
  Ch: atChar;
  ValueR, ValueG, ValueB: byte;
  ValueH, ValueS, ValueL: word;
  i: integer;
begin
  if AStr='' then Exit;
  if not IsFilenameListedInExtensionList(FileName, EditorOps.OpUnderlineColorFiles)
    then exit;

  for i:= 1 to Length(AStr)-3 do
  begin
    Ch:= AStr[i];

    //find #rgb, #rrggbb
    if Ch='#' then
    begin
      NColor:= SHtmlColorToColor(Copy(AStr, i+1, 7), NLen, clNone);
      if NColor=clNone then Continue;

      if i-2>=0 then
        X1:= AX+AExtent[i-2]
      else
        X1:= AX;
      X2:= AX+AExtent[i-1+NLen];
      Y:= AY+ACharSize.Y;

      C.Brush.Color:= NColor;
      C.FillRect(X1, Y-EditorOps.OpUnderlineColorSize, X2, Y);
    end
    else
    //find rgb(...), rgba(...)
    if (Ch='r') and
      (i+6<=Length(AStr)) and
      (AStr[i+1]='g') and
      (AStr[i+2]='b') and
      ((AStr[i+3]='(') or ((AStr[i+3]='a') and (AStr[i+4]='('))) and
      ((i=1) or not IsCharWord(AStr[i-1], '')) //word boundary
    then
    begin
      if SRegexFindParts(cRegexRGB, Copy(AStr, i, MaxInt), Parts) then
        if Parts[0].Pos=1 then //need at i-th char
        begin
          ValueR:= Min(255, StrToIntDef(Parts[1].Str, 0));
          ValueG:= Min(255, StrToIntDef(Parts[2].Str, 0));
          ValueB:= Min(255, StrToIntDef(Parts[3].Str, 0));

          NColor:= RGB(ValueR, ValueG, ValueB);
          NLen:= Parts[0].Len;

          if i-2>=0 then
            X1:= AX+AExtent[i-2]
          else
            X1:= AX;
          X2:= AX+AExtent[i-2+NLen];
          Y:= AY+ACharSize.Y;

          C.Brush.Color:= NColor;
          C.FillRect(X1, Y-EditorOps.OpUnderlineColorSize, X2, Y);
        end;
    end
    else
    //find hsl(...), hsla(...)
    if (Ch='h') and
      (i+6<=Length(AStr)) and
      (AStr[i+1]='s') and
      (AStr[i+2]='l') and
      ((AStr[i+3]='(') or ((AStr[i+3]='a') and (AStr[i+4]='('))) and
      ((i=1) or not IsCharWord(AStr[i-1], '')) //word boundary
      then
      begin
        if SRegexFindParts(cRegexHSL, Copy(AStr, i, MaxInt), Parts) then
          if Parts[0].Pos=1 then //need at i-th char
          begin
            ValueH:= StrToIntDef(Parts[1].Str, 0) * 255 div 360; //degrees -> 0..255
            ValueS:= StrToIntDef(Parts[2].Str, 0) * 255 div 100; //percents -> 0..255
            ValueL:= StrToIntDef(Parts[3].Str, 0) * 255 div 100; //percents -> 0..255

            NColor:= HLStoColor(ValueH, ValueL, ValueS);
            NLen:= Parts[0].Len;

            if i-2>=0 then
              X1:= AX+AExtent[i-2]
            else
              X1:= AX;
            X2:= AX+AExtent[i-2+NLen];
            Y:= AY+ACharSize.Y;

            C.Brush.Color:= NColor;
            C.FillRect(X1, Y-EditorOps.OpUnderlineColorSize, X2, Y);
          end;
      end;
  end;
end;


function TEditorFrame.GetLineEnds(Ed: TATSynEdit): TATLineEnds;
begin
  Result:= Ed.Strings.Endings;
end;

function TEditorFrame.GetNotifEnabled: boolean;
begin
  Result:= FNotif.Timer.Enabled;
end;

function TEditorFrame.GetNotifTime: integer;
begin
  Result:= FNotif.Timer.Interval;
end;

function TEditorFrame.GetPictureScale: integer;
begin
  if Assigned(FImageBox) then
    Result:= FImageBox.ImageZoom
  else
    Result:= 100;
end;

function TEditorFrame.GetReadOnly(Ed: TATSynEdit): boolean;
begin
  Result:= Ed.ModeReadOnly;
end;

function TEditorFrame.GetTabKeyCollectMarkers: boolean;
begin
  Result:= FTabKeyCollectMarkers and (Editor.Markers.Count>0);
end;

function TEditorFrame.GetUnprintedEnds: boolean;
begin
  Result:= Ed1.OptUnprintedEnds;
end;

function TEditorFrame.GetUnprintedEndsDetails: boolean;
begin
  Result:= Ed1.OptUnprintedEndsDetails;
end;

function TEditorFrame.GetUnprintedShow: boolean;
begin
  Result:= Ed1.OptUnprintedVisible;
end;

function TEditorFrame.GetUnprintedSpaces: boolean;
begin
  Result:= Ed1.OptUnprintedSpaces;
end;

procedure TEditorFrame.SetFileName(const AValue: string);
begin
  if SameFileName(FFileName, AValue) then Exit;
  FFileName:= AValue;

  //update Notif obj
  NotifEnabled:= NotifEnabled;
end;

procedure TEditorFrame.UpdateTabTooltip;
var
  Gr: TATGroups;
  Pages: TATPages;
  NLocalGroups, NGlobalGroup, NTab: integer;
  D: TATTabData;
  SHint: string;
begin
  GetFrameLocation(Self, Gr, Pages, NLocalGroups, NGlobalGroup, NTab);
  D:= Pages.Tabs.GetTabData(NTab);
  if Assigned(D) then
  begin
    SHint:= '';
    if EditorsLinked then
    begin
      if FFileName<>'' then
        SHint:= SCollapseHomeDirInFilename(FFileName);
    end
    else
    begin
      if (FFileName<>'') or (FFileName2<>'') then
        SHint:= SCollapseHomeDirInFilename(FFileName) + #10 +
                SCollapseHomeDirInFilename(FFileName2);
    end;

    D.TabHint:= SHint;
    Pages.Tabs.Invalidate;
  end;
end;

procedure TEditorFrame.SetFileName2(AValue: string);
begin
  if SameFileName(FFileName2, AValue) then Exit;
  FFileName2:= AValue;
end;

procedure TEditorFrame.SetFileWasBig(AValue: boolean);
begin
  FFileWasBig:= AValue;
  if AValue then
  begin
    Ed1.OptWrapMode:= cWrapOff;
    Ed2.OptWrapMode:= cWrapOff;
    Ed1.OptMicromapVisible:= false;
    Ed2.OptMicromapVisible:= false;
    Ed1.OptMinimapVisible:= false;
    Ed2.OptMinimapVisible:= false;
  end;
end;

procedure TEditorFrame.SetLocked(AValue: boolean);
begin
  if AValue=FLocked then exit;
  FLocked:= AValue;

  if FLocked then
  begin
    Ed1.BeginUpdate;
    Ed2.BeginUpdate;
  end
  else
  begin
    Ed1.EndUpdate;
    Ed2.EndUpdate;
  end;
end;

procedure TEditorFrame.SetNotifEnabled(AValue: boolean);
begin
  FNotif.Timer.Enabled:= false;
  FNotif.FileName:= '';

  if AValue and IsText and FileExistsUTF8(FileName) then
  begin
    FNotif.FileName:= FileName;
    FNotif.Timer.Enabled:= true;
  end;
end;

procedure TEditorFrame.SetNotifTime(AValue: integer);
begin
  FNotif.Timer.Interval:= AValue;
end;

procedure TEditorFrame.SetPictureScale(AValue: integer);
begin
  if Assigned(FImageBox) then
  begin
    if AValue>0 then
      FImageBox.ImageZoom:= AValue
    else
    if AValue=-1 then
      FImageBox.OptFitToWindow:= true;
  end;
end;

procedure TEditorFrame.SetReadOnly(Ed: TATSynEdit; AValue: boolean);
begin
  Ed.ModeReadOnly:= AValue;
  if (Ed=Ed1) and EditorsLinked then
    Ed2.ModeReadOnly:= AValue;
end;

procedure TEditorFrame.UpdateEds(AUpdateWrapInfo: boolean = false);
begin
  Ed2.OptUnprintedVisible:= Ed1.OptUnprintedVisible;
  Ed2.OptUnprintedSpaces:= Ed1.OptUnprintedSpaces;
  Ed2.OptUnprintedEnds:= Ed1.OptUnprintedEnds;
  Ed2.OptUnprintedEndsDetails:= Ed1.OptUnprintedEndsDetails;

  Ed1.Update(AUpdateWrapInfo);
  Ed2.Update(AUpdateWrapInfo);
end;

function TEditorFrame.GetLexer(Ed: TATSynEdit): TecSyntAnalyzer;
begin
  if Assigned(FInitialLexer) then
    Result:= FInitialLexer
  else
  if Ed.AdapterForHilite is TATAdapterEControl then
    Result:= TATAdapterEControl(Ed.AdapterForHilite).Lexer
  else
    Result:= nil;
end;

function TEditorFrame.GetLexerLite(Ed: TATSynEdit): TATLiteLexer;
begin
  if Ed.AdapterForHilite is TATLiteLexer then
    Result:= TATLiteLexer(Ed.AdapterForHilite)
  else
    Result:= nil;
end;

function TEditorFrame.GetLexerName(Ed: TATSynEdit): string;
var
  CurAdapter: TATAdapterHilite;
  an: TecSyntAnalyzer;
begin
  Result:= '';

  if Assigned(FInitialLexer) then
    exit(FInitialLexer.LexerName);

  CurAdapter:= Ed.AdapterForHilite;
  if CurAdapter=nil then exit;

  if CurAdapter is TATAdapterEControl then
  begin
    an:= TATAdapterEControl(CurAdapter).Lexer;
    if Assigned(an) then
      Result:= an.LexerName;
  end
  else
  if CurAdapter is TATLiteLexer then
  begin
    Result:= TATLiteLexer(CurAdapter).LexerName+msgLiteLexerSuffix;
  end;
end;


procedure TEditorFrame.SetLexerName(Ed: TATSynEdit; const AValue: string);
var
  SName: string;
  anLite: TATLiteLexer;
begin
  if AValue='' then
  begin
    if EditorsLinked then
    begin
      Adapter1.Lexer:= nil;
      Ed1.AdapterForHilite:= nil;
      Ed2.AdapterForHilite:= nil;
      UpdateEds;
    end
    else
    begin
      Lexer[Ed]:= nil;
      Ed.AdapterForHilite:= nil;
      Ed.Update;
    end;
    exit;
  end;

  if SEndsWith(AValue, msgLiteLexerSuffix) then
  begin
    SName:= Copy(AValue, 1, Length(AValue)-Length(msgLiteLexerSuffix));
    anLite:= AppManagerLite.FindLexerByName(SName);
    if Assigned(anLite) then
      LexerLite[Ed]:= anLite
    else
      Lexer[Ed]:= nil;
  end
  else
  begin
    Lexer[Ed]:= AppManager.FindLexerByName(AValue);
  end;
end;


function TEditorFrame.LexerNameAtPos(Ed: TATSynEdit; APos: TPoint): string;
var
  CurAdapter: TATAdapterHilite;
  an: TecSyntAnalyzer;
begin
  Result:= '';
  CurAdapter:= Ed.AdapterForHilite;
  if CurAdapter=nil then exit;

  if CurAdapter is TATAdapterEControl then
  begin
    an:= TATAdapterEControl(CurAdapter).LexerAtPos(APos);
    if Assigned(an) then
      Result:= an.LexerName;
  end
  else
  if CurAdapter is TATLiteLexer then
    Result:= TATLiteLexer(CurAdapter).LexerName+msgLiteLexerSuffix;
end;

procedure TEditorFrame.SetSplitHorz(AValue: boolean);
var
  al: TAlign;
begin
  FSplitHorz:= AValue;
  if not IsText then exit;

  if FSplitHorz then
    al:= alBottom
  else
    al:= alRight;
  Splitter.Align:= al;
  Ed2.Align:= al;

  //restore relative splitter ratio (e.g. 50%)
  SplitPos:= SplitPos;
end;

procedure TEditorFrame.SetSplitPos(AValue: double);
const
  Delta = 70;
var
  N: integer;
begin
  if not IsText then exit;
  if not Splitted then exit;

  AValue:= Max(0.0, Min(1.0, AValue));
  FSplitPos:= AValue;

  if FSplitHorz then
  begin
    N:= Round(AValue*Height);
    Ed2.Height:= Max(Delta, Min(Height-Delta, N));
    Splitter.Top:= 0;
  end
  else
  begin
    N:= Round(AValue*Width);
    Ed2.Width:= Max(Delta, Min(Width-Delta, N));
    Splitter.Left:= 0;
  end;
end;

procedure TEditorFrame.SetSplitted(AValue: boolean);
begin
  if not IsText then exit;
  if GetSplitted=AValue then exit;

  if not AValue and Ed2.Focused then
    if Ed1.CanFocus then
      Ed1.SetFocus;

  Ed2.Visible:= AValue;
  Splitter.Visible:= AValue;

  if AValue then
  begin
    SplitPos:= 0.5;
    //enable linking
    if FEditorsLinked then
      Ed2.Strings:= Ed1.Strings;
  end
  else
  begin
    //disable linking
    Ed2.Strings:= nil;
  end;

  Ed2.Update(true);
end;

procedure TEditorFrame.EditorOnChange(Sender: TObject);
var
  Ed: TATSynEdit;
begin
  Ed:= Sender as TATSynEdit;

  if (Ed=Ed1) and Splitted and EditorsLinked then
  begin
    Ed2.UpdateIncorrectCaretPositions;
    Ed2.Update(true);
  end;

  DoPyEvent(Ed, cEventOnChange, []);

  TimerChange.Enabled:= false;
  TimerChange.Interval:= UiOps.PyChangeSlow;
  TimerChange.Enabled:= true;
end;

procedure TEditorFrame.EditorOnChangeModified(Sender: TObject);
begin
  UpdateModified(Sender as TATSynEdit);
end;

procedure TEditorFrame.EditorOnChangeState(Sender: TObject);
begin
  //
end;

procedure TEditorFrame.UpdateModified(Ed: TATSynEdit; AWithEvent: boolean);
begin
  //when modified, remove "Preview tab" style (italic caption)
  if (Ed=Ed1) and Ed.Modified then
    DoRemovePreviewStyle;

  UpdateCaptionFromFilename;
  DoOnChangeCaption;

  if AWithEvent then
    DoPyEvent(Ed, cEventOnState, [IntToStr(EDSTATE_MODIFIED)]);

  DoOnUpdateStatus;
end;

procedure TEditorFrame.EditorOnEnter(Sender: TObject);
var
  IsEd2: boolean;
begin
  IsEd2:= Sender=Ed2;
  if IsEd2<>FActiveSecondaryEd then
  begin
    FActiveSecondaryEd:= IsEd2;
    DoOnUpdateStatus;
  end;

  if Assigned(FOnFocusEditor) then
    FOnFocusEditor(Sender);

  DoPyEvent(Sender as TATSynEdit, cEventOnFocus, []);

  FActivationTime:= GetTickCount64;
end;

procedure TEditorFrame.EditorOnCommand(Sender: TObject; ACmd: integer;
  const AText: string; var AHandled: boolean);
var
  Ed: TATSynEdit;
  Caret: TATCaretItem;
  Str: atString;
  ch: char;
begin
  Ed:= Sender as TATSynEdit;
  if Ed.Carets.Count=0 then exit;
  Caret:= Ed.Carets[0];

  case ACmd of
    cCommand_TextInsert:
      begin
        //improve auto-closing brackets, avoid duplicate ')' after '('
        if Ed.Strings.IsIndexValid(Caret.PosY) then
          if Length(AText)=1 then
          begin
            ch:= EditorGetPairForCloseBracket(AText[1]);
            if (ch<>#0) and (Pos(ch, UiOps.AutoCloseBrackets)>0) then
            begin
              Str:= Ed.Strings.Lines[Caret.PosY];
              if (Caret.PosX>0) and (Caret.PosX<Length(Str)) then
                if Copy(Str, Caret.PosX, 2) = ch+AText then
                begin
                  Ed.DoCommand(cCommand_KeyRight);
                  AHandled:= true;
                  exit;
                end;
            end;
          end;
      end;

    cCommand_KeyTab,
    cCommand_KeyEnter,
    cCommand_TextDeleteLine,
    cCommand_TextDeleteToLineBegin,
    cCommand_KeyUp,
    cCommand_KeyUp_Sel,
    cCommand_KeyDown,
    cCommand_KeyDown_Sel,
    cCommand_KeyLeft,
    cCommand_KeyLeft_Sel,
    cCommand_KeyRight,
    cCommand_KeyRight_Sel,
    cCommand_KeyHome,
    cCommand_KeyHome_Sel,
    cCommand_KeyEnd,
    cCommand_KeyEnd_Sel,
    cCommand_TextDeleteWordNext,
    cCommand_TextDeleteWordPrev:
      begin
        FTextCharsTyped:= 0;
      end;

    cCommand_ClipboardPaste,
    cCommand_ClipboardPaste_Select,
    cCommand_ClipboardPaste_KeepCaret,
    cCommand_ClipboardPaste_Column,
    cCommand_ClipboardPaste_ColumnKeepCaret:
      begin
        Adapter[Ed].StopTreeUpdate;
        Adapter[Ed].Stop;
      end;
  end;

  if Assigned(FOnEditorCommand) then
    FOnEditorCommand(Sender, ACmd, AText, AHandled);
end;

procedure TEditorFrame.EditorOnCommandAfter(Sender: TObject; ACommand: integer;
  const AText: string);
var
  Ed: TATSynEdit;
  Caret: TATCaretItem;
  SLexerName: string;
  bWordChar, bIdentChar: boolean;
begin
  Ed:= Sender as TATSynEdit;
  if Ed.Carets.Count<>1 then exit;
  Caret:= Ed.Carets[0];
  if not Ed.Strings.IsIndexValid(Caret.PosY) then exit;

  //some commands affect FTextCharsTyped
  if (ACommand=cCommand_KeyBackspace) then
  begin
    if FTextCharsTyped>0 then
      Dec(FTextCharsTyped);
    exit;
  end;

  //autoshow autocompletion
  if (ACommand=cCommand_TextInsert) and
    (Length(AText)=1) then
  begin
    //autoshow by trigger chars
    if (UiOps.AutocompleteTriggerChars<>'') and
      (Pos(AText[1], UiOps.AutocompleteTriggerChars)>0) then
    begin
      //check that we are not inside comment/string
      if IsCaretInsideCommentOrString(Ed, Caret.PosX, Caret.PosY) then exit;

      FTextCharsTyped:= 0;
      Ed.DoCommand(cmd_AutoComplete);
      exit;
    end;

    //other conditions need word-char
    bWordChar:= IsCharWordInIdentifier(AText[1]);
    if not bWordChar then
    begin
      FTextCharsTyped:= 0;
      exit;
    end;

    SLexerName:= LexerNameAtPos(Ed, Point(Caret.PosX, Caret.PosY));

    //autoshow for HTML
    if UiOps.AutocompleteHtml and (Pos('HTML', SLexerName)>0) then
    begin
      if Ed.Strings.LineSub(Caret.PosY, Caret.PosX-1, 1)='<' then
        Ed.DoCommand(cmd_AutoComplete);
      exit;
    end;

    (*
    //autoshow for CSS
    //seems bad, so commented, CSS must work like other lexers with AutoshowCharCount
    if UiOps.AutocompleteCss and (SLexerName='CSS') then
    begin
      if EditorIsAutocompleteCssPosition(Ed, Caret.PosX-1, Caret.PosY) then
        Ed.DoCommand(cmd_AutoComplete);
      exit;
    end;
    *)

    //autoshow for others, when typed N chars
    if (UiOps.AutocompleteAutoshowCharCount>0) then
    begin
      //ignore if number typed
      bIdentChar:= bWordChar and not IsCharDigit(AText[1]);
      if (FTextCharsTyped=0) and (not bIdentChar) then exit;

      //check that we are not inside comment/string
      if IsCaretInsideCommentOrString(Ed, Caret.PosX, Caret.PosY) then exit;

      Inc(FTextCharsTyped);
      if FTextCharsTyped=UiOps.AutocompleteAutoshowCharCount then
      begin
        FTextCharsTyped:= 0;
        Ed.DoCommand(cmd_AutoComplete);
        exit;
      end;
    end
    else
      FTextCharsTyped:= 0;
  end;

  if Ed.LastCommandChangedLines>0 then
    if Assigned(FOnMsgStatus) then
      FOnMsgStatus(Self, Format(msgStatusChangedLinesCount, [Ed.LastCommandChangedLines]));
end;

procedure TEditorFrame.EditorOnClickDouble(Sender: TObject; var AHandled: boolean);
var
  Str: string;
{'fraction_non_alphanumeric': 0.09228701273763042, 'fraction_numerical': 0.007591371695577034, 'mean_word_length': 4.0589987695097465, 'pattern_counts': {'":': 0, '<': 36, '<?xml version=': 0, '>': 58, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3170356', 'n_tokens_mistral': 27427, 'n_tokens_neox': 26083, 'n_words': 6215}