{-------------------------------------------------------------------------------
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: SynEditMarkupFoldColoring.pas, released 2015-12-07.
Copyleft (c) 2015-2016 x2nie - Fathony Luth.

The Original SynEdit Project is based on mwCustomEdit.pas by Martin Waldenburg,
part of the mwEdit component suite.
Portions created by Martin Waldenburg are Copyright (C) 1998 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.



Features:
  - paint keywords in multiple colors, depends on fold block level or by config
  - paint vertical line between paired open~close fold
  - vertical line and/or keyword can be disabled
  - independent, can be used for any SynHighlighter
  - many features are well tested for PasSynPas.pas
  - only active when SynEdit.Highlighter is TSynCustomFoldHighlighter

-------------------------------------------------------------------------------}
unit SynEditMarkupFoldColoring;

{$mode objfpc}{$H+}
{ $define SynEditMarkupFoldColoringDebug}
{ $define WithSynMarkupFoldColorDebugGutter}

interface

uses
  Classes, SysUtils, Graphics, SynEditMarkup, SynEditMiscClasses, Controls,
  LCLProc, LCLType, SynEditHighlighter,
  SynEditHighlighterFoldBase, LazSynEditText, SynEditTextBase, SynEditTypes
  {$IFDEF WithSynMarkupFoldColorDebugGutter}, SynGutterBase, SynTextDrawer{$ENDIF}
;

type

  {$IFDEF WithSynMarkupFoldColorDebugGutter}
  TSynEditMarkupFoldColors = class;

  { TIDESynMarkupFoldColorDebugGutter }

  TIDESynMarkupFoldColorDebugGutter = class(TSynGutterPartBase)
  protected
    FOwner: TSynEditMarkupFoldColors;
    function  PreferedWidth: Integer; override;
  public
    procedure Paint(Canvas: TCanvas; AClip: TRect; FirstLine, LastLine: integer);
      override;
  end;
  {$ENDIF}

  PMarkupFoldColorInfo = ^TMarkupFoldColorInfo;
  TMarkupFoldColorInfo = record
    PhysX, PhysX2, PhysCol: Integer;
    ColorIdx: Integer;
    Border  : Boolean;
    Ignore  : Boolean; //no color no line
    SrcNode : TSynFoldNodeInfo;
    Level, LevelAfter : integer; //needed by non nest nodes
  end;

  TMarkupFoldColorInfos = array of TMarkupFoldColorInfo;
  TSynFoldNodeInfos     = array of TSynFoldNodeInfo; //for quick compare detection

  TColumnCacheEntry = Integer;
  PColumnCacheEntry = ^TColumnCacheEntry;

  { TMarkupFoldColorsLineColor }

  TMarkupFoldColorsLineColor = class
  private
    FAlpha: Byte;
    FColor: TColor;
    FOnChange: TNotifyEvent;
    FOnChanged: TNotifyEvent;
    FPriority: Integer;
    FStyle: TSynLineStyle;
    procedure SetAlpha(AValue: Byte);
    procedure SetColor(AValue: TColor);
    procedure SetPriority(AValue: Integer);
    procedure SetStyle(AValue: TSynLineStyle);
    procedure Changed;
  public
    constructor Create;
    property Color: TColor read FColor write SetColor; // clDefault will take Color[].Frame or Color[].Foreground
    property Style: TSynLineStyle read FStyle write SetStyle;
    property Alpha: Byte read FAlpha write SetAlpha;
    property Priority: Integer read FPriority write SetPriority;
    property OnChange: TNotifyEvent read FOnChanged write FOnChanged;
  end;

  { TSynEditMarkupFoldColorsColumnCache }

  TSynEditMarkupFoldColorsColumnCache = class(TSynManagedStorageMem)
  private
    function GetColumnData(Index: Integer): TColumnCacheEntry;
    function GetIsValidForLine(Index: Integer): Boolean;
    procedure SetColumnData(Index: Integer; AValue: TColumnCacheEntry);
  protected
    procedure LineTextChanged(AIndex: Integer; ACount: Integer = 1); override;
    procedure InsertedLines(AIndex, ACount: Integer); override;
    //procedure DeletedLines(AIndex, ACount: Integer); override;
    procedure Invalidate;
  public
    constructor Create;
    property ColumnData[Index: Integer]: TColumnCacheEntry read GetColumnData write SetColumnData; default;
    property IsValidForLine[Index: Integer]: Boolean read GetIsValidForLine;
  end;

  { TSynEditMarkupFoldColors }

  TSynEditMarkupFoldColors = class(TSynEditMarkup)
  private
    {$IFDEF WithSynMarkupFoldColorDebugGutter}
    FDebugGutter: TIDESynMarkupFoldColorDebugGutter;
    {$ENDIF}
    function GetFirstCharacterColumn(pIndex: Integer): TColumnCacheEntry;
    procedure TextBufferChanged(pSender: TObject);
  private
    FColorCount: Integer;
    fHighlighter: TSynCustomFoldHighlighter;
    fMarkupColors: array of TSynSelectedColor;
    fLineColors : array of TMarkupFoldColorsLineColor;
    fNestList, fNestList2: TLazSynEditNestedFoldsList;

    // cache
    FColumnCache: TSynEditMarkupFoldColorsColumnCache;
    fFoldColorInfosCount,
    fFoldColorInfosCapacity: Integer;

    fDefaultGroup: integer;
    fFoldColorInfos: TMarkupFoldColorInfos;

    fPreparedRow: integer;
    fLastOpenNode: TSynFoldNodeInfo;
    fLastIndex,
    fLastOpenIndex: Integer;
    fLastEnabled: Boolean;

    procedure DoMarkupParentFoldAtRow(pRow: Integer);
    procedure DoMarkupParentCloseFoldAtRow(pRow: Integer);
    function  GetColor(pIndex: Integer): TSynSelectedColor;
    function  GetLineColor(pIndex: Integer): TMarkupFoldColorsLineColor;
    procedure SetColorCount(AValue: Integer);
    procedure SetDefaultGroup(pValue: integer);
    procedure SetFoldColorInfosCount(pNewCount: Integer);
    procedure InitNestList;
    property FirstCharacterColumn[pIindex: Integer]: TColumnCacheEntry read GetFirstCharacterColumn;
  protected
    // Notifications about Changes to the text
    procedure DoTextChanged({%H-}pStartLine, pEndLine, {%H-}pCountDiff: Integer); override; // 1 based
    procedure SetLines(const pValue: TSynEditStrings); override;
    procedure HighlightChanged(pSender: TSynEditStrings; pIndex, pCount: Integer);
    procedure DoEnabledChanged(pSender: TObject); override;
    procedure ColorChanged(pMarkup: TObject);
  public
    constructor Create(pSynEdit : TSynEditBase);
    destructor Destroy; override;
    function RealEnabled: Boolean; override;
    procedure BeginMarkup; override;
    function GetMarkupAttributeAtRowCol(const pRow: Integer;
                                        const pStartCol: TLazSynDisplayTokenBound;
                                        const {%H-}pRtlInfo: TLazSynDisplayRtlInfo): TSynSelectedColor; override;
    procedure GetNextMarkupColAfterRowCol(const pRow: Integer;
                                         const pStartCol: TLazSynDisplayTokenBound;
                                         const {%H-}pRtlInfo: TLazSynDisplayRtlInfo;
                                         out   pNextPhys, pNextLog: Integer); override;

    procedure PrepareMarkupForRow(pRow : Integer); override;
    property DefaultGroup : integer read fDefaultGroup write SetDefaultGroup;
    property ColorCount: Integer read FColorCount write SetColorCount;
    property Color[pIndex: Integer]: TSynSelectedColor read GetColor;
    property LineColor[pIndex: Integer]: TMarkupFoldColorsLineColor read GetLineColor;
  end;

implementation
uses
  SynEdit,
  SynEditMiscProcs,
  {$IFDEF SynEditMarkupFoldColoringDebug}
  SynHighlighterPas,
  strutils,
  {$endif}
  Dialogs;

{ TMarkupFoldColorsLineColor }

procedure TMarkupFoldColorsLineColor.SetColor(AValue: TColor);
begin
  if FColor = AValue then Exit;
  FColor := AValue;
  Changed;
end;

procedure TMarkupFoldColorsLineColor.SetAlpha(AValue: Byte);
begin
  if FAlpha = AValue then Exit;
  FAlpha := AValue;
  Changed;
end;

procedure TMarkupFoldColorsLineColor.SetPriority(AValue: Integer);
begin
  if FPriority = AValue then Exit;
  FPriority := AValue;
  Changed;
end;

procedure TMarkupFoldColorsLineColor.SetStyle(AValue: TSynLineStyle);
begin
  if FStyle = AValue then Exit;
  FStyle := AValue;
  Changed;
end;

procedure TMarkupFoldColorsLineColor.Changed;
begin
  if FOnChange <> nil then
    FOnChange(Self);
end;

constructor TMarkupFoldColorsLineColor.Create;
begin
  FStyle := slsSolid;
  FColor := clDefault;
  FPriority := 0;
  inherited;
end;

{ TSynEditMarkupFoldColorsColumnCache }

function TSynEditMarkupFoldColorsColumnCache.GetColumnData(Index: Integer
  ): TColumnCacheEntry;
begin
  Result := PColumnCacheEntry(ItemPointer[Index])^;
end;

function TSynEditMarkupFoldColorsColumnCache.GetIsValidForLine(Index: Integer
  ): Boolean;
begin
  Result := PColumnCacheEntry(ItemPointer[Index])^ > 0;
end;

procedure TSynEditMarkupFoldColorsColumnCache.SetColumnData(Index: Integer;
  AValue: TColumnCacheEntry);
begin
  PColumnCacheEntry(ItemPointer[Index])^ := AValue;
end;

procedure TSynEditMarkupFoldColorsColumnCache.LineTextChanged(AIndex: Integer;
  ACount: Integer);
var
  i: Integer;
begin
  for i := AIndex to AIndex + ACount - 1 do
    ColumnData[i] := 0;
end;

procedure TSynEditMarkupFoldColorsColumnCache.InsertedLines(AIndex,
  ACount: Integer);
var
  i: Integer;
begin
  for i := AIndex to AIndex + ACount - 1 do
    ColumnData[i] := 0;
end;

procedure TSynEditMarkupFoldColorsColumnCache.Invalidate;
var
  i: Integer;
begin
  for i := 0 to Count - 1 do
    ColumnData[i] := 0;
end;

constructor TSynEditMarkupFoldColorsColumnCache.Create;
begin
  inherited;
  ItemSize := SizeOf(TColumnCacheEntry);
end;

{$IFDEF WithSynMarkupFoldColorDebugGutter}
{ TIDESynMarkupFoldColorDebugGutter }

function TIDESynMarkupFoldColorDebugGutter.PreferedWidth: Integer;
begin
  Result := 600;
end;

procedure TIDESynMarkupFoldColorDebugGutter.Paint(Canvas: TCanvas;
  AClip: TRect; FirstLine, LastLine: integer);
var
  TextDrawer: TheTextDrawer;
  dc: HDC;
  rcLine: TRect;
  LineHeight, c, i, j: Integer;
  iLine: LongInt;
  s, fc: string;
begin
  TextDrawer := Gutter.TextDrawer;
  dc := Canvas.Handle;
  TextDrawer.BeginDrawing(dc);
  try
    TextDrawer.SetBackColor(Gutter.Color);
    TextDrawer.SetForeColor(TCustomSynEdit(SynEdit).Font.Color);
    TextDrawer.SetFrameColor(clNone);
    with AClip do
      TextDrawer.ExtTextOut(Left, Top, ETO_OPAQUE, AClip, nil, 0);

    rcLine := AClip;
    rcLine.Bottom := AClip.Top;
    LineHeight := TCustomSynEdit(SynEdit).LineHeight;
    c := TCustomSynEdit(SynEdit).Lines.Count;
    for i := FirstLine to LastLine do
    begin
      iLine := FoldView.DisplayNumber[i];
      if (iLine <= 0) or (iLine > c) then break;
      // next line rect
      rcLine.Top := rcLine.Bottom;
      rcLine.Bottom := rcLine.Bottom + LineHeight;

      FOwner.PrepareMarkupForRow(iLine);
      s := '';
      for j := 0 to FOwner.fFoldColorInfosCount - 1 do begin
        with FOwner.fFoldColorInfos[j] do
          s := s + '('
           + IntToStr(PhysX) + ',' + IntToStr(PhysX2) + ',' + IntToStr(PhysCol) + '/'
           + IntToStr(ColorIdx) + '/'
           + BoolToStr(Border, True)[1] + BoolToStr(Ignore, True)[1] + '/'
           + IntToStr(Level) + ',' + IntToStr(LevelAfter)
           + ') ';
      while length(s) < 21 * (j+1) do s := s + ' ';
      end;
      s := IntToStr(FOwner.fFoldColorInfosCount) + s;
      if iLine < FOwner.FColumnCache.Count then
        s := s + ', '+IntToStr(FOwner.FColumnCache[ToIdx(iLine)]);

      TextDrawer.ExtTextOut(rcLine.Left, rcLine.Top, ETO_OPAQUE or ETO_CLIPPED, rcLine,
        PChar(Pointer(S)),Length(S));
    end;

  finally
    TextDrawer.EndDrawing;
  end;
end;
{$ENDIF}


{$IFDEF SynEditMarkupFoldColoringDebug}
function FoldTypeToStr(p_FoldType: Pointer): String;
begin
  WriteStr(Result, TPascalCodeFoldBlockType(PtrUInt(p_FoldType)));
  while length(Result) < 17 do Result := Result + ' ';
end;
{$ENDIF}


{ TSynEditMarkupFoldColors }

constructor TSynEditMarkupFoldColors.Create(pSynEdit: TSynEditBase);
begin
  inherited Create(pSynEdit);

  {$IFDEF WithSynMarkupFoldColorDebugGutter}
  FDebugGutter := TIDESynMarkupFoldColorDebugGutter.Create(TSynEdit(pSynEdit).RightGutter.Parts);
  FDebugGutter.FOwner := Self;
  {$ENDIF}

  FColumnCache := TSynEditMarkupFoldColorsColumnCache.Create;

  fHighlighter := TSynCustomFoldHighlighter(TCustomSynEdit(SynEdit).Highlighter);
  if Assigned(fHighlighter)
  and not (fHighlighter  is TSynCustomFoldHighlighter) then
    fHighlighter := nil;

  fDefaultGroup := 0;
  fFoldColorInfosCount := 0;
  SetLength(fFoldColorInfos, 50);
  fFoldColorInfosCapacity := 50;

  fNestList := TLazSynEditNestedFoldsList.Create(Lines, fHighlighter);
  fNestList.ResetFilter;
  fNestList.FoldGroup := fDefaultGroup;
  fNestList.FoldFlags := [sfbIncludeDisabled];
  fNestList.IncludeOpeningOnLine := True;

  // for scanning the "if" of a "then" to find the indent
  fNestList2 := TLazSynEditNestedFoldsList.Create(Lines, fHighlighter);
  fNestList2.ResetFilter;
  fNestList2.FoldGroup := fDefaultGroup;
  fNestList2.FoldFlags := [sfbIncludeDisabled];
  fNestList2.IncludeOpeningOnLine := False;

  MarkupInfo.Foreground := clGreen;
  MarkupInfo.Background := clNone;
  MarkupInfo.Style := [];
  MarkupInfo.StyleMask := [];
  MarkupInfo.FrameEdges:= sfeLeft;

  SetColorCount(6);
  fMarkupColors[0].Foreground  := clRed;
  fMarkupColors[1].Foreground  := $000098F7; //orange
  fMarkupColors[2].Foreground  := $0022CC40; //green
  fMarkupColors[3].Foreground  := $00CCCC00; //cyan
  fMarkupColors[4].Foreground  := $00FF682A; //blue
  fMarkupColors[5].Foreground  := $00CF00C4; //purple
end;

destructor TSynEditMarkupFoldColors.Destroy;
begin
  if Lines <> nil then
    Lines.Ranges[Self] := nil;
  FColumnCache.Free;

  ColorCount := 0;
  if Assigned(Lines) then begin
    Lines.RemoveChangeHandler(senrHighlightChanged, @HighlightChanged);
    Lines.RemoveNotifyHandler(senrTextBufferChanged, @TextBufferChanged);
  end;
  FreeAndNil(fNestList);
  FreeAndNil(fNestList2);
  inherited Destroy;
end;

function TSynEditMarkupFoldColors.RealEnabled: Boolean;
begin
  Result := (not IsTempDisabled) and Enabled and (FColorCount > 0);
end;

procedure TSynEditMarkupFoldColors.BeginMarkup;
begin
  {$IFDEF SynEditMarkupFoldColoringDebug}
  //DebugLn('BeginMarkup');
  {$ENDIF}
  inherited BeginMarkup;
  if not Assigned(fHighlighter) then
    exit;
  fNestList.Clear; // for next markup start
  fNestList2.Clear;
end;

function TSynEditMarkupFoldColors.GetMarkupAttributeAtRowCol(
  const pRow: Integer; const pStartCol: TLazSynDisplayTokenBound;
  const pRtlInfo: TLazSynDisplayRtlInfo): TSynSelectedColor;
var
  i, x2both: integer;
begin
  Result := nil;
  if not Assigned(fHighlighter) then exit;
  if (fPreparedRow = pRow) then begin
    {$IFDEF SynEditMarkupFoldColoringDebug}
    //DebugLn('   GetMarkupAttributeAtRowCol %d/%d', [aRow, aStartCol.Logical]);
    {$ENDIF}

    x2both := 0;
    for i := 0 to fFoldColorInfosCount - 1 do
      with fFoldColorInfos[i] do
        if not Ignore
        and (PhysX < PhysX2)
        and (ColorIdx >= 0)
        and (pStartCol.Physical >= PhysX)
        and (pStartCol.Physical < PhysX2) then begin
          {$IFDEF SynEditMarkupFoldColoringDebug}
          //DebugLn('      X=%d X2=%d Y=%d, C=%d B=%s I=%s', [X, X2, Y, ColorIdx, IfThen(Border, 'X', '-'), IfThen(Ignore, 'X', '-')]);
          {$ENDIF}

          Result := MarkupInfo;
          x2both := max(x2both, PhysX2);
          if Border then begin
            MarkupInfo.Clear;
            MarkupInfo.SetFrameBoundsPhys(PhysX, x2both);
            MarkupInfo.FrameAlpha := fLineColors[ColorIdx].Alpha;
            MarkupInfo.FramePriority := fLineColors[ColorIdx].Priority;
            MarkupInfo.FrameColor := fLineColors[ColorIdx].Color;
            if MarkupInfo.FrameColor = clDefault then begin
              if (fMarkupColors[ColorIdx].FrameColor <> clNone) and
                 (fMarkupColors[ColorIdx].FrameColor <> clDefault)
              then
                MarkupInfo.FrameColor := fMarkupColors[ColorIdx].FrameColor
              else
                MarkupInfo.FrameColor := fMarkupColors[ColorIdx].Foreground;
            end;
            MarkupInfo.FrameStyle := fLineColors[ColorIdx].Style;
            MarkupInfo.FrameEdges := sfeLeft;
          end else begin
            MarkupInfo.Assign(fMarkupColors[ColorIdx]);
            MarkupInfo.SetFrameBoundsPhys(PhysX, PhysX2);
          end;
        end;
  end;
end;

procedure TSynEditMarkupFoldColors.GetNextMarkupColAfterRowCol(
  const pRow: Integer; const pStartCol: TLazSynDisplayTokenBound;
  const pRtlInfo: TLazSynDisplayRtlInfo; out pNextPhys, pNextLog: Integer);
var i : integer;
begin
  {$IFDEF SynEditMarkupFoldColoringDebug}
  //DebugLn('GetNextMarkupColAfterRowCol %d/%d', [aRow, aStartCol.Logical]);
  {$ENDIF}
  if not Assigned(fHighlighter)
  or (fPreparedRow <> pRow) then
    exit;

  pNextLog := -1;
  pNextPhys := -1;
  for i := 0 to fFoldColorInfosCount - 1  do
    with fFoldColorInfos[i] do begin
      if not Ignore and (ColorIdx >= 0) and
         (  ((not Border) and  fMarkupColors[ColorIdx].IsEnabled) or
            (Border and (fLineColors[ColorIdx].Color <> clNone))
         ) and
         (PhysX < PhysX2) and (pStartCol.Physical < PhysX) and (pStartCol.Physical <= PhysX2)
      then begin
        pNextPhys := fFoldColorInfos[i].PhysX;
        break;
      end;
    end;
end;

function TSynEditMarkupFoldColors.GetFirstCharacterColumn(pIndex: Integer
  ): TColumnCacheEntry;
var
  l: String;
  s, p: Integer;
begin
  l := SynEdit.Lines[pIndex];
  s := length(l);
  p := 1;
  while (p <= s)
  //and (l[p] in [#9, #32, '/']) do inc(p);
  and (l[p] in [#9, #32]) do inc(p);
  if p > s then
    exit(high(Result));
  Result := TCustomSynEdit(SynEdit).LogicalToPhysicalPos(Point(p, toPos(pIndex))).x;
end;

procedure TSynEditMarkupFoldColors.TextBufferChanged(pSender: TObject);
begin
  if pSender <> nil then
    TSynEditStrings(pSender).Ranges[Self] := nil;

  if not Enabled then
    exit;
  InitNestList;

  FColumnCache.Capacity := SynEdit.Lines.Capacity;
  FColumnCache.Count := SynEdit.Lines.Count;
  Lines.Ranges[Self] := FColumnCache;

  FColumnCache.Invalidate;
end;

procedure TSynEditMarkupFoldColors.DoMarkupParentFoldAtRow(pRow: Integer);
var
  lNodeCol: TColumnCacheEntry;
  i, lLvl, lLineIdx, lCurIndex: Integer;
  lCurNode: TSynFoldNodeInfo;

  procedure AddVerticalLine;
  begin
    with fFoldColorInfos[lCurIndex] do begin
      SrcNode:= lCurNode; //needed by close node
      PhysCol := lNodeCol;
      PhysX := lNodeCol;
      PhysX2 := PhysX + 1;
      Border := PhysX < GetFirstCharacterColumn(lLineIdx); // use real one here not cache
      Ignore :=
        (Border and (sfaOutlineNoLine in lCurNode.FoldAction))
        or (not Border);
      Level := lLvl;
      ColorIdx := Max(0, lLvl) mod FColorCount;
      {$IFDEF SynEditMarkupFoldColoringDebug}
      //DebugLn('  %.5d %.2d %.2d-%.2d: %d - %s %.5d:%s', [Row, PhysCol, PhysX, PhysX2, Level, IfThen(sfaClose in SrcNode.FoldAction, 'C ', IfThen(sfaOpen in SrcNode.FoldAction, 'O ', '??')),ToPos(SrcNode.LineIndex),FoldTypeToStr(SrcNode.FoldType)]);
      {$ENDIF}
    end;
  end;

  procedure InitColumnForKeepLvl(LineIdx, FoldGroup: Integer);
  var
    LevelOpenNode: TSynFoldNodeInfo;
    i, ONodeFirstCol: integer;
  begin
    ONodeFirstCol := FirstCharacterColumn[LineIdx];
    FColumnCache[LineIdx] := ONodeFirstCol;

    fNestList2.Line := LineIdx;
    fNestList2.FoldGroup := FoldGroup;
    if fNestList2.Count = 0 then
      exit;
    LevelOpenNode := fNestList2.HLNode[fNestList2.Count-1];
    if sfaInvalid in LevelOpenNode.FoldAction then
      exit; // try node before ??

    if not FColumnCache.IsValidForLine[LevelOpenNode.LineIndex] then begin
      if (LevelOpenNode.NestLvlStart <  fHighlighter.FoldBlockEndLevel(LevelOpenNode.LineIndex-1, lCurNode.FoldGroup, [sfbIncludeDisabled])) and
         (sfaCloseAndOpen in LevelOpenNode.FoldAction)
      then
        InitColumnForKeepLvl(LevelOpenNode.LineIndex, FoldGroup)
      else
        FColumnCache[LevelOpenNode.LineIndex] := FirstCharacterColumn[LevelOpenNode.LineIndex];
      if not FColumnCache.IsValidForLine[LevelOpenNode.LineIndex] then
        exit;
    end;
    i := FColumnCache[LevelOpenNode.LineIndex];
    FColumnCache[LineIdx] := min(i, ONodeFirstCol);
  end;

var
  lKeepLevel: Boolean;
  LastNode: TSynFoldNodeInfo;
  cnf: TSynCustomFoldConfig;
  cnfCnt, fType: Integer;
begin
  lLineIdx := ToIdx(pRow);
  fNestList.Line := lLineIdx;
  fHighlighter.CurrentLines := Lines;
  LastNode.LineIndex := -1;

  // get first character for current line
  if not FColumnCache.IsValidForLine[lLineIdx] then
    FColumnCache[lLineIdx] := FirstCharacterColumn[lLineIdx];

  lLvl := 0;
  cnfCnt := TSynCustomFoldHighlighter(Highlighter).FoldConfigCount;
  i := 0; // starting at the node with the lowest line number
  while i < fNestList.Count do begin
    fType := PtrInt(fNestList.NodeFoldType[i]);
    if fType >= cnfCnt then begin
      inc(i);
      continue;
    end;
    cnf := TSynCustomFoldHighlighter(Highlighter).FoldConfig[fType];
    if (not cnf.Enabled) or not(fmOutline in cnf.Modes) then begin
      inc(i);
      continue;
    end;
    lCurNode := fNestList.HLNode[i];
    // sanity check
    Assert(sfaOpen in lCurNode.FoldAction, 'no sfaOpen in lCurNode.FoldAction');
    {$IFDEF SynEditMarkupFoldColoringDebug}
    //DebugLn('  O: %s %s %s', [IfThen(sfaOutline in lCurNode.FoldAction, 'X', '-'), IfThen(sfaClose in lCurNode.FoldAction, 'C ', IfThen(sfaOpen in lCurNode.FoldAction, 'O ', '??')),FoldTypeToStr(lCurNode.FoldType)]);
    {$ENDIF}
    if (sfaOutline in lCurNode.FoldAction)
    and not (sfaInvalid in lCurNode.FoldAction)
    and (lCurNode.LineIndex <> lLineIdx) then begin

      if ( sfaOutlineForceIndent in lCurNode.FoldAction) then
        inc(lLvl)
      else if ( sfaOutlineMergeParent in lCurNode.FoldAction) then
        dec(lLvl);

      // new FoldColorInfo
      SetFoldColorInfosCount(fFoldColorInfosCount + 1);
      lCurIndex := fFoldColorInfosCount - 1;

      {$IFDEF SynEditMarkupFoldColoringDebug}
      //if (fLastOpenNode.LineIndex >= 0) then
      //  DebugLn('   %s %s - %s %s', [FoldTypeToStr(fLastOpenNode.FoldType), IfThen(sfaOutlineKeepLevel in fLastOpenNode.FoldAction, '(Keep)', ''), FoldTypeToStr(lCurNode.FoldType), IfThen(sfaOutlineKeepLevel in lCurNode.FoldAction, '(Keep)', '')]);
      {$ENDIF}


      // find lastnode // first opening node on this line, that is = hl.line[-1].endnestlevel (-1)
      if (lCurNode.NestLvlStart <  fHighlighter.FoldBlockEndLevel(lCurNode.LineIndex-1, lCurNode.FoldGroup, [sfbIncludeDisabled])) and
         (sfaCloseAndOpen in lCurNode.FoldAction)
// // TODO: check that this is the FIRST sfaCloseAndOpen on this line
      then
        InitColumnForKeepLvl(lCurNode.LineIndex, lCurNode.FoldGroup);

      if not FColumnCache.IsValidForLine[lCurNode.LineIndex] then
        FColumnCache[lCurNode.LineIndex] := FirstCharacterColumn[lCurNode.LineIndex];
      lNodeCol := FColumnCache[lCurNode.LineIndex];

      { do not keep level if two consecutive sfaOutlineKeepLevel nodes are
        on different lines and start on different columns                  }
      if (LastNode.LineIndex >= 0)
      and (sfaOutlineKeepLevel in LastNode.FoldAction) then begin
        {$IFDEF SynEditMarkupFoldColoringDebug}
        //DebugLn('   %.5d/%.5d %.2d/%.2d', [LastNode.LineIndex+1,lCurNode.LineIndex+1, FFoldColorInfos[fLastIndex].PhysCol, lNodeCol]);
        //DbgOut('    keep');
        {$ENDIF}
        lKeepLevel := True;
        if (sfaOutlineKeepLevel in lCurNode.FoldAction)
        and not (LastNode.LineIndex = lCurNode.LineIndex)
        and not (fFoldColorInfos[fLastIndex].PhysCol = lNodeCol) then begin
          {$IFDEF SynEditMarkupFoldColoringDebug}
          //DbgOut(' not');
          {$ENDIF}
          inc(lLvl);
          lKeepLevel := False;
        end;
        {$IFDEF SynEditMarkupFoldColoringDebug}
        //DebugLn('');
        {$ENDIF}
      end else
        lKeepLevel := False;

      AddVerticalLine;

      if lKeepLevel then begin
        // keep level for none sfaOutlineKeepLevel after sfaOutlineKeepLevel
        lLvl := fFoldColorInfos[fLastIndex].Level;
        if fFoldColorInfos[fLastIndex].PhysX < fFoldColorInfos[lCurIndex].PhysX then
          fFoldColorInfos[lCurIndex].PhysX := fFoldColorInfos[fLastIndex].PhysX;
        fFoldColorInfos[lCurIndex].Level := lLvl;
        fFoldColorInfos[lCurIndex].ColorIdx := Max(0, lLvl) mod FColorCount;
        // overwrite first character column with new value
        if fFoldColorInfos[fLastIndex].PhysX < FColumnCache[fFoldColorInfos[lCurIndex].SrcNode.LineIndex] then
          FColumnCache[fFoldColorInfos[lCurIndex].SrcNode.LineIndex] := fFoldColorInfos[fLastIndex].PhysX;
        {$IFDEF SynEditMarkupFoldColoringDebug}
        //with FFoldColorInfos[lCurIndex] do
        //  DebugLn('  > > > %.2d %.2d-%.2d: %d - %s %.5d:%s', [PhysCol, PhysX, PhysX2, Level, IfThen(sfaClose in SrcNode.FoldAction, 'C ', IfThen(sfaOpen in SrcNode.FoldAction, 'O ', '??')),ToPos(SrcNode.LineIndex),FoldTypeToStr(SrcNode.FoldType)]);
        {$ENDIF}
      end;

      if not (sfaOutlineKeepLevel in lCurNode.FoldAction) then
        inc(lLvl);

      LastNode := lCurNode;
      fLastIndex := lCurIndex;
      fLastOpenNode := lCurNode;
      fLastOpenIndex := lCurIndex;

      with fFoldColorInfos[fFoldColorInfosCount - 1] do begin
        LevelAfter  := lLvl;  // used in DoMarkupParentCloseFoldAtRow
      end;
    end;
    inc(i);
  end;
end;

procedure TSynEditMarkupFoldColors.DoMarkupParentCloseFoldAtRow(pRow: Integer);
var
  lMaxLevel, lvl, lCurIndex: Integer;
  lCurNode: TSynFoldNodeInfo;
  lKeepLevel: Boolean;
  lNodeCol: TColumnCacheEntry;

  function AddHighlight: Boolean;
  var
    lPhysX, lPhysX2, j: Integer;
  begin
    Result := False;
    // ignore implicit close nodes at end of line, especially if line is empty
    // or at least has less characters as vertical line is on
    if not(sfaCloseForNextLine in lCurNode.FoldAction) then begin
      Result := True;
      lPhysX := TCustomSynEdit(SynEdit).LogicalToPhysicalPos(Point(ToPos(lCurNode.LogXStart), ToPos(lCurNode.LineIndex))).x;
      lPhysX2 := TCustomSynEdit(SynEdit).LogicalToPhysicalPos(Point(ToPos(lCurNode.LogXEnd), ToPos(lCurNode.LineIndex))).x;
      if lCurNode.LogXStart < lCurNode.LogXEnd then begin
        {$IFDEF SynEditMarkupFoldColoringDebug}
        //DebugLn('    %d < %d', [lCurNode.LogXStart, lCurNode.LogXEnd]);
        {$ENDIF}
        for j := 0 to fFoldColorInfosCount - 1 do
          if (fFoldColorInfos[j].PhysX = lPhysX)
          and (fFoldColorInfos[j].Border)
          and (fFoldColorInfos[j].SrcNode.FoldType = lCurNode.FoldType )
          and (fFoldColorInfos[j].SrcNode.FoldLvlEnd = lCurNode.FoldLvlStart ) then begin
            {$IFDEF SynEditMarkupFoldColoringDebug}
            //DebugLn('      X2: %d->%d', [FFoldColorInfos[j].X2, lCurNode.LogXEnd + 1]);
            {$ENDIF}
            fFoldColorInfos[j].PhysX2 := lPhysX2;
            fFoldColorInfos[j].Border := False;
          end;
      end;

      SetFoldColorInfosCount(fFoldColorInfosCount + 1);
      lCurIndex := fFoldColorInfosCount - 1;
      with fFoldColorInfos[lCurIndex] do begin
        Ignore := False;
        Border := False;
        SrcNode:= lCurNode; //needed by close node
        PhysX := lPhysX;
        //if not FColumnCache.IsValidForLine[lCurNode.LineIndex] then
        //  FColumnCache[lCurNode.LineIndex] := FirstCharacterColumn[lCurNode.LineIndex];
        PhysCol := lNodeCol; //FColumnCache[lCurNode.LineIndex];
        PhysX2 := lPhysX2;
        Level := lvl;
        lMaxLevel := Max(lMaxLevel, lvl);
        if not (sfaOutlineNoColor in lCurNode.FoldAction) then
           ColorIdx := Max(0, lvl) mod FColorCount
        else
           ColorIdx := -1;

        {$IFDEF SynEditMarkupFoldColoringDebug}
        //DebugLn('  %.5d %.2d %.2d-%.2d: %d - %s %.5d:%s - %s', [Row, PhysCol, PhysX, PhysX2, Level, IfThen(sfaClose in SrcNode.FoldAction, 'C ', IfThen(sfaOpen in SrcNode.FoldAction, 'O ', '??')),ToPos(SrcNode.LineIndex),FoldTypeToStr(SrcNode.FoldType), IfThen(lKeepLevel, 'Keep', '')]);
        {$ENDIF}
      end;
    end;
  end;

var
  lLineIdx,i,j,lvlA , k: integer;
  lNodeList: TLazSynFoldNodeInfoList;

begin
  lLineIdx := ToIdx(pRow);

  // as all nodes will be on pRow we can set lNodeCol here already
  if not FColumnCache.IsValidForLine[lLineIdx] then
    FColumnCache[lLineIdx] := FirstCharacterColumn[lLineIdx];
  lNodeCol := FColumnCache[lLineIdx];

  fHighlighter.CurrentLines := Lines;

  lNodeList := fHighlighter.FoldNodeInfo[lLineIdx];
  lNodeList.ClearFilter; // only needed once, in case the line was already used
  lNodeList.AddReference;
  try
    lNodeList.ActionFilter := [sfaOutline];
    lvl := 0;
    J := fFoldColorInfosCount - 1;
    if J >=0 then
      lvl := max(0,fFoldColorInfos[J].LevelAfter);
    lMaxLevel := lvl;
    i := 0;
    repeat
      lCurNode := lNodeList[i];
      lCurIndex := fFoldColorInfosCount - 1;
      // sanity check
      Assert(lCurNode.LineIndex = lLineIdx, 'Node not on aRow');

      {$IFDEF SynEditMarkupFoldColoringDebug}
      //if not (sfaInvalid in lCurNode.FoldAction) then
      //  DebugLn('  C: %s %s %s', [IfThen(sfaOutline in lCurNode.FoldAction, 'X', '-'), IfThen(sfaClose in lCurNode.FoldAction, 'C ', IfThen(sfaOpen in lCurNode.FoldAction, 'O ', '??')),FoldTypeToStr(lCurNode.FoldType)]);
      {$ENDIF}

      if not (sfaInvalid in lCurNode.FoldAction)
      and (sfaOutline in lCurNode.FoldAction) then begin
        if sfaOpen in lCurNode.FoldAction then begin

          if ( sfaOutlineForceIndent in lCurNode.FoldAction) then
            inc(lvl)
          else if ( sfaOutlineMergeParent in lCurNode.FoldAction) then
            dec(lvl);

          {$IFDEF SynEditMarkupFoldColoringDebug}
          //if (fLastOpenNode.LineIndex >= 0) then
          //  DebugLn('   %s %s - %s %s', [FoldTypeToStr(fLastOpenNode.FoldType), IfThen(sfaOutlineKeepLevel in fLastOpenNode.FoldAction, '(Keep)', ''), FoldTypeToStr(lCurNode.FoldType), IfThen(sfaOutlineKeepLevel in lCurNode.FoldAction, '(Keep)', '')]);
          {$ENDIF}

          { do not keep level if two consecutive sfaOutlineKeepLevel nodes are
            on different lines and start on different columns                  }
          if (fLastOpenNode.LineIndex >= 0)
          and (sfaOutlineKeepLevel in fLastOpenNode.FoldAction) then begin
            {$IFDEF SynEditMarkupFoldColoringDebug}
            //DebugLn('    keep');
            {$ENDIF}
            lKeepLevel := True;
            if (sfaOutlineKeepLevel in lCurNode.FoldAction)
            and not (fLastOpenNode.LineIndex = lLineIdx)
            and not (fFoldColorInfos[fLastIndex].PhysCol = lNodeCol) then begin
              inc(lvl);
              lKeepLevel := False;
            end;

          end else
            lKeepLevel := False;

          if AddHighlight then begin
            if lKeepLevel then begin
              // overwrite first character column with new value
              if fFoldColorInfos[fLastOpenIndex].PhysX < FColumnCache[fFoldColorInfos[lCurIndex].SrcNode.LineIndex] then begin
                FColumnCache[fFoldColorInfos[lCurIndex].SrcNode.LineIndex] := fFoldColorInfos[fLastOpenIndex].PhysX;
                Assert(fFoldColorInfos[lCurIndex].SrcNode.LineIndex = lLineIdx, 'fFoldColorInfos[lCurIndex].SrcNode.LineIndex <> lLineIdx');
                lNodeCol := FColumnCache[lLineIdx]
              end;
              {$IFDEF SynEditMarkupFoldColoringDebug}
              //with FFoldColorInfos[lCurIndex] do
              //  DebugLn('  > > > %.2d %.2d-%.2d: %d - %s %.5d:%s', [PhysCol, PhysX, PhysX2, Level, IfThen(sfaClose in SrcNode.FoldAction, 'C ', IfThen(sfaOpen in SrcNode.FoldAction, 'O ', '??')),ToPos(SrcNode.LineIndex),FoldTypeToStr(SrcNode.FoldType)]);
              {$ENDIF}
            end;

            if not (sfaOutlineKeepLevel in lCurNode.FoldAction) then
              inc(lvl);

            lvlA := lvl;
            fLastIndex := lCurIndex;
            fLastOpenNode := lCurNode;
            fLastOpenIndex := lCurIndex;

            with fFoldColorInfos[fFoldColorInfosCount - 1] do begin
              LevelAfter  := lvlA;
            end;
          end;
        end else if sfaClose in lCurNode.FoldAction then begin
          lKeepLevel := False;
          for j := fFoldColorInfosCount - 1 downto 0 do begin
            with fFoldColorInfos[j].SrcNode do begin
              if (FoldType = lCurNode.FoldType)
              and (FoldGroup = lCurNode.FoldGroup)
              and (sfaOpen in FoldAction)
              and (NestLvlEnd = lCurNode.NestLvlStart) then begin
                lvl := fFoldColorInfos[j].Level;
                lvlA := fFoldColorInfos[j].LevelAfter;
                if AddHighlight then begin
                  fLastIndex := lCurIndex;

                  with fFoldColorInfos[fFoldColorInfosCount - 1] do begin
                    LevelAfter  := lvlA;
                  end;
                  // if found opening position is behind closing position:
                  // delete this as it does not have to be drawn
                  if fFoldColorInfos[j].PhysX > fFoldColorInfos[fFoldColorInfosCount - 1].PhysX then begin
                    for k := j to fFoldColorInfosCount - 1 - 1 do begin
                      fFoldColorInfos[k] := fFoldColorInfos[k+1];
                    end;
                    dec(fFoldColorInfosCount);
                  end;
                end;
                break;
              end;
            end;
          end;
        end;
      end;
      inc(i);
    until i >= lNodeList.Count;
  finally
    lNodeList.ReleaseReference;
  end;
end;

function TSynEditMarkupFoldColors.GetColor(pIndex: Integer): TSynSelectedColor;
begin
  Assert((pIndex >= 0) and (pIndex < FColorCount), 'Index out of range');
  Result := fMarkupColors[pIndex];
end;

function TSynEditMarkupFoldColors.GetLineColor(pIndex: Integer
  ): TMarkupFoldColorsLineColor;
begin
  Assert((pIndex >= 0) and (pIndex < FColorCount), 'Index out of range');
  Result := fLineColors[pIndex];
end;

procedure TSynEditMarkupFoldColors.SetColorCount(AValue: Integer);
var
  i: Integer;
begin
  if FColorCount = AValue then Exit;

  for i := AValue to FColorCount - 1 do begin
    fMarkupColors[i].Free;
    fLineColors[i].Free;
  end;

  SetLength(fMarkupColors, AValue);
  SetLength(fLineColors, AValue);

  for i := FColorCount to AValue - 1 do begin
    fMarkupColors[i] := TSynSelectedColor.Create;
    fMarkupColors[i].Clear;
    fMarkupColors[i].OnChange := @ColorChanged;
    fLineColors[i] := TMarkupFoldColorsLineColor.Create;
    fLineColors[i].OnChange := @ColorChanged;
  end;

  FColorCount := AValue;
end;

procedure TSynEditMarkupFoldColors.PrepareMarkupForRow(pRow: Integer);
var
  i, lLastX, j: Integer;

begin
  if not Assigned(fHighlighter)
  and not (TCustomSynEdit(Self.SynEdit).Highlighter is TSynCustomFoldHighlighter) then
    exit;

  {$IFDEF SynEditMarkupFoldColoringDebug}
  //DebugLn(#10'PrepareMarkupForRow %d', [aRow]);
  {$ENDIF}

  fPreparedRow := pRow;
  fFoldColorInfosCount := 0; //reset needed to prevent using of invalid area

  // invalidate LastNode
  fLastIndex := -1;
  fLastOpenNode.LineIndex := -1;
  fLastOpenIndex := -1;

  {$IFDEF SynEditMarkupFoldColoringDebug}
  //DebugLn('  ----- DoMarkupParentFoldAtRow ------');
  {$ENDIF}
  DoMarkupParentFoldAtRow(pRow);

  {$IFDEF SynEditMarkupFoldColoringDebug}
  //DebugLn('  --- DoMarkupParentCloseFoldAtRow ---');
  {$ENDIF}
  DoMarkupParentCloseFoldAtRow(pRow);

  // delete parents with bigger x
  // to keep out mis indented blocks
  lLastX := MaxInt;
  for i := fFoldColorInfosCount - 1 downto 0 do begin
    if fFoldColorInfos[i].PhysX > lLastX then begin
      for j := i to length(fFoldColorInfos) - 2 do begin
        fFoldColorInfos[j] := fFoldColorInfos[j + 1];
      end;
      dec(fFoldColorInfosCount);
    end;
    lLastX := fFoldColorInfos[i].PhysX;
  end;
  {$IFDEF SynEditMarkupFoldColoringDebug}
  //DebugLn('  -------------- Final ---------------');
  //for i := 0 to FFoldColorInfosCount - 1 do with FFoldColorInfos[i] do begin
  //  DebugLn('  %.5d %.2d %.2d-%.2d: %d - %s %.5d:%s - %d %s', [Row, PhysCol, PhysX, PhysX2, Level, IfThen(sfaClose in SrcNode.FoldAction, 'C ', IfThen(sfaOpen in SrcNode.FoldAction, 'O ', '??')),ToPos(SrcNode.LineIndex),FoldTypeToStr(SrcNode.FoldType), ColorIdx, IfThen(Ignore, 'Ignore', '')]);
  //end;
  {$ENDIF}
end;

procedure TSynEditMarkupFoldColors.SetDefaultGroup(pValue: integer);
begin
  if fDefaultGroup = pValue then Exit;
  fDefaultGroup := pValue;
  fNestList.FoldGroup := fDefaultGroup;
end;

procedure TSynEditMarkupFoldColors.SetFoldColorInfosCount(pNewCount: Integer);
begin
  if pNewCount > fFoldColorInfosCapacity then begin
    // expand array
    fFoldColorInfosCapacity := pNewCount + 49;
    SetLength(fFoldColorInfos, fFoldColorInfosCapacity);
  end;
  fFoldColorInfosCount := pNewCount;
end;

procedure TSynEditMarkupFoldColors.InitNestList;
begin
  if Assigned(fNestList) then
    fNestList.Lines := Lines;
  if Assigned(fNestList2) then
    fNestList2.Lines := Lines;
end;

procedure TSynEditMarkupFoldColors.DoTextChanged(pStartLine, pEndLine, pCountDiff: Integer);
var
  lNode: TSynFoldNodeInfo;
  lNodeIdx, lEndLine, lLineIdx, lBottomLine, lDecreaseCount, lOuterNodeIdx: Integer;
  nl: LongInt;
  x: TColumnCacheEntry;
  {$IFDEF SynEditMarkupFoldColoringDebug}
  t: QWord;
  {$ENDIF}
begin
  if not Enabled then
    exit;

  {$IFDEF SynEditMarkupFoldColoringDebug}
  //DebugLn('   DoTextChanged %d-%d: %d', [StartLine, EndLine, ACountDiff]);
  {$ENDIF}

  // lines available?
  if Lines.Count = 0 then
    exit;

  // called by accident
  if pStartLine = 0 then
    exit;

  // no TSynCustomFoldHighlighter
  if not Assigned(fHighlighter) then
    exit;

  fHighlighter.CurrentLines := Lines;
  // highlighter still scanning
  if fHighlighter.NeedScan then
    exit;

  {$IFDEF SynEditMarkupFoldColoringDebug}
  t := GetTickCount64;
  {$ENDIF}

  if pEndLine < 0 then
    pEndLine := pStartLine
  else
    // pEndLine seems to be the first line after the change
    pEndLine := pEndLine - 1;
  lEndLine := pEndLine;
  FColumnCache[ToIdx(lEndLine)] := FirstCharacterColumn[ToIdx(lEndLine)];
  x := FColumnCache[ToIdx(lEndLine)];
  lBottomLine := TCustomSynEdit(SynEdit).TopLine + TCustomSynEdit(SynEdit).LinesInWindow;

  fNestList.Clear;
  fNestList2.Clear;
  lLineIdx := ToIdx(pStartLine);
  fNestList.Line := lLineIdx;
  lNodeIdx := fNestList.Count - 1;
  lOuterNodeIdx := -1;
  if lNodeIdx >= 0 then begin
    lDecreaseCount := 2;
    while (lNodeIdx >= 0)
    and (lDecreaseCount > 0) do begin
      dec(lDecreaseCount);
      while lNodeIdx >= 0 do begin
        dec(lNodeIdx);
        lNode := fNestList.HLNode[lNodeIdx];
        if not (sfaInvalid in lNode.FoldAction)
        and (sfaOutline in lNode.FoldAction)
        and not (sfaOutlineKeepLevel in lNode.FoldAction)
        and not (
          (sfaOpen in lNode.FoldAction)
          and (lNode.LineIndex = lLineIdx)
        ) then begin
          if lNodeIdx >= 0 then
            lOuterNodeIdx := lNodeIdx;
          break;
        end;
      end;
    end;
  end;
  if (lOuterNodeIdx >= 0) then begin
    lEndLine := ToPos(fNestList.NodeEndLine[lOuterNodeIdx]);
  end else begin
    // if there is no outer Outline:
    // expand lEndline if x is left to FirstCharacterColumn;
    lLineIdx := ToIdx(lEndLine);
    while x <= FirstCharacterColumn[lLineIdx] do begin
      // if x (FirstCharacterColoum of pStartLine) is left or equal to
      // the FirstCharacterColumn of line lLineIdx
      // then the real pEndLine is at the pEndLine of the lines last node
      fNestList.Line := lLineIdx;
      if fNestList.Count = 0 then
        break;
      nl := fNestList.NodeEndLine[fNestList.Count - 1];
      if nl = lLineIdx then
        break;
      {$IFDEF SynEditMarkupFoldColoringDebug}
      DebugLn('   %d -> %d [%d/%d]', [lLineIdx, nl, x, FirstCharacterColumn[nl]]);
      {$ENDIF}
      lLineIdx := nl;
    end;
    lLineIdx := ToPos(lLineIdx);
    lEndLine := Max(lEndLine, lLineIdx);
  end;

  // invalidate cache
  FColumnCache.LineTextChanged(ToIdx(pStartLine), Max(pEndLine, lEndLine) - pStartLine);

  if lEndLine > pEndLine then begin
    {$IFDEF SynEditMarkupFoldColoringDebug}
    //DebugLn('   InvalidateSynLines(%d, %d)', [EndLine + 1, lEndLine]);
    {$ENDIF}
    InvalidateSynLines(pEndLine + 1 , Min(lEndLine, lBottomLine));
  end;

  {$IFDEF SynEditMarkupFoldColoringDebug}
  DebugLn('*** DoTextChanged %d-%d-%d-%d duration=%d', [pStartLine, pEndLine, lEndLine, lBottomLine, GetTickCount64 - t]);
  {$ENDIF}

end;

procedure TSynEditMarkupFoldColors.SetLines(const pValue: TSynEditStrings);
var
  old: TSynEditStrings;
begin
  if Lines <> nil then
    Lines.Ranges[Self] := nil;
  if Enabled then begin
    old := Lines;
    if Assigned(old)
    and (pValue <> old) then begin
      // change:
      // remove Changehandler
      old.RemoveChangeHandler(senrHighlightChanged, @HighlightChanged);
      old.RemoveNotifyHandler(senrTextBufferChanged, @TextBufferChanged);
      FColumnCache.Invalidate;
    end;
  end;
  inherited SetLines(pValue);
  if Enabled then begin
    if (pValue <> old) then begin
      // change:
      if Assigned(pValue) then begin
        // add Changehandler
        pValue.AddChangeHandler(senrHighlightChanged, @HighlightChanged);
        pValue.AddNotifyHandler(senrTextBufferChanged, @TextBufferChanged);
        InitNestList;
      end else begin
        if Assigned(fNestList) then
          fNestList.Lines := nil;
        if Assigned(fNestList2) then
          fNestList2.Lines := nil;
        //DebugLn('*** SetLines');
      end;
    end;
  end;
  if (Lines <> nil) and Enabled then begin
    FColumnCache.Capacity := Lines.Capacity;
    FColumnCache.Count := Lines.Count;
    Lines.Ranges[Self] := FColumnCache;
  end;
  FColumnCache.Invalidate;
end;

procedure TSynEditMarkupFoldColors.HighlightChanged(pSender: TSynEditStrings;
  pIndex, pCount: Integer);
var
  newHighlighter: TSynCustomHighlighter;
begin
  {$IFDEF SynEditMarkupFoldColoringDebug}
  //DebugLn('   HighlightChanged: aIndex=%d aCount=%d', [aIndex, aCount]);
  {$ENDIF}

  if (pIndex <> -1)
  or (pCount <> -1) then
    exit;

  newHighlighter := TCustomSynEdit(self.SynEdit).Highlighter;
  if Assigned(newHighlighter)
  and not (newHighlighter is TSynCustomFoldHighlighter) then
    newHighlighter := nil;

  if (newHighlighter = fHighlighter) then
    exit;

  fHighlighter := TSynCustomFoldHighlighter(newHighlighter);

  fNestList.HighLighter := fHighlighter;
  fNestList2.HighLighter := fHighlighter;

  if not Enabled then
    exit;

  FColumnCache.Invalidate;
end;

procedure TSynEditMarkupFoldColors.DoEnabledChanged(pSender: TObject);
begin
  if Enabled = fLastEnabled then
    exit;
  fLastEnabled := Enabled;
  if fLastEnabled then begin
    {$IFDEF SynEditMarkupFoldColoringDebug}
    //DebugLn('   *** TSynEditMarkupFoldColors Enabled');
    {$ENDIF}
    if Assigned(Lines) then begin
      // add Changehandler
      Lines.AddChangeHandler(senrHighlightChanged, @HighlightChanged);
      Lines.AddNotifyHandler(senrTextBufferChanged, @TextBufferChanged);
      InitNestList;
    end;
  end else begin
    {$IFDEF SynEditMarkupFoldColoringDebug}
    //DebugLn('   *** TSynEditMarkupFoldColors Disabled');
    {$ENDIF}
    if Assigned(Lines) then begin
      // remove Changehandler
      Lines.RemoveChangeHandler(senrHighlightChanged, @HighlightChanged);
      Lines.RemoveNotifyHandler(senrTextBufferChanged, @TextBufferChanged);
    end;
  end;

  if Assigned(Lines) then begin
    if Enabled then begin
      FColumnCache.Capacity := Lines.Capacity;
      FColumnCache.Count := Lines.Count;
      Lines.Ranges[Self] := FColumnCache;
      FColumnCache.Invalidate;
    end
    else
      Lines.Ranges[Self] := nil;

    InvalidateSynLines(1, Lines.Count);
  end;
end;

procedure TSynEditMarkupFoldColors.ColorChanged(pMarkup: TObject);
begin
  if Assigned(Lines) then
    InvalidateSynLines(1, Lines.Count);
end;

end.


