1 {-------------------------------------------------------------------------------
2 The contents of this file are subject to the Mozilla Public License
3 Version 1.1 (the "License"); you may not use this file except in compliance
4 with the License. You may obtain a copy of the License at
5 http://www.mozilla.org/MPL/
6 
7 Software distributed under the License is distributed on an "AS IS" basis,
8 WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
9 the specific language governing rights and limitations under the License.
10 
11 The Original Code is: SynEditMarkupFoldColoring.pas, released 2015-12-07.
12 Copyleft (c) 2015-2016 x2nie - Fathony Luth.
13 
14 The Original SynEdit Project is based on mwCustomEdit.pas by Martin Waldenburg,
15 part of the mwEdit component suite.
16 Portions created by Martin Waldenburg are Copyright (C) 1998 Martin Waldenburg.
17 All Rights Reserved.
18 
19 Contributors to the SynEdit and mwEdit projects are listed in the
20 Contributors.txt file.
21 
22 Alternatively, the contents of this file may be used under the terms of the
23 GNU General Public License Version 2 or later (the "GPL"), in which case
24 the provisions of the GPL are applicable instead of those above.
25 If you wish to allow use of your version of this file only under the terms
26 of the GPL and not to allow others to use your version of this file
27 under the MPL, indicate your decision by deleting the provisions above and
28 replace them with the notice and other provisions required by the GPL.
29 If you do not delete the provisions above, a recipient may use your version
30 of this file under either the MPL or the GPL.
31 
32 
33 
34 Features:
35   - paint keywords in multiple colors, depends on fold block level or by config
36   - paint vertical line between paired open~close fold
37   - vertical line and/or keyword can be disabled
38   - independent, can be used for any SynHighlighter
39   - many features are well tested for PasSynPas.pas
40   - only active when SynEdit.Highlighter is TSynCustomFoldHighlighter
41 
42 -------------------------------------------------------------------------------}
43 unit SynEditMarkupFoldColoring;
44 
45 {$mode objfpc}{$H+}
46 { $define SynEditMarkupFoldColoringDebug}
47 { $define WithSynMarkupFoldColorDebugGutter}
48 
49 interface
50 
51 uses
52   Classes, SysUtils, Graphics, SynEditMarkup, SynEditMiscClasses, Controls,
53   LCLProc, LCLType, SynEditHighlighter,
54   SynEditHighlighterFoldBase, LazSynEditText, SynEditTextBase, SynEditTypes
55   {$IFDEF WithSynMarkupFoldColorDebugGutter}, SynGutterBase, SynTextDrawer{$ENDIF}
56 ;
57 
58 type
59 
60   {$IFDEF WithSynMarkupFoldColorDebugGutter}
61   TSynEditMarkupFoldColors = class;
62 
63   { TIDESynMarkupFoldColorDebugGutter }
64 
65   TIDESynMarkupFoldColorDebugGutter = class(TSynGutterPartBase)
66   protected
67     FOwner: TSynEditMarkupFoldColors;
PreferedWidthnull68     function  PreferedWidth: Integer; override;
69   public
70     procedure Paint(Canvas: TCanvas; AClip: TRect; FirstLine, LastLine: integer);
71       override;
72   end;
73   {$ENDIF}
74 
75   PMarkupFoldColorInfo = ^TMarkupFoldColorInfo;
76   TMarkupFoldColorInfo = record
77     PhysX, PhysX2, PhysCol: Integer;
78     ColorIdx: Integer;
79     Border  : Boolean;
80     Ignore  : Boolean; //no color no line
81     SrcNode : TSynFoldNodeInfo;
82     Level, LevelAfter : integer; //needed by non nest nodes
83   end;
84 
85   TMarkupFoldColorInfos = array of TMarkupFoldColorInfo;
86   TSynFoldNodeInfos     = array of TSynFoldNodeInfo; //for quick compare detection
87 
88   TColumnCacheEntry = Integer;
89   PColumnCacheEntry = ^TColumnCacheEntry;
90 
91   { TMarkupFoldColorsLineColor }
92 
93   TMarkupFoldColorsLineColor = class
94   private
95     FAlpha: Byte;
96     FColor: TColor;
97     FOnChange: TNotifyEvent;
98     FOnChanged: TNotifyEvent;
99     FPriority: Integer;
100     FStyle: TSynLineStyle;
101     procedure SetAlpha(AValue: Byte);
102     procedure SetColor(AValue: TColor);
103     procedure SetPriority(AValue: Integer);
104     procedure SetStyle(AValue: TSynLineStyle);
105     procedure Changed;
106   public
107     constructor Create;
108     property Color: TColor read FColor write SetColor; // clDefault will take Color[].Frame or Color[].Foreground
109     property Style: TSynLineStyle read FStyle write SetStyle;
110     property Alpha: Byte read FAlpha write SetAlpha;
111     property Priority: Integer read FPriority write SetPriority;
112     property OnChange: TNotifyEvent read FOnChanged write FOnChanged;
113   end;
114 
115   { TSynEditMarkupFoldColorsColumnCache }
116 
117   TSynEditMarkupFoldColorsColumnCache = class(TSynManagedStorageMem)
118   private
GetColumnDatanull119     function GetColumnData(Index: Integer): TColumnCacheEntry;
GetIsValidForLinenull120     function GetIsValidForLine(Index: Integer): Boolean;
121     procedure SetColumnData(Index: Integer; AValue: TColumnCacheEntry);
122   protected
123     procedure LineTextChanged(AIndex: Integer; ACount: Integer = 1); override;
124     procedure InsertedLines(AIndex, ACount: Integer); override;
125     //procedure DeletedLines(AIndex, ACount: Integer); override;
126     procedure Invalidate;
127   public
128     constructor Create;
129     property ColumnData[Index: Integer]: TColumnCacheEntry read GetColumnData write SetColumnData; default;
130     property IsValidForLine[Index: Integer]: Boolean read GetIsValidForLine;
131   end;
132 
133   { TSynEditMarkupFoldColors }
134 
135   TSynEditMarkupFoldColors = class(TSynEditMarkup)
136   private
137     {$IFDEF WithSynMarkupFoldColorDebugGutter}
138     FDebugGutter: TIDESynMarkupFoldColorDebugGutter;
139     {$ENDIF}
GetFirstCharacterColumnnull140     function GetFirstCharacterColumn(pIndex: Integer): TColumnCacheEntry;
141     procedure TextBufferChanged(pSender: TObject);
142   private
143     FColorCount: Integer;
144     fHighlighter: TSynCustomFoldHighlighter;
145     fMarkupColors: array of TSynSelectedColor;
146     fLineColors : array of TMarkupFoldColorsLineColor;
147     fNestList, fNestList2: TLazSynEditNestedFoldsList;
148 
149     // cache
150     FColumnCache: TSynEditMarkupFoldColorsColumnCache;
151     fFoldColorInfosCount,
152     fFoldColorInfosCapacity: Integer;
153 
154     fDefaultGroup: integer;
155     fFoldColorInfos: TMarkupFoldColorInfos;
156 
157     fPreparedRow: integer;
158     fLastOpenNode: TSynFoldNodeInfo;
159     fLastIndex,
160     fLastOpenIndex: Integer;
161     fLastEnabled: Boolean;
162 
163     procedure DoMarkupParentFoldAtRow(pRow: Integer);
164     procedure DoMarkupParentCloseFoldAtRow(pRow: Integer);
GetColornull165     function  GetColor(pIndex: Integer): TSynSelectedColor;
GetLineColornull166     function  GetLineColor(pIndex: Integer): TMarkupFoldColorsLineColor;
167     procedure SetColorCount(AValue: Integer);
168     procedure SetDefaultGroup(pValue: integer);
169     procedure SetFoldColorInfosCount(pNewCount: Integer);
170     procedure InitNestList;
171     property FirstCharacterColumn[pIindex: Integer]: TColumnCacheEntry read GetFirstCharacterColumn;
172   protected
173     // Notifications about Changes to the text
174     procedure DoTextChanged({%H-}pStartLine, pEndLine, {%H-}pCountDiff: Integer); override; // 1 based
175     procedure SetLines(const pValue: TSynEditStrings); override;
176     procedure HighlightChanged(pSender: TSynEditStrings; pIndex, pCount: Integer);
177     procedure DoEnabledChanged(pSender: TObject); override;
178     procedure ColorChanged(pMarkup: TObject);
179   public
180     constructor Create(pSynEdit : TSynEditBase);
181     destructor Destroy; override;
RealEnablednull182     function RealEnabled: Boolean; override;
183     procedure BeginMarkup; override;
GetMarkupAttributeAtRowColnull184     function GetMarkupAttributeAtRowCol(const pRow: Integer;
185                                         const pStartCol: TLazSynDisplayTokenBound;
186                                         const {%H-}pRtlInfo: TLazSynDisplayRtlInfo): TSynSelectedColor; override;
187     procedure GetNextMarkupColAfterRowCol(const pRow: Integer;
188                                          const pStartCol: TLazSynDisplayTokenBound;
189                                          const {%H-}pRtlInfo: TLazSynDisplayRtlInfo;
190                                          out   pNextPhys, pNextLog: Integer); override;
191 
192     procedure PrepareMarkupForRow(pRow : Integer); override;
193     property DefaultGroup : integer read fDefaultGroup write SetDefaultGroup;
194     property ColorCount: Integer read FColorCount write SetColorCount;
195     property Color[pIndex: Integer]: TSynSelectedColor read GetColor;
196     property LineColor[pIndex: Integer]: TMarkupFoldColorsLineColor read GetLineColor;
197   end;
198 
199 implementation
200 uses
201   SynEdit,
202   SynEditMiscProcs,
203   {$IFDEF SynEditMarkupFoldColoringDebug}
204   SynHighlighterPas,
205   strutils,
206   {$endif}
207   Dialogs;
208 
209 { TMarkupFoldColorsLineColor }
210 
211 procedure TMarkupFoldColorsLineColor.SetColor(AValue: TColor);
212 begin
213   if FColor = AValue then Exit;
214   FColor := AValue;
215   Changed;
216 end;
217 
218 procedure TMarkupFoldColorsLineColor.SetAlpha(AValue: Byte);
219 begin
220   if FAlpha = AValue then Exit;
221   FAlpha := AValue;
222   Changed;
223 end;
224 
225 procedure TMarkupFoldColorsLineColor.SetPriority(AValue: Integer);
226 begin
227   if FPriority = AValue then Exit;
228   FPriority := AValue;
229   Changed;
230 end;
231 
232 procedure TMarkupFoldColorsLineColor.SetStyle(AValue: TSynLineStyle);
233 begin
234   if FStyle = AValue then Exit;
235   FStyle := AValue;
236   Changed;
237 end;
238 
239 procedure TMarkupFoldColorsLineColor.Changed;
240 begin
241   if FOnChange <> nil then
242     FOnChange(Self);
243 end;
244 
245 constructor TMarkupFoldColorsLineColor.Create;
246 begin
247   FStyle := slsSolid;
248   FColor := clDefault;
249   FPriority := 0;
250   inherited;
251 end;
252 
253 { TSynEditMarkupFoldColorsColumnCache }
254 
TSynEditMarkupFoldColorsColumnCache.GetColumnDatanull255 function TSynEditMarkupFoldColorsColumnCache.GetColumnData(Index: Integer
256   ): TColumnCacheEntry;
257 begin
258   Result := PColumnCacheEntry(ItemPointer[Index])^;
259 end;
260 
GetIsValidForLinenull261 function TSynEditMarkupFoldColorsColumnCache.GetIsValidForLine(Index: Integer
262   ): Boolean;
263 begin
264   Result := PColumnCacheEntry(ItemPointer[Index])^ > 0;
265 end;
266 
267 procedure TSynEditMarkupFoldColorsColumnCache.SetColumnData(Index: Integer;
268   AValue: TColumnCacheEntry);
269 begin
270   PColumnCacheEntry(ItemPointer[Index])^ := AValue;
271 end;
272 
273 procedure TSynEditMarkupFoldColorsColumnCache.LineTextChanged(AIndex: Integer;
274   ACount: Integer);
275 var
276   i: Integer;
277 begin
278   for i := AIndex to AIndex + ACount - 1 do
279     ColumnData[i] := 0;
280 end;
281 
282 procedure TSynEditMarkupFoldColorsColumnCache.InsertedLines(AIndex,
283   ACount: Integer);
284 var
285   i: Integer;
286 begin
287   for i := AIndex to AIndex + ACount - 1 do
288     ColumnData[i] := 0;
289 end;
290 
291 procedure TSynEditMarkupFoldColorsColumnCache.Invalidate;
292 var
293   i: Integer;
294 begin
295   for i := 0 to Count - 1 do
296     ColumnData[i] := 0;
297 end;
298 
299 constructor TSynEditMarkupFoldColorsColumnCache.Create;
300 begin
301   inherited;
302   ItemSize := SizeOf(TColumnCacheEntry);
303 end;
304 
305 {$IFDEF WithSynMarkupFoldColorDebugGutter}
306 { TIDESynMarkupFoldColorDebugGutter }
307 
PreferedWidthnull308 function TIDESynMarkupFoldColorDebugGutter.PreferedWidth: Integer;
309 begin
310   Result := 600;
311 end;
312 
313 procedure TIDESynMarkupFoldColorDebugGutter.Paint(Canvas: TCanvas;
314   AClip: TRect; FirstLine, LastLine: integer);
315 var
316   TextDrawer: TheTextDrawer;
317   dc: HDC;
318   rcLine: TRect;
319   LineHeight, c, i, j: Integer;
320   iLine: LongInt;
321   s, fc: string;
322 begin
323   TextDrawer := Gutter.TextDrawer;
324   dc := Canvas.Handle;
325   TextDrawer.BeginDrawing(dc);
326   try
327     TextDrawer.SetBackColor(Gutter.Color);
328     TextDrawer.SetForeColor(TCustomSynEdit(SynEdit).Font.Color);
329     TextDrawer.SetFrameColor(clNone);
330     with AClip do
331       TextDrawer.ExtTextOut(Left, Top, ETO_OPAQUE, AClip, nil, 0);
332 
333     rcLine := AClip;
334     rcLine.Bottom := AClip.Top;
335     LineHeight := TCustomSynEdit(SynEdit).LineHeight;
336     c := TCustomSynEdit(SynEdit).Lines.Count;
337     for i := FirstLine to LastLine do
338     begin
339       iLine := FoldView.DisplayNumber[i];
340       if (iLine <= 0) or (iLine > c) then break;
341       // next line rect
342       rcLine.Top := rcLine.Bottom;
343       rcLine.Bottom := rcLine.Bottom + LineHeight;
344 
345       FOwner.PrepareMarkupForRow(iLine);
346       s := '';
347       for j := 0 to FOwner.fFoldColorInfosCount - 1 do begin
348         with FOwner.fFoldColorInfos[j] do
349           s := s + '('
350            + IntToStr(PhysX) + ',' + IntToStr(PhysX2) + ',' + IntToStr(PhysCol) + '/'
351            + IntToStr(ColorIdx) + '/'
352            + BoolToStr(Border, True)[1] + BoolToStr(Ignore, True)[1] + '/'
353            + IntToStr(Level) + ',' + IntToStr(LevelAfter)
354            + ') ';
355       while length(s) < 21 * (j+1) do s := s + ' ';
356       end;
357       s := IntToStr(FOwner.fFoldColorInfosCount) + s;
358       if iLine < FOwner.FColumnCache.Count then
359         s := s + ', '+IntToStr(FOwner.FColumnCache[ToIdx(iLine)]);
360 
361       TextDrawer.ExtTextOut(rcLine.Left, rcLine.Top, ETO_OPAQUE or ETO_CLIPPED, rcLine,
362         PChar(Pointer(S)),Length(S));
363     end;
364 
365   finally
366     TextDrawer.EndDrawing;
367   end;
368 end;
369 {$ENDIF}
370 
371 
372 {$IFDEF SynEditMarkupFoldColoringDebug}
FoldTypeToStrnull373 function FoldTypeToStr(p_FoldType: Pointer): String;
374 begin
375   WriteStr(Result, TPascalCodeFoldBlockType(PtrUInt(p_FoldType)));
376   while length(Result) < 17 do Result := Result + ' ';
377 end;
378 {$ENDIF}
379 
380 
381 { TSynEditMarkupFoldColors }
382 
383 constructor TSynEditMarkupFoldColors.Create(pSynEdit: TSynEditBase);
384 begin
385   inherited Create(pSynEdit);
386 
387   {$IFDEF WithSynMarkupFoldColorDebugGutter}
388   FDebugGutter := TIDESynMarkupFoldColorDebugGutter.Create(TSynEdit(pSynEdit).RightGutter.Parts);
389   FDebugGutter.FOwner := Self;
390   {$ENDIF}
391 
392   FColumnCache := TSynEditMarkupFoldColorsColumnCache.Create;
393 
394   fHighlighter := TSynCustomFoldHighlighter(TCustomSynEdit(SynEdit).Highlighter);
395   if Assigned(fHighlighter)
396   and not (fHighlighter  is TSynCustomFoldHighlighter) then
397     fHighlighter := nil;
398 
399   fDefaultGroup := 0;
400   fFoldColorInfosCount := 0;
401   SetLength(fFoldColorInfos, 50);
402   fFoldColorInfosCapacity := 50;
403 
404   fNestList := TLazSynEditNestedFoldsList.Create(Lines, fHighlighter);
405   fNestList.ResetFilter;
406   fNestList.FoldGroup := fDefaultGroup;
407   fNestList.FoldFlags := [sfbIncludeDisabled];
408   fNestList.IncludeOpeningOnLine := True;
409 
410   // for scanning the "if" of a "then" to find the indent
411   fNestList2 := TLazSynEditNestedFoldsList.Create(Lines, fHighlighter);
412   fNestList2.ResetFilter;
413   fNestList2.FoldGroup := fDefaultGroup;
414   fNestList2.FoldFlags := [sfbIncludeDisabled];
415   fNestList2.IncludeOpeningOnLine := False;
416 
417   MarkupInfo.Foreground := clGreen;
418   MarkupInfo.Background := clNone;
419   MarkupInfo.Style := [];
420   MarkupInfo.StyleMask := [];
421   MarkupInfo.FrameEdges:= sfeLeft;
422 
423   SetColorCount(6);
424   fMarkupColors[0].Foreground  := clRed;
425   fMarkupColors[1].Foreground  := $000098F7; //orange
426   fMarkupColors[2].Foreground  := $0022CC40; //green
427   fMarkupColors[3].Foreground  := $00CCCC00; //cyan
428   fMarkupColors[4].Foreground  := $00FF682A; //blue
429   fMarkupColors[5].Foreground  := $00CF00C4; //purple
430 end;
431 
432 destructor TSynEditMarkupFoldColors.Destroy;
433 begin
434   if Lines <> nil then
435     Lines.Ranges[Self] := nil;
436   FColumnCache.Free;
437 
438   ColorCount := 0;
439   if Assigned(Lines) then begin
440     Lines.RemoveChangeHandler(senrHighlightChanged, @HighlightChanged);
441     Lines.RemoveNotifyHandler(senrTextBufferChanged, @TextBufferChanged);
442   end;
443   FreeAndNil(fNestList);
444   FreeAndNil(fNestList2);
445   inherited Destroy;
446 end;
447 
TSynEditMarkupFoldColors.RealEnablednull448 function TSynEditMarkupFoldColors.RealEnabled: Boolean;
449 begin
450   Result := (not IsTempDisabled) and Enabled and (FColorCount > 0);
451 end;
452 
453 procedure TSynEditMarkupFoldColors.BeginMarkup;
454 begin
455   {$IFDEF SynEditMarkupFoldColoringDebug}
456   //DebugLn('BeginMarkup');
457   {$ENDIF}
458   inherited BeginMarkup;
459   if not Assigned(fHighlighter) then
460     exit;
461   fNestList.Clear; // for next markup start
462   fNestList2.Clear;
463 end;
464 
TSynEditMarkupFoldColors.GetMarkupAttributeAtRowColnull465 function TSynEditMarkupFoldColors.GetMarkupAttributeAtRowCol(
466   const pRow: Integer; const pStartCol: TLazSynDisplayTokenBound;
467   const pRtlInfo: TLazSynDisplayRtlInfo): TSynSelectedColor;
468 var
469   i, x2both: integer;
470 begin
471   Result := nil;
472   if not Assigned(fHighlighter) then exit;
473   if (fPreparedRow = pRow) then begin
474     {$IFDEF SynEditMarkupFoldColoringDebug}
475     //DebugLn('   GetMarkupAttributeAtRowCol %d/%d', [aRow, aStartCol.Logical]);
476     {$ENDIF}
477 
478     x2both := 0;
479     for i := 0 to fFoldColorInfosCount - 1 do
480       with fFoldColorInfos[i] do
481         if not Ignore
482         and (PhysX < PhysX2)
483         and (ColorIdx >= 0)
484         and (pStartCol.Physical >= PhysX)
485         and (pStartCol.Physical < PhysX2) then begin
486           {$IFDEF SynEditMarkupFoldColoringDebug}
487           //DebugLn('      X=%d X2=%d Y=%d, C=%d B=%s I=%s', [X, X2, Y, ColorIdx, IfThen(Border, 'X', '-'), IfThen(Ignore, 'X', '-')]);
488           {$ENDIF}
489 
490           Result := MarkupInfo;
491           x2both := max(x2both, PhysX2);
492           if Border then begin
493             MarkupInfo.Clear;
494             MarkupInfo.SetFrameBoundsPhys(PhysX, x2both);
495             MarkupInfo.FrameAlpha := fLineColors[ColorIdx].Alpha;
496             MarkupInfo.FramePriority := fLineColors[ColorIdx].Priority;
497             MarkupInfo.FrameColor := fLineColors[ColorIdx].Color;
498             if MarkupInfo.FrameColor = clDefault then begin
499               if (fMarkupColors[ColorIdx].FrameColor <> clNone) and
500                  (fMarkupColors[ColorIdx].FrameColor <> clDefault)
501               then
502                 MarkupInfo.FrameColor := fMarkupColors[ColorIdx].FrameColor
503               else
504                 MarkupInfo.FrameColor := fMarkupColors[ColorIdx].Foreground;
505             end;
506             MarkupInfo.FrameStyle := fLineColors[ColorIdx].Style;
507             MarkupInfo.FrameEdges := sfeLeft;
508           end else begin
509             MarkupInfo.Assign(fMarkupColors[ColorIdx]);
510             MarkupInfo.SetFrameBoundsPhys(PhysX, PhysX2);
511           end;
512         end;
513   end;
514 end;
515 
516 procedure TSynEditMarkupFoldColors.GetNextMarkupColAfterRowCol(
517   const pRow: Integer; const pStartCol: TLazSynDisplayTokenBound;
518   const pRtlInfo: TLazSynDisplayRtlInfo; out pNextPhys, pNextLog: Integer);
519 var i : integer;
520 begin
521   {$IFDEF SynEditMarkupFoldColoringDebug}
522   //DebugLn('GetNextMarkupColAfterRowCol %d/%d', [aRow, aStartCol.Logical]);
523   {$ENDIF}
524   if not Assigned(fHighlighter)
525   or (fPreparedRow <> pRow) then
526     exit;
527 
528   pNextLog := -1;
529   pNextPhys := -1;
530   for i := 0 to fFoldColorInfosCount - 1  do
531     with fFoldColorInfos[i] do begin
532       if not Ignore and (ColorIdx >= 0) and
533          (  ((not Border) and  fMarkupColors[ColorIdx].IsEnabled) or
534             (Border and (fLineColors[ColorIdx].Color <> clNone))
535          ) and
536          (PhysX < PhysX2) and (pStartCol.Physical < PhysX) and (pStartCol.Physical <= PhysX2)
537       then begin
538         pNextPhys := fFoldColorInfos[i].PhysX;
539         break;
540       end;
541     end;
542 end;
543 
TSynEditMarkupFoldColors.GetFirstCharacterColumnnull544 function TSynEditMarkupFoldColors.GetFirstCharacterColumn(pIndex: Integer
545   ): TColumnCacheEntry;
546 var
547   l: String;
548   s, p: Integer;
549 begin
550   l := SynEdit.Lines[pIndex];
551   s := length(l);
552   p := 1;
553   while (p <= s)
554   //and (l[p] in [#9, #32, '/']) do inc(p);
555   and (l[p] in [#9, #32]) do inc(p);
556   if p > s then
557     exit(high(Result));
558   Result := TCustomSynEdit(SynEdit).LogicalToPhysicalPos(Point(p, toPos(pIndex))).x;
559 end;
560 
561 procedure TSynEditMarkupFoldColors.TextBufferChanged(pSender: TObject);
562 begin
563   if pSender <> nil then
564     TSynEditStrings(pSender).Ranges[Self] := nil;
565 
566   if not Enabled then
567     exit;
568   InitNestList;
569 
570   FColumnCache.Capacity := SynEdit.Lines.Capacity;
571   FColumnCache.Count := SynEdit.Lines.Count;
572   Lines.Ranges[Self] := FColumnCache;
573 
574   FColumnCache.Invalidate;
575 end;
576 
577 procedure TSynEditMarkupFoldColors.DoMarkupParentFoldAtRow(pRow: Integer);
578 var
579   lNodeCol: TColumnCacheEntry;
580   i, lLvl, lLineIdx, lCurIndex: Integer;
581   lCurNode: TSynFoldNodeInfo;
582 
583   procedure AddVerticalLine;
584   begin
585     with fFoldColorInfos[lCurIndex] do begin
586       SrcNode:= lCurNode; //needed by close node
587       PhysCol := lNodeCol;
588       PhysX := lNodeCol;
589       PhysX2 := PhysX + 1;
590       Border := PhysX < GetFirstCharacterColumn(lLineIdx); // use real one here not cache
591       Ignore :=
592         (Border and (sfaOutlineNoLine in lCurNode.FoldAction))
593         or (not Border);
594       Level := lLvl;
595       ColorIdx := Max(0, lLvl) mod FColorCount;
596       {$IFDEF SynEditMarkupFoldColoringDebug}
597       //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)]);
598       {$ENDIF}
599     end;
600   end;
601 
602   procedure InitColumnForKeepLvl(LineIdx, FoldGroup: Integer);
603   var
604     LevelOpenNode: TSynFoldNodeInfo;
605     i, ONodeFirstCol: integer;
606   begin
607     ONodeFirstCol := FirstCharacterColumn[LineIdx];
608     FColumnCache[LineIdx] := ONodeFirstCol;
609 
610     fNestList2.Line := LineIdx;
611     fNestList2.FoldGroup := FoldGroup;
612     if fNestList2.Count = 0 then
613       exit;
614     LevelOpenNode := fNestList2.HLNode[fNestList2.Count-1];
615     if sfaInvalid in LevelOpenNode.FoldAction then
616       exit; // try node before ??
617 
618     if not FColumnCache.IsValidForLine[LevelOpenNode.LineIndex] then begin
619       if (LevelOpenNode.NestLvlStart <  fHighlighter.FoldBlockEndLevel(LevelOpenNode.LineIndex-1, lCurNode.FoldGroup, [sfbIncludeDisabled])) and
620          (sfaCloseAndOpen in LevelOpenNode.FoldAction)
621       then
622         InitColumnForKeepLvl(LevelOpenNode.LineIndex, FoldGroup)
623       else
624         FColumnCache[LevelOpenNode.LineIndex] := FirstCharacterColumn[LevelOpenNode.LineIndex];
625       if not FColumnCache.IsValidForLine[LevelOpenNode.LineIndex] then
626         exit;
627     end;
628     i := FColumnCache[LevelOpenNode.LineIndex];
629     FColumnCache[LineIdx] := min(i, ONodeFirstCol);
630   end;
631 
632 var
633   lKeepLevel: Boolean;
634   LastNode: TSynFoldNodeInfo;
635   cnf: TSynCustomFoldConfig;
636   cnfCnt, fType: Integer;
637 begin
638   lLineIdx := ToIdx(pRow);
639   fNestList.Line := lLineIdx;
640   fHighlighter.CurrentLines := Lines;
641   LastNode.LineIndex := -1;
642 
643   // get first character for current line
644   if not FColumnCache.IsValidForLine[lLineIdx] then
645     FColumnCache[lLineIdx] := FirstCharacterColumn[lLineIdx];
646 
647   lLvl := 0;
648   cnfCnt := TSynCustomFoldHighlighter(Highlighter).FoldConfigCount;
649   i := 0; // starting at the node with the lowest line number
650   while i < fNestList.Count do begin
651     fType := PtrInt(fNestList.NodeFoldType[i]);
652     if fType >= cnfCnt then begin
653       inc(i);
654       continue;
655     end;
656     cnf := TSynCustomFoldHighlighter(Highlighter).FoldConfig[fType];
657     if (not cnf.Enabled) or not(fmOutline in cnf.Modes) then begin
658       inc(i);
659       continue;
660     end;
661     lCurNode := fNestList.HLNode[i];
662     // sanity check
663     Assert(sfaOpen in lCurNode.FoldAction, 'no sfaOpen in lCurNode.FoldAction');
664     {$IFDEF SynEditMarkupFoldColoringDebug}
665     //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)]);
666     {$ENDIF}
667     if (sfaOutline in lCurNode.FoldAction)
668     and not (sfaInvalid in lCurNode.FoldAction)
669     and (lCurNode.LineIndex <> lLineIdx) then begin
670 
671       if ( sfaOutlineForceIndent in lCurNode.FoldAction) then
672         inc(lLvl)
673       else if ( sfaOutlineMergeParent in lCurNode.FoldAction) then
674         dec(lLvl);
675 
676       // new FoldColorInfo
677       SetFoldColorInfosCount(fFoldColorInfosCount + 1);
678       lCurIndex := fFoldColorInfosCount - 1;
679 
680       {$IFDEF SynEditMarkupFoldColoringDebug}
681       //if (fLastOpenNode.LineIndex >= 0) then
682       //  DebugLn('   %s %s - %s %s', [FoldTypeToStr(fLastOpenNode.FoldType), IfThen(sfaOutlineKeepLevel in fLastOpenNode.FoldAction, '(Keep)', ''), FoldTypeToStr(lCurNode.FoldType), IfThen(sfaOutlineKeepLevel in lCurNode.FoldAction, '(Keep)', '')]);
683       {$ENDIF}
684 
685 
686       // find lastnode // first opening node on this line, that is = hl.line[-1].endnestlevel (-1)
687       if (lCurNode.NestLvlStart <  fHighlighter.FoldBlockEndLevel(lCurNode.LineIndex-1, lCurNode.FoldGroup, [sfbIncludeDisabled])) and
688          (sfaCloseAndOpen in lCurNode.FoldAction)
689 // // TODO: check that this is the FIRST sfaCloseAndOpen on this line
690       then
691         InitColumnForKeepLvl(lCurNode.LineIndex, lCurNode.FoldGroup);
692 
693       if not FColumnCache.IsValidForLine[lCurNode.LineIndex] then
694         FColumnCache[lCurNode.LineIndex] := FirstCharacterColumn[lCurNode.LineIndex];
695       lNodeCol := FColumnCache[lCurNode.LineIndex];
696 
697       { do not keep level if two consecutive sfaOutlineKeepLevel nodes are
698         on different lines and start on different columns                  }
699       if (LastNode.LineIndex >= 0)
700       and (sfaOutlineKeepLevel in LastNode.FoldAction) then begin
701         {$IFDEF SynEditMarkupFoldColoringDebug}
702         //DebugLn('   %.5d/%.5d %.2d/%.2d', [LastNode.LineIndex+1,lCurNode.LineIndex+1, FFoldColorInfos[fLastIndex].PhysCol, lNodeCol]);
703         //DbgOut('    keep');
704         {$ENDIF}
705         lKeepLevel := True;
706         if (sfaOutlineKeepLevel in lCurNode.FoldAction)
707         and not (LastNode.LineIndex = lCurNode.LineIndex)
708         and not (fFoldColorInfos[fLastIndex].PhysCol = lNodeCol) then begin
709           {$IFDEF SynEditMarkupFoldColoringDebug}
710           //DbgOut(' not');
711           {$ENDIF}
712           inc(lLvl);
713           lKeepLevel := False;
714         end;
715         {$IFDEF SynEditMarkupFoldColoringDebug}
716         //DebugLn('');
717         {$ENDIF}
718       end else
719         lKeepLevel := False;
720 
721       AddVerticalLine;
722 
723       if lKeepLevel then begin
724         // keep level for none sfaOutlineKeepLevel after sfaOutlineKeepLevel
725         lLvl := fFoldColorInfos[fLastIndex].Level;
726         if fFoldColorInfos[fLastIndex].PhysX < fFoldColorInfos[lCurIndex].PhysX then
727           fFoldColorInfos[lCurIndex].PhysX := fFoldColorInfos[fLastIndex].PhysX;
728         fFoldColorInfos[lCurIndex].Level := lLvl;
729         fFoldColorInfos[lCurIndex].ColorIdx := Max(0, lLvl) mod FColorCount;
730         // overwrite first character column with new value
731         if fFoldColorInfos[fLastIndex].PhysX < FColumnCache[fFoldColorInfos[lCurIndex].SrcNode.LineIndex] then
732           FColumnCache[fFoldColorInfos[lCurIndex].SrcNode.LineIndex] := fFoldColorInfos[fLastIndex].PhysX;
733         {$IFDEF SynEditMarkupFoldColoringDebug}
734         //with FFoldColorInfos[lCurIndex] do
735         //  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)]);
736         {$ENDIF}
737       end;
738 
739       if not (sfaOutlineKeepLevel in lCurNode.FoldAction) then
740         inc(lLvl);
741 
742       LastNode := lCurNode;
743       fLastIndex := lCurIndex;
744       fLastOpenNode := lCurNode;
745       fLastOpenIndex := lCurIndex;
746 
747       with fFoldColorInfos[fFoldColorInfosCount - 1] do begin
748         LevelAfter  := lLvl;  // used in DoMarkupParentCloseFoldAtRow
749       end;
750     end;
751     inc(i);
752   end;
753 end;
754 
755 procedure TSynEditMarkupFoldColors.DoMarkupParentCloseFoldAtRow(pRow: Integer);
756 var
757   lMaxLevel, lvl, lCurIndex: Integer;
758   lCurNode: TSynFoldNodeInfo;
759   lKeepLevel: Boolean;
760   lNodeCol: TColumnCacheEntry;
761 
AddHighlightnull762   function AddHighlight: Boolean;
763   var
764     lPhysX, lPhysX2, j: Integer;
765   begin
766     Result := False;
767     // ignore implicit close nodes at end of line, especially if line is empty
768     // or at least has less characters as vertical line is on
769     if not(sfaCloseForNextLine in lCurNode.FoldAction) then begin
770       Result := True;
771       lPhysX := TCustomSynEdit(SynEdit).LogicalToPhysicalPos(Point(ToPos(lCurNode.LogXStart), ToPos(lCurNode.LineIndex))).x;
772       lPhysX2 := TCustomSynEdit(SynEdit).LogicalToPhysicalPos(Point(ToPos(lCurNode.LogXEnd), ToPos(lCurNode.LineIndex))).x;
773       if lCurNode.LogXStart < lCurNode.LogXEnd then begin
774         {$IFDEF SynEditMarkupFoldColoringDebug}
775         //DebugLn('    %d < %d', [lCurNode.LogXStart, lCurNode.LogXEnd]);
776         {$ENDIF}
777         for j := 0 to fFoldColorInfosCount - 1 do
778           if (fFoldColorInfos[j].PhysX = lPhysX)
779           and (fFoldColorInfos[j].Border)
780           and (fFoldColorInfos[j].SrcNode.FoldType = lCurNode.FoldType )
781           and (fFoldColorInfos[j].SrcNode.FoldLvlEnd = lCurNode.FoldLvlStart ) then begin
782             {$IFDEF SynEditMarkupFoldColoringDebug}
783             //DebugLn('      X2: %d->%d', [FFoldColorInfos[j].X2, lCurNode.LogXEnd + 1]);
784             {$ENDIF}
785             fFoldColorInfos[j].PhysX2 := lPhysX2;
786             fFoldColorInfos[j].Border := False;
787           end;
788       end;
789 
790       SetFoldColorInfosCount(fFoldColorInfosCount + 1);
791       lCurIndex := fFoldColorInfosCount - 1;
792       with fFoldColorInfos[lCurIndex] do begin
793         Ignore := False;
794         Border := False;
795         SrcNode:= lCurNode; //needed by close node
796         PhysX := lPhysX;
797         //if not FColumnCache.IsValidForLine[lCurNode.LineIndex] then
798         //  FColumnCache[lCurNode.LineIndex] := FirstCharacterColumn[lCurNode.LineIndex];
799         PhysCol := lNodeCol; //FColumnCache[lCurNode.LineIndex];
800         PhysX2 := lPhysX2;
801         Level := lvl;
802         lMaxLevel := Max(lMaxLevel, lvl);
803         if not (sfaOutlineNoColor in lCurNode.FoldAction) then
804            ColorIdx := Max(0, lvl) mod FColorCount
805         else
806            ColorIdx := -1;
807 
808         {$IFDEF SynEditMarkupFoldColoringDebug}
809         //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', '')]);
810         {$ENDIF}
811       end;
812     end;
813   end;
814 
815 var
816   lLineIdx,i,j,lvlA , k: integer;
817   lNodeList: TLazSynFoldNodeInfoList;
818 
819 begin
820   lLineIdx := ToIdx(pRow);
821 
822   // as all nodes will be on pRow we can set lNodeCol here already
823   if not FColumnCache.IsValidForLine[lLineIdx] then
824     FColumnCache[lLineIdx] := FirstCharacterColumn[lLineIdx];
825   lNodeCol := FColumnCache[lLineIdx];
826 
827   fHighlighter.CurrentLines := Lines;
828 
829   lNodeList := fHighlighter.FoldNodeInfo[lLineIdx];
830   lNodeList.ClearFilter; // only needed once, in case the line was already used
831   lNodeList.AddReference;
832   try
833     lNodeList.ActionFilter := [sfaOutline];
834     lvl := 0;
835     J := fFoldColorInfosCount - 1;
836     if J >=0 then
837       lvl := max(0,fFoldColorInfos[J].LevelAfter);
838     lMaxLevel := lvl;
839     i := 0;
840     repeat
841       lCurNode := lNodeList[i];
842       lCurIndex := fFoldColorInfosCount - 1;
843       // sanity check
844       Assert(lCurNode.LineIndex = lLineIdx, 'Node not on aRow');
845 
846       {$IFDEF SynEditMarkupFoldColoringDebug}
847       //if not (sfaInvalid in lCurNode.FoldAction) then
848       //  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)]);
849       {$ENDIF}
850 
851       if not (sfaInvalid in lCurNode.FoldAction)
852       and (sfaOutline in lCurNode.FoldAction) then begin
853         if sfaOpen in lCurNode.FoldAction then begin
854 
855           if ( sfaOutlineForceIndent in lCurNode.FoldAction) then
856             inc(lvl)
857           else if ( sfaOutlineMergeParent in lCurNode.FoldAction) then
858             dec(lvl);
859 
860           {$IFDEF SynEditMarkupFoldColoringDebug}
861           //if (fLastOpenNode.LineIndex >= 0) then
862           //  DebugLn('   %s %s - %s %s', [FoldTypeToStr(fLastOpenNode.FoldType), IfThen(sfaOutlineKeepLevel in fLastOpenNode.FoldAction, '(Keep)', ''), FoldTypeToStr(lCurNode.FoldType), IfThen(sfaOutlineKeepLevel in lCurNode.FoldAction, '(Keep)', '')]);
863           {$ENDIF}
864 
865           { do not keep level if two consecutive sfaOutlineKeepLevel nodes are
866             on different lines and start on different columns                  }
867           if (fLastOpenNode.LineIndex >= 0)
868           and (sfaOutlineKeepLevel in fLastOpenNode.FoldAction) then begin
869             {$IFDEF SynEditMarkupFoldColoringDebug}
870             //DebugLn('    keep');
871             {$ENDIF}
872             lKeepLevel := True;
873             if (sfaOutlineKeepLevel in lCurNode.FoldAction)
874             and not (fLastOpenNode.LineIndex = lLineIdx)
875             and not (fFoldColorInfos[fLastIndex].PhysCol = lNodeCol) then begin
876               inc(lvl);
877               lKeepLevel := False;
878             end;
879 
880           end else
881             lKeepLevel := False;
882 
883           if AddHighlight then begin
884             if lKeepLevel then begin
885               // overwrite first character column with new value
886               if fFoldColorInfos[fLastOpenIndex].PhysX < FColumnCache[fFoldColorInfos[lCurIndex].SrcNode.LineIndex] then begin
887                 FColumnCache[fFoldColorInfos[lCurIndex].SrcNode.LineIndex] := fFoldColorInfos[fLastOpenIndex].PhysX;
888                 Assert(fFoldColorInfos[lCurIndex].SrcNode.LineIndex = lLineIdx, 'fFoldColorInfos[lCurIndex].SrcNode.LineIndex <> lLineIdx');
889                 lNodeCol := FColumnCache[lLineIdx]
890               end;
891               {$IFDEF SynEditMarkupFoldColoringDebug}
892               //with FFoldColorInfos[lCurIndex] do
893               //  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)]);
894               {$ENDIF}
895             end;
896 
897             if not (sfaOutlineKeepLevel in lCurNode.FoldAction) then
898               inc(lvl);
899 
900             lvlA := lvl;
901             fLastIndex := lCurIndex;
902             fLastOpenNode := lCurNode;
903             fLastOpenIndex := lCurIndex;
904 
905             with fFoldColorInfos[fFoldColorInfosCount - 1] do begin
906               LevelAfter  := lvlA;
907             end;
908           end;
909         end else if sfaClose in lCurNode.FoldAction then begin
910           lKeepLevel := False;
911           for j := fFoldColorInfosCount - 1 downto 0 do begin
912             with fFoldColorInfos[j].SrcNode do begin
913               if (FoldType = lCurNode.FoldType)
914               and (FoldGroup = lCurNode.FoldGroup)
915               and (sfaOpen in FoldAction)
916               and (NestLvlEnd = lCurNode.NestLvlStart) then begin
917                 lvl := fFoldColorInfos[j].Level;
918                 lvlA := fFoldColorInfos[j].LevelAfter;
919                 if AddHighlight then begin
920                   fLastIndex := lCurIndex;
921 
922                   with fFoldColorInfos[fFoldColorInfosCount - 1] do begin
923                     LevelAfter  := lvlA;
924                   end;
925                   // if found opening position is behind closing position:
926                   // delete this as it does not have to be drawn
927                   if fFoldColorInfos[j].PhysX > fFoldColorInfos[fFoldColorInfosCount - 1].PhysX then begin
928                     for k := j to fFoldColorInfosCount - 1 - 1 do begin
929                       fFoldColorInfos[k] := fFoldColorInfos[k+1];
930                     end;
931                     dec(fFoldColorInfosCount);
932                   end;
933                 end;
934                 break;
935               end;
936             end;
937           end;
938         end;
939       end;
940       inc(i);
941     until i >= lNodeList.Count;
942   finally
943     lNodeList.ReleaseReference;
944   end;
945 end;
946 
GetColornull947 function TSynEditMarkupFoldColors.GetColor(pIndex: Integer): TSynSelectedColor;
948 begin
949   Assert((pIndex >= 0) and (pIndex < FColorCount), 'Index out of range');
950   Result := fMarkupColors[pIndex];
951 end;
952 
GetLineColornull953 function TSynEditMarkupFoldColors.GetLineColor(pIndex: Integer
954   ): TMarkupFoldColorsLineColor;
955 begin
956   Assert((pIndex >= 0) and (pIndex < FColorCount), 'Index out of range');
957   Result := fLineColors[pIndex];
958 end;
959 
960 procedure TSynEditMarkupFoldColors.SetColorCount(AValue: Integer);
961 var
962   i: Integer;
963 begin
964   if FColorCount = AValue then Exit;
965 
966   for i := AValue to FColorCount - 1 do begin
967     fMarkupColors[i].Free;
968     fLineColors[i].Free;
969   end;
970 
971   SetLength(fMarkupColors, AValue);
972   SetLength(fLineColors, AValue);
973 
974   for i := FColorCount to AValue - 1 do begin
975     fMarkupColors[i] := TSynSelectedColor.Create;
976     fMarkupColors[i].Clear;
977     fMarkupColors[i].OnChange := @ColorChanged;
978     fLineColors[i] := TMarkupFoldColorsLineColor.Create;
979     fLineColors[i].OnChange := @ColorChanged;
980   end;
981 
982   FColorCount := AValue;
983 end;
984 
985 procedure TSynEditMarkupFoldColors.PrepareMarkupForRow(pRow: Integer);
986 var
987   i, lLastX, j: Integer;
988 
989 begin
990   if not Assigned(fHighlighter)
991   and not (TCustomSynEdit(Self.SynEdit).Highlighter is TSynCustomFoldHighlighter) then
992     exit;
993 
994   {$IFDEF SynEditMarkupFoldColoringDebug}
995   //DebugLn(#10'PrepareMarkupForRow %d', [aRow]);
996   {$ENDIF}
997 
998   fPreparedRow := pRow;
999   fFoldColorInfosCount := 0; //reset needed to prevent using of invalid area
1000 
1001   // invalidate LastNode
1002   fLastIndex := -1;
1003   fLastOpenNode.LineIndex := -1;
1004   fLastOpenIndex := -1;
1005 
1006   {$IFDEF SynEditMarkupFoldColoringDebug}
1007   //DebugLn('  ----- DoMarkupParentFoldAtRow ------');
1008   {$ENDIF}
1009   DoMarkupParentFoldAtRow(pRow);
1010 
1011   {$IFDEF SynEditMarkupFoldColoringDebug}
1012   //DebugLn('  --- DoMarkupParentCloseFoldAtRow ---');
1013   {$ENDIF}
1014   DoMarkupParentCloseFoldAtRow(pRow);
1015 
1016   // delete parents with bigger x
1017   // to keep out mis indented blocks
1018   lLastX := MaxInt;
1019   for i := fFoldColorInfosCount - 1 downto 0 do begin
1020     if fFoldColorInfos[i].PhysX > lLastX then begin
1021       for j := i to length(fFoldColorInfos) - 2 do begin
1022         fFoldColorInfos[j] := fFoldColorInfos[j + 1];
1023       end;
1024       dec(fFoldColorInfosCount);
1025     end;
1026     lLastX := fFoldColorInfos[i].PhysX;
1027   end;
1028   {$IFDEF SynEditMarkupFoldColoringDebug}
1029   //DebugLn('  -------------- Final ---------------');
1030   //for i := 0 to FFoldColorInfosCount - 1 do with FFoldColorInfos[i] do begin
1031   //  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', '')]);
1032   //end;
1033   {$ENDIF}
1034 end;
1035 
1036 procedure TSynEditMarkupFoldColors.SetDefaultGroup(pValue: integer);
1037 begin
1038   if fDefaultGroup = pValue then Exit;
1039   fDefaultGroup := pValue;
1040   fNestList.FoldGroup := fDefaultGroup;
1041 end;
1042 
1043 procedure TSynEditMarkupFoldColors.SetFoldColorInfosCount(pNewCount: Integer);
1044 begin
1045   if pNewCount > fFoldColorInfosCapacity then begin
1046     // expand array
1047     fFoldColorInfosCapacity := pNewCount + 49;
1048     SetLength(fFoldColorInfos, fFoldColorInfosCapacity);
1049   end;
1050   fFoldColorInfosCount := pNewCount;
1051 end;
1052 
1053 procedure TSynEditMarkupFoldColors.InitNestList;
1054 begin
1055   if Assigned(fNestList) then
1056     fNestList.Lines := Lines;
1057   if Assigned(fNestList2) then
1058     fNestList2.Lines := Lines;
1059 end;
1060 
1061 procedure TSynEditMarkupFoldColors.DoTextChanged(pStartLine, pEndLine, pCountDiff: Integer);
1062 var
1063   lNode: TSynFoldNodeInfo;
1064   lNodeIdx, lEndLine, lLineIdx, lBottomLine, lDecreaseCount, lOuterNodeIdx: Integer;
1065   nl: LongInt;
1066   x: TColumnCacheEntry;
1067   {$IFDEF SynEditMarkupFoldColoringDebug}
1068   t: QWord;
1069   {$ENDIF}
1070 begin
1071   if not Enabled then
1072     exit;
1073 
1074   {$IFDEF SynEditMarkupFoldColoringDebug}
1075   //DebugLn('   DoTextChanged %d-%d: %d', [StartLine, EndLine, ACountDiff]);
1076   {$ENDIF}
1077 
1078   // lines available?
1079   if Lines.Count = 0 then
1080     exit;
1081 
1082   // called by accident
1083   if pStartLine = 0 then
1084     exit;
1085 
1086   // no TSynCustomFoldHighlighter
1087   if not Assigned(fHighlighter) then
1088     exit;
1089 
1090   fHighlighter.CurrentLines := Lines;
1091   // highlighter still scanning
1092   if fHighlighter.NeedScan then
1093     exit;
1094 
1095   {$IFDEF SynEditMarkupFoldColoringDebug}
1096   t := GetTickCount64;
1097   {$ENDIF}
1098 
1099   if pEndLine < 0 then
1100     pEndLine := pStartLine
1101   else
1102     // pEndLine seems to be the first line after the change
1103     pEndLine := pEndLine - 1;
1104   lEndLine := pEndLine;
1105   FColumnCache[ToIdx(lEndLine)] := FirstCharacterColumn[ToIdx(lEndLine)];
1106   x := FColumnCache[ToIdx(lEndLine)];
1107   lBottomLine := TCustomSynEdit(SynEdit).TopLine + TCustomSynEdit(SynEdit).LinesInWindow;
1108 
1109   fNestList.Clear;
1110   fNestList2.Clear;
1111   lLineIdx := ToIdx(pStartLine);
1112   fNestList.Line := lLineIdx;
1113   lNodeIdx := fNestList.Count - 1;
1114   lOuterNodeIdx := -1;
1115   if lNodeIdx >= 0 then begin
1116     lDecreaseCount := 2;
1117     while (lNodeIdx >= 0)
1118     and (lDecreaseCount > 0) do begin
1119       dec(lDecreaseCount);
1120       while lNodeIdx >= 0 do begin
1121         dec(lNodeIdx);
1122         lNode := fNestList.HLNode[lNodeIdx];
1123         if not (sfaInvalid in lNode.FoldAction)
1124         and (sfaOutline in lNode.FoldAction)
1125         and not (sfaOutlineKeepLevel in lNode.FoldAction)
1126         and not (
1127           (sfaOpen in lNode.FoldAction)
1128           and (lNode.LineIndex = lLineIdx)
1129         ) then begin
1130           if lNodeIdx >= 0 then
1131             lOuterNodeIdx := lNodeIdx;
1132           break;
1133         end;
1134       end;
1135     end;
1136   end;
1137   if (lOuterNodeIdx >= 0) then begin
1138     lEndLine := ToPos(fNestList.NodeEndLine[lOuterNodeIdx]);
1139   end else begin
1140     // if there is no outer Outline:
1141     // expand lEndline if x is left to FirstCharacterColumn;
1142     lLineIdx := ToIdx(lEndLine);
1143     while x <= FirstCharacterColumn[lLineIdx] do begin
1144       // if x (FirstCharacterColoum of pStartLine) is left or equal to
1145       // the FirstCharacterColumn of line lLineIdx
1146       // then the real pEndLine is at the pEndLine of the lines last node
1147       fNestList.Line := lLineIdx;
1148       if fNestList.Count = 0 then
1149         break;
1150       nl := fNestList.NodeEndLine[fNestList.Count - 1];
1151       if nl = lLineIdx then
1152         break;
1153       {$IFDEF SynEditMarkupFoldColoringDebug}
1154       DebugLn('   %d -> %d [%d/%d]', [lLineIdx, nl, x, FirstCharacterColumn[nl]]);
1155       {$ENDIF}
1156       lLineIdx := nl;
1157     end;
1158     lLineIdx := ToPos(lLineIdx);
1159     lEndLine := Max(lEndLine, lLineIdx);
1160   end;
1161 
1162   // invalidate cache
1163   FColumnCache.LineTextChanged(ToIdx(pStartLine), Max(pEndLine, lEndLine) - pStartLine);
1164 
1165   if lEndLine > pEndLine then begin
1166     {$IFDEF SynEditMarkupFoldColoringDebug}
1167     //DebugLn('   InvalidateSynLines(%d, %d)', [EndLine + 1, lEndLine]);
1168     {$ENDIF}
1169     InvalidateSynLines(pEndLine + 1 , Min(lEndLine, lBottomLine));
1170   end;
1171 
1172   {$IFDEF SynEditMarkupFoldColoringDebug}
1173   DebugLn('*** DoTextChanged %d-%d-%d-%d duration=%d', [pStartLine, pEndLine, lEndLine, lBottomLine, GetTickCount64 - t]);
1174   {$ENDIF}
1175 
1176 end;
1177 
1178 procedure TSynEditMarkupFoldColors.SetLines(const pValue: TSynEditStrings);
1179 var
1180   old: TSynEditStrings;
1181 begin
1182   if Lines <> nil then
1183     Lines.Ranges[Self] := nil;
1184   if Enabled then begin
1185     old := Lines;
1186     if Assigned(old)
1187     and (pValue <> old) then begin
1188       // change:
1189       // remove Changehandler
1190       old.RemoveChangeHandler(senrHighlightChanged, @HighlightChanged);
1191       old.RemoveNotifyHandler(senrTextBufferChanged, @TextBufferChanged);
1192       FColumnCache.Invalidate;
1193     end;
1194   end;
1195   inherited SetLines(pValue);
1196   if Enabled then begin
1197     if (pValue <> old) then begin
1198       // change:
1199       if Assigned(pValue) then begin
1200         // add Changehandler
1201         pValue.AddChangeHandler(senrHighlightChanged, @HighlightChanged);
1202         pValue.AddNotifyHandler(senrTextBufferChanged, @TextBufferChanged);
1203         InitNestList;
1204       end else begin
1205         if Assigned(fNestList) then
1206           fNestList.Lines := nil;
1207         if Assigned(fNestList2) then
1208           fNestList2.Lines := nil;
1209         //DebugLn('*** SetLines');
1210       end;
1211     end;
1212   end;
1213   if (Lines <> nil) and Enabled then begin
1214     FColumnCache.Capacity := Lines.Capacity;
1215     FColumnCache.Count := Lines.Count;
1216     Lines.Ranges[Self] := FColumnCache;
1217   end;
1218   FColumnCache.Invalidate;
1219 end;
1220 
1221 procedure TSynEditMarkupFoldColors.HighlightChanged(pSender: TSynEditStrings;
1222   pIndex, pCount: Integer);
1223 var
1224   newHighlighter: TSynCustomHighlighter;
1225 begin
1226   {$IFDEF SynEditMarkupFoldColoringDebug}
1227   //DebugLn('   HighlightChanged: aIndex=%d aCount=%d', [aIndex, aCount]);
1228   {$ENDIF}
1229 
1230   if (pIndex <> -1)
1231   or (pCount <> -1) then
1232     exit;
1233 
1234   newHighlighter := TCustomSynEdit(self.SynEdit).Highlighter;
1235   if Assigned(newHighlighter)
1236   and not (newHighlighter is TSynCustomFoldHighlighter) then
1237     newHighlighter := nil;
1238 
1239   if (newHighlighter = fHighlighter) then
1240     exit;
1241 
1242   fHighlighter := TSynCustomFoldHighlighter(newHighlighter);
1243 
1244   fNestList.HighLighter := fHighlighter;
1245   fNestList2.HighLighter := fHighlighter;
1246 
1247   if not Enabled then
1248     exit;
1249 
1250   FColumnCache.Invalidate;
1251 end;
1252 
1253 procedure TSynEditMarkupFoldColors.DoEnabledChanged(pSender: TObject);
1254 begin
1255   if Enabled = fLastEnabled then
1256     exit;
1257   fLastEnabled := Enabled;
1258   if fLastEnabled then begin
1259     {$IFDEF SynEditMarkupFoldColoringDebug}
1260     //DebugLn('   *** TSynEditMarkupFoldColors Enabled');
1261     {$ENDIF}
1262     if Assigned(Lines) then begin
1263       // add Changehandler
1264       Lines.AddChangeHandler(senrHighlightChanged, @HighlightChanged);
1265       Lines.AddNotifyHandler(senrTextBufferChanged, @TextBufferChanged);
1266       InitNestList;
1267     end;
1268   end else begin
1269     {$IFDEF SynEditMarkupFoldColoringDebug}
1270     //DebugLn('   *** TSynEditMarkupFoldColors Disabled');
1271     {$ENDIF}
1272     if Assigned(Lines) then begin
1273       // remove Changehandler
1274       Lines.RemoveChangeHandler(senrHighlightChanged, @HighlightChanged);
1275       Lines.RemoveNotifyHandler(senrTextBufferChanged, @TextBufferChanged);
1276     end;
1277   end;
1278 
1279   if Assigned(Lines) then begin
1280     if Enabled then begin
1281       FColumnCache.Capacity := Lines.Capacity;
1282       FColumnCache.Count := Lines.Count;
1283       Lines.Ranges[Self] := FColumnCache;
1284       FColumnCache.Invalidate;
1285     end
1286     else
1287       Lines.Ranges[Self] := nil;
1288 
1289     InvalidateSynLines(1, Lines.Count);
1290   end;
1291 end;
1292 
1293 procedure TSynEditMarkupFoldColors.ColorChanged(pMarkup: TObject);
1294 begin
1295   if Assigned(Lines) then
1296     InvalidateSynLines(1, Lines.Count);
1297 end;
1298 
1299 end.
1300 
1301 
1302