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

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

The Original Code is: SynHighlighterGeneral.pas, released 2000-04-07.
The Original Code is based on the mwGeneralSyn.pas file from the
mwEdit component suite by Martin Waldenburg and other developers, the Initial
Author of this file is Martin Waldenburg.
Portions written by Martin Waldenburg are copyright 1999 Martin Waldenburg.
All Rights Reserved.

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

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

$Id: SynHighlighterGeneral.pas,v 1.3 2000/11/08 22:09:59 mghie Exp $

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

{$I synedit.inc}

interface

uses
  Classes, SysUtils, math, LCLProc, SynEditMiscClasses, LazSynEditText, SynEditPointClasses,
  SynEditKeyCmds, SynHighlighterPas, SynEditHighlighterFoldBase, SynRegExpr;

type

  TSynCustomBeautifier = class;

  // Callback for indent
  TSynBeautifierSetIndentProc =
    procedure(
      (* LinePos:
           1-based, the line that should be changed *)
      LinePos: Integer;
      (* Indent:
           New indent in spaces (Logical = Physical *)
      Indent: Integer;
      (* RelativeToLinePos:
           Indent specifies +/- offset from indent on RTLine (0: for absolute indent) *)
      RelativeToLinePos: Integer = 0;
      (* IndentChars:
           String used to build indent; maybe empty, single char, or string (usually 1 tab or 1 space)
           The String will be repeated and cut as needed, then filled with spaces at the end
         * NOTE: If this is specified the TSynBeautifierIndentType is ignored
      *)
      IndentChars: String = '';
      (* IndentCharsFromLinePos:
           Use tab/space mix from this Line for indent (if specified > 0)
           "IndentChars" will only be used, if the found tab/space mix is to short
         * NOTE: If this is specified the TSynBeautifierIndentType is ignored
      *)
      IndentCharsFromLinePos: Integer = 0
    ) of object;

  // Event triggered if Lines may needs Indend
  TSynBeautifierGetIndentEvent =
    function(
      Sender: TObject;                       // the beautifier
      Editor: TObject;                       // the synedit
      LogCaret, OldLogCaret: TPoint;         // Caret after and before the edit action
      FirstLinePos, LastLinePos: Integer;    // Changed lines. this can include lines outside the range of OldLogCaret to LogCaret
      Reason: TSynEditorCommand;             // what caused the event
      SetIndentProc: TSynBeautifierSetIndentProc
     ): boolean of object;


  { TSynCustomBeautifier }

  TSynCustomBeautifier = class(TComponent)
  private
    FAutoIndent: Boolean;
    FOnGetDesiredIndent: TSynBeautifierGetIndentEvent;
    FCurrentEditor: TSynEditBase; // For callback / applyIndent
    FCurrentLines: TSynEditStrings;
  protected
    procedure DoBeforeCommand(const ACaret: TSynEditCaret;
                              var Command: TSynEditorCommand); virtual; abstract;
    procedure DoAfterCommand(const ACaret: TSynEditCaret;
                             var Command: TSynEditorCommand;
                             StartLinePos, EndLinePos: Integer); virtual; abstract;
    property CurrentEditor: TSynEditBase read FCurrentEditor;
    property CurrentLines: TSynEditStrings read FCurrentLines;
  public
    procedure Assign(Src: TPersistent); override;
    function  GetCopy: TSynCustomBeautifier;
    procedure BeforeCommand(const Editor: TSynEditBase; const Lines: TSynEditStrings;
                            const ACaret: TSynEditCaret; var Command: TSynEditorCommand;
                            InitialCmd: TSynEditorCommand);
    procedure AfterCommand(const Editor: TSynEditBase; const Lines: TSynEditStrings;
                           const ACaret: TSynEditCaret; var Command: TSynEditorCommand;
                           InitialCmd: TSynEditorCommand; StartLinePos, EndLinePos: Integer);
    // GetDesiredIndentForLine: Returns the 1-based Physical x pos
    function GetDesiredIndentForLine
             (Editor: TSynEditBase; const Lines: TSynEditStrings;
              const ACaret: TSynEditCaret): Integer; virtual; abstract;
    function GetDesiredIndentForLine
             (Editor: TSynEditBase; const Lines: TSynEditStrings;
              const ACaret: TSynEditCaret; out ReplaceIndent: Boolean;
              out DesiredIndent: String): Integer; virtual; abstract;
    property OnGetDesiredIndent: TSynBeautifierGetIndentEvent
      read FOnGetDesiredIndent write FOnGetDesiredIndent;
    property AutoIndent: Boolean read FAutoIndent write FAutoIndent;
  end;

  TSynCustomBeautifierClass = class of TSynCustomBeautifier;
  TSynBeautifierIndentType = (sbitSpace, sbitCopySpaceTab, sbitPositionCaret);

  { TSynBeautifier }

  TSynBeautifier = class(TSynCustomBeautifier)
  private
    FIndentType: TSynBeautifierIndentType;
  protected
    FLogicalIndentLen: Integer;

    function GetLine(AnIndex: Integer): String; virtual;
    procedure GetIndentInfo(const LinePos: Integer;
                            out ACharMix: String; out AnIndent: Integer;
                            AStopScanAtLine: Integer = 0);
    // requiring FCurrentEditor, FCurrentLines
    procedure DoBeforeCommand(const ACaret: TSynEditCaret;
                              var Command: TSynEditorCommand); override;
    procedure DoAfterCommand(const ACaret: TSynEditCaret;
                             var Command: TSynEditorCommand;
                             StartLinePos, EndLinePos: Integer); override;
    function GetIndent(const LinePos: Integer; out BasedOnLine: Integer;
                       AStopScanAtLine: Integer = 0): Integer;
    function AdjustCharMix(DesiredIndent: Integer; CharMix, AppendMix: String): String;
    function GetCharMix(const LinePos, Indent: Integer;
                        var IndentCharsFromLinePos: Integer = 0): String;
    procedure ApplyIndent(LinePos: Integer; Indent: Integer;
                          RelativeToLinePos: Integer = 0; IndentChars: String = '';
                          IndentCharsFromLinePos: Integer = 0);
    function UnIndentLine(const ACaret: TSynEditCaret; out CaretNewX: Integer): Boolean;
  public
    procedure Assign(Src: TPersistent); override;
    // Retruns a 0-based position (even 0-based physical)
    function GetIndentForLine(Editor: TSynEditBase; const Line: string;
                        Physical: boolean): Integer;
    function GetDesiredIndentForLine
             (Editor: TSynEditBase; const Lines: TSynEditStrings;
              const ACaret: TSynEditCaret): Integer; override;
    function GetDesiredIndentForLine
             (Editor: TSynEditBase; const Lines: TSynEditStrings;
              const ACaret: TSynEditCaret; out ReplaceIndent: Boolean;
              out DesiredIndent: String): Integer; override;
  published
    property IndentType: TSynBeautifierIndentType read FIndentType write FIndentType;
  end;

  TSynCommentContineMode = (
      sccNoPrefix,      // May still do indent, if matched
      sccPrefixAlways,  // If the pattern did not match all will be done, except the indent AFTER the prefix (can not be detected)
      sccPrefixMatch
      //sccPrefixMatchFirst
      //sccPrefixMatchPrev
      //sccPrefixMatchPrevTwo           // last 2 lines match
      //sccPrefixMatchPrevTwoExact      // last 2 lines match and same result for prefix
    );

  TSynCommentMatchLine = (
      sclMatchFirst, // Match the first line of the comment to get substitutes for Prefix ($1)
      sclMatchPrev   // Match the previous line of the comment to get substitutes for Prefix ($1)
      //sclMatchNestedFirst // For nested comments, first line of this nest.
    );

  TSynCommentMatchMode = (
    // Which parts of the first line to match sclMatchFirst or sclMatchPrev (if prev = first)
      scmMatchAfterOpening, // will not include (*,{,//. The ^ will match the first char after
      scmMatchOpening,      // will include (*,{,//. The ^ will match the ({/
      scmMatchWholeLine,    // Match the entire line
      scmMatchAtAsterisk    // AnsiComment only, will match the * of (*, but not the (
    );

  TSynCommentIndentFlag = (
    // * For Matching lines (FCommentMode)
      // By default indent is the same as for none comment lines (none overrides sciAlignOpen)
      sciNone,      // Does not Indent comment lines (Prefix may contain a fixed indent)
      sciAlignOpen, // Indent to real opening pos on first line, if comment does not start at BOL "Foo(); (*"
                    // Will force every new line back to Align.
                    // To align only once, use IndentFirstLineExtra=MaxInt

      // sciAdd...: if (only if) previous line had started with the opening token "(*" or "{".
      //            or if sciAlignOpen is set
      //            This is not done for "//", as Comment Extension will add a new "//"
      //            But Applies to sciNone
      sciAddTokenLen,        // add 1 or 2 spaces to indent (for the length of the token)
                             // in case of scmMatchAtAsterisk, 1 space is added. ("(" only)
      sciAddPastTokenIndent, // Adds any indent found past the opening token  "(*", "{" or "//".
                             // For "//" this is added after the nem "//", but before the prefix.


      sciMatchOnlyTokenLen,        // Apply the Above only if first line matches. (Only if sciAddTokenLen is specified)
      sciMatchOnlyPastTokenIndent,
      sciAlignOnlyTokenLen,        // Apply the Above only if sciAlignOpen was used (include via max)
      sciAlignOnlyPastTokenIndent,

      // flag to ignore spaces, that are matched.
      // flag to be smart, if not matched

      sciApplyIndentForNoMatch  // Apply above rules For NONE Matching lines (FCommentMode),
                                // includes FIndentFirstLineExtra
    );
  TSynCommentIndentFlags = set of TSynCommentIndentFlag;

  TSynCommentExtendMode = (
      sceNever,                // Never Extend
      sceAlways,               // Always
      sceSplitLine,            // If the line was split (caret was not at EOL, when enter was pressed
      sceMatching,             // If the line matched (even if sccPrefixAlways or sccNoPrefix
      sceMatchingSplitLine
    );

  TSynCommentType = (sctAnsi, sctBor, sctSlash);

    // end mode
  { TSynBeautifierPascal }

  TSynBeautifierPascal = class(TSynBeautifier)
  private
    FIndentMode:           Array [TSynCommentType] of TSynCommentIndentFlags;
    FIndentFirstLineExtra: Array [TSynCommentType] of String;
    FIndentFirstLineMax:   Array [TSynCommentType] of Integer;

    FCommentMode:          Array [TSynCommentType] of TSynCommentContineMode;
    FMatchMode:            Array [TSynCommentType] of TSynCommentMatchMode;
    FMatchLine:            Array [TSynCommentType] of TSynCommentMatchLine;
    FMatch:                Array [TSynCommentType] of String;
    FPrefix:               Array [TSynCommentType] of String;
    FCommentIndent:        Array [TSynCommentType] of TSynBeautifierIndentType;

    FEolMatch:             Array [TSynCommentType] of String;
    FEolMode:              Array [TSynCommentType] of TSynCommentContineMode;
    FEolPostfix:           Array [TSynCommentType] of String;
    FEolSkipLongerLine:  Array [TSynCommentType] of Boolean;

    FExtendSlashCommentMode: TSynCommentExtendMode;

  private
    FPasHighlighter: TSynPasSyn;

    FCaretAtEOL: Boolean;
    FWorkLine: Integer; // 1 based
    FWorkFoldType: TSynCommentType;
    FGetLineAfterComment: Boolean;
    FRegExprEngine: TRegExpr;

    FCacheFoldEndLvlIdx,         FCacheFoldEndLvl: Integer;
    FCacheCommentLvlIdx,         FCacheCommentLvl: Integer;
    FCacheFoldTypeForIdx: Integer;
                                 FCacheFoldType: TPascalCodeFoldBlockType;
    FCacheFirstLineForIdx,       FCacheFirstLine: Integer;
    FCacheSlashColumnForIdx,     FCacheSlashColumn: Integer;
    FCacheCommentStartColForIdx, FCacheCommentStartCol: Integer;

    FCacheWorkFoldEndLvl: Integer;
    FCacheWorkCommentLvl: Integer;
    FCacheWorkFoldType: TPascalCodeFoldBlockType;
    FCacheWorkFirstLine: Integer;
    FCacheWorkSlashCol: Integer;
    FCacheWorkCommentStartCol: Integer;

  protected
    function  IsPasHighlighter: Boolean;
    procedure InitCache;
    procedure InitPasHighlighter;
    // Generic Helpers
    function  GetFoldEndLevelForIdx(AIndex: Integer): Integer;
    function  GetFoldCommentLevelForIdx(AIndex: Integer): Integer;
    function  GetFoldTypeAtEndOfLineForIdx(AIndex: Integer): TPascalCodeFoldBlockType;
    function  GetFirstCommentLineForIdx(AIndex: Integer): Integer; // Result is 1 based
    function  GetSlashStartColumnForIdx(AIndex: Integer): Integer;
    function  GetCommentStartColForIdx(AIndex: Integer): Integer; // Returns Column (logic) for GetFirstCommentLineForIdx(AIndex)
    // Values based on FWorkLine
    function  GetFoldEndLevel: Integer;
    function  GetFoldCommentLevel: Integer;
    function  GetFoldTypeAtEndOfLine: TPascalCodeFoldBlockType;
    function  GetFirstCommentLine: Integer; // Result is 1 based
    function  GetSlashStartColumn: Integer; // Acts on FWorkLine-1
    function  GetCommentStartCol: Integer;  // Acts on GetFirstCommentLineForIdx(FWorkLine) / Logical Resulc

    function  GetMatchStartColForIdx(AIndex: Integer): Integer; // Res=1-based
    function  GetMatchStartPos(AIndex: Integer = -1; AForceFirst: Boolean = False): TPoint; // Res=1-based / AIndex-1 is only used for sclMatchPrev

  protected
    function GetLine(AnIndex: Integer): String; override;

    procedure DoBeforeCommand(const ACaret: TSynEditCaret;
                              var Command: TSynEditorCommand); override;
    procedure DoAfterCommand(const ACaret: TSynEditCaret;
                             var Command: TSynEditorCommand;
                             StartLinePos, EndLinePos: Integer); override;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure Assign(Src: TPersistent); override;
    // Retruns a 0-based position (even 0-based physical)
  published
    // *** coments with (* *)

    (* AnsiIndentFirstLineMax:
       * For comments that do NOT start at the BOL in their opening line "  Foo; ( * comment":
         if AnsiIndentFirstLineMax is is set:
         - If the comment starts before or at the "Max-Column, then sciAlignOpen is always used.
         - If the comment starts after max column, then the specified mode is used.
           If the specified mode is sciAlignOpen it will be cut at Max
       * For comments that start at BOL in their opening line "  ( * comment":
         This is ignored


       AnsiIndentFirstLineExtra:
         For comments that do NOT start at the BOL, an extra indent is added
         on the 2nd line (except if sciAlignOpen is used (in Flags, or via A.I.FirstLineMax))
    *)
    property AnsiIndentMode: TSynCommentIndentFlags read FIndentMode[sctAnsi]
                                                   write FIndentMode[sctAnsi];
    property AnsiIndentFirstLineMax:   Integer     read FIndentFirstLineMax[sctAnsi]
                                                   write FIndentFirstLineMax[sctAnsi];
    property AnsiIndentFirstLineExtra: String      read FIndentFirstLineExtra[sctAnsi]
                                                   write FIndentFirstLineExtra[sctAnsi];

    (* match should start with ^, and leading spaces should not be in ()  "^\s?(\*\*?\*?)"
       prefix can refer to $1 and should have spaces to indent after the match "$1 "
    *)
    property AnsiCommentMode: TSynCommentContineMode read FCommentMode[sctAnsi]
                                                     write FCommentMode[sctAnsi];
    property AnsiMatch:       String                 read FMatch[sctAnsi]
                                                     write FMatch[sctAnsi];
    property AnsiPrefix :     String                 read FPrefix[sctAnsi]
                              write FPrefix[sctAnsi];
    property AnsiMatchMode:   TSynCommentMatchMode   read FMatchMode[sctAnsi]
                                                     write FMatchMode[sctAnsi];
    property AnsiMatchLine:   TSynCommentMatchLine   read FMatchLine[sctAnsi]
                                                     write FMatchLine[sctAnsi];
    property AnsiCommentIndent: TSynBeautifierIndentType read FCommentIndent[sctAnsi]
                                                         write FCommentIndent[sctAnsi];

    // Add postfix at EOL
    property AnsiEolMode: TSynCommentContineMode read FEolMode[sctAnsi]
                                                 write FEolMode[sctAnsi];
    property AnsiEolPostfix: String              read FEolPostfix[sctAnsi]
                                                 write FEolPostfix[sctAnsi];
    property AnsiEolMatch:   String              read FEolMatch[sctAnsi]
                                                 write FEolMatch[sctAnsi];
    property AnsiEolSkipLongerLine: Boolean      read FEolSkipLongerLine[sctAnsi]
                                                 write FEolSkipLongerLine[sctAnsi];

    // *** coments with { }

    property BorIndentMode: TSynCommentIndentFlags read FIndentMode[sctBor]
                                                  write FIndentMode[sctBor];
    property BorIndentFirstLineMax:   Integer     read FIndentFirstLineMax[sctBor]
                                                  write FIndentFirstLineMax[sctBor];
    property BorIndentFirstLineExtra: String      read FIndentFirstLineExtra[sctBor]
                                                  write FIndentFirstLineExtra[sctBor];

    property BorCommentMode: TSynCommentContineMode read FCommentMode[sctBor]
                                                    write FCommentMode[sctBor];
    property BorMatch:       String                 read FMatch[sctBor]
                                                    write FMatch[sctBor];
    property BorPrefix :     String                 read FPrefix[sctBor]
                             write FPrefix[sctBor];
    property BorMatchMode:   TSynCommentMatchMode   read FMatchMode[sctBor]
                                                    write FMatchMode[sctBor];
    property BorMatchLine:   TSynCommentMatchLine   read FMatchLine[sctBor]
                                                    write FMatchLine[sctBor];
    property BorCommentIndent: TSynBeautifierIndentType read FCommentIndent[sctBor]
                                                         write FCommentIndent[sctBor];

    property BorEolMode: TSynCommentContineMode read FEolMode[sctBor]
                                                write FEolMode[sctBor];
    property BorEolPostfix : String             read FEolPostfix[sctBor]
                             write FEolPostfix[sctBor];
    property BorEolMatch:    String             read FEolMatch[sctBor]
                                                write FEolMatch[sctBor];
    property BorEolSkipLongerLine: Boolean      read FEolSkipLongerLine[sctBor]
                                                write FEolSkipLongerLine[sctBor];

    // *** coments with //
    // Continue only, if Extended

    property ExtendSlashCommentMode: TSynCommentExtendMode read FExtendSlashCommentMode
                                                           write FExtendSlashCommentMode;

    property SlashIndentMode: TSynCommentIndentFlags read FIndentMode[sctSlash]
                                                    write FIndentMode[sctSlash];
    property SlashIndentFirstLineMax:   Integer     read FIndentFirstLineMax[sctSlash]
                                                    write FIndentFirstLineMax[sctSlash];
    property SlashIndentFirstLineExtra: String      read FIndentFirstLineExtra[sctSlash]
                                                    write FIndentFirstLineExtra[sctSlash];

    property SlashCommentMode: TSynCommentContineMode read FCommentMode[sctSlash]
                                                      write FCommentMode[sctSlash];
    property SlashMatch:     String                   read FMatch[sctSlash]
                                                      write FMatch[sctSlash];
    property SlashPrefix :   String                   read FPrefix[sctSlash]
                             write FPrefix[sctSlash];
    property SlashMatchMode: TSynCommentMatchMode     read FMatchMode[sctSlash]
                                                      write FMatchMode[sctSlash];
    property SlashMatchLine:   TSynCommentMatchLine   read FMatchLine[sctSlash]
                                                      write FMatchLine[sctSlash];
    property SlashCommentIndent: TSynBeautifierIndentType read FCommentIndent[sctSlash]
                                                         write FCommentIndent[sctSlash];

    property SlashEolMode: TSynCommentContineMode read FEolMode[sctSlash]
                                                  write FEolMode[sctSlash];
    property SlashEolPostfix : String             read FEolPostfix[sctSlash]
                               write FEolPostfix[sctSlash];
    property SlashEolMatch:    String             read FEolMatch[sctSlash]
                                                  write FEolMatch[sctSlash];
    property SlashEolSkipLongerLine: Boolean      read FEolSkipLongerLine[sctSlash]
                                                  write FEolSkipLongerLine[sctSlash];
  end;

function dbgs(ACommentType: TSynCommentType): String; overload;
function dbgs(AContinueMode: TSynCommentContineMode): String; overload;
function dbgs(AMatchLine: TSynCommentMatchLine): String; overload;
function dbgs(AMatchMode: TSynCommentMatchMode): String; overload;
function dbgs(AIndentFlag: TSynCommentIndentFlag): String; overload;
function dbgs(AIndentFlags: TSynCommentIndentFlags): String; overload;
function dbgs(AExtendMode: TSynCommentExtendMode): String; overload;

implementation
uses SynEdit;

function ToIdx(APos: Integer): Integer; inline;
begin
  Result := APos - 1;
end;

function ToPos(AIdx: Integer): Integer; inline;
begin
  Result := AIdx + 1;
end;

function dbgs(ACommentType: TSynCommentType): String;
begin
  Result := ''; WriteStr(Result, ACommentType);
end;

function dbgs(AContinueMode: TSynCommentContineMode): String;
begin
  Result := ''; WriteStr(Result, AContinueMode);
end;

function dbgs(AMatchLine: TSynCommentMatchLine): String;
begin
  Result := ''; WriteStr(Result, AMatchLine);
end;

function dbgs(AMatchMode: TSynCommentMatchMode): String;
begin
  Result := ''; WriteStr(Result, AMatchMode);
end;

function dbgs(AIndentFlag: TSynCommentIndentFlag): String;
begin
  Result := ''; WriteStr(Result, AIndentFlag);
end;

function dbgs(AIndentFlags: TSynCommentIndentFlags): String;
var
  i: TSynCommentIndentFlag;
begin
  Result := '';
  for i := low(TSynCommentIndentFlag) to high(TSynCommentIndentFlag) do
    if i in AIndentFlags then
      if Result = ''
      then Result := dbgs(i)
      else Result := Result + ',' + dbgs(i);
  if Result <> '' then
    Result := '[' + Result + ']';
end;

function dbgs(AExtendMode: TSynCommentExtendMode): String;
begin
  Result := ''; WriteStr(Result, AExtendMode);
end;


{ TSynBeautifierPascal }

function TSynBeautifierPascal.IsPasHighlighter: Boolean;
begin
  Result := (TSynEdit(FCurrentEditor).Highlighter <> nil) and
            (TSynEdit(FCurrentEditor).Highlighter is TSynPasSyn);
end;

procedure TSynBeautifierPascal.InitCache;
begin
  FCacheFoldEndLvlIdx         := -1;
  FCacheCommentLvlIdx         := -1;
  FCacheFoldTypeForIdx        := -1;
  FCacheFirstLineForIdx       := -1;
  FCacheSlashColumnForIdx     := -1;
  FCacheCommentStartColForIdx := -1;

  FCacheWorkFoldEndLvl         := -2;
  FCacheWorkCommentLvl         := -2;
  FCacheWorkFoldType           := cfbtIfThen; // dummy for not yet cached
  FCacheWorkFirstLine          := -2;
  FCacheWorkSlashCol           := -2;
  FCacheWorkCommentStartCol := -2;

end;

procedure TSynBeautifierPascal.InitPasHighlighter;
begin
  FPasHighlighter := TSynPasSyn(TSynEdit(FCurrentEditor).Highlighter);
  FPasHighlighter.CurrentLines := FCurrentLines;
  FPasHighlighter.ScanRanges;
end;

function TSynBeautifierPascal.GetFoldEndLevelForIdx(AIndex: Integer): Integer;
begin
  Result := FCacheFoldEndLvl;
  if AIndex = FCacheFoldEndLvlIdx then
    exit;
  FCacheFoldEndLvlIdx := AIndex;
  FCacheFoldEndLvl := FPasHighlighter.FoldBlockEndLevel(AIndex, FOLDGROUP_PASCAL, [sfbIncludeDisabled]);
  Result := FCacheFoldEndLvl;
end;

function TSynBeautifierPascal.GetFoldCommentLevelForIdx(AIndex: Integer): Integer;
var
  tmp: Pointer;
  Block: TPascalCodeFoldBlockType;
begin
  Result := FCacheCommentLvl;
  if AIndex = FCacheCommentLvlIdx then
    exit;
  FCacheCommentLvlIdx := AIndex;

  FCacheCommentLvl := GetFoldEndLevelForIdx(AIndex) - 1;
  while (FCacheCommentLvl > 0) do begin
    FPasHighlighter.FoldBlockNestedTypes(AIndex , FCacheCommentLvl - 1,
                 tmp, FOLDGROUP_PASCAL, [sfbIncludeDisabled]);
    Block:=TPascalCodeFoldBlockType(PtrUInt(tmp));
    if not (Block in [cfbtAnsiComment, cfbtBorCommand, cfbtSlashComment]) then
      break;
    dec(FCacheCommentLvl);
  end;
  inc(FCacheCommentLvl);
  Result := FCacheCommentLvl;
end;

function TSynBeautifierPascal.GetFoldTypeAtEndOfLineForIdx(AIndex: Integer): TPascalCodeFoldBlockType;
var
  EndLevel: Integer;
  tmp: Pointer;
begin
  // TODO cfbtNestedComment
  Result := FCacheFoldType;
  if AIndex = FCacheFoldTypeForIdx then
    exit;

  FCacheFoldTypeForIdx := AIndex;
  EndLevel := GetFoldEndLevelForIdx(AIndex);
  if (EndLevel > 0) then
  begin
    if FPasHighlighter.FoldBlockNestedTypes(AIndex, EndLevel - 1,
       tmp, FOLDGROUP_PASCAL, [sfbIncludeDisabled])
    then
      FCacheFoldType:=TPascalCodeFoldBlockType(PtrUInt(tmp))
    else
      FCacheFoldType := cfbtNone;
  end;

  while (FCacheFoldType = cfbtNestedComment) and (EndLevel > 1) do begin
    dec(EndLevel);
    if FPasHighlighter.FoldBlockNestedTypes(AIndex, EndLevel - 1,
         tmp, FOLDGROUP_PASCAL, [sfbIncludeDisabled])
    then
      FCacheFoldType:=TPascalCodeFoldBlockType(PtrUInt(tmp))
    else
      FCacheFoldType := cfbtNone;
  end;

  Result := FCacheFoldType;
end;

function TSynBeautifierPascal.GetFirstCommentLineForIdx(AIndex: Integer): Integer;
var
  FoldType: TPascalCodeFoldBlockType;
  ANewIndex, EndLvl, CommentLvl: Integer;
begin
  Result := FCacheFirstLine;
  if AIndex = FCacheFirstLineForIdx then
    exit;

  ANewIndex := AIndex;
  FoldType := GetFoldTypeAtEndOfLineForIdx(ANewIndex);
  EndLvl   := GetFoldEndLevelForIdx(ANewIndex);

  //if (EndLvl = 0) or not(FoldType in [cfbtAnsiComment, cfbtBorCommand, cfbtSlashComment]) and
  //   (AIndex > 0) and (GetSlashStartColumnForIdx(AIndex - 1) > 0)
  //then begin
  //  dec(ANewIndex);
  //  FoldType := GetFoldTypeAtEndOfLineForIdx(ANewIndex);
  //  EndLvl   := GetFoldEndLevelForIdx(ANewIndex);
  //end;

  if (EndLvl = 0) or not(FoldType in [cfbtAnsiComment, cfbtBorCommand, cfbtSlashComment])
  then begin
    Result := ToPos(AIndex) - 1; // 1 based - the line above ANewIndex
    // maybe the line above has a trailing comment
    //if (AIndex <> ANewIndex) and (ANewIndex > 0) and (GetSlashStartColumnForIdx(ANewIndex-1) > 0) then
    //  Result := ToPos(ANewIndex) - 1;
    exit;
  end;

  FCacheFirstLineForIdx := AIndex;
  FCacheFirstLine       := ToPos(ANewIndex) - 1;
  CommentLvl            := GetFoldCommentLevelForIdx(ANewIndex);

  while (FCacheFirstLine > 0) do begin
    if CommentLvl > FPasHighlighter.FoldBlockMinLevel(FCacheFirstLine-1, FOLDGROUP_PASCAL, [sfbIncludeDisabled]) then
      break;
    dec(FCacheFirstLine);
  end;

  if FoldType = cfbtSlashComment then begin
    // maybe the line above has a trailing comment
    if GetSlashStartColumnForIdx(ToIdx(FCacheFirstLine)) > 0 then
      dec(FCacheFirstLine);
  end;

  Result := FCacheFirstLine;
//debugln(['FIRST LINE ', FCacheFirstLine]);
end;

function TSynBeautifierPascal.GetSlashStartColumnForIdx(AIndex: Integer): Integer;
var
  Tk, LastTk: TtkTokenKind;
begin
  Result := FCacheSlashColumn;
  if AIndex = FCacheSlashColumnForIdx then
    exit;

  FCacheSlashColumnForIdx := AIndex;
  FCacheSlashColumn := -1;
  if (FCurrentLines[AIndex] <> '') then begin
    FPasHighlighter.StartAtLineIndex(AIndex);
    Tk := SynHighlighterPas.tkUnknown;
    while not FPasHighlighter.GetEol do begin
      LastTk := Tk;
      Tk := TtkTokenKind(FPasHighlighter.GetTokenKind);
      if (Tk = SynHighlighterPas.tkComment) and (LastTk <> SynHighlighterPas.tkComment) and
         (copy(FPasHighlighter.GetToken,1,2) = '//')
      then begin
        FCacheSlashColumn := FPasHighlighter.GetTokenPos + 1;
        exit;
      end;
      FPasHighlighter.Next;
    end;
  end;
  Result := FCacheSlashColumn;
end;

function TSynBeautifierPascal.GetCommentStartColForIdx(AIndex: Integer): Integer;
var
  nl: TLazSynFoldNodeInfoList;
  i: Integer;
  FoldCommentLvl: Integer;
begin
  Result := FCacheCommentStartCol;
  if AIndex = FCacheCommentStartColForIdx then
    exit;
  FCacheCommentStartColForIdx := AIndex;

  if (GetFoldEndLevelForIdx(AIndex) = 0) or
     not(GetFoldTypeAtEndOfLineForIdx(AIndex) in [cfbtAnsiComment, cfbtBorCommand, cfbtSlashComment])
  then begin
    // must be SlashComment
    FCacheCommentStartCol := GetSlashStartColumnForIdx(AIndex - 1);
    //FCacheCommentStartCol := GetSlashStartColumnForIdx(AIndex);
    //if FCacheCommentStartCol > 0 then begin
    //  i := GetSlashStartColumnForIdx(AIndex - 1);
    //  if i > 0 then
    //    FCacheCommentStartCol := i;
    //end;
    Result := FCacheCommentStartCol;
//debugln(['FIRST COL prev-// ', FCacheCommentStartCol]);
    exit;
  end;

  FCacheCommentStartCol := -1;
  FoldCommentLvl := GetFoldCommentLevelForIdx(AIndex);
  nl := FPasHighlighter.FoldNodeInfo[GetFirstCommentLineForIdx(AIndex) - 1];
  nl.AddReference;
  nl.ClearFilter;
  nl.ActionFilter := [sfaOpen];
  nl.GroupFilter  := FOLDGROUP_PASCAL;
  i := nl.Count - 1;
  while i >= 0 do  begin
    if (not (sfaInvalid in nl[i].FoldAction)) and
       (nl[i].NestLvlEnd = FoldCommentLvl)
    then begin
      FCacheCommentStartCol := nl[i].LogXStart + 1;  // Highlighter pos are 0 based
      break;
    end;
    dec(i);
  end;
  nl.ReleaseReference;
//debugln(['FIRST COL ', FCacheCommentStartCol]);

  Result := FCacheCommentStartCol;
end;

function TSynBeautifierPascal.GetFoldEndLevel: Integer;
begin
  Result := FCacheWorkFoldEndLvl;
  if FCacheWorkFoldEndLvl > -2 then
    exit;
  FCacheWorkFoldEndLvl := GetFoldEndLevelForIdx(FWorkLine-1);
  Result := FCacheWorkFoldEndLvl;
end;

function TSynBeautifierPascal.GetFoldCommentLevel: Integer;
begin
  Result := FCacheWorkCommentLvl;
  if FCacheWorkCommentLvl > -2 then
    exit;
  FCacheWorkCommentLvl := GetFoldCommentLevelForIdx(FWorkLine-1);
  Result := FCacheWorkCommentLvl;
end;

function TSynBeautifierPascal.GetFoldTypeAtEndOfLine: TPascalCodeFoldBlockType;
begin
  Result := FCacheWorkFoldType;
  if FCacheWorkFoldType <> cfbtIfThen then
    exit;
  FCacheWorkFoldType := GetFoldTypeAtEndOfLineForIdx(FWorkLine-1);
  Result := FCacheWorkFoldType;
end;

function TSynBeautifierPascal.GetFirstCommentLine: Integer;
begin
  Result := FCacheWorkFirstLine;
  if FCacheWorkFirstLine > -2 then
    exit;
  FCacheWorkFirstLine := GetFirstCommentLineForIdx(FWorkLine-1);
  Result := FCacheWorkFirstLine;
end;

function TSynBeautifierPascal.GetSlashStartColumn: Integer;
begin
  Result := FCacheWorkSlashCol;
  if FCacheWorkSlashCol > -2 then
    exit;
  FCacheWorkSlashCol := GetSlashStartColumnForIdx(FWorkLine-2);
  Result := FCacheWorkSlashCol;
end;

function TSynBeautifierPascal.GetCommentStartCol: Integer;
begin
  Result := FCacheWorkCommentStartCol;
  if FCacheWorkCommentStartCol > -2 then
    exit;
  FCacheWorkCommentStartCol := GetCommentStartColForIdx(FWorkLine-1);
  Result := FCacheWorkCommentStartCol;
end;

function TSynBeautifierPascal.GetMatchStartColForIdx(AIndex: Integer): Integer;
begin
  if ToPos(AIndex) = GetFirstCommentLine then begin
    // Match on FirstLine
    case FMatchMode[FWorkFoldType] of
      scmMatchAfterOpening: begin
          if FWorkFoldType = sctBor
          then Result := GetCommentStartCol + 1
          else Result := GetCommentStartCol + 2;
        end;
      scmMatchOpening:    Result := GetCommentStartCol;
      scmMatchWholeLine:  Result := 1;
      scmMatchAtAsterisk: Result := GetCommentStartCol + 1;
    end;
  end
  else begin
    // Match on prev, not first
    case FMatchMode[FWorkFoldType] of
      scmMatchAfterOpening, scmMatchOpening, scmMatchAtAsterisk:
          Result := 1 + GetIndentForLine(nil, FCurrentLines[AIndex], False);
      scmMatchWholeLine:
          Result := 1;
    end;
  end;
end;

function TSynBeautifierPascal.GetMatchStartPos(AIndex: Integer; AForceFirst: Boolean): TPoint;
begin
  if AIndex < 0 then
    AIndex := ToIdx(FWorkLine);

  if AForceFirst then
    Result.Y := GetFirstCommentLine
  else
    case FMatchLine[FWorkFoldType] of
      sclMatchFirst: Result.Y := GetFirstCommentLine;
      sclMatchPrev:  Result.Y := ToPos(AIndex)-1; // FWorkLine - 1
    end;

  Result.X := GetMatchStartColForIdx(ToIdx(Result.Y));
end;


function TSynBeautifierPascal.GetLine(AnIndex: Integer): String;
var
  ReplacedPrefix: String;
  Indent, PreFixPos: Integer;
  r: Boolean;
  p: TPoint;
begin
  if not FGetLineAfterComment then begin
    Result := inherited GetLine(AnIndex);
    exit;
  end;

  // Need to cut of existing Prefix to find indent after prefix

  if AnIndex < GetFirstCommentLine-1 then begin
    Result := '';
//debugln(['GETLINE FROM (< First) ', AnIndex,' ''', FCurrentLines[AnIndex], ''' to empty']);
  end;

  // 1) Run the match for this line (match prev, or first)
  //    and see if we can find the replaced prefix in this line
  if AnIndex > GetFirstCommentLine-1 then begin
    p := GetMatchStartPos(AnIndex);
    FRegExprEngine.InputString:= copy(FCurrentLines[ToIdx(p.y)], p.x, MaxInt);
    FRegExprEngine.Expression := FMatch[FWorkFoldType];
    if FRegExprEngine.ExecPos(1) then begin
      ReplacedPrefix := FRegExprEngine.Substitute(FPrefix[FWorkFoldType]);
      while (ReplacedPrefix <> '') and (ReplacedPrefix[1] in [#9, ' ']) do
        delete(ReplacedPrefix, 1, 1);
      while (ReplacedPrefix <> '') and (ReplacedPrefix[length(ReplacedPrefix)] in [#9, ' ']) do
        delete(ReplacedPrefix, length(ReplacedPrefix), 1);
      if ReplacedPrefix <> '' then begin
        Result := FCurrentLines[AnIndex];
        PreFixPos := pos(ReplacedPrefix, Result);
        if (PreFixPos > 0) and (PreFixPos <= GetMatchStartColForIdx(AnIndex)) then begin
          delete(Result, 1, PreFixPos - 1 + Length(ReplacedPrefix));
//debugln(['GETLINE FROM (1) ', AnIndex,' ''', FCurrentLines[AnIndex], ''' to ''', Result, '''']);
          exit;
        end;
      end;
    end;
  end;

  // 2) See, if the current-1 line can be matched
  r := False;
  Result := FCurrentLines[AnIndex];
  Indent := GetMatchStartColForIdx(AnIndex);
  FRegExprEngine.InputString:= copy(FCurrentLines[AnIndex], Indent, MaxInt);
  FRegExprEngine.Expression := FMatch[FWorkFoldType];
  r := FRegExprEngine.ExecPos(1);
  if (not r) and (Indent > 1) and
     ((p.y <> GetFirstCommentLine) or (FMatchMode[FWorkFoldType] = scmMatchWholeLine))
  then begin
    // try whole line
// TODO: only if not first, or if setting
    FRegExprEngine.InputString := FCurrentLines[AnIndex];
    r := FRegExprEngine.ExecPos(1);
    if r then
      Indent := 1;
  end;
  if (r) then begin
    Indent := Indent + FRegExprEngine.MatchPos[0] - 1 + FRegExprEngine.MatchLen[0];
    Result := FCurrentLines[AnIndex];
    if (Indent <= Length(Result)) then
      while (Indent > 1) and (Result[Indent] in [#9, ' ']) do
        dec(Indent);
    inc(Indent);
    if Indent > 0 then begin
      Result := copy(Result, Indent, MaxInt);
//debugln(['GETLINE FROM (2) ', AnIndex,' ''', FCurrentLines[AnIndex], ''' to ''', Result, '''']);
      exit;
    end;
  end;

  // 3) maybe try currest replace, if different from 1?

  // Nothing found
  Result := '';
//debugln(['GETLINE FROM (X) ', AnIndex,' ''', FCurrentLines[AnIndex], ''' to empty']);
end;

procedure TSynBeautifierPascal.DoBeforeCommand(const ACaret: TSynEditCaret;
  var Command: TSynEditorCommand);
begin
  FCaretAtEOL := ACaret.BytePos > Length(FCurrentLines[ToIdx(ACaret.LinePos)]);
  FGetLineAfterComment := False;
  inherited DoBeforeCommand(ACaret, Command);
end;

procedure TSynBeautifierPascal.DoAfterCommand(const ACaret: TSynEditCaret;
  var Command: TSynEditorCommand; StartLinePos, EndLinePos: Integer);

var
  WorkLine, PrevLineShlasCol: Integer;
  ReplacedPrefix: String; // Each run matches only one Type
  MatchResultIntern, MatchedBOLIntern: Array [Boolean] of Boolean;  // Each run matches only one Type

  function CheckMatch(AType: TSynCommentType; AFailOnNoPattern: Boolean = False;
    AForceFirst: Boolean = False): Boolean;
  var
    p: TPoint;
  begin
    if (FMatch[AType] = '') and AFailOnNoPattern then begin
      Result := False;
      exit;
    end;

    if MatchedBOLIntern[AForceFirst] then begin
      Result := MatchResultIntern[AForceFirst];
      exit;
    end;

    p := GetMatchStartPos(-1, AForceFirst);

    FRegExprEngine.InputString:= copy(FCurrentLines[ToIdx(p.y)], p.x, MaxInt);
    FRegExprEngine.Expression := FMatch[AType];
    if not FRegExprEngine.ExecPos(1) then begin
      ReplacedPrefix := FRegExprEngine.Substitute(FPrefix[AType]);
      MatchedBOLIntern[AForceFirst] := True;
      MatchResultIntern[AForceFirst] := False;
      Result := MatchResultIntern[AForceFirst];
      exit;
    end;

    ReplacedPrefix := FRegExprEngine.Substitute(FPrefix[AType]);
    MatchedBOLIntern[AForceFirst] := True;
    MatchResultIntern[AForceFirst] := True;
    Result := MatchResultIntern[AForceFirst];

  end;

  function IsFoldTypeEnabled(AType: TSynCommentType): Boolean;
  begin
  Result := (  ( (AType <> sctSlash) or
               ( ((not FCaretAtEOL) and (FExtendSlashCommentMode <> sceNever)) or
                 ((FCaretAtEOL)     and not(FExtendSlashCommentMode in [sceNever, sceSplitLine, sceMatchingSplitLine]))
               )
             ) and
             ( (FIndentMode[AType] <> []) or
               (FCommentMode[AType] <> sccNoPrefix) or
               (FEolMode[AType] <> sccNoPrefix)
            ))
  end;

var
  Indent, dummy: Integer;
  s: String;
  FoldTyp: TSynCommentType;
  AnyEnabled: Boolean;
  ExtendSlash, BeSmart, Matching: Boolean;
  PreviousIsFirst, IsAtBOL, DidAlignOpen: Boolean;

  IndentTypeBackup: TSynBeautifierIndentType;

begin
  if (EndLinePos < 1) or
     ((Command <> ecLineBreak) and (Command <> ecInsertLine)) or
     (not IsPasHighlighter)
  then begin
    inherited DoAfterCommand(ACaret, Command, StartLinePos, EndLinePos);
    exit;
  end;

  AnyEnabled := False;
  for FoldTyp := low(TSynCommentType) to high(TSynCommentType) do
    AnyEnabled := AnyEnabled or IsFoldTypeEnabled(FoldTyp);
  if not AnyEnabled then begin
    inherited DoAfterCommand(ACaret, Command, StartLinePos, EndLinePos);
    exit;
  end;

  InitCache;
  InitPasHighlighter;
  FGetLineAfterComment := False;
  MatchedBOLIntern[True] := False;
  MatchedBOLIntern[False] := False;
  PrevLineShlasCol := -1;
  dummy := 0;

  if (Command = ecLineBreak)
  then WorkLine := ACaret.LinePos
  else WorkLine := ACaret.LinePos + 1;
  FWorkLine := WorkLine;


  // Find Foldtype
  case GetFoldTypeAtEndOfLine of
    cfbtAnsiComment:  FoldTyp := sctAnsi;
    cfbtBorCommand:   FoldTyp := sctBor;
    cfbtSlashComment: FoldTyp := sctSlash;
    else
      begin
        if (FCurrentLines[ToIdx(WorkLine)-1] <> '') and
           (FExtendSlashCommentMode <> sceNever) and
           ( (not FCaretAtEOL) or not(FExtendSlashCommentMode in [sceSplitLine, sceMatchingSplitLine]) )
        then begin
          PrevLineShlasCol := GetSlashStartColumn;
          if PrevLineShlasCol > 0 then
            FoldTyp := sctSlash;
        end;
        if PrevLineShlasCol < 0 then begin
          inherited DoAfterCommand(ACaret, Command, StartLinePos, EndLinePos);
          exit;
        end;
      end;
  end;

  if not IsFoldTypeEnabled(FoldTyp) then begin
    inherited DoAfterCommand(ACaret, Command, StartLinePos, EndLinePos);
    exit;
  end;

  FWorkFoldType := FoldTyp;

  // Check if we need extend
  ExtendSlash := False;
  if (FoldTyp = sctSlash) and (ACaret.OldLineBytePos.x > GetSlashStartColumn+2) then begin
    // Check if extension is needed
    case FExtendSlashCommentMode of
      sceAlways:    ExtendSlash := True;
      sceSplitLine: ExtendSlash := not FCaretAtEOL;
      sceMatching:  ExtendSlash := CheckMatch(FoldTyp);
      sceMatchingSplitLine: ExtendSlash := CheckMatch(FoldTyp) and (not FCaretAtEOL);
    end;

    if not ExtendSlash then begin
      inherited DoAfterCommand(ACaret, Command, StartLinePos, EndLinePos);
      exit;
    end;
  end;

  // Indent
  Matching := CheckMatch(FoldTyp);
  PreviousIsFirst := (GetFirstCommentLine = WorkLine -1);
  DidAlignOpen := False;
  IsAtBOL := True;
  if PreviousIsFirst then
    IsAtBOL := GetCommentStartCol = 1 + GetIndentForLine(nil, FCurrentLines[ToIdx(GetFirstCommentLine)], False);


  // Aply indent before prefix
  if Matching or (FCommentMode[FoldTyp] = sccPrefixAlways) or (sciApplyIndentForNoMatch in FIndentMode[FoldTyp])
  then begin
    IndentTypeBackup := IndentType;
    try
      if IndentType = sbitPositionCaret then
        IndentType := sbitSpace;

      if PreviousIsFirst and not IsAtBOL and
         (FIndentFirstLineMax[FoldTyp] > 0) and
         ( (FIndentFirstLineMax[FoldTyp] + 1 >= GetCommentStartCol) or
           (FIndentMode[FoldTyp] * [sciNone, sciAlignOpen] = [sciAlignOpen])
         )
      then begin
        // Use sciAlignOpen
        Indent := Min(
          FCurrentLines.LogicalToPhysicalCol(FCurrentLines[ToIdx(GetFirstCommentLine)], ToIdx(GetFirstCommentLine), GetCommentStartCol-1),
          FIndentFirstLineMax[FoldTyp]);
        s := GetCharMix(WorkLine, Indent, dummy);
        FLogicalIndentLen := length(s);
        FCurrentLines.EditInsert(1, WorkLine, s);
        DidAlignOpen := True;
      end
      else
      if (FIndentMode[FoldTyp] * [sciNone, sciAlignOpen] = [sciAlignOpen]) and
         (GetCommentStartCol > 0)
      then begin
        Indent := FCurrentLines.LogicalToPhysicalCol(FCurrentLines[ToIdx(GetFirstCommentLine)], ToIdx(GetFirstCommentLine), GetCommentStartCol-1);
        if FIndentFirstLineMax[FoldTyp] > 0
        then Indent := Min(Indent, FIndentFirstLineMax[FoldTyp]);
        s := GetCharMix(WorkLine, Indent, dummy);
        FLogicalIndentLen := length(s);
        FCurrentLines.EditInsert(1, WorkLine, s);
        DidAlignOpen := True;
      end
      else
      if (sciNone in FIndentMode[FoldTyp]) then begin
        // No indent
      end
      else
      begin
        inherited DoAfterCommand(ACaret, Command, StartLinePos, EndLinePos);
      end;
    finally
      IndentType := IndentTypeBackup;
    end;

    // AnsiIndentFirstLineExtra
    if PreviousIsFirst and (not IsAtBOL) and (not DidAlignOpen) then begin
      FCurrentLines.EditInsert(1 + FLogicalIndentLen, WorkLine, FIndentFirstLineExtra[FoldTyp]);
      FLogicalIndentLen := FLogicalIndentLen + length(FIndentFirstLineExtra[FoldTyp]);
    end;

  end
  else
    inherited DoAfterCommand(ACaret, Command, StartLinePos, EndLinePos);

  Indent := 0; // Extra Indent
  BeSmart := (PreviousIsFirst or (sciAlignOpen in FIndentMode[FoldTyp])) and
             (Matching or ExtendSlash or (sciApplyIndentForNoMatch in FIndentMode[FoldTyp]) or
              (FCommentMode[FoldTyp] = sccPrefixAlways)  );

  // sciAddTokenLen -- Spaces for (* or {
  if BeSmart and
     ( (sciAddTokenLen in FIndentMode[FoldTyp]) and
       ( (not(sciMatchOnlyTokenLen in FIndentMode[FoldTyp])) or CheckMatch(FoldTyp, False, True) ) and
       ( (not(sciAlignOnlyTokenLen in FIndentMode[FoldTyp])) or DidAlignOpen )
     )
  then begin
    case FoldTyp of
      sctAnsi:  if FMatchMode[FoldTyp] = scmMatchAtAsterisk
                then Indent := 1
                else Indent := 2;
      sctBor:   Indent := 1;
      sctSlash: if ExtendSlash
                then Indent := 0 // do the slashes
                else Indent := 2;
    end;
  end;

  // sciAddPastTokenIndent -- Spaces from after (* or { (to go befare prefix e.g " {  * foo")
  if BeSmart and
     ( (sciAddPastTokenIndent in FIndentMode[FoldTyp]) and
       ( (not(sciMatchOnlyPastTokenIndent in FIndentMode[FoldTyp])) or CheckMatch(FoldTyp, False, True) ) and
       ( (not(sciAlignOnlyPastTokenIndent in FIndentMode[FoldTyp])) or DidAlignOpen )
     ) and
     (GetCommentStartCol > 0) // foundStartCol
  then begin
    case FoldTyp of
      // ignores scmMatchAtAsterisk
      sctAnsi:  s := copy(FCurrentLines[ToIdx(GetFirstCommentLine)], GetCommentStartCol+2, MaxInt);
      sctBor:   s := copy(FCurrentLines[ToIdx(GetFirstCommentLine)], GetCommentStartCol+1, MaxInt);
      sctSlash: s := copy(FCurrentLines[ToIdx(GetFirstCommentLine)], GetCommentStartCol+2, MaxInt);
    end;
    Indent := Indent + GetIndentForLine(nil, s, False);
  end;
  // Extend //
  if ExtendSlash then begin
    FCurrentLines.EditInsert(1 + FLogicalIndentLen, WorkLine, '//');
    FLogicalIndentLen := FLogicalIndentLen + 2;
  end;
  if (Indent > 0) then begin
    FCurrentLines.EditInsert(1 + FLogicalIndentLen, WorkLine, StringOfChar(' ', Indent ));
    FLogicalIndentLen := FLogicalIndentLen + Indent;
  end;

  // Apply prefix
  if (FCommentMode[FoldTyp] = sccPrefixAlways) or
     ((FCommentMode[FoldTyp] = sccPrefixMatch) and Matching)
  then begin
    FCurrentLines.EditInsert(1 + FLogicalIndentLen, WorkLine, ReplacedPrefix);
    FLogicalIndentLen := FLogicalIndentLen + length(ReplacedPrefix);

    // Post  prefix indent
    FGetLineAfterComment := True;
    try
      GetIndentInfo(WorkLine, s, Indent, GetFirstCommentLine);
      if s <> '' then begin
        FCurrentLines.EditInsert(1 + FLogicalIndentLen, WorkLine, s);
        FLogicalIndentLen := FLogicalIndentLen + length(s); // logical (Indent is phisical)
      end
      else
        FLogicalIndentLen := FLogicalIndentLen + Indent; // maybe position caret
    finally
      FGetLineAfterComment := False;
    end;
  end;


  if (Command = ecLineBreak) then begin
    ACaret.IncForcePastEOL;
    ACaret.BytePos := 1 + FLogicalIndentLen;
    ACaret.DecForcePastEOL;
  end;

end;

constructor TSynBeautifierPascal.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FRegExprEngine := TRegExpr.Create;
  FRegExprEngine.ModifierI := True;

  FIndentMode[sctAnsi]           := [sciAddTokenLen, sciAddPastTokenIndent];
  FIndentFirstLineExtra[sctAnsi] := '';
  FIndentFirstLineMax[sctAnsi]   := 0;

  FCommentMode[sctAnsi]          := sccPrefixMatch;
  FMatch[sctAnsi]                := '^ ?(\*)';
  FMatchMode[sctAnsi]            := scmMatchAfterOpening;
  FMatchLine[sctAnsi]            := sclMatchPrev;
  FPrefix[sctAnsi]               := '$1';

  FEolMatch[sctAnsi]             := '';
  FEolMode[sctAnsi]              := sccNoPrefix;
  FEolPostfix[sctAnsi]           := '';
  FEolSkipLongerLine[sctAnsi]    := False;


  FIndentMode[sctBor]           := [sciAddTokenLen, sciAddPastTokenIndent];
  FIndentFirstLineExtra[sctBor] := '';
  FIndentFirstLineMax[sctBor]   := 0;

  FCommentMode[sctBor]          := sccPrefixMatch;
  FMatch[sctBor]                := '^ ?(\*)';
  FMatchMode[sctBor]            := scmMatchAfterOpening;
  FMatchLine[sctBor]            := sclMatchPrev;
  FPrefix[sctBor]               := '$1';

  FEolMatch[sctBor]             := '';
  FEolMode[sctBor]              := sccNoPrefix;
  FEolPostfix[sctBor]           := '';
  FEolSkipLongerLine[sctBor]    := False;


  FExtendSlashCommentMode         := sceNever;

  FIndentMode[sctSlash]           := [];
  FIndentFirstLineExtra[sctSlash] := '';
  FIndentFirstLineMax[sctSlash]   := 0;

  FCommentMode[sctSlash]          := sccPrefixMatch;
  FMatch[sctSlash]                := '^ ?(\*)';
  FMatchMode[sctSlash]            := scmMatchAfterOpening;
  FMatchLine[sctSlash]            := sclMatchPrev;
  FPrefix[sctSlash]               := '$1';

  FEolMatch[sctSlash]             := '';
  FEolMode[sctSlash]              := sccNoPrefix;
  FEolPostfix[sctSlash]           := '';
  FEolSkipLongerLine[sctSlash]    := False;

end;

destructor TSynBeautifierPascal.Destroy;
begin
  inherited Destroy;
  FreeAndNil(FRegExprEngine);
end;

procedure TSynBeautifierPascal.Assign(Src: TPersistent);
var
  i: TSynCommentType;
begin
  inherited Assign(Src);
  if not(Src is TSynBeautifierPascal) then exit;

  FExtendSlashCommentMode := TSynBeautifierPascal(Src).FExtendSlashCommentMode;

  for i := low(TSynCommentType) to high(TSynCommentType) do begin
    FIndentMode[i]           := TSynBeautifierPascal(Src).FIndentMode[i];
    FIndentFirstLineExtra[i] := TSynBeautifierPascal(Src).FIndentFirstLineExtra[i];
    FIndentFirstLineMax[i]   := TSynBeautifierPascal(Src).FIndentFirstLineMax[i];

    FCommentMode[i]          := TSynBeautifierPascal(Src).FCommentMode[i];
    FMatch[i]                := TSynBeautifierPascal(Src).FMatch[i];
    FMatchMode[i]            := TSynBeautifierPascal(Src).FMatchMode[i];
    FMatchLine[i]            := TSynBeautifierPascal(Src).FMatchLine[i];
    FPrefix[i]               := TSynBeautifierPascal(Src).FPrefix[i];

    FEolMatch[i]             := TSynBeautifierPascal(Src).FEolMatch[i];
    FEolMode[i]              := TSynBeautifierPascal(Src).FEolMode[i];
    FEolPostfix[i]           := TSynBeautifierPascal(Src).FEolPostfix[i];
    FEolSkipLongerLine[i]    := TSynBeautifierPascal(Src).FEolSkipLongerLine[i];
  end;
end;

{ TSynCustomBeautifier }

procedure TSynCustomBeautifier.Assign(Src: TPersistent);
begin
  if assigned(Src) and (src is TSynCustomBeautifier) then begin
    FCurrentEditor := TSynCustomBeautifier(Src).FCurrentEditor;
    FCurrentLines := TSynCustomBeautifier(Src).FCurrentLines;
    FOnGetDesiredIndent := TSynCustomBeautifier(Src).FOnGetDesiredIndent;
    FAutoIndent := TSynCustomBeautifier(Src).FAutoIndent;
  end
  else
    inherited;
end;

function TSynCustomBeautifier.GetCopy: TSynCustomBeautifier;
begin
  // Since all synedits share one beautifier, create a temp instance.
  // Todo: have individual beautifiers
  Result := TSynCustomBeautifierClass(self.ClassType).Create(nil);
  Result.assign(self);
end;

procedure TSynCustomBeautifier.BeforeCommand(const Editor: TSynEditBase;
  const Lines: TSynEditStrings; const ACaret: TSynEditCaret;
  var Command: TSynEditorCommand; InitialCmd: TSynEditorCommand);
begin
  // Must be called on GetCopy
  // Todo: have individual beautifiers
  FCurrentEditor := Editor;
  FCurrentLines := Lines;
  DoBeforeCommand(ACaret, Command);
end;

procedure TSynCustomBeautifier.AfterCommand(const Editor: TSynEditBase;
  const Lines: TSynEditStrings; const ACaret: TSynEditCaret;
  var Command: TSynEditorCommand; InitialCmd: TSynEditorCommand;
  StartLinePos, EndLinePos: Integer);
begin
  // Must be called on GetCopy
  // Todo: have individual beautifiers
  FCurrentEditor := Editor;
  FCurrentLines := Lines;
  DoAfterCommand(ACaret, Command, StartLinePos, EndLinePos);
end;

{ TSynBeautifier }

procedure TSynBeautifier.Assign(Src: TPersistent);
begin
  inherited Assign(Src);
  if assigned(Src) and (src is TSynBeautifier) then begin
    FIndentType := TSynBeautifier(Src).FIndentType;
    FCurrentEditor := TSynBeautifier(Src).FCurrentEditor;
    FCurrentLines := TSynBeautifier(Src).FCurrentLines;
  end;
end;

function TSynBeautifier.GetLine(AnIndex: Integer): String;
begin
  Result := FCurrentLines[AnIndex];
end;

procedure TSynBeautifier.GetIndentInfo(const LinePos: Integer; out ACharMix: String; out
  AnIndent: Integer; AStopScanAtLine: Integer);
var
  b: Integer;
begin
  ACharMix := '';
  if (GetLine(LinePos-2) = '') and (GetLine(LinePos-1) <> '') then
    AnIndent := 0
  else
    AnIndent := GetIndent(LinePos, b, AStopScanAtLine);

  if AnIndent > 0 then begin
    ACharMix := GetCharMix(LinePos, AnIndent, b);
    if (FIndentType = sbitPositionCaret) and (GetLine(LinePos-1) = '') then
      ACharMix := '';
  end;
end;

procedure TSynBeautifier.DoBeforeCommand(const ACaret: TSynEditCaret;
  var Command: TSynEditorCommand);
var
  x: Integer;
begin
  if (Command = ecDeleteLastChar) and
     (FAutoIndent) and
     (ACaret.CharPos > 1) and
     (not TCustomSynEdit(FCurrentEditor).ReadOnly) and
     ( (not TCustomSynEdit(FCurrentEditor).SelAvail) or
       (eoPersistentBlock in TCustomSynEdit(FCurrentEditor).Options2) ) and
     (GetIndentForLine(FCurrentEditor, ACaret.LineText, True) = ACaret.CharPos - 1)
  then begin
    FCurrentLines.UndoList.CurrentReason := ecSmartUnindent;

    UnIndentLine(ACaret, x);
    ACaret.CharPos := x;
    Command := ecNone;
  end;
end;

procedure TSynBeautifier.DoAfterCommand(const ACaret: TSynEditCaret;
  var Command: TSynEditorCommand; StartLinePos, EndLinePos: Integer);
var
  y, Indent: Integer;
  s: String;
begin
  FLogicalIndentLen := 0;
  if EndLinePos < 1 then
    exit;
  if assigned(FOnGetDesiredIndent) and
    FOnGetDesiredIndent(self, FCurrentEditor, ACaret.LineBytePos,
                        ACaret.OldLineBytePos, StartLinePos, EndLinePos, Command,
                        @ApplyIndent)
  then
    exit;

  if ((Command = ecLineBreak) or (Command = ecInsertLine)) and FAutoIndent then begin
    if (Command = ecLineBreak) then
      y := ACaret.LinePos
    else
      y := ACaret.LinePos + 1;


    GetIndentInfo(y, s, Indent);

    if s <> '' then begin;
      FCurrentLines.EditInsert(1, y, s);
      FLogicalIndentLen := length(s);
    end;

    if (Command = ecLineBreak) then begin
      ACaret.IncForcePastEOL;
      ACaret.CharPos := Indent + 1;
      ACaret.DecForcePastEOL;
    end;
  end;
end;

function TSynBeautifier.UnIndentLine(const ACaret: TSynEditCaret;
  out CaretNewX: Integer): Boolean;
var
  SpaceCount1, SpaceCount2: Integer;
  BackCounter, LogSpacePos, FillSpace: Integer;
  LogCaret: TPoint;
  Line, Temp: String;
begin
  Line := ACaret.LineText;
  SpaceCount1 := GetIndentForLine(FCurrentEditor, Line, true); // physical, desired pos
  SpaceCount2 := 0;
  if (SpaceCount1 > 0) then begin
    BackCounter := ACaret.LinePos - 2;
    while BackCounter >= 0 do begin
      Temp := FCurrentLines[BackCounter];
      SpaceCount2 := GetIndentForLine(FCurrentEditor, Temp, true);
      if (SpaceCount2 < SpaceCount1) and (temp <> '') then
        break;
      Dec(BackCounter);
    end;
  end;
  if SpaceCount2 = SpaceCount1 then
    SpaceCount2 := 0;
  // remove visible spaces
  LogSpacePos := FCurrentLines.PhysicalToLogicalCol(Line, ACaret.LinePos-1, SpaceCount2 + 1);
  FillSpace := SpaceCount2 + 1 - FCurrentLines.LogicalToPhysicalCol(Line, ACaret.LinePos-1, LogSpacePos);
  LogCaret := ACaret.LineBytePos;
  CaretNewX :=  SpaceCount2 + 1;
  FCurrentLines.EditDelete(LogSpacePos, ACaret.LinePos, LogCaret.X - LogSpacePos);
  if FillSpace > 0 then
    FCurrentLines.EditInsert(LogSpacePos, ACaret.LinePos, StringOfChar(' ', FillSpace));
  Result := True;
end;

function TSynBeautifier.GetIndent(const LinePos: Integer; out BasedOnLine: Integer;
  AStopScanAtLine: Integer): Integer;
var
  Temp: string;
begin
  if AStopScanAtLine > 0 then
    dec(AStopScanAtLine); // Convert to index
  BasedOnLine := LinePos - 1; // Convert to index
  while (BasedOnLine > AStopScanAtLine) do begin
    dec(BasedOnLine);
    Temp := GetLine(BasedOnLine);
    if Temp <> '' then begin
      Result := GetIndentForLine(FCurrentEditor, Temp, True);
      exit;
    end;
  end;
  Result := 0;
  //BasedOnLine := LinePos;
  //Result := GetIndentForLine(FCurrentEditor, GetLine(BasedOnLine), True);
end;

function TSynBeautifier.AdjustCharMix(DesiredIndent: Integer; CharMix, AppendMix: String): String;
var
  i: Integer;
  CurLen: Integer;
begin
  CurLen := FCurrentLines.LogicalToPhysicalCol(CharMix, -1, length(CharMix)+1) - 1; // TODO: Need the real index of the line
  if AppendMix <> '' then begin
    while CurLen < DesiredIndent do begin
      CharMix := CharMix + AppendMix;
      CurLen := FCurrentLines.LogicalToPhysicalCol(CharMix, -1, length(CharMix)+1) - 1; // TODO: Need the real index of the line
    end
  end;

  i := length(CharMix);
  while CurLen > DesiredIndent do begin
    Dec(i);
    CurLen := FCurrentLines.LogicalToPhysicalCol(CharMix, -1, i+1) - 1; // TODO: Need the real index of the line
  end;

  CharMix := copy(CharMix, 1, i) + StringOfChar(' ', DesiredIndent - CurLen);
  Result := CharMix;
end;

function TSynBeautifier.GetCharMix(const LinePos, Indent: Integer;
  var IndentCharsFromLinePos: Integer = 0): String;
var
  Temp, KnownMix, BasedMix: string;
  KnownPhysLen, PhysLen: Integer;
  BackCounter: LongInt;
begin
  if FIndentType <> sbitCopySpaceTab then begin
    IndentCharsFromLinePos := 0;
    Result := StringOfChar(' ', Indent);
    exit;
  end;

  if (IndentCharsFromLinePos > 0) and (IndentCharsFromLinePos <= FCurrentLines.Count) then
  begin
    Temp := GetLine(IndentCharsFromLinePos);
    KnownMix := copy(Temp, 1, GetIndentForLine(FCurrentEditor, Temp, False));
  end
  else
    KnownMix := '';
  BasedMix := KnownMix;
  KnownPhysLen := GetIndentForLine(FCurrentEditor, KnownMix, True);

  BackCounter := LinePos;
  while (BackCounter > 0) and (KnownPhysLen < Indent) do begin
    dec(BackCounter);
    Temp := GetLine(BackCounter);
    if Temp <> '' then begin
      Temp := copy(Temp, 1, GetIndentForLine(FCurrentEditor, Temp, False));
      PhysLen := GetIndentForLine(FCurrentEditor, Temp, True);
      if (PhysLen > KnownPhysLen) and (copy(temp, 1, length(BasedMix)) = BasedMix) then
      begin
        KnownMix := Temp;
        KnownPhysLen := PhysLen;
        IndentCharsFromLinePos := BackCounter + 1;
      end;
    end;
  end;

  Result := AdjustCharMix(Indent, KnownMix, '');
end;

procedure TSynBeautifier.ApplyIndent(LinePos: Integer;
  Indent: Integer; RelativeToLinePos: Integer; IndentChars: String = '';
  IndentCharsFromLinePos: Integer = 0);
var
  CharMix: String;
  i: Integer;
begin
  if (LinePos < 1) or (LinePos > FCurrentEditor.Lines.Count) then
    exit;

  // calculate the final indent needed
  if (RelativeToLinePos > 0) and (RelativeToLinePos <= FCurrentEditor.Lines.Count) then
    Indent := Indent + GetIndentForLine(FCurrentEditor, GetLine(RelativeToLinePos-1), True);
  if Indent< 0 then
    Indent := 0;

  // Calculate the charmix
  CharMix := '';
  if Indent > 0 then begin
    if (IndentCharsFromLinePos > 0) and (IndentCharsFromLinePos <= FCurrentEditor.Lines.Count) then begin
      CharMix := GetLine(IndentCharsFromLinePos-1);
      i :=  GetIndentForLine(FCurrentEditor, CharMix, False);
      CharMix := AdjustCharMix(Indent, copy(CharMix, 1, i), IndentChars);
    end
    else if IndentChars <> '' then begin
      CharMix := AdjustCharMix(Indent, '', IndentChars);
    end
    else begin
      i := LinePos;
      CharMix := GetCharMix(LinePos, Indent, i);
    end;
  end;

  {$IFDEF VerboseIndenter}
  DebugLn(['TSynBeautifier.ApplyIndent IndentChars="',dbgstr(IndentChars),' Indent=',Indent]);
  {$ENDIF}

  i :=  GetIndentForLine(FCurrentEditor, GetLine(LinePos-1), False);
  FCurrentLines.EditDelete(1, LinePos, i);
  if (CharMix <> '') and not((FIndentType = sbitPositionCaret) and (GetLine(LinePos-1) = '')) then
    FCurrentLines.EditInsert(1, LinePos, CharMix);
  FLogicalIndentLen := length(CharMix);

  {$IFDEF VerboseIndenter}
  DebugLn(['TSynBeautifier.ApplyIndent Line="',dbgstr(FCurrentLines.ExpandedStrings[LinePos-1]),'"']);
  {$ENDIF}
end;

function TSynBeautifier.GetIndentForLine(Editor: TSynEditBase; const Line: string; Physical: boolean): Integer;
var
  p: PChar;
begin
  p := PChar(Line);
  if Assigned(p) then begin
    Result := 0;
    while p^ in [#1..#32] do begin
      Inc(p);
      Inc(Result);
    end;
    if Physical and (Result>0) then
      Result := FCurrentLines.LogicalToPhysicalCol(Line, -1, Result+1)-1; // TODO, Need the real index of the line
  end else
    Result := 0;
end;

function TSynBeautifier.GetDesiredIndentForLine(Editor: TSynEditBase;
  const Lines: TSynEditStrings; const ACaret: TSynEditCaret;
  out ReplaceIndent: Boolean; out DesiredIndent: String): Integer;
var
  BackCounter, PhysLen: Integer;
  Temp: string;
  FoundLine: LongInt;
begin
  Result := 1;
  FCurrentLines := Lines; // for GetCurrentIndent
  BackCounter := ACaret.LinePos - 1;
  if BackCounter > 0 then
    repeat
      Dec(BackCounter);
      Temp := Lines[BackCounter];
      Result := GetIndentForLine(Editor, Temp, True) + 1; // Physical
    until (BackCounter = 0) or (Temp <> '');

  FoundLine := BackCounter + 1;
  ReplaceIndent := False;
  //if assigned(FOnGetDesiredIndent) then
  //  FOnGetDesiredIndent(Editor, ACaret.LineBytePos, ACaret.LinePos, Result,
  //                      FoundLine, ReplaceIndent);

  //if Result < 0 then exit;

  //if FoundLine > 0 then
  //  Temp := Lines[FoundLine-1]
  //else
  //  FoundLine := BackCounter + 1;
  Temp := copy(Temp, 1, GetIndentForLine(Editor, Temp, False));

  case FIndentType of
    sbitCopySpaceTab:
      begin
        DesiredIndent := copy(Temp, 1,
                   Lines.PhysicalToLogicalCol(Temp, FoundLine - 1, Result) - 1);
        PhysLen := Lines.LogicalToPhysicalCol(Temp, ACaret.LinePos - 1, Length(Temp) + 1);
        if PhysLen < Result then
          DesiredIndent := DesiredIndent + StringOfChar(' ', Result - PhysLen);
      end;
    else
      DesiredIndent := StringOfChar(' ', Result - 1);
  end;
end;

function TSynBeautifier.GetDesiredIndentForLine(Editor: TSynEditBase;
  const Lines: TSynEditStrings; const ACaret: TSynEditCaret): Integer;
var
  Dummy: String;
  Replace: Boolean;
begin
  Result := GetDesiredIndentForLine(Editor, Lines, ACaret, Replace, Dummy);
end;

end.

