1{-------------------------------------------------------------------------------
2The contents of this file are subject to the Mozilla Public License
3Version 1.1 (the "License"); you may not use this file except in compliance
4with the License. You may obtain a copy of the License at
5http://www.mozilla.org/MPL/
6
7Software distributed under the License is distributed on an "AS IS" basis,
8WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
9the specific language governing rights and limitations under the License.
10
11Alternatively, the contents of this file may be used under the terms of the
12GNU General Public License Version 2 or later (the "GPL"), in which case
13the provisions of the GPL are applicable instead of those above.
14If you wish to allow use of your version of this file only under the terms
15of the GPL and not to allow others to use your version of this file
16under the MPL, indicate your decision by deleting the provisions above and
17replace them with the notice and other provisions required by the GPL.
18If you do not delete the provisions above, a recipient may use your version
19of this file under either the MPL or the GPL.
20
21-------------------------------------------------------------------------------}
22(* some parts (AdjustBalance...) of this unit are based on the AVLTree unit *)
23(* TODO: Implement node.eof / node.bof *)
24unit SynEditFoldedView;
25
26{$mode objfpc}{$H+}
27{$IFDEF CPUPOWERPC} {$INLINE OFF} {$ENDIF} (* Workaround for bug 12576 (fpc) see bugs.freepascal.org/view.php?id=12576 *)
28
29{$IFOPT C+}
30  {$DEFINE SynAssertFold}
31{$ENDIF}
32{$IFDEF SynAssert}
33  {$DEFINE SynAssertFold}
34{$ENDIF}
35
36{$IFDEF SynFoldDebug}
37  {$DEFINE SynDebug}
38  {$DEFINE SynFoldSaveDebug}
39{$ENDIF}
40{$IFDEF SynFoldSaveDebug}
41  {$DEFINE SynDebug}
42{$ENDIF}
43
44interface
45
46uses
47  Classes, SysUtils,
48  // LCL
49  LCLProc, Graphics, LCLType,
50  // LazUtils
51  LazLoggerBase, LazMethodList,
52  // SynEdit
53  LazSynEditText, SynEditTypes, SynEditMiscClasses, SynEditMiscProcs,
54  SynEditPointClasses, SynEditHighlighter, SynEditHighlighterFoldBase,
55  SynEditKeyCmds;
56
57type
58
59  TFoldNodeClassification = (
60    fncInvalid,
61    fncHighlighter,     // Fold provided by HL
62    fncHighlighterEx,   // Fold originally provided by HL, but no longer exists in HL (text edited)
63    fncBlockSelection
64  );
65  TFoldNodeClassifications = set of TFoldNodeClassification;
66
67  { TSynTextFoldAVLNodeData }
68
69  TSynTextFoldAVLNodeData = class(TSynSizedDifferentialAVLNode)
70  protected
71    function Left: TSynTextFoldAVLNodeData;
72    function Parent: TSynTextFoldAVLNodeData;
73    function Right: TSynTextFoldAVLNodeData;
74    procedure FreeAllChildrenAndNested;
75  public    (* Position / Size *)
76    (* FullCount:  Amount of lines in source for this fold only
77                   (excluding overlaps) *)
78    FullCount : Integer;
79    (* LineOffset: Line-Number Offset to parent node
80                   All line numbers are stored as offsets,
81                   for faster updates if lines are inserted/deleted *)
82    property LineOffset: Integer read FPositionOffset write FPositionOffset;
83    (* LeftCount:  Lines folded in left tree.
84                   Used to calculate how many lines are folded up to a specified line *)
85    property LeftCount: Integer read FLeftSizeSum write FLeftSizeSum;
86    (* MergedLineCount: Amount of lines folded away by this fold,
87                        FullCount + Lines covered by overlaps *)
88    property MergedLineCount: Integer read FSize write FSize;
89  public
90    (* Sub-Tree  *)
91    Nested : TSynTextFoldAVLNodeData; (* Nested folds (folds within this fold) do not need to be part of the searchable tree
92                             They will be restored, if the outer fold (this fold) is unfolded
93                             Nested points to a standalone tree, the root node in the nested tree, does *not* point back to this node *)
94
95
96    (* Source Info *)
97    FoldIndex: Integer;    (* Index of fold in line; if a line has more than one fold starting *)
98    FoldColumn, FoldColumnLen: Integer; (* The column (1-based) and len of the keywordm which starts this fold *)
99    FoldTypeCompatible: Pointer; (* help identifying in FixFolding *)
100    Classification: TFoldNodeClassification;
101    VisibleLines: Integer; (* Visible Source lines, containing the "fold keyword"
102                              0: Hiden block (the fold-keyword is inside the fold)
103                              1: Normal fold (There is *1* visible line with the fold-keyword)
104                            *)
105
106
107    function RecursiveFoldCount : Integer; (* Amount of lines covered by this and all child nodes *)
108    function Precessor : TSynTextFoldAVLNodeData; reintroduce;
109    function Successor : TSynTextFoldAVLNodeData; reintroduce;
110    function Precessor(var aStartPosition, aSizesBeforeSum : Integer) : TSynTextFoldAVLNodeData; reintroduce;
111    function Successor(var aStartPosition, aSizesBeforeSum : Integer) : TSynTextFoldAVLNodeData; reintroduce;
112  end;
113
114  { TSynTextFoldAVLNode }
115
116  TSynTextFoldAVLNode = object
117  private
118    function GetClassification: TFoldNodeClassification;
119    function GetFoldColumn: Integer;
120    function GetFoldColumnLen: Integer;
121    function GetFoldIndex: Integer;
122    function GetMergedLineCount : Integer;
123    function GetFullCount : Integer;
124    function GetSourceLine: integer;
125    function GetSourceLineOffset: integer;
126    procedure SetFoldColumn(const AValue: Integer);
127  protected
128    fData : TSynTextFoldAVLNodeData; // nil if unfolded
129    fStartLine : Integer;            // start of folded
130    fFoldedBefore : Integer;
131  public
132    procedure Init(aData : TSynTextFoldAVLNodeData; aStartLine, aFoldedBefore: Integer);
133    function IsInFold : Boolean;
134    function Next : TSynTextFoldAVLNode;
135    function Prev : TSynTextFoldAVLNode;
136
137    property MergedLineCount: Integer read GetMergedLineCount; // Zero, if Not in a fold
138    property FullCount: Integer read GetFullCount; // Zero, if Not in a fold
139    property StartLine: Integer read fStartLine;   // 1st Line of Current Fold
140    property FoldedBefore: Integer read fFoldedBefore;  // Count of Lines folded before Startline
141
142    function IsHide: Boolean;
143    property FoldIndex: Integer read GetFoldIndex;
144    property FoldColumn: Integer read GetFoldColumn write SetFoldColumn;
145    property FoldColumnLen: Integer read GetFoldColumnLen;
146    property SourceLine: integer read GetSourceLine;    // The SourceLine with the fold-keyword
147    property SourceLineOffset: integer read GetSourceLineOffset;    // The SourceLine with the fold-keyword
148    property Classification: TFoldNodeClassification read GetClassification;
149  end;
150
151  { TSynTextFoldAVLNodeNestedIterator:
152    Iterates included nested nodes
153    FoldedBefore is not valid in nested nodes
154  }
155
156  TSynTextFoldAVLNodeNestedIterator = class
157  private
158    FCurrentNode: TSynTextFoldAVLNode;
159    FOuterNodes: Array of TSynTextFoldAVLNode;
160  public
161    constructor Create(ANode: TSynTextFoldAVLNode);
162    destructor Destroy; override;
163    function Next: TSynTextFoldAVLNode;
164    function Prev: TSynTextFoldAVLNode;
165    function EOF: Boolean;
166    function BOF: Boolean;
167    function IsInFold: Boolean;
168    property Node: TSynTextFoldAVLNode read FCurrentNode;
169  end;
170
171  { TSynTextFoldAVLTree
172    - Nodes in the tree cover the folded lines only.
173      The (visible) cfCollapsed line at the start of a fold, is *not* part of a node.
174    - In the public methods "ALine" indicates the first invisible/hidden line
175    - TSynEditFoldedView uses this with 1-based lines (ToDo: make 0-based)
176  }
177
178  TSynTextFoldAVLTree = class(TSynSizedDifferentialAVLTree)
179  protected
180    fNestParent: TSynTextFoldAVLNodeData;
181    fNestedNodesTree: TSynTextFoldAVLTree; // FlyWeight Tree used for any nested subtree.
182
183    function NewNode : TSynTextFoldAVLNodeData; inline;
184    Function RemoveFoldForNodeAtLine(ANode: TSynTextFoldAVLNode;
185                                     ALine : Integer) : Integer; overload; // Line is for Nested Nodes
186
187    // SetRoot, does not obbey fRootOffset => use SetRoot(node, -fRootOffset)
188    procedure SetRoot(ANode : TSynSizedDifferentialAVLNode); overload; override;
189    procedure SetRoot(ANode : TSynSizedDifferentialAVLNode; anAdjustChildLineOffset : Integer); overload; override;
190
191    Function  InsertNode(ANode : TSynTextFoldAVLNodeData) : Integer; reintroduce; // returns FoldedBefore // ANode may not have children
192    function TreeForNestedNode(ANode: TSynTextFoldAVLNodeData; aOffset : Integer) : TSynTextFoldAVLTree;
193  public
194    constructor Create;
195    destructor Destroy; override;
196    procedure Clear; override;
197
198    (* Find Fold by Line in Real Text *)
199    Function FindFoldForLine(ALine : Integer; FindNextNode : Boolean = False) : TSynTextFoldAVLNode;
200    (* Find Fold by Line in Folded Text // always returns unfolded, unless next=true *)
201    Function FindFoldForFoldedLine(ALine : Integer; FindNextNode: Boolean = False) : TSynTextFoldAVLNode;
202    Function InsertNewFold(ALine, AFoldIndex, AColumn, AColumnLen, ACount, AVisibleLines: Integer;
203                           AClassification: TFoldNodeClassification;
204                           AFoldTypeCompatible: Pointer
205                          ) : TSynTextFoldAVLNode;
206    (* This will unfold the block which either contains tALine, or has Aline as its cgColapsed line
207       If IgnoreFirst, the cfCollapsed will *not* unfold => Hint: IgnoreFirst = Make folded visible
208       Returns the pos(1-based) of the cfCollapsed Line that was expanded; or ALine, if nothing was done
209    *)
210    Function RemoveFoldForLine(ALine : Integer; OnlyCol: Integer = -1) : Integer; overload;
211    Procedure AdjustForLinesInserted(AStartLine, ALineCount, ABytePos: Integer);
212    Procedure AdjustForLinesDeleted(AStartLine, ALineCount, ABytePos: Integer);
213    procedure AdjustColumn(ALine, ABytePos, ACount: Integer; InLineBreak: boolean = False);
214    Function FindLastFold : TSynTextFoldAVLNode;
215    Function FindFirstFold : TSynTextFoldAVLNode;
216    Function LastFoldedLine : integer; // The actual line; LastNode.StartLine + LastNode.LineCount - 1
217    {$IFDEF SynDebug}
218    procedure Debug; reintroduce;
219    {$ENDIF}
220  end;
221
222  { TSynFoldNodeInfoHelper }
223
224  TSynFoldNodeInfoHelper = class
225    FCurInfo: TSynFoldNodeInfo;
226    FActions: TSynFoldActions;
227    FHighlighter: TSynCustomFoldHighlighter;
228  protected
229    procedure Invalidate;
230  public
231    constructor Create(AHighlighter: TSynCustomFoldHighlighter);
232
233    function FirstOpen: TSynFoldNodeInfo;
234    function Next: TSynFoldNodeInfo;
235    function Prev: TSynFoldNodeInfo;
236    function FindClose: TSynFoldNodeInfo;
237    function GotoOpenPos(aLineIdx, aNodeIdx: integer): TSynFoldNodeInfo;
238    function GotoOpenAtChar(aLineIdx, aXPos: integer): TSynFoldNodeInfo;
239    function GotoNodeOpenPos(ANode : TSynTextFoldAVLNode): TSynFoldNodeInfo;
240    function GotoNodeClosePos(ANode : TSynTextFoldAVLNode): TSynFoldNodeInfo;
241    function IsAtNodeOpenPos(ANode : TSynTextFoldAVLNode): Boolean;
242    function IsValid: Boolean;
243    function Equals(AnInfo: TSynFoldNodeInfo): Boolean;
244    function Equals(AHelper: TSynFoldNodeInfoHelper): Boolean;
245
246    property Info: TSynFoldNodeInfo read FCurInfo write FCurInfo;
247    property Actions: TSynFoldActions read FActions write FActions;
248  end;
249
250  TFoldChangedEvent = procedure(aLine: Integer) of object;
251  TInvalidateLineProc = procedure(FirstLine, LastLine: integer) of object;
252
253  TFoldViewNodeInfo = record
254    HNode: TSynFoldNodeInfo;    // Highlighter Node
255    IsFold, IsHide: Boolean;
256    Text, Keyword: String;
257    LineNum, ColIndex: Integer;
258    OpenCount: Integer; // Highlighter-Nodes opening on this line (limited to the FoldGroup requested)
259  end;
260
261  TSynEditFoldLineCapability = (
262    // Capabilities of Line
263    cfFoldStart, cfHideStart,
264    cfFoldBody,
265    cfFoldEnd,
266    // State indicators
267    cfCollapsedFold,
268    cfCollapsedHide,   // lines hidden, after this line
269    // Special flags
270    cfSingleLineHide,
271    cfNone
272  );
273  TSynEditFoldLineCapabilities = set of TSynEditFoldLineCapability;
274  TSynEditFoldType = (scftOpen, scftFold, scftHide, scftAll, scftInvalid);
275
276  TSynEditFoldLineMapInfo = record
277    Capability: TSynEditFoldLineCapabilities;
278    Classifications :TFoldNodeClassifications;
279  end;
280
281  {$IFDEF SynFoldSaveDebug}
282const
283  SynEditFoldTypeNames: Array [TSynEditFoldType] of string =
284    ('scftOpen', 'scftFold', 'scftHide', 'scftAll', 'scftInvalid');
285type
286  {$ENDIF}
287
288  { TSynEditFoldProvider }
289  TSynEditFoldProviderNodeInfo = record
290    LineCount: Integer;
291    Column, ColumnLen: Integer;
292    DefaultCollapsed: Boolean;
293    FoldTypeCompatible: Pointer;  // eg begin, var, procedure
294    FoldGroup: Integer; // eg.: pas, region, ifdef
295    Classification: TFoldNodeClassification;
296  end;
297
298  TSynEditFoldProviderNodeInfoList = array of TSynEditFoldProviderNodeInfo;
299  TSynEditFoldProvider = class;
300
301  TSynEditFoldProvider = class
302  private
303    FEnabled: boolean;
304    FHighlighter: TSynCustomFoldHighlighter;
305    FLines : TSynEditStrings;
306    FEdit: TSynEditBase;
307    FNestedFoldsList: TLazSynEditNestedFoldsList;
308    function GetFoldsAvailable: Boolean;
309    function GetHighLighterWithLines: TSynCustomFoldHighlighter;
310    function GetLineCapabilities(ALineIdx: Integer): TSynEditFoldLineCapabilities;
311    function GetLineClassification(ALineIdx: Integer): TFoldNodeClassifications;
312    function GetNestedFoldsList: TLazSynEditNestedFoldsList;
313    procedure SetHighLighter(const AValue: TSynCustomFoldHighlighter);
314    procedure SetLines(AValue: TSynEditStrings);
315  protected
316    property HighLighterWithLines: TSynCustomFoldHighlighter read GetHighLighterWithLines;
317  public
318    constructor Create;
319    destructor Destroy; override;
320
321    // Info about Folds opening on ALineIdx
322    function  FoldOpenCount(ALineIdx: Integer; AType: Integer = 0): Integer;
323    function  FoldOpenInfo(ALineIdx, AFoldIdx: Integer; AType: Integer = 0): TSynFoldNodeInfo;
324    //property FoldOpenInfo[ALineIdx, AColumnIdx: Integer]: Integer read GetFoldOpenInfo;
325
326    function  FoldLineLength(ALine, AFoldIndex: Integer): integer;
327    function  InfoForFoldAtTextIndex(ALine, AFoldIndex : Integer;
328                                     HideLen: Boolean = False;
329                                     NeedLen: Boolean = True): TSynEditFoldProviderNodeInfo;
330    function  InfoListForFoldsAtTextIndex(ALine: Integer; NeedLen: Boolean = False): TSynEditFoldProviderNodeInfoList;
331
332    property LineCapabilities[ALineIdx: Integer]: TSynEditFoldLineCapabilities
333             read GetLineCapabilities;
334    property LineClassification[ALineIdx: Integer]: TFoldNodeClassifications
335             read GetLineClassification;
336    property Lines: TSynEditStrings read FLines write SetLines;
337    property HighLighter: TSynCustomFoldHighlighter read FHighlighter write SetHighLighter;
338    property FoldsAvailable: Boolean read GetFoldsAvailable;
339    property NestedFoldsList: TLazSynEditNestedFoldsList read GetNestedFoldsList;
340    property Enabled: boolean read FEnabled write FEnabled;
341  end;
342
343  { TFoldChangedHandlerList }
344
345  TFoldChangedHandlerList = class(TMethodList)
346  public
347    procedure CallFoldChangedEvents(AnIndex: Integer);
348  end;
349
350  TSynEditFoldedView = class;
351
352  { TLazSynDisplayFold }
353
354  TLazSynDisplayFold = class(TLazSynDisplayViewEx)
355  private
356    FFoldView: TSynEditFoldedView;
357    FLineState: integer;
358    FTokenAttr: TSynHighlighterAttributesModifier;
359    FMarkupLine: TSynSelectedColorMergeResult;
360    FLineFlags, FLineFlags2: TSynEditFoldLineCapabilities;
361  public
362    constructor Create(AFoldView: TSynEditFoldedView);
363    destructor Destroy; override;
364    procedure SetHighlighterTokensLine(ALine: TLineIdx; out ARealLine: TLineIdx; out AStartBytePos, ALineByteLen: Integer); override;
365    function GetNextHighlighterToken(out ATokenInfo: TLazSynDisplayTokenInfo): Boolean; override;
366    function GetLinesCount: Integer; override;
367
368    function TextToViewIndex(ATextIndex: TLineIdx): TLineRange; override;
369    function ViewToTextIndex(AViewIndex: TLineIdx): TLineIdx; override;
370    function ViewToTextIndexEx(AViewIndex: TLineIdx; out AViewRange: TLineRange): TLineIdx; override;
371  end;
372
373  { TSynTextFoldedView
374      *Line      = Line (0-based) on Screen (except TopLine which should be TopViewPos)
375      *ViewPos   = Line (1-based) in the array of viewable/visible lines
376      *TextIndex = Line (0-based) in the complete text(folded and unfolded)
377  }
378
379  TSynEditFoldedViewFlag = (fvfNeedCaretCheck, fvfNeedCalcMaps);
380  TSynEditFoldedViewFlags = set of TSynEditFoldedViewFlag;
381
382  { TSynEditFoldedView }
383
384  TSynEditFoldedView = class(TSynEditStringsLinked)
385  private
386    FOwner: TSynEditBase;
387    fCaret: TSynEditCaret;
388    FFoldProvider: TSynEditFoldProvider;
389    fFoldTree : TSynTextFoldAVLTree;   // Folds are stored 1-based (the 1st line is 1)
390    FMarkupInfoFoldedCode: TSynSelectedColor;
391    FMarkupInfoFoldedCodeLine: TSynSelectedColor;
392    FMarkupInfoHiddenCodeLine: TSynSelectedColor;
393    fTopLine : Integer;
394    fLinesInWindow : Integer;          // there may be an additional part visible line
395    fTextIndexList : Array of integer;   (* Map each Screen line into a line in textbuffer *)
396    fFoldTypeList : Array of TSynEditFoldLineMapInfo;
397    fLockCount : Integer;
398    fNeedFixFrom, fNeedFixMinEnd : Integer;
399    FFlags: TSynEditFoldedViewFlags;
400    FInTopLineChanged: Boolean;
401    FDisplayView: TLazSynDisplayFold;
402
403    function GetFoldClasifications(index : Integer): TFoldNodeClassifications;
404    function GetHighLighter: TSynCustomHighlighter;
405    function GetDisplayNumber(index : Integer) : Integer;
406    function GetTextIndex(index : Integer) : Integer;
407    function GetFoldType(index : Integer) : TSynEditFoldLineCapabilities;
408    function IsFolded(index : integer) : Boolean;  // TextIndex
409    procedure ProcessMySynCommand(Sender: TObject; AfterProcessing: boolean;
410      var Handled: boolean; var Command: TSynEditorCommand;
411      var AChar: TUTF8Char; Data: pointer; HandlerData: pointer);
412    procedure SetHighLighter(AValue: TSynCustomHighlighter);
413    procedure SetTopLine(const ALine : integer);
414    function  GetTopTextIndex : integer;
415    procedure SetTopTextIndex(const AIndex : integer);
416    procedure SetLinesInWindow(const AValue : integer);
417    procedure DoFoldChanged(AnIndex: Integer);
418    function TextIndexAddLines(aTextIndex, LineOffset : Integer) : Integer;     (* Add/Sub to/from TextIndex (0-based) skipping folded *)
419  protected
420    procedure SetManager(AManager: TSynTextViewsManager); override;
421    procedure SetSynStrings(AValue: TSynEditStrings); override;
422    function GetViewedLines(index : Integer) : String; override;
423    function GetViewedCount: integer; override;
424    function GetDisplayView: TLazSynDisplayView; override;
425    procedure InternalGetInfoForViewedXY(AViewedXY: TPhysPoint;
426      AFlags: TViewedXYInfoFlags; out AViewedXYInfo: TViewedXYInfo;
427      ALogPhysConvertor: TSynLogicalPhysicalConvertor); override;
428    procedure DoBlockSelChanged(Sender: TObject; Changes: TSynStatusChanges);
429    Procedure CalculateMaps;
430    function  FoldNodeAtTextIndex(AStartIndex, ColIndex: Integer): TSynTextFoldAVLNode; (* Returns xth Fold at nth TextIndex (all lines in buffer) / 1-based *)
431    function  FixFolding(AStart : Integer; AMinEnd : Integer; aFoldTree : TSynTextFoldAVLTree) : Boolean;
432
433    procedure DoCaretChanged(Sender : TObject);
434    Procedure LineCountChanged(Sender: TSynEditStrings; AIndex, ACount : Integer);
435    Procedure LinesCleared(Sender: TObject);
436    Procedure LineEdited(Sender: TSynEditStrings; aLinePos, aBytePos, aCount,
437                            aLineBrkCnt: Integer; aText: String);
438    Procedure LinesInsertedAtTextIndex(AStartIndex, ALineCount, ABytePos: Integer;
439                                       SkipFixFolding : Boolean = False);
440    //Procedure LinesInsertedAtViewPos(AStartPos, ALineCount : Integer;
441    //                                 SkipFixFolding : Boolean = False);
442    Procedure LinesDeletedAtTextIndex(AStartIndex, ALineCount, ABytePos: Integer;
443                                      SkipFixFolding : Boolean = False);
444    //Procedure LinesDeletedAtViewPos(AStartPos, ALineCount : Integer;
445    //                                SkipFixFolding : Boolean = False);
446    property FoldTree: TSynTextFoldAVLTree read fFoldTree;
447  public
448    constructor Create(AOwner: TSynEditBase; ACaret: TSynEditCaret);
449    destructor Destroy; override;
450
451    // Converting between Folded and Unfolded Lines/Indexes
452    function TextToViewIndex(aTextIndex : TLineIdx) : TLineIdx; override;   (* Convert TextIndex (0-based) to ViewPos (1-based) *)
453    function ViewToTextIndex(aViewIndex : TLineIdx) : TLineIdx; override;     (* Convert ViewPos (1-based) to TextIndex (0-based) *)
454    function TextXYToViewXY(APhysTextXY: TPhysPoint): TPhysPoint; override;
455    function ViewXYToTextXY(APhysViewXY: TPhysPoint): TPhysPoint; override;
456
457    function InternTextToViewIndex(aTextIndex : TLineIdx) : TLineIdx;           (* Convert TextIndex (0-based) to ViewPos (1-based) *)
458    function InternViewToTextIndex(aViewIndex : TLineIdx) : TLineIdx;             (* Convert ViewPos (1-based) to TextIndex (0-based) *)
459
460    function TextIndexToScreenLine(aTextIndex : Integer) : Integer; (* Convert TextIndex (0-based) to Screen (0-based) *)
461    function ScreenLineToTextIndex(aLine : Integer) : Integer;      (* Convert Screen (0-based) to TextIndex (0-based) *)
462
463    function AddVisibleOffsetToTextIndex(aTextIndex: TLineIdx; LineOffset: Integer): TLineIdx; override;
464    function IsTextIdxVisible(aTextIndex: TLineIdx): Boolean; override;
465
466    // Attributes for Visible-Lines-On-screen
467    property DisplayNumber[index : Integer] : Integer   (* LineNumber for display in Gutter / result is 1-based *)
468      read GetDisplayNumber;
469    property FoldType[index : Integer] : TSynEditFoldLineCapabilities (* FoldIcon / State *)
470      read GetFoldType;
471    property FoldClasifications[index : Integer] : TFoldNodeClassifications (* FoldIcon / State *)
472      read GetFoldClasifications;
473    property TextIndex[index : Integer] : Integer       (* Position in SynTextBuffer / result is 0-based *)
474      read GetTextIndex; // maybe writable
475
476    // Define Visible Area
477    property TopLine : integer                          (* refers to visible (unfolded) lines / 1-based *)
478      read fTopLine write SetTopLine;
479    property TopTextIndex : integer                     (* refers to TextIndex (folded + unfolded lines) / 1-based *)
480      read GetTopTextIndex write SetTopTextIndex;
481    property LinesInWindow : integer                    (* Fully Visible lines in Window; There may be one half visible line *)
482      read fLinesInWindow write SetLinesInWindow;
483
484    property MarkupInfoFoldedCode: TSynSelectedColor read FMarkupInfoFoldedCode;
485    property MarkupInfoFoldedCodeLine: TSynSelectedColor read FMarkupInfoFoldedCodeLine;
486    property MarkupInfoHiddenCodeLine: TSynSelectedColor read FMarkupInfoHiddenCodeLine;
487  public
488    procedure Lock;
489    procedure UnLock;
490    {$IFDEF SynDebug}
491    procedure debug;
492    {$ENDIF}
493    (* Arguments for (Un)FoldAt* (Line, ViewPos, TextIndex):
494       - ColumnIndex (0-based)
495           Can be negative, to access the highest(-1) available, 2nd highest(-2) ...
496           If negative, count points downward
497       - ColCount = 0 => all
498       - Skip => Do not count nodes that are already in the desired state
499           (or can not archive the desired state: e.g. can not hide)
500       - AVisibleLines: 0 = Hide / 1 = Fold
501    *)
502    procedure FoldAtLine(AStartLine: Integer; ColIndex : Integer = -1;          (* Folds at ScreenLine / 0-based *)
503                         ColCount : Integer = 1; Skip: Boolean = False;
504                         AVisibleLines: Integer = 1);
505    procedure FoldAtViewPos(AStartPos: Integer; ColIndex : Integer = -1;        (* Folds at nth visible/unfolded Line / 1-based *)
506                            ColCount : Integer = 1; Skip: Boolean = False;
507                            AVisibleLines: Integer = 1);
508    procedure FoldAtTextIndex(AStartIndex: Integer; ColIndex : Integer = -1;    (* Folds at nth TextIndex (all lines in buffer) / 1-based *)
509                              ColCount : Integer = 1; Skip: Boolean = False;
510                              AVisibleLines: Integer = 1);
511    procedure UnFoldAtLine(AStartLine: Integer; ColIndex : Integer = -1;        (* UnFolds at ScreenLine / 0-based *)
512                         ColCount : Integer = 0; Skip: Boolean = False;
513                         AVisibleLines: Integer = 1);
514    procedure UnFoldAtViewPos(AStartPos: Integer; ColIndex : Integer = -1;      (* UnFolds at nth visible/unfolded Line / 1-based *)
515                         ColCount : Integer = 0; Skip: Boolean = False;
516                         AVisibleLines: Integer = 1);
517    procedure UnFoldAtTextIndex(AStartIndex: Integer; ColIndex : Integer = -1;  (* UnFolds at nth TextIndex (all lines in buffer) / 1-based *)
518                         ColCount : Integer = 0; Skip: Boolean = False;
519                         AVisibleLines: Integer = 1);
520    procedure UnFoldAtTextIndexCollapsed(AStartIndex: Integer);   (* UnFolds only if Index is in the fold, ignores cfcollapsed line, if unfolded / 1-based *)
521
522    function LogicalPosToNodeIndex(AStartIndex: Integer; LogX: Integer;         (* Returns the index of the node, at the logical char pos *)
523                                   Previous: Boolean = False): Integer;
524
525    procedure CollapseDefaultFolds;
526    // Load/Save folds to string
527    // AStartIndex, AEndIndex: (0 based) First/last line (EndIndex = -1 = open end)
528    // AStartCol, AEndCol: (1 based) Logical text pos in Line. (AEndCol = -1 = full line)
529    function  GetFoldDescription(AStartIndex, AStartCol, AEndIndex,
530                                 AEndCol: Integer; AsText: Boolean = False;
531                                 Extended: Boolean = False) :String;
532    procedure ApplyFoldDescription(AStartIndex, AStartCol, AEndIndex,
533                                   AEndCol: Integer; FoldDesc: PChar;
534                                   FoldDescLen: Integer; IsText: Boolean = False);
535
536    procedure UnfoldAll;
537    procedure FoldAll(StartLevel : Integer = 0; IgnoreNested : Boolean = False);
538    procedure FixFoldingAtTextIndex(AStartIndex: Integer; AMinEndLine: Integer = 0); // Real/All lines
539  public
540    function OpenFoldCount(aStartIndex: Integer; AType: Integer = 0): Integer;
541    function OpenFoldInfo(aStartIndex, ColIndex: Integer; AType: Integer = 0): TFoldViewNodeInfo;
542
543  public
544    // Find the visible first line of the fold at ALine. Returns -1 if Aline is not folded
545    function CollapsedLineForFoldAtLine(ALine : Integer) : Integer;
546    function ExpandedLineForBlockAtLine(ALine : Integer; HalfExpanded: Boolean = True) : Integer;
547
548    function GetPhysicalCharWidths(Index: Integer): TPhysicalCharWidths;
549
550    function  IsFoldedAtTextIndex(AStartIndex, ColIndex: Integer): Boolean;      (* Checks xth Fold at nth TextIndex (all lines in buffer) / 1-based *)
551    property FoldedAtTextIndex [index : integer] : Boolean read IsFolded;
552
553    property HighLighter: TSynCustomHighlighter read GetHighLighter
554                                                write SetHighLighter;
555    property FoldProvider: TSynEditFoldProvider read FFoldProvider;
556  end;
557
558function dbgs(AClassification: TFoldNodeClassification): String; overload;
559
560implementation
561
562//var
563//  SYN_FOLD_DEBUG: PLazLoggerLogGroup;
564
565type
566  TFoldExportEntry = Record
567    // Lines and Pos (o 1st line) are relative to Scan-Start
568    Line, LogX, LogX2: Integer;     // StartLine and Pos
569    ELine, ELogX, ELogX2: Integer;  // EndLine and pos
570    FType: Integer;                 // e.g ord(cfbtBeginEnd)
571    LinesFolded: Integer;          // Lines Folded according to AVL-Node
572  end;
573
574  { TSynEditFoldExportStream }
575
576  TSynEditFoldExportStream = class
577  private
578    FData: String;
579    FLen, FPos: Integer;
580    FMem: PChar;
581    function  GetLen: Integer;
582    procedure SetLen(const AValue: Integer);
583    function  GetMem: PChar;
584    procedure SetMem(const AValue: PChar);
585    function  GetText: String;
586    procedure SetText(const AValue: String);
587  protected
588    function GrowData(AppendSize: Integer): PChar;
589    function EncodeIntEx(Anum: Integer): String;  // base 43, with leading continue bit
590    function EncodeIntEx2(Anum: Integer): String; // for numbers expected below 467; specially 0..80
591    function InternalReadNum(var APos: Integer): Integer;
592    function InternalReadNumEx(var APos: Integer): Integer;
593  public
594    constructor Create;
595    procedure Compress;
596    procedure Decompress;
597
598    procedure AddChecksum;
599    function  VerifyChecksum: Boolean;
600
601    // see notes for Compression
602    Procedure AppendMem(AMem: Pointer; ALen: Integer);
603    Procedure AppendString(ATxt: String);
604    Procedure AppendNum(ANum: Integer);
605    Procedure AppendNumEx(ANum: Integer);
606
607    Procedure Reset;
608    Procedure Clear;
609    function ReadMem(AMem: Pointer; ALen: Integer): Boolean;
610    function PeakString(ALen: Integer): String;
611    function FindChar(AChar: Char): Integer; // 0 based
612    function ReadString(ALen: Integer): String;
613    function ReadNum: Integer;
614    function ReadNumEx: Integer;
615    function EOF: Boolean;
616
617    property Text: String read GetText write SetText;
618    property Mem: PChar read GetMem write SetMem;
619    property Len: Integer read GetLen write SetLen;
620    property Pos: Integer read FPos;
621  end;
622
623  TSynEditFoldExportCoderEntry = record
624    aX, aY, aLen: Integer;
625    aFoldType: TSynEditFoldType;
626  end;
627  TSynEditFoldExportCoderStates =
628    (sfecAtBegin, sfecAtPoint, sfecInRepeatCount, sfecInvalid, sfecAtEOF);
629  {$IFDEF SynFoldSaveDebug}
630const
631  SynEditFoldExportCoderStates: Array [TSynEditFoldExportCoderStates] of String =
632    ('sfecAtBegin', 'sfecAtPoint', 'sfecInRepeatCount', 'sfecInvalid', 'sfecAtEOF');
633type
634  {$ENDIF}
635
636  { TSynEditFoldExportCoder }
637
638  TSynEditFoldExportCoder = class
639  private
640    FExportStream: TSynEditFoldExportStream;
641    FFoldType: Pointer;
642
643    FReadY, FReadLastY, FReadX, FReadSumLen, FReadCount: Integer;
644    FReadType: TSynEditFoldType;
645    FReadDefaultType: TSynEditFoldType;
646    FReadState: TSynEditFoldExportCoderStates;
647
648    FWriteCache: Array of TSynEditFoldExportCoderEntry;
649    FWriteCacheLen: Integer;
650    FWriteCacheTypes: set of TSynEditFoldType;
651    function GetReadIsValid: Boolean;
652  public
653    constructor Create(AFoldType: Pointer);
654    constructor Create(AStream: TSynEditFoldExportStream);
655    destructor Destroy; override;
656
657    procedure AddNode(aX, aY, aLen: Integer; aFoldType: TSynEditFoldType);
658    procedure Finish;
659
660    function ReadNode(aX, aY: Integer; aLen: Integer): TSynEditFoldType;
661    function EOF: Boolean;
662    procedure Reset;
663    property ReadIsValid: Boolean read GetReadIsValid;
664
665    property FoldType: Pointer read FFoldType;
666    property Stream: TSynEditFoldExportStream read FExportStream;
667  end;
668
669const
670  // use only xml encode-able ascii
671  // do not use [ or ], they are reserved for compression
672  // space can be used a special indicator
673  NumEncode86Chars: string[86] = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-+;:,.@=*/\!?$%()''^{}~_#';
674
675  NumEncodeAsOneMax = 80;                        // Maximum Value to encode as 1 char
676  NumEncodeAsTwoMax = 81 + 4*86 + 43;            // =  467; Maximum Value to encode as 2 char
677  NumEncodeAsThreeMax = 81 + 4*86 + 43 * 43 - 1; // = 2273  Maximum Value to encode as 3 char
678
679
680  SEQMaxNodeCount     = 75;  // New Full entry at least every 75 folds
681  SEQMaxLineDistEach  = 500;  //  New Full entry, if folds startlines are more than 500 appart
682  SEQMaxLineDistTotal = 2500; // New Full entry at least every 2500; check position
683
684var
685  NumEncode86Values: Array [Char] of integer;
686
687procedure InitNumEncodeValues;
688var
689  i: integer;
690  c : Char;
691begin
692  for c := low(Char) to high(Char) do begin
693    NumEncode86Values[c] := -1;
694  end;
695  for i := 1 to length(NumEncode86Chars) do
696    NumEncode86Values[NumEncode86Chars[i]] := i - 1;
697end;
698
699{ TFoldChangedHandlerList }
700
701procedure TFoldChangedHandlerList.CallFoldChangedEvents(AnIndex: Integer);
702var
703  i: LongInt;
704begin
705  i:=Count;
706  while NextDownIndex(i) do
707    TFoldChangedEvent(Items[i])(AnIndex);
708end;
709
710{ TLazSynDisplayFold }
711
712constructor TLazSynDisplayFold.Create(AFoldView: TSynEditFoldedView);
713begin
714  inherited Create;
715  FFoldView := AFoldView;
716  FTokenAttr := TSynHighlighterAttributesModifier.Create(nil);
717  FMarkupLine := TSynSelectedColorMergeResult.Create(nil);
718end;
719
720destructor TLazSynDisplayFold.Destroy;
721begin
722  FreeAndNil(FTokenAttr);
723  FreeAndNil(FMarkupLine);
724  inherited Destroy;
725end;
726
727procedure TLazSynDisplayFold.SetHighlighterTokensLine(ALine: TLineIdx; out
728  ARealLine: TLineIdx; out AStartBytePos, ALineByteLen: Integer);
729begin
730  FLineState := 0;
731  CurrentTokenLine := ALine;
732  FLineFlags := FFoldView.FoldType[CurrentTokenLine + 1 - FFoldView.TopLine] * [cfCollapsedFold, cfCollapsedHide];
733  FLineFlags2 := FLineFlags;
734
735  if not FFoldView.MarkupInfoFoldedCodeLine.IsEnabled then
736    Exclude(FLineFlags2, cfCollapsedFold);
737  if not FFoldView.MarkupInfoHiddenCodeLine.IsEnabled then
738    Exclude(FLineFlags2, cfCollapsedHide);
739
740  if (FLineFlags2 <> []) then begin
741    FFoldView.MarkupInfoFoldedCodeLine.SetFrameBoundsLog(1, MaxInt, 0);
742    FFoldView.MarkupInfoHiddenCodeLine.SetFrameBoundsLog(1, MaxInt, 0);
743  end;
744
745  inherited SetHighlighterTokensLine(FFoldView.InternViewToTextIndex(ALine), ARealLine, AStartBytePos, ALineByteLen);
746end;
747
748function TLazSynDisplayFold.GetNextHighlighterToken(out ATokenInfo: TLazSynDisplayTokenInfo): Boolean;
749const
750  MarkSpaces: string = '   ';
751  MarkDots: string = '...';
752  LSTATE_BOL       = 0; // at BOL
753  LSTATE_TEXT      = 1; // in text
754  LSTATE_BOL_GAP   = 2; // BOL and in Gap (empty line)         // must be LSTATE_BOL + 2
755  LSTATE_GAP       = 3; // In Gap betwen txt and dots          // must be LSTATE_TEXT + 2
756  LSTATE_DOTS      = 4; // In Dots
757  LSTATE_EOL       = 5; // at start of EOL
758var
759  EolAttr: TSynHighlighterAttributes;
760  MergeStartX, MergeEndX: TLazSynDisplayTokenBound;
761begin
762  case FLineState of
763    LSTATE_BOL, LSTATE_TEXT: begin
764        Result := inherited GetNextHighlighterToken(ATokenInfo);
765        if ( (not Result) or (ATokenInfo.TokenStart = nil)) and (FLineFlags <> [])
766        then begin
767          inc(FLineState, 2); // LSTATE_BOL_GAP(2), if was at bol // LSTATE_GAP(3) otherwise
768          ATokenInfo.TokenStart := PChar(MarkSpaces);
769          ATokenInfo.TokenLength := 3;
770          if Assigned(CurrentTokenHighlighter)
771          then EolAttr := CurrentTokenHighlighter.GetEndOfLineAttribute
772          else EolAttr := nil;
773          if EolAttr <> nil then begin
774            FTokenAttr.Assign(EolAttr);
775            ATokenInfo.TokenAttr := FTokenAttr;
776          end
777          else begin
778            ATokenInfo.TokenAttr := nil;
779          end;
780          Result := True;
781        end;
782      end;
783    LSTATE_GAP: begin
784        FLineState := LSTATE_DOTS;
785        FTokenAttr.Assign(FFoldView.MarkupInfoFoldedCode);
786        FTokenAttr.SetAllPriorities(MaxInt);
787        ATokenInfo.TokenStart := PChar(MarkDots);
788        ATokenInfo.TokenLength := 3;
789        ATokenInfo.TokenAttr := FTokenAttr;
790        Result := True;
791      end;
792    else begin
793      Result := inherited GetNextHighlighterToken(ATokenInfo);
794    end;
795  end;
796
797  if (FLineFlags2 <> []) then begin
798    FMarkupLine.Clear;
799    if ATokenInfo.TokenAttr = nil then begin
800      // Text Area does not expect StartX/Endx
801      // So we must merge, to eliminate unwanted borders
802      //  if (cfCollapsedFold in FLineFlags2)
803      //  then ATokenInfo.TokenAttr := FFoldView.MarkupInfoFoldedCodeLine
804      //  else ATokenInfo.TokenAttr := FFoldView.MarkupInfoHiddenCodeLine;
805      //  exit;
806      FMarkupLine.Clear;
807    end //;
808    else
809      FMarkupLine.Assign(ATokenInfo.TokenAttr);
810
811    MergeStartX.Physical := -1;
812    MergeStartX.Logical := -1;
813    MergeEndX.Physical := -1;
814    MergeEndX.Logical := -1;
815    if FLineState in [LSTATE_BOL, LSTATE_BOL_GAP] then
816      MergeStartX := FFoldView.MarkupInfoFoldedCodeLine.StartX;
817    if FLineState = LSTATE_EOL then // LSTATE_GAP; // or result := true
818      MergeEndX := FFoldView.MarkupInfoFoldedCodeLine.EndX;
819
820    // fully expand all frames
821    //FMarkupLine.SetFrameBoundsLog(0,0,0);
822    //FMarkupLine.CurrentStartX := FMarkupLine.StartX;
823    //FMarkupLine.CurrentEndX := FMarkupLine.EndX;
824
825    if (cfCollapsedFold in FLineFlags2) then
826      FMarkupLine.Merge(FFoldView.MarkupInfoFoldedCodeLine, MergeStartX, MergeEndX)
827    else
828      FMarkupLine.Merge(FFoldView.MarkupInfoHiddenCodeLine, MergeStartX, MergeEndX);
829
830    ATokenInfo.TokenAttr := FMarkupLine;
831  end;
832
833  if FLineState in [LSTATE_BOL, LSTATE_BOL_GAP, LSTATE_DOTS, LSTATE_EOL] then
834    inc(FLineState);
835end;
836
837function TLazSynDisplayFold.GetLinesCount: Integer;
838begin
839  Result := FFoldView.ViewedCount;
840end;
841
842function TLazSynDisplayFold.TextToViewIndex(ATextIndex: TLineIdx): TLineRange;
843begin
844// TODO: inherited AFTER fold mapping?
845  Result := inherited TextToViewIndex(ATextIndex);
846  if Result.Top = Result.Bottom then begin
847    Result.Top    := FFoldView.InternTextToViewIndex(Result.Top);
848    Result.Bottom := Result.Top;
849  end
850  else begin
851    Result.Top    := FFoldView.InternTextToViewIndex(Result.Top);
852    Result.Bottom := FFoldView.InternTextToViewIndex(Result.Bottom);
853  end;
854end;
855
856function TLazSynDisplayFold.ViewToTextIndex(AViewIndex: TLineIdx): TLineIdx;
857begin
858  Result := FFoldView.InternViewToTextIndex(inherited ViewToTextIndex(AViewIndex));
859end;
860
861function TLazSynDisplayFold.ViewToTextIndexEx(AViewIndex: TLineIdx; out
862  AViewRange: TLineRange): TLineIdx;
863begin
864  Result := FFoldView.InternViewToTextIndex(inherited ViewToTextIndexEx(AViewIndex, AViewRange));
865end;
866
867{ TSynEditFoldExportStream }
868
869constructor TSynEditFoldExportStream.Create;
870begin
871  inherited;
872  FPos := 0;
873  FLen := 0;
874  FMem := nil;
875end;
876
877
878function TSynEditFoldExportStream.GetLen: Integer;
879begin
880  Result := FLen;
881end;
882
883procedure TSynEditFoldExportStream.SetLen(const AValue: Integer);
884begin
885  FPos := 0;
886  FLen:= AValue;
887end;
888
889function TSynEditFoldExportStream.GetMem: PChar;
890begin
891  if FData <> '' then
892    Result := @FData[1]
893  else
894    Result := FMem;
895end;
896
897procedure TSynEditFoldExportStream.SetMem(const AValue: PChar);
898begin
899  FData := '';
900  FMem := AValue;
901  FPos := 0;
902end;
903
904function TSynEditFoldExportStream.GetText: String;
905begin
906  // only valid for FData
907  SetLength(FData, FLen);
908  Result := FData;
909end;
910
911procedure TSynEditFoldExportStream.SetText(const AValue: String);
912begin
913  FData := AValue;
914  FMem := nil;
915  FPos := 0;
916end;
917
918function TSynEditFoldExportStream.GrowData(AppendSize: Integer): PChar;
919var
920  l: integer;
921begin
922  l := length(FData);
923  if l < FLen + AppendSize then
924    SetLength(FData, l + AppendSize + Max((l+AppendSize) div 4, 1024));
925  Result := @FData[FLen + 1];
926  inc(FLen, AppendSize);
927end;
928
929function TSynEditFoldExportStream.EncodeIntEx(Anum: Integer): String;
930var
931  n: integer;
932begin
933  //    0 -   42 => 1 byte
934  //   43 - 1848 => 2 byte
935  // 1849 - .... => 3 and more
936  Result := '';
937  if ANum = 0 then Result := NumEncode86Chars[1];
938  n := 0;
939  while ANum > 0 do begin
940    Result := NumEncode86Chars[1 + (Anum mod 43) + n] + Result;
941    ANum := ANum div 43;
942    n := 43;
943  end;
944end;
945
946function TSynEditFoldExportStream.EncodeIntEx2(Anum: Integer): String;
947var
948  n: Integer;
949begin
950  //   0 -   80 => 1 char
951  //  81 -  424 => 2 char   (80 + 4 * 86)
952  // 425 -  467 => 2 char (len(EncodeIntEx) = 1)
953  // 468 - 2272 => 3 and more char
954  //2273 - .... => 4 and more char
955  Result := '';
956  if Anum <= 80 then
957    Result := NumEncode86Chars[1 + Anum]
958  else
959  begin
960    n := (Anum-81) div 86;
961    if n <= 3 then
962      Result := NumEncode86Chars[1 + 81 + n] + NumEncode86Chars[1 + (Anum - 81) mod 86]
963    else
964      Result := NumEncode86Chars[1 + 85] + EncodeIntEx(Anum - 81 - 4*86);
965  end;
966end;
967
968function TSynEditFoldExportStream.InternalReadNum(var APos: Integer): Integer;
969var
970  n: Integer;
971begin
972  Result := 0;
973  while True do begin
974    if FPos >= FLen then exit(-1);
975    n := NumEncode86Values[(FMem + APos)^];
976    if n < 43 then break;
977    dec(n, 43);
978    Result := Result * 43 + n;
979    inc(APos);
980  end;
981  Result := Result * 43 + n;
982  inc(APos);
983end;
984
985function TSynEditFoldExportStream.InternalReadNumEx(var APos: Integer): Integer;
986begin
987  if FPos >= FLen then exit(-1);
988  Result := NumEncode86Values[(FMem + APos)^];
989  inc(APos);
990  if Result <= 80 then
991    exit;
992  if FPos >= FLen then exit(-1);
993  if Result < 85 then begin
994    Result := 81 + (Result-81)*86 +  NumEncode86Values[(FMem + APos)^];
995    inc(APos);
996    exit;
997  end;
998  Result := 81 + 4*86 + InternalReadNum(APos);
999end;
1000
1001procedure TSynEditFoldExportStream.Compress;
1002(* Known Sequences: XX = Enc64Num (copy sequence from XX chars before)
1003                    NN = ENc22 Num / n = enc22digit (copy n bytes)
1004     [XXn     (up to 21 bytes, from up to 64*64 back)
1005     [NNXX[   (more then 21 bytes, from up to 64*64 back)
1006     ]X       (3 bytes from max 64 back)
1007     ]nx      ( reocurring space,x times, ever n pos)
1008const
1009  max_single_len = 22 - 1;
1010  *)
1011var
1012  CurPos, EndPos, SearchPos: Integer;
1013  FndLen, FndPos, FndPos2: Integer;
1014  BestLen, BestPos, BestPos2: Integer;
1015  s: string;
1016begin
1017  AppendString(#0);
1018  dec(FLen);
1019
1020  EndPos := FLen;
1021  CurPos := FLen - 3;
1022  while CurPos >= 4 do begin
1023    SearchPos := CurPos - 3;
1024    BestLen := 0;
1025    while (SearchPos >= 1) do begin
1026      if CompareMem(@FData[CurPos], @FData[SearchPos], 3) then begin
1027        FndLen := 3;
1028        FndPos := SearchPos;
1029        FndPos2 := CurPos;
1030        while (SearchPos + FndLen < FndPos2) and
1031              (FndPos2 + FndLen < EndPos - 1) and
1032              (FData[SearchPos + FndLen] = FData[CurPos + FndLen])
1033        do
1034          inc(FndLen);
1035        while (FndPos > 1) and (FndPos + FndLen < FndPos2) and
1036              (FData[FndPos - 1] = FData[FndPos2 - 1]) do
1037        begin
1038          dec(FndPos);
1039          dec(FndPos2);
1040          inc(FndLen);
1041        end;
1042
1043        if (FndLen > BestLen) and
1044           ((FndPos2 - FndPos <= NumEncodeAsOneMax) or (FndLen >= 4)) and
1045           ((FndPos2 - FndPos <= NumEncodeAsTwoMax) or (FndLen >= 5)) and
1046           ((FndPos2 - FndPos <= NumEncodeAsThreeMax) or (FndLen >= 6))
1047        then begin
1048          BestLen := FndLen;
1049          BestPos := FndPos;
1050          BestPos2 := FndPos2;
1051        end;
1052      end;
1053      dec(SearchPos);
1054    end;
1055
1056    s := '';
1057    if (BestLen >= 4) then
1058      s := '[' + EncodeIntEx2(BestPos2 - BestPos) + EncodeIntEx2(BestLen)
1059    else
1060    if (BestLen = 3) and (BestPos2 - BestPos <= NumEncodeAsOneMax) then
1061      s := ']' + EncodeIntEx2(BestPos2 - BestPos);
1062    if (s<>'') and (length(s) < BestLen) then begin
1063      System.Move(s[1], FData[BestPos2], length(s));
1064      System.Move(FData[BestPos2 + BestLen], FData[BestPos2 + length(s)], FLen + 1 - (BestPos2 + BestLen));
1065      dec(FLen, BestLen - length(s));
1066      EndPos := BestPos;
1067      CurPos := BestPos2 - 3;
1068    end
1069    else
1070      dec(CurPos);
1071  end;
1072end;
1073
1074procedure TSynEditFoldExportStream.Decompress;
1075var
1076  i, j, n: Integer;
1077  p, p2: PChar;
1078  NewLen: Integer;
1079begin
1080  // curently assumes that FMem points NOT at FData
1081  if FLen = 0 then
1082    exit;
1083  NewLen := 0;
1084  i := 0;
1085  while i < Flen do begin
1086    case (FMem+i)^ of
1087      '[' :
1088        begin
1089          inc(i);
1090          j := InternalReadNumEx(i);
1091          n := InternalReadNumEx(i);
1092          if (j < n) or (j > NewLen) then raise ESynEditError.Create('fold format error');
1093          inc(NewLen, n);
1094        end;
1095      ']' :
1096        begin
1097          inc(i, 1);
1098          j := InternalReadNumEx(i);
1099          if (j < 3) or (j > NewLen) then raise ESynEditError.Create('fold format error');
1100          inc(NewLen, 3);
1101        end;
1102      else
1103        begin
1104          inc(NewLen);
1105          inc(i);
1106        end;
1107    end;
1108  end;
1109  SetLength(FData, NewLen);
1110
1111  i := 0;
1112  p := PChar(FData);
1113  while i < Flen do begin
1114    case (FMem+i)^ of
1115      '[' :
1116        begin
1117          inc(i);
1118          j := InternalReadNumEx(i);
1119          n := InternalReadNumEx(i);
1120          p2 := p;
1121          while n > 0 do begin
1122            p^ := (p2 - j)^;
1123            inc(p);
1124            dec(j);
1125            dec(n);
1126          end;
1127        end;
1128      ']' :
1129        begin
1130          inc(i);
1131          j := InternalReadNumEx(i);
1132          p2 := p;
1133          for n := 0 to 2 do begin
1134            p^ := (p2 - j)^;
1135            inc(p);
1136            dec(j);
1137          end;
1138        end;
1139      else
1140        begin
1141          p^ := (FMem + i)^;
1142          inc(p);
1143          inc(i);
1144        end;
1145    end;
1146  end;
1147
1148  FLen := NewLen;
1149  FMem := PChar(FData);
1150  FPos := 0;
1151end;
1152
1153procedure TSynEditFoldExportStream.AddChecksum;
1154var
1155  i, c: Integer;
1156begin
1157  if FLen = 0 then
1158    exit;
1159  if FMem = nil then
1160    FMem := @FData[1];
1161  c := 0;
1162  for i := 0 to FLen - 1 do
1163    c := c xor (ord((FMem + i)^) * (i+1));
1164  c := (c mod 256) xor ((c div 256) mod 256) xor ((c div 65536) mod 256);
1165  AppendString(NumEncode86Chars[1 + (c mod 86)]);
1166end;
1167
1168function TSynEditFoldExportStream.VerifyChecksum: Boolean;
1169var
1170  i, c: Integer;
1171begin
1172  if FLen = 0 then
1173    exit(True);
1174  if FMem = nil then
1175    FMem := @FData[1];
1176  dec(Flen);
1177  c := 0;
1178  for i := 0 to FLen - 1 do
1179    c := c xor (ord((FMem + i)^) * (i+1));
1180  c := (c mod 256) xor ((c div 256) mod 256) xor ((c div 65536) mod 256);
1181  Result := (FMem + FLen)^ = NumEncode86Chars[1 + (c mod 86)];
1182end;
1183
1184procedure TSynEditFoldExportStream.AppendMem(AMem: Pointer; ALen: Integer);
1185begin
1186  {$IFDEF SynFoldSaveDebug}
1187  DebugLn(['TSynEditFoldExportStream.AppendMem len=', ALen]);
1188  {$ENDIF}
1189  FMem := nil;
1190  if ALen > 0 then
1191    System.Move(AMem^, GrowData(ALen)^, ALen);
1192end;
1193
1194procedure TSynEditFoldExportStream.AppendString(ATxt: String);
1195var
1196  l: Integer;
1197begin
1198  {$IFDEF SynFoldSaveDebug}
1199  DebugLn(['TSynEditFoldExportStream.AppendString ', ATxt]);
1200  {$ENDIF}
1201  FMem := nil;
1202  l := length(ATxt);
1203  if l > 0 then
1204    System.Move(ATxt[1], GrowData(l)^, l);
1205end;
1206
1207procedure TSynEditFoldExportStream.AppendNum(ANum: Integer);
1208begin
1209  {$IFDEF SynFoldSaveDebug}
1210  DebugLn(['TSynEditFoldExportStream.AppendNum ', ANum]);
1211  {$ENDIF}
1212  FMem := nil;
1213  AppendString(EncodeIntEx(ANum));
1214end;
1215
1216procedure TSynEditFoldExportStream.AppendNumEx(ANum: Integer);
1217begin
1218  {$IFDEF SynFoldSaveDebug}
1219  DebugLn(['TSynEditFoldExportStream.AppendNumEx ', ANum]);
1220  {$ENDIF}
1221  FMem := nil;
1222  AppendString(EncodeIntEx2(ANum));
1223end;
1224
1225procedure TSynEditFoldExportStream.Reset;
1226begin
1227  FPos := 0;
1228  if (FMem = nil) and (FData <> '') then
1229    FMem := @FData[1];
1230end;
1231
1232procedure TSynEditFoldExportStream.Clear;
1233begin
1234  FLen := 0;
1235  FMem := nil;
1236  FPos := 0;
1237  SetLength(FData, 0);
1238end;
1239
1240function TSynEditFoldExportStream.ReadMem(AMem: Pointer; ALen: Integer): Boolean;
1241begin
1242  Result := FPos+ ALen <= FLen;
1243  If not Result then
1244    exit;
1245  System.Move((FMem + FPos)^, AMem^, ALen);
1246  inc(FPos, ALen);
1247end;
1248
1249function TSynEditFoldExportStream.PeakString(ALen: Integer): String;
1250begin
1251  If not(FPos+ ALen <= FLen) then
1252    exit('');
1253  SetLength(Result, ALen);
1254  if ALen > 0 then
1255    System.Move((FMem + FPos)^, Result[1], ALen);
1256end;
1257
1258function TSynEditFoldExportStream.FindChar(AChar: Char): Integer;
1259begin
1260  Result := 0;
1261  While (FPos + Result < FLen) and ((FMem + FPos + Result)^ <> AChar) do
1262    inc(Result);
1263  if FPos + Result = FLen then
1264    Result := -1;
1265end;
1266
1267function TSynEditFoldExportStream.ReadString(ALen: Integer): String;
1268begin
1269  If not(FPos+ ALen <= FLen) then
1270    exit('');
1271  SetLength(Result, ALen);
1272  if ALen > 0 then
1273    System.Move((FMem + FPos)^, Result[1], ALen);
1274  inc(FPos, ALen);
1275end;
1276
1277function TSynEditFoldExportStream.ReadNum: Integer;
1278begin
1279  Result := InternalReadNum(FPos);
1280  {$IFDEF SynFoldSaveDebug}
1281  DebugLn(['TSynEditFoldExportStream.ReadNum ', Result]);
1282  {$ENDIF}
1283end;
1284
1285function TSynEditFoldExportStream.ReadNumEx: Integer;
1286begin
1287  Result := InternalReadNumEx(FPos);
1288  {$IFDEF SynFoldSaveDebug}
1289  DebugLn(['TSynEditFoldExportStream.ReadNumEx ', Result]);
1290  {$ENDIF}
1291end;
1292
1293function TSynEditFoldExportStream.EOF: Boolean;
1294begin
1295  Result := FPos >= FLen;
1296end;
1297
1298{ TSynEditFoldExportCoder }
1299
1300function TSynEditFoldExportCoder.GetReadIsValid: Boolean;
1301begin
1302  Result := FReadState <> sfecInvalid;
1303end;
1304
1305constructor TSynEditFoldExportCoder.Create(AFoldType: Pointer);
1306begin
1307  inherited Create;
1308  FExportStream := TSynEditFoldExportStream.Create;
1309  FExportStream.AppendString(' T');                // Type Marker
1310  FExportStream.AppendNum(PtrUInt(AFoldType));
1311  FFoldType := AFoldType;
1312  FWriteCacheLen := 0;
1313  FWriteCache := nil;
1314  FWriteCacheTypes := [];
1315end;
1316
1317constructor TSynEditFoldExportCoder.Create(AStream: TSynEditFoldExportStream);
1318var
1319  i: Integer;
1320begin
1321  inherited Create;
1322  FExportStream := TSynEditFoldExportStream.Create;
1323  FReadState := sfecInvalid;
1324  if AStream.PeakString(2) <> ' T' then exit;
1325
1326  AStream.ReadString(2);
1327
1328  FFoldType := Pointer(PtrUInt(AStream.ReadNum));
1329  while(true) do begin
1330    i := AStream.FindChar(' ');
1331    if i < 0 then i := AStream.Len - AStream.Pos;
1332    FExportStream.AppendString(AStream.ReadString(i));
1333    if AStream.EOF or (AStream.PeakString(2) = ' T') then
1334      break;
1335    FExportStream.AppendString(AStream.ReadString(2));
1336  end;
1337  {$IFDEF SynFoldSaveDebug}
1338  DebugLn(['TSynEditFoldExportCoder.Create(<from input-stream> FType=', dbgs(FFoldType), '  txtLen=', FExportStream.Len, ' Txt="', FExportStream.Text, '"']);
1339  {$ENDIF}
1340  Reset;
1341end;
1342
1343destructor TSynEditFoldExportCoder.Destroy;
1344begin
1345  FreeAndNil(FExportStream);
1346  Inherited;
1347end;
1348
1349procedure TSynEditFoldExportCoder.AddNode(aX, aY, aLen: Integer; aFoldType: TSynEditFoldType);
1350(* Format:  [Num] <NumEX>
1351  ' T' [type] [yo] <X> <len> ( <c>* ' p' [sum]  [yo] <X> <len> )* <c>* (' P' [sum] [yo] <X> <len>)?
1352
1353  //////////////////////////
1354  // Version info
1355  V1 - no entries
1356  V2  July 2010  0.9.29
1357     - added fold-hide <HideInfo>
1358
1359  //////////////////////////
1360
1361  <Stream> = { <TypeStream> };
1362
1363  <TypeStream> = " T" <TypeId>  <TypeData>;        [* Stores all folds for the given type (eg cfbtBeginEnd) *]
1364
1365  <TypeId>   = ord(cfbtBeginEnd) or similar
1366  <TypeData> = [<HideInfo>],
1367               <NodePos>,
1368               [ [<FoldList>,]  [{ <FoldListEndCont>, <NodePos>, [<FoldList>] }] ],
1369               [ <FoldListEnd> ];
1370
1371
1372  <FoldList> = [{ <ConsecutiveFoldedCount>,  <ConsecutiveUnFoldedCount> }],
1373               <ConsecutiveFoldedCount>,
1374               ;
1375  [* NodePos: is the  position of a folded node (of the type matching the current stream)
1376     ConsecutiveFoldedCount: more folded nodes of the same type, without any
1377                             unfolded node (of this type) inbetween.
1378     ConsecutiveUnFoldedCount: amount of unfolded nodes (of this type) before the next folded node.
1379  *]
1380
1381  <NodePos> =  <YOffset> <XPos> <len>;
1382  <YOffset>                  = <Number>
1383  <XPos>                     = <ExNumber>
1384  <len>                      = <ExNumber>
1385  <ConsecutiveFoldedCount>   = <ExNumber>
1386  <ConsecutiveUnFoldedCount> = <ExNumber>
1387
1388  <FoldListEndCont> = ' p', <SumFoldedLines>;
1389    [* FoldListEndCont is mandotory, if another block of <NodePos>, <FoldList> is coming *]
1390  <FoldListEnd>     = ' P'  <SumFoldedLines>, <EndY>, <EndX>;
1391    [* FoldListEnd is optional. It is expected if the previous <FoldList> has more than 10 folded lines*]
1392
1393  <SumFoldedLines> = <Number>
1394  [* The sum of all lines folded by folds in <ConsecutiveFoldedCount>.
1395     Not including the fold in <NodePos>, which has it's own len.
1396  *]
1397
1398  <Number> = bigger numbers
1399  <ExNumber> = for numbers expected below 467; specially 0..80
1400
1401  <HideInfo> = ' h' | ' H'
1402    not present: all folds, no hides (default)
1403    ' H': all hides, no folds
1404    ' h': mixed hides and folds
1405      For mixed lists the following applies:
1406      - XPos is doubled; bit 0 (odd <number>) indicates the first node is a hide
1407      - ConsecutiveFoldedCount, ConsecutiveUnFoldedCount are doubled;
1408        bit 0 indicates:
1409          If last was fold: 1-odd = hide  /  0-even = open
1410          If last was hide: 1-odd = fold  /  0-even = open
1411          If last was open: 1-odd = hide  /  0-even = fold
1412        In the first <ConsecutiveFoldedCount> after <NodePos> the bit is unused, since nodepos is continued.
1413*)
1414begin
1415  {$IFDEF SynFoldSaveDebug}
1416  debugln(['TSynEditFoldExportCoder.AddNode FType=', dbgs(FFoldType),'   X=', aX, ' Y=', aY, 'Len=', aLen, 'FType=', SynEditFoldTypeNames[aFoldType], ' WCacheLen=', FWriteCacheLen]);
1417  {$ENDIF}
1418  if (FWriteCacheLen = 0) and (aFoldType = scftOpen) then
1419    exit;
1420  if FWriteCacheLen >= length(FWriteCache) then
1421    SetLength(FWriteCache, Max(1000, FWriteCacheLen*2));
1422  FWriteCache[FWriteCacheLen].aY := aY;
1423  FWriteCache[FWriteCacheLen].aX := aX;
1424  FWriteCache[FWriteCacheLen].aLen := aLen;
1425  FWriteCache[FWriteCacheLen].aFoldType := aFoldType;
1426  inc(FWriteCacheLen);
1427  include(FWriteCacheTypes, aFoldType);
1428end;
1429
1430procedure TSynEditFoldExportCoder.Finish;
1431var
1432  FirstLine, HideFactor, HideBit: Integer;
1433  CntSum, LinesSum: Integer;
1434  LastFoldType: TSynEditFoldType;
1435
1436  procedure WriteCachedNode(AIndex: Integer);
1437  begin
1438    HideBit := 0;
1439    LastFoldType := FWriteCache[AIndex].aFoldType;
1440    if (HideFactor = 2) and (LastFoldType = scftHide) then
1441      HideBit := 1;
1442    FExportStream.AppendNum  (FWriteCache[AIndex].aY - FirstLine);
1443    FExportStream.AppendNumEx(FWriteCache[AIndex].aX * HideFactor + HideBit);
1444    FExportStream.AppendNumEx(FWriteCache[AIndex].aLen);
1445    FirstLine := FWriteCache[AIndex].aY;
1446  end;
1447
1448  function CountConsecutiveNodes(var AStartIndex: Integer; out ACount, ALines: Integer;
1449    ASkipFirst: Boolean = True): Boolean;
1450  var l1, l2: Integer;
1451      t: TSynEditFoldType;
1452  begin
1453    // reset counters for following <FoldList>
1454    CntSum := 0;
1455    LinesSum := 0;
1456
1457    HideBit := 0;;
1458    case LastFoldType of
1459      scftOpen: if scftHide = FWriteCache[AStartIndex].aFoldType then HideBit := 1;
1460      scftFold: if scftHide = FWriteCache[AStartIndex].aFoldType then HideBit := 1;
1461      scftHide: if scftFold = FWriteCache[AStartIndex].aFoldType then HideBit := 1;
1462    end;
1463    LastFoldType := FWriteCache[AStartIndex].aFoldType;
1464
1465    Result := False;
1466    ACount := 0;
1467    ALines := 0;
1468
1469    l2 := FirstLine;
1470    t := FWriteCache[AStartIndex].aFoldType;
1471    Repeat
1472      if (AStartIndex >= FWriteCacheLen) then
1473        exit;
1474      l1 := FWriteCache[AStartIndex].aY;
1475      if (ACount         > SEQMaxNodeCount) or
1476         (ALines         > SEQMaxNodeCount) or
1477         (l1 - l2        > SEQMaxLineDistEach) or
1478         (l1 - FirstLine > SEQMaxLineDistTotal)
1479      then
1480        exit;
1481
1482      if not ASkipFirst then begin
1483        ALines := ALines + FWriteCache[AStartIndex].aLen;
1484        inc(ACount);
1485      end;
1486      inc(AStartIndex);
1487      l2 := l1;
1488      ASkipFirst := False;
1489    until FWriteCache[AStartIndex].aFoldType <> t;
1490    Result := True;
1491  end;
1492
1493  var DeferredZero: Boolean;
1494  procedure WriteNodeCount(ACount, ALines: Integer; AState: TSynEditFoldType);
1495  begin
1496    inc(CntSum, ACount);
1497    inc(LinesSum, ALines); // non folds are always 0
1498    if ACount = 0 then begin
1499      DeferredZero := True;
1500      exit;
1501    end;
1502    if DeferredZero then
1503      FExportStream.AppendNumEx(0);
1504    DeferredZero := False;
1505    FExportStream.AppendNumEx(ACount * HideFactor + HideBit);
1506  end;
1507
1508  function ScanForFold(var AIndex: Integer): Boolean;
1509  begin
1510    Result := True;
1511    while AIndex < FWriteCacheLen do begin
1512      if FWriteCache[AIndex].aFoldType in [scftFold, scftHide] then exit;
1513      inc(AIndex);
1514    end;
1515    Result := False;
1516  end;
1517var
1518  i, i2, CntF, CntNF, LinesF, LinesNF: Integer;
1519  r: boolean;
1520begin
1521  if (FWriteCacheLen = 0) or (FWriteCacheTypes * [scftFold, scftHide] = []) then begin
1522    FExportStream.Clear;
1523    exit;
1524  end;
1525  {$IFDEF SynFoldSaveDebug}
1526  DebugLnEnter(['TSynEditFoldExportCoder.Finish FType=', dbgs(FFoldType)]);
1527  {$ENDIF}
1528
1529  FirstLine := 0;
1530  if (FWriteCacheTypes * [scftFold, scftHide] = [scftFold, scftHide]) then begin
1531    HideFactor := 2;
1532    FExportStream.AppendString(' h');
1533  end
1534  else begin
1535    HideFactor := 1; // no bit for hide/fold differentation needed
1536    if scftHide in FWriteCacheTypes then
1537      FExportStream.AppendString(' H');
1538  end;
1539  i := 0;
1540  while i < FWriteCacheLen do begin
1541    WriteCachedNode(i);
1542
1543    DeferredZero := False;   // special case at start, there may be 0 more folded nodes
1544    r := CountConsecutiveNodes(i, cntF, linesF, True);
1545    WriteNodeCount(CntF, LinesF, scftFold); // or hide, no matter here
1546    while r do begin
1547      r := CountConsecutiveNodes(i, cntNF, linesNF, False);
1548      if not r then break;
1549      r := CountConsecutiveNodes(i, cntF, linesF, False);
1550      WriteNodeCount(CntNF, LinesNF, scftOpen);
1551      WriteNodeCount(CntF, LinesF, scftFold); // or hide, no matter here
1552    end;
1553
1554    i2 := i;
1555    ScanForFold(i);
1556    if (i < FWriteCacheLen) then begin
1557      // another node will follow, must insert ' p' marker
1558      FExportStream.AppendString(' p');      // point marker (no marker needed for first entry)
1559      FExportStream.AppendNum(LinesSum);   // Start with sum from last sequence
1560    end;
1561  end;
1562
1563  if LinesSum > 10 then begin
1564    // end of data; write ' P' marker if needed
1565    FExportStream.AppendString(' P');      // point marker (no marker needed for first entry)
1566    FExportStream.AppendNum  (LinesSum);   // Start with sum from last sequence
1567    FExportStream.AppendNum  (FWriteCache[i2-1].aY - FirstLine);  // Last folded Coords
1568    FExportStream.AppendNumEx(FWriteCache[i2-1].aX);
1569  end;
1570  {$IFDEF SynFoldSaveDebug}
1571  DebugLnExit(['TSynEditFoldExportCoder.Finish FType=', dbgs(FFoldType), '  txtLen=', FExportStream.Len, ' Txt="', FExportStream.Text, '"']);
1572  {$ENDIF}
1573end;
1574
1575function TSynEditFoldExportCoder.ReadNode(aX, aY: Integer; aLen: Integer): TSynEditFoldType;
1576(* Format:  [Num] <NumEX>
1577  ' T' [type]
1578   [yo] <X> <len> ( <c>* ' p' [sum]  [yo] <X> <len> )* <c>* (' P' [sum] [yo] <X>)?
1579*)
1580  function GetCommand: Char;
1581  begin
1582    Result := #0;
1583    if (FExportStream.PeakString(1) = ' ') and (FExportStream.Len > FExportStream.Pos+1) then
1584      Result := FExportStream.ReadString(2)[2];
1585  end;
1586
1587  function Invalidate: TSynEditFoldType;
1588  begin
1589    {$IFDEF SynFoldSaveDebug}
1590    DebugLn(['Invalidate']);
1591    {$ENDIF}
1592    FReadState := sfecInvalid;
1593    Result := scftInvalid;
1594  end;
1595
1596var
1597  i: Integer;
1598begin
1599  {$IFDEF SynFoldSaveDebug}
1600  DebugLnEnter(['TSynEditFoldExportCoder.Readnode  X=', aX, ' Y=', aY, ' Len=',aLen,
1601                '   ReadState=',SynEditFoldExportCoderStates[FReadState],
1602                ' FReadCount=', FReadCount, ' FReadY=', FReadY, ' FReadX=', FReadX,
1603                ' FReadSumLen=', FReadSumLen, ' FReadType=', SynEditFoldTypeNames[FReadType]
1604                 ]);
1605  try
1606  {$ENDIF}
1607  Result := scftInvalid;
1608  case FReadState of
1609    sfecAtBegin, sfecAtPoint:
1610      begin
1611        if (FReadState = sfecAtBegin) then begin
1612          case GetCommand of
1613            'H': begin
1614                FReadDefaultType := scftHide;
1615                FReadType := scftHide;
1616              end;
1617            'h': begin
1618                FReadDefaultType := scftAll;
1619              end;
1620          end;
1621          FReadState := sfecAtPoint;
1622        end;
1623
1624        if FReadCount = 0 then begin
1625          FReadCount  := 1;
1626          FReadY      := FExportStream.ReadNum + FReadLastY;
1627          FReadX      := FExportStream.ReadNumEx;
1628          FReadSumLen := FExportStream.ReadNumEx;
1629          if FReadSumLen < 0 then exit(Invalidate);
1630
1631          if FReadDefaultType = scftAll then begin
1632            if (FReadX and 1) = 1 then
1633              FReadType := scftHide
1634            else
1635              FReadType := scftFold;
1636            FReadX := FReadX div 2;
1637          end
1638          else
1639            FReadType := FReadDefaultType;
1640        end;
1641
1642        // ax may be off by one, since pas highlighter changed to include $ in $IFDEF
1643        if ((aY < FReadY) or ((aY = FReadY) and (aX+1 < FReadX))) then
1644          exit(scftOpen);  // actually, read before point
1645
1646        i := 0;
1647        if FReadType = scftHide then i := 1; // fold one more than len
1648        if (aY <> FReadY) or (abs(aX - FReadX) > 1) or (aLen + i <> FReadSumLen) then
1649          exit(Invalidate);
1650
1651        FReadLastY := FReadY;
1652        FReadSumLen := 0; // was len of current fold, no len remaining => prepare for counting consecutive folds
1653        Result := FReadType;
1654
1655        if FExportStream.EOF then
1656          FReadState := sfecAtEOF
1657        else case GetCommand of
1658          'p':
1659            begin
1660              FExportStream.ReadNum;  // skip len (must be 0) since there was no <ConsecutiveFoldedCount>
1661              FReadCount := 0;
1662              FReadState := sfecAtPoint;
1663            end;
1664          'P':
1665            begin
1666              // end marker isnt expected? there were no <ConsecutiveFoldedCount>
1667              FReadState := sfecAtEOF;
1668            end;
1669          else
1670            begin
1671              FReadState := sfecInRepeatCount;
1672              FReadCount := FExportStream.ReadNumEx;  // count up and check at end
1673            end;
1674        end;
1675      end;
1676
1677    sfecInRepeatCount:
1678      begin
1679        if FReadCount = 0 then begin
1680          if FExportStream.EOF then begin
1681            FReadState := sfecAtEOF;
1682            exit(scftOpen);
1683          end
1684          else case GetCommand of
1685            'p':
1686              begin
1687                if FReadSumLen <> FExportStream.ReadNum then
1688                  exit(Invalidate);
1689                FReadCount := 0;
1690                FReadState := sfecAtPoint;
1691                exit(ReadNode(aX, aY, aLen));
1692              end;
1693            'P':
1694              begin
1695                if (FReadSumLen <> FExportStream.ReadNum) or
1696                   (FReadY <> FExportStream.ReadNum + FReadLastY) or
1697                   (FReadX <> FExportStream.ReadNumEx)
1698                then
1699                  exit(Invalidate);
1700                FReadState := sfecAtEOF;
1701                exit(scftOpen);
1702              end;
1703            else
1704              begin
1705                FReadCount := FExportStream.ReadNumEx;  // count up and check at end
1706                if FReadDefaultType = scftAll then begin
1707                  if (FReadCount and 1) = 1 then begin
1708                    case FReadType of
1709                      scftOpen: FReadType := scftHide;
1710                      scftFold: FReadType := scftHide;
1711                      scftHide: FReadType := scftFold;
1712                    end;
1713                  end else begin
1714                    case FReadType of
1715                      scftOpen: FReadType := scftFold;
1716                      scftFold: FReadType := scftOpen;
1717                      scftHide: FReadType := scftOpen;
1718                    end;
1719                  end;
1720                  FReadCount := FReadCount div 2;
1721                end
1722                else begin
1723                  if FReadType = scftOpen then
1724                    FReadType := FReadDefaultType
1725                  else
1726                    FReadType := scftOpen;
1727                end;
1728              end;
1729          end;
1730        end;
1731        dec(FReadCount);
1732        inc(FReadSumLen, aLen);
1733        Result := FReadType;
1734      end;
1735
1736    sfecAtEOF:
1737      begin
1738        exit(scftOpen);
1739      end;
1740    sfecInvalid:
1741      begin
1742        exit(scftInvalid);
1743      end;
1744  end;
1745  {$IFDEF SynFoldSaveDebug}
1746  finally
1747    DebugLnExit(['TSynEditFoldExportCoder.Readnode << ']);
1748  end;
1749  {$ENDIF}
1750end;
1751
1752function TSynEditFoldExportCoder.EOF: Boolean;
1753begin
1754  Result := FExportStream.EOF;
1755end;
1756
1757procedure TSynEditFoldExportCoder.Reset;
1758begin
1759  FExportStream.Reset;
1760  FReadY := -1;
1761  FReadX := -1;
1762  FReadLastY := 0;
1763  FReadCount := 0;
1764  FReadSumLen := 0;
1765  FReadState := sfecAtBegin;
1766  if FExportStream.Len = 0 then
1767    FReadState := sfecInvalid;
1768  FReadDefaultType := scftFold;
1769  FReadType := scftFold;
1770end;
1771
1772{ TSynTextFoldAVLNodeData }
1773
1774function TSynTextFoldAVLNodeData.Left: TSynTextFoldAVLNodeData;
1775begin
1776  Result := TSynTextFoldAVLNodeData(FLeft);
1777end;
1778
1779function TSynTextFoldAVLNodeData.Parent: TSynTextFoldAVLNodeData;
1780begin
1781  Result := TSynTextFoldAVLNodeData(FParent);
1782end;
1783
1784function TSynTextFoldAVLNodeData.Right: TSynTextFoldAVLNodeData;
1785begin
1786  Result := TSynTextFoldAVLNodeData(FRight);
1787end;
1788
1789procedure TSynTextFoldAVLNodeData.FreeAllChildrenAndNested;
1790begin
1791  if FLeft <> nil then begin
1792    Left.FreeAllChildrenAndNested;
1793    FreeAndNil(FLeft);
1794  end;
1795
1796  if FRight <> nil then begin
1797    Right.FreeAllChildrenAndNested;
1798    FreeAndNil(FRight);
1799  end;
1800
1801  if Nested <> nil then begin
1802    Nested.FreeAllChildrenAndNested;
1803    FreeAndNil(Nested);
1804  end;
1805end;
1806
1807function TSynTextFoldAVLNodeData.RecursiveFoldCount : Integer;
1808var
1809  ANode: TSynTextFoldAVLNodeData;
1810begin
1811  Result := 0;
1812  ANode := self;
1813  while ANode <> nil do begin
1814    Result := Result + ANode.MergedLineCount + ANode.LeftCount;
1815    ANode := ANode.Right;
1816  end;
1817end;
1818
1819function TSynTextFoldAVLNodeData.Precessor: TSynTextFoldAVLNodeData;
1820begin
1821  Result := TSynTextFoldAVLNodeData(inherited Precessor);
1822end;
1823
1824function TSynTextFoldAVLNodeData.Successor: TSynTextFoldAVLNodeData;
1825begin
1826  Result := TSynTextFoldAVLNodeData(inherited Successor);
1827end;
1828
1829function TSynTextFoldAVLNodeData.Precessor(var aStartPosition,
1830  aSizesBeforeSum: Integer): TSynTextFoldAVLNodeData;
1831begin
1832  Result := TSynTextFoldAVLNodeData(inherited Precessor(aStartPosition, aSizesBeforeSum));
1833end;
1834
1835function TSynTextFoldAVLNodeData.Successor(var aStartPosition,
1836  aSizesBeforeSum: Integer): TSynTextFoldAVLNodeData;
1837begin
1838  Result := TSynTextFoldAVLNodeData(inherited Successor(aStartPosition, aSizesBeforeSum));
1839end;
1840
1841{ TSynTextFoldAVLNode }
1842
1843function TSynTextFoldAVLNode.GetClassification: TFoldNodeClassification;
1844begin
1845  if fData = nil
1846  then Result := fncInvalid
1847  else Result := fData.Classification;
1848end;
1849
1850function TSynTextFoldAVLNode.GetFoldColumn: Integer;
1851begin
1852  if fData = nil
1853  then Result := -1
1854  else Result := fData.FoldColumn;
1855end;
1856
1857function TSynTextFoldAVLNode.GetFoldColumnLen: Integer;
1858begin
1859  if fData = nil
1860  then Result := -1
1861  else Result := fData.FoldColumnLen;
1862end;
1863
1864function TSynTextFoldAVLNode.GetFoldIndex: Integer;
1865begin
1866  if fData = nil
1867  then Result := -1
1868  else Result := fData.FoldIndex;
1869end;
1870
1871function TSynTextFoldAVLNode.GetMergedLineCount : Integer;
1872begin
1873  if fData = nil
1874  then Result := 0
1875  else Result := fData.MergedLineCount;
1876end;
1877
1878function TSynTextFoldAVLNode.GetFullCount: Integer;
1879begin
1880  if fData = nil
1881  then Result := -1
1882  else Result := fData.FullCount;
1883end;
1884
1885function TSynTextFoldAVLNode.GetSourceLine: integer;
1886begin
1887  if fData = nil then
1888    Result := -1
1889  else
1890    Result := StartLine - fData.VisibleLines;
1891end;
1892
1893function TSynTextFoldAVLNode.GetSourceLineOffset: integer;
1894begin
1895  if fData = nil then
1896    Result := 0
1897  else
1898    Result := fData.VisibleLines;
1899end;
1900
1901procedure TSynTextFoldAVLNode.SetFoldColumn(const AValue: Integer);
1902begin
1903  if fData <> nil then
1904    fData.FoldColumn :=  AValue;
1905end;
1906
1907procedure TSynTextFoldAVLNode.Init(aData: TSynTextFoldAVLNodeData; aStartLine,
1908  aFoldedBefore: Integer);
1909begin
1910  fData := aData;
1911  fStartLine :=  aStartLine;
1912  fFoldedBefore := aFoldedBefore;
1913end;
1914
1915function TSynTextFoldAVLNode.IsInFold : Boolean;
1916begin
1917  Result := fData <> nil;
1918end;
1919
1920function TSynTextFoldAVLNode.Next : TSynTextFoldAVLNode;
1921var aStart, aBefore : Integer;
1922begin
1923  if fData <> nil then begin
1924    aStart := StartLine;
1925    aBefore := FoldedBefore;
1926    Result.fData := fData.Successor(aStart, aBefore);
1927    Result.fStartLine := aStart;
1928    Result.fFoldedBefore := aBefore;
1929  end
1930  else Result.fData := nil;
1931end;
1932
1933function TSynTextFoldAVLNode.Prev : TSynTextFoldAVLNode;
1934var aStart, aBefore : Integer;
1935begin
1936  if fData <> nil then begin
1937    aStart := StartLine;
1938    aBefore := FoldedBefore;
1939    Result.fData := fData.Precessor(aStart, aBefore);
1940    Result.fStartLine := aStart;
1941    Result.fFoldedBefore := aBefore;
1942  end
1943  else Result.fData := nil;
1944end;
1945
1946function TSynTextFoldAVLNode.IsHide: Boolean;
1947begin
1948  Result := (fData <> nil) and (fData.VisibleLines = 0);
1949end;
1950
1951{ TSynTextFoldAVLNodeNestedIterator }
1952
1953constructor TSynTextFoldAVLNodeNestedIterator.Create(ANode: TSynTextFoldAVLNode);
1954begin
1955  SetLength(FOuterNodes, 0);
1956  FCurrentNode := ANode;
1957end;
1958
1959destructor TSynTextFoldAVLNodeNestedIterator.Destroy;
1960begin
1961  SetLength(FOuterNodes, 0);
1962  inherited Destroy;
1963end;
1964
1965function TSynTextFoldAVLNodeNestedIterator.Next: TSynTextFoldAVLNode;
1966var
1967  NewData: TSynTextFoldAVLNodeData;
1968  i: Integer;
1969  PNode: TSynTextFoldAVLNode;
1970begin
1971  i := length(FOuterNodes);
1972  if FCurrentNode.fData.Nested = nil then begin
1973    FCurrentNode := FCurrentNode.Next;
1974    while (not FCurrentNode.IsInFold) and (i > 0) do begin
1975      dec(i);
1976      FCurrentNode := FOuterNodes[i];
1977      SetLength(FOuterNodes, i);
1978      FCurrentNode := FCurrentNode.Next;
1979    end;
1980  end else begin
1981    SetLength(FOuterNodes, i + 1);
1982    FOuterNodes[i] := FCurrentNode;
1983    NewData := FCurrentNode.fData.Nested;
1984    FCurrentNode.fData := NewData;
1985    FCurrentNode.FStartLine := FCurrentNode.FStartLine + NewData.LineOffset;
1986
1987    PNode := FCurrentNode.Prev;
1988    while PNode.IsInFold do begin
1989      FCurrentNode := PNode;
1990      PNode := FCurrentNode.Prev;
1991    end;
1992  end;
1993  Result := FCurrentNode;
1994end;
1995
1996function TSynTextFoldAVLNodeNestedIterator.Prev: TSynTextFoldAVLNode;
1997var
1998  i: Integer;
1999  NewData: TSynTextFoldAVLNodeData;
2000  PNode: TSynTextFoldAVLNode;
2001begin
2002  FCurrentNode := FCurrentNode.Prev;
2003  i := length(FOuterNodes);
2004  if FCurrentNode.IsInFold then begin
2005    while (FCurrentNode.fData.Nested <> nil) do begin
2006      SetLength(FOuterNodes, i + 1);
2007      FOuterNodes[i] := FCurrentNode;
2008      NewData := FCurrentNode.fData.Nested;
2009      FCurrentNode.fData := NewData;
2010      FCurrentNode.FStartLine := FCurrentNode.FStartLine + NewData.LineOffset;
2011
2012      PNode := FCurrentNode.Next;
2013      while PNode.IsInFold do begin
2014        FCurrentNode := PNode;
2015        PNode := FCurrentNode.Next;
2016      end;
2017    end;
2018  end
2019  else // not IsInFold
2020  if (i > 0) then begin
2021    dec(i);
2022    FCurrentNode := FOuterNodes[i];
2023    SetLength(FOuterNodes, i);
2024  end;
2025  Result := FCurrentNode;
2026end;
2027
2028function TSynTextFoldAVLNodeNestedIterator.EOF: Boolean;
2029begin
2030  Result := not FCurrentNode.Next.IsInFold;
2031end;
2032
2033function TSynTextFoldAVLNodeNestedIterator.BOF: Boolean;
2034begin
2035  Result := not FCurrentNode.Prev.IsInFold;
2036end;
2037
2038function TSynTextFoldAVLNodeNestedIterator.IsInFold: Boolean;
2039begin
2040  Result := FCurrentNode.IsInFold;
2041end;
2042
2043{ TSynFoldNodeInfoHelper }
2044
2045constructor TSynFoldNodeInfoHelper.Create(AHighlighter: TSynCustomFoldHighlighter);
2046begin
2047  inherited Create;
2048  FHighlighter := AHighlighter;
2049  Invalidate;
2050end;
2051
2052function TSynFoldNodeInfoHelper.FirstOpen: TSynFoldNodeInfo;
2053begin
2054  FActions := [sfaOpen, sfaFold];
2055  FCurInfo.NodeIndex := -1;
2056  FCurInfo.LineIndex := 0;
2057  Result := Next;
2058end;
2059
2060procedure TSynFoldNodeInfoHelper.Invalidate;
2061begin
2062  FCurInfo.FoldAction := [sfaInvalid];
2063end;
2064
2065function TSynFoldNodeInfoHelper.Next: TSynFoldNodeInfo;
2066var
2067  Cnt, Line, Idx: LongInt;
2068begin
2069  Idx := FCurInfo.NodeIndex + 1;
2070  Line := FCurInfo.LineIndex;
2071  Cnt := FHighlighter.FoldNodeInfo[Line].CountEx(FActions);
2072  if Idx >= Cnt then begin
2073    Idx := 0;
2074    inc(Line);
2075    while (Line < FHighlighter.CurrentLines.Count) and
2076          (FHighlighter.FoldNodeInfo[Line].CountEx(FActions) = 0)
2077    do
2078      inc(Line);
2079  end;
2080  if (Line < FHighlighter.CurrentLines.Count) then
2081    FCurInfo := FHighlighter.FoldNodeInfo[Line].NodeInfoEx(Idx, FActions)
2082  else
2083    Invalidate;
2084  Result := FCurInfo;
2085end;
2086
2087function TSynFoldNodeInfoHelper.Prev: TSynFoldNodeInfo;
2088var
2089  Line, Idx: LongInt;
2090begin
2091  Idx := FCurInfo.NodeIndex - 1;
2092  Line := FCurInfo.LineIndex;
2093  if Idx < 0 then begin
2094    dec(Line);
2095    while (Line >= 0) and
2096          (FHighlighter.FoldNodeInfo[Line].CountEx(FActions) = 0)
2097    do
2098      dec(Line);
2099    Idx := FHighlighter.FoldNodeInfo[Line].CountEx(FActions) - 1;
2100  end;
2101  if (Line >= 0) then
2102    FCurInfo := FHighlighter.FoldNodeInfo[Line].NodeInfoEx(Idx, FActions)
2103  else
2104    Invalidate;
2105  Result := FCurInfo;
2106end;
2107
2108function TSynFoldNodeInfoHelper.FindClose: TSynFoldNodeInfo;
2109var
2110  Line, EndLine, Cnt: Integer;
2111  NdInfo: TSynFoldNodeInfo;
2112begin
2113  Line := FCurInfo.LineIndex;
2114  EndLine := FHighlighter.FoldEndLine(Line, FCurInfo.NodeIndex);
2115  FActions := [sfaClose, sfaFold];
2116  Cnt := FHighlighter.FoldNodeInfo[EndLine].CountEx(FActions) - 1;
2117  while Cnt >= 0 do begin
2118    NdInfo := FHighlighter.FoldNodeInfo[EndLine].NodeInfoEx(Cnt, FActions);
2119    if (NdInfo.FoldLvlStart = FCurInfo.FoldLvlEnd) and
2120       (NdInfo.FoldType = FCurInfo.FoldType)
2121    then
2122      break;
2123    dec(Cnt);
2124  end;
2125  if Cnt < 0 then
2126    Invalidate
2127  else
2128    FCurInfo := NdInfo;
2129  Result := FCurInfo;
2130end;
2131
2132function TSynFoldNodeInfoHelper.GotoOpenPos(aLineIdx, aNodeIdx: integer): TSynFoldNodeInfo;
2133begin
2134  FActions := [sfaOpen, sfaFold];
2135  FCurInfo := FHighlighter.FoldNodeInfo[aLineIdx].NodeInfoEx(aNodeIdx, FActions);
2136  Result := FCurInfo;
2137end;
2138
2139function TSynFoldNodeInfoHelper.GotoOpenAtChar(aLineIdx, aXPos: integer): TSynFoldNodeInfo;
2140var
2141  Cnt: Integer;
2142begin
2143  FActions := [sfaOpen, sfaFold];
2144  Cnt := FHighlighter.FoldNodeInfo[aLineIdx].CountEx(FActions) - 1;
2145  while Cnt >= 0 do begin
2146    FCurInfo := FHighlighter.FoldNodeInfo[aLineIdx].NodeInfoEx(Cnt, FActions);
2147    if FCurInfo.LogXStart = aXPos then break;
2148    dec(Cnt);
2149  end;
2150  if Cnt < 0 then
2151    Invalidate;
2152  Result := FCurInfo;
2153end;
2154
2155function TSynFoldNodeInfoHelper.GotoNodeOpenPos(ANode: TSynTextFoldAVLNode): TSynFoldNodeInfo;
2156begin
2157  FActions := [sfaOpen, sfaFold];
2158  FCurInfo := FHighlighter.FoldNodeInfo[ANode.StartLine - ANode.SourceLineOffset - 1]
2159              .NodeInfoEx(ANode.FoldIndex, FActions);
2160  Result := FCurInfo;
2161end;
2162
2163function TSynFoldNodeInfoHelper.GotoNodeClosePos(ANode: TSynTextFoldAVLNode): TSynFoldNodeInfo;
2164var
2165  NdInfo, NdInfo2: TSynFoldNodeInfo;
2166  Cnt, EndCol, EndLineIdx: Integer;
2167begin
2168  FActions := [sfaClose, sfaFold];
2169  NdInfo := FHighlighter.FoldNodeInfo[ANode.StartLine - ANode.SourceLineOffset - 1]
2170            .NodeInfoEx(ANode.FoldIndex, [sfaOpen, sfaFold]);
2171  if sfaInvalid in NdInfo.FoldAction then exit(NdInfo);
2172
2173  EndLineIdx := FHighlighter.FoldEndLine(ANode.StartLine - ANode.SourceLineOffset - 1,
2174                                        ANode.FoldIndex);
2175  {$IFDEF SynAssertFold}
2176  SynAssert(EndLineIdx >= 0, 'TSynFoldNodeInfoHelper.GotoNodeClosePos: Bad EndLineIdx=%d # Anode: StartLine=%d SrcLOffs=%d ColIdx=%d FoldCol=%d', [EndLineIdx, ANode.StartLine, ANode.SourceLineOffset, ANode.FoldIndex, ANode.FoldColumn]);
2177  {$ENDIF}
2178  Cnt := FHighlighter.FoldNodeInfo[EndLineIdx].CountEx([sfaClose, sfaFold]);
2179  EndCol := 0;
2180  while EndCol < Cnt do begin
2181    NdInfo2 := FHighlighter.FoldNodeInfo[EndLineIdx].NodeInfoEx(EndCol, [sfaClose, sfaFold]);
2182    if (NdInfo2.FoldLvlStart = NdInfo.FoldLvlEnd) and
2183       (NdInfo2.FoldType = NdInfo.FoldType) then break;
2184    inc(EndCol);
2185  end;
2186  if (EndCol = Cnt) or (sfaInvalid in NdInfo2.FoldAction) then
2187    Invalidate
2188  else
2189    FCurInfo := NdInfo2;
2190  Result := FCurInfo;
2191end;
2192
2193function TSynFoldNodeInfoHelper.IsAtNodeOpenPos(ANode: TSynTextFoldAVLNode): Boolean;
2194begin
2195  Result := (not (sfaInvalid in FCurInfo.FoldAction)) and
2196            (ANode.IsInFold) and
2197            (FCurInfo.LineIndex = ANode.StartLine - ANode.SourceLineOffset - 1) and
2198            (FCurInfo.NodeIndex = ANode.FoldIndex);
2199end;
2200
2201function TSynFoldNodeInfoHelper.IsValid: Boolean;
2202begin
2203  Result := (not (sfaInvalid in FCurInfo.FoldAction));
2204end;
2205
2206function TSynFoldNodeInfoHelper.Equals(AnInfo: TSynFoldNodeInfo): Boolean;
2207begin
2208  Result := (FCurInfo.LineIndex = AnInfo.LineIndex) and
2209            (FCurInfo.NodeIndex = AnInfo.NodeIndex) and
2210            (FCurInfo.LogXStart = AnInfo.LogXStart) and
2211            (FCurInfo.LogXEnd   = AnInfo.LogXEnd) and
2212            (FCurInfo.FoldLvlStart = AnInfo.FoldLvlStart) and
2213            (FCurInfo.FoldLvlEnd   = AnInfo.FoldLvlEnd) and
2214            (FCurInfo.FoldAction = AnInfo.FoldAction) and
2215            (FCurInfo.FoldType   = AnInfo.FoldType) and
2216            (FCurInfo.FoldGroup  = AnInfo.FoldGroup);
2217end;
2218
2219function TSynFoldNodeInfoHelper.Equals(AHelper: TSynFoldNodeInfoHelper): Boolean;
2220begin
2221  Result := Equals(AHelper.Info);
2222end;
2223
2224{ TSynTextFoldAVLTree }
2225
2226function TSynTextFoldAVLTree.NewNode : TSynTextFoldAVLNodeData;
2227begin
2228  Result := TSynTextFoldAVLNodeData.Create;
2229end;
2230
2231destructor TSynTextFoldAVLTree.Destroy;
2232begin
2233  Clear;
2234  if fNestedNodesTree <> nil then begin
2235    fNestedNodesTree.fRoot := nil; //was freed in self.Clear
2236    fNestedNodesTree.fNestParent := nil; // Or Destroy will access invalid memory
2237    fNestedNodesTree.Free;
2238  end;
2239  inherited Destroy;
2240end;
2241
2242procedure TSynTextFoldAVLTree.Clear;
2243  procedure DeleteNode({var} ANode: TSynTextFoldAVLNodeData);
2244  begin
2245    if ANode.Left <>nil   then DeleteNode(ANode.Left);
2246    if ANode.Right <>nil  then DeleteNode(ANode.Right);
2247    if ANode.Nested <>nil then DeleteNode(ANode.Nested);
2248    DisposeNode(TSynSizedDifferentialAVLNode(ANode));
2249  end;
2250begin
2251  if fRoot <> nil then DeleteNode(TSynTextFoldAVLNodeData(fRoot));
2252  SetRoot(nil);
2253end;
2254
2255procedure TSynTextFoldAVLTree.SetRoot(ANode : TSynSizedDifferentialAVLNode);
2256begin
2257  inherited;;
2258  if fNestParent <> nil then fNestParent.Nested := TSynTextFoldAVLNodeData(ANode);
2259end;
2260
2261procedure TSynTextFoldAVLTree.SetRoot(ANode : TSynSizedDifferentialAVLNode; anAdjustChildLineOffset : Integer);
2262begin
2263  inherited;;
2264  if fNestParent <> nil then fNestParent.Nested := TSynTextFoldAVLNodeData(ANode);
2265end;
2266
2267(* Find Fold by Line in Real Text *)
2268function TSynTextFoldAVLTree.FindFoldForLine(ALine : Integer;
2269  FindNextNode : Boolean = False) : TSynTextFoldAVLNode;
2270var
2271  r : TSynTextFoldAVLNodeData;
2272  rStartLine : Integer;
2273  rFoldedBefore : Integer;
2274begin
2275  r := TSynTextFoldAVLNodeData(fRoot);
2276  rStartLine := fRootOffset;
2277  rFoldedBefore := 0;
2278  while (r <> nil) do begin
2279    rStartLine := rStartLine + r.LineOffset;
2280
2281    if ALine < rStartLine then begin
2282      if FindNextNode and (r.Left = nil) then break;
2283      r := r.Left; // rStartLine points to r, so if r.Left is nil then it is pointing to the next fold;
2284      continue;
2285    end;
2286
2287    rFoldedBefore := rFoldedBefore + r.LeftCount;
2288    if ALine < rStartLine + r.MergedLineCount
2289    then break;
2290
2291    if FindNextNode and (r.Right = nil) then begin
2292      r := r.Successor(rStartLine, rFoldedBefore);
2293      break;
2294    end;
2295
2296    rFoldedBefore := rFoldedBefore + r.MergedLineCount;
2297    r := r.Right; // rStartLine points to r, which now is the start of the previous fold;
2298  end;
2299
2300  Result{%H-}.Init(r, rStartLine, rFoldedBefore);
2301end;
2302
2303(* Find Fold by Line in Folded Text // always returns unfolded, unless next=true *)
2304function TSynTextFoldAVLTree.FindFoldForFoldedLine(ALine : Integer;
2305  FindNextNode : Boolean) : TSynTextFoldAVLNode;
2306var
2307  r : TSynTextFoldAVLNodeData;
2308  rStartLine : Integer;
2309  rFoldedBefore : Integer;
2310begin
2311  r := TSynTextFoldAVLNodeData(fRoot);
2312  rStartLine := fRootOffset;
2313  rFoldedBefore := 0;
2314  while (r <> nil) do begin
2315    rStartLine := rStartLine + r.LineOffset;
2316
2317    // r.LeftCount => "FoldedBefore"
2318    if ALine + r.LeftCount < rStartLine then begin
2319      if FindNextNode and (r.Left = nil) then break;
2320      r := r.Left; // rStartLine points to r, so if r.Left is nil then it is pointing to the next fold;
2321      continue;
2322    end;
2323
2324    ALine := ALine + r.LeftCount + r.MergedLineCount;
2325    rFoldedBefore := rFoldedBefore + r.LeftCount;
2326
2327    if FindNextNode and (r.Right = nil) then begin
2328      r := r.Successor(rStartLine, rFoldedBefore);
2329      break;
2330    end;
2331
2332    rFoldedBefore := rFoldedBefore + r.MergedLineCount;
2333    r := r.Right; // rStartLine points to r, which now is the start of the previous fold;
2334  end;
2335
2336  Result{%H-}.Init(r, rStartLine, rFoldedBefore);
2337end;
2338
2339procedure TSynTextFoldAVLTree.AdjustForLinesInserted(AStartLine, ALineCount, ABytePos: Integer);
2340  Procedure DoAdjustForLinesInserted(Current : TSynTextFoldAVLNodeData;
2341    CurrentLine : Integer);
2342  var
2343    t: LongInt;
2344  begin
2345    while (Current <> nil) do begin
2346      CurrentLine := CurrentLine + Current.LineOffset;
2347
2348      if (AStartLine <= CurrentLine - Current.VisibleLines) or
2349         ( (AStartLine - 1 = CurrentLine - Current.VisibleLines) and
2350           (ABytePos <= Current.FoldColumn) )
2351      then begin
2352        // move current node
2353        Current.LineOffset := Current.LineOffset + ALineCount;
2354        CurrentLine := CurrentLine + ALineCount;
2355        if Current.Left <> nil then
2356          Current.Left.LineOffset := Current.Left.LineOffset - ALineCount;
2357        Current := Current.Left;
2358      end
2359      else if AStartLine > CurrentLine + Current.MergedLineCount- 1 then begin
2360        // The new lines are entirly behind the current node
2361        Current := Current.Right;
2362      end
2363      else begin
2364        // grow current node (there is only one node one the line, the others are nested)
2365        // CurrentLine <= AStartLine  <= CurrentLine + Current.FullCount - 1
2366        t := Current.FullCount;
2367        if AStartLine <= CurrentLine + t - 1 then
2368          Current.FullCount := t + ALineCount;
2369        Current.MergedLineCount:= Current.MergedLineCount+ ALineCount;
2370        Current.AdjustParentLeftCount(ALineCount);
2371        TreeForNestedNode(Current, CurrentLine).AdjustForLinesInserted(AStartLine, ALineCount, ABytePos);
2372
2373        if Current.Right <> nil then // and move entire right
2374          Current.Right.LineOffset := Current.Right.LineOffset + ALineCount;
2375        break;
2376      end;
2377    end;
2378  end;
2379
2380begin
2381  {$IFDEF SynFoldDebug}debugln(['FOLD-- AdjustForLinesInsertedAStartLine:=', AStartLine, ' ALineCount=',ALineCount, '  ABytePos=',ABytePos ]); {$ENDIF}
2382  DoAdjustForLinesInserted(TSynTextFoldAVLNodeData(fRoot), fRootOffset);
2383  AdjustColumn(AStartLine+ALineCount-1, ABytePos, -ABytePos+1, True);
2384end;
2385
2386procedure TSynTextFoldAVLTree.AdjustForLinesDeleted(AStartLine,
2387  ALineCount, ABytePos: Integer);
2388  Procedure AdjustNodeForLinesDeleted(Current : TSynTextFoldAVLNodeData;
2389    CurrentLine, FirstLineToDelete, CountLinesToDelete : Integer);
2390  var
2391    LastLineToDelete, LinesBefore, LinesInside, LinesAfter, t : Integer;
2392  begin
2393    LastLineToDelete := FirstLineToDelete + CountLinesToDelete - 1; // only valid for delete; CountLinesToDelete < 0
2394
2395    while (Current <> nil) do begin
2396      CurrentLine := CurrentLine + Current.LineOffset;
2397
2398      if FirstLineToDelete <= CurrentLine - Current.VisibleLines then begin
2399        // move current node
2400        if LastLineToDelete > CurrentLine  - Current.VisibleLines then begin
2401          // overlap => shrink
2402          LinesBefore := CurrentLine - FirstLineToDelete;
2403          LinesInside := CountLinesToDelete - LinesBefore;
2404          // shrink
2405          t := Current.MergedLineCount;
2406          Current.FullCount := Max(Current.FullCount - LinesInside, -1);
2407          Current.MergedLineCount := Max(Current.MergedLineCount - LinesInside, 0);
2408          Current.AdjustParentLeftCount(Current.MergedLineCount - t); // If LineCount = -1; LeftCount will be correctd on delete node
2409          TreeForNestedNode(Current, CurrentLine).AdjustForLinesDeleted(CurrentLine, LinesInside, ABytePos);
2410
2411          if (Current.Right <> nil) then begin
2412            // move right // Calculate from the new curent.LineOffset, as below
2413            AdjustNodeForLinesDeleted(Current.Right, CurrentLine - LinesBefore,
2414                                      FirstLineToDelete, LinesInside);
2415          end;
2416        end
2417        else LinesBefore := CountLinesToDelete;
2418
2419        // move current node (includes right subtree / left subtree needs eval)
2420        Current.LineOffset := Current.LineOffset - LinesBefore;
2421        CurrentLine := CurrentLine - LinesBefore;
2422        //if AStartLine = CurrentLine then begin
2423        //  Current.FoldColumn := Current.FoldColumn + ABytePos-1;
2424        //  TreeForNestedNode(Current, CurrentLine).AdjustColumn(CurrentLine, 1, ABytePos-1);
2425        //end;
2426        if Current.Left <> nil then
2427          Current.Left.LineOffset := Current.Left.LineOffset + LinesBefore;
2428        Current := Current.Left;
2429      end
2430      else if FirstLineToDelete > CurrentLine + Current.MergedLineCount - 1 then begin
2431        // The deleted lines are entirly behind the current node
2432        Current := Current.Right;
2433      end
2434      else begin
2435        // (FirstLineToDelete >= CurrentLine) AND (FirstLineToDelete < CurrentLine + Current.LineCount);
2436        LinesAfter  := LastLineToDelete - (CurrentLine + Current.MergedLineCount - 1);
2437        if LinesAfter < 0 then LinesAfter := 0;
2438        LinesInside := CountLinesToDelete - LinesAfter;
2439
2440        // shrink current node
2441        t := Current.MergedLineCount;
2442        Current.MergedLineCount := Current.MergedLineCount- LinesInside;
2443        if Current.FullCount > Current.MergedLineCount then
2444          Current.FullCount := Current.MergedLineCount;
2445        Current.AdjustParentLeftCount(Current.MergedLineCount - t); // If MergedLineCount = -1; LeftCount will be correctd on delete node
2446
2447        TreeForNestedNode(Current, CurrentLine).AdjustForLinesDeleted(FirstLineToDelete, LinesInside, ABytePos);
2448        Current := Current.Right;
2449      end;
2450
2451    end;
2452  end;
2453
2454begin
2455  {$IFDEF SynFoldDebug}debugln(['FOLD-- AdjustForLinesDeleted AStartLine:=', AStartLine, ' ALineCount=',ALineCount, '  ABytePos=',ABytePos ]); {$ENDIF}
2456  if ABytePos > 1 then
2457    AdjustColumn(AStartLine+ALineCount-1, 1, ABytePos-1);
2458  AdjustNodeForLinesDeleted(TSynTextFoldAVLNodeData(fRoot), fRootOffset, AStartLine, ALineCount);
2459end;
2460
2461procedure TSynTextFoldAVLTree.AdjustColumn(ALine, ABytePos, ACount: Integer;
2462  InLineBreak: boolean = False);
2463var
2464  Node: TSynTextFoldAVLNode;
2465begin
2466  Node := FindFoldForLine(ALine, True);
2467  {$IFDEF SynFoldDebug}debugln(['FOLD-- AdjustColumn ALine:=', ALine, '  ABytePos=',ABytePos, ' ACount=',ACount, ' // node.srcline=',Node.SourceLine, '  StartLine=', node.StartLine, 'column=',Node.FoldColumn ]); {$ENDIF}
2468  if (not Node.IsInFold) or (Node.SourceLine > ALine) then exit;
2469  if (Node.SourceLine = ALine) and (node.FoldColumn >= ABytePos) then begin
2470    node.FoldColumn := Node.FoldColumn + ACount;
2471    if (not InLineBreak) and (node.FoldColumn < ABytePos) then node.FoldColumn := ABytePos;
2472  end;
2473  TreeForNestedNode(Node.fData, node.StartLine).AdjustColumn(ALine, ABytePos, ACount);
2474end;
2475
2476function TSynTextFoldAVLTree.FindLastFold : TSynTextFoldAVLNode;
2477var
2478  r : TSynTextFoldAVLNodeData;
2479  rStartLine : Integer;
2480  rFoldedBefore : Integer;
2481begin
2482  r := TSynTextFoldAVLNodeData(fRoot);
2483  rStartLine := fRootOffset;
2484  rFoldedBefore := 0;
2485  while (r <> nil) do begin
2486    rStartLine := rStartLine + r.LineOffset;
2487    rFoldedBefore := rFoldedBefore + r.LeftCount + r.MergedLineCount;
2488    if r.Right = nil then break;
2489    r := r.Right; // rStartLine points to r, which now is the start of the previous fold;
2490  end;
2491
2492  Result{%H-}.Init(r, rStartLine, rFoldedBefore);
2493end;
2494
2495function TSynTextFoldAVLTree.FindFirstFold : TSynTextFoldAVLNode;
2496var
2497  r : TSynTextFoldAVLNodeData;
2498  rStartLine : Integer;
2499begin
2500  r := TSynTextFoldAVLNodeData(fRoot);
2501  rStartLine := fRootOffset;
2502  while (r <> nil) do begin
2503    rStartLine := rStartLine + r.LineOffset;
2504    if r.Left = nil then break;
2505    r := r.Left;
2506  end;
2507
2508  Result{%H-}.Init(r, rStartLine, 0);
2509end;
2510
2511function TSynTextFoldAVLTree.LastFoldedLine: integer;
2512var
2513  n: TSynTextFoldAVLNode;
2514begin
2515  n := FindFirstFold;
2516  if not n.IsInFold then exit(0);
2517  Result := n.StartLine + n.MergedLineCount - 1;
2518end;
2519
2520{$IFDEF SynDebug}
2521procedure TSynTextFoldAVLTree.debug;
2522  function debug2(ind, typ : String; ANode, AParent : TSynTextFoldAVLNodeData; offset : integer) :integer;
2523  begin
2524    result := 0;
2525    if ANode = nil then exit;
2526    with ANode do
2527      DebugLn([Format('Lines=%3d-%3d (e=%3d / idx=%d) %2d:%d;  Lcnt=%2d / Fcnt=%2d  | ',
2528                      [offset + ANode.LineOffset, offset + ANode.LineOffset + ANode.FullCount -1,
2529                       offset + ANode.LineOffset + ANode.MergedLineCount-1, ANode.FoldIndex,
2530					   ANode.FoldColumn, ANode.FoldColumnLen,
2531                       MergedLineCount, FullCount]),
2532               ind, typ, ' (',LineOffset, ')  LeftCount: ', LeftCount,
2533               '     Balance: ',FBalance]);
2534    if ANode.Parent <> AParent then DebugLn([ind,'* Bad parent']);
2535    Result := debug2(ind+'   ', 'L', ANode.Left, ANode, offset+ANode.LineOffset);
2536    If Result <> ANode.LeftCount then  debugln([ind,'   ***** Leftcount was ',Result, ' but should be ', ANode.LeftCount]);
2537    Result := Result + debug2(ind+'   ', 'R', ANode.Right, ANode, offset+ANode.LineOffset);
2538    debug2(ind+'  #', 'N', ANode.Nested, nil, offset+ANode.LineOffset);
2539    Result := Result + ANode.MergedLineCount;
2540  end;
2541begin
2542  debugln('StartLine, EndLine (MergedEnd, FoldIndex) - Column:Len; MergedLineCnt / FullCCnt | .. (LineOffset) ...');
2543  debug2('', ' -', TSynTextFoldAVLNodeData(fRoot), nil, 0);
2544end;
2545{$ENDIF}
2546
2547function TSynTextFoldAVLTree.InsertNewFold(ALine, AFoldIndex, AColumn, AColumnLen,
2548   ACount, AVisibleLines: Integer; AClassification: TFoldNodeClassification;
2549   AFoldTypeCompatible: Pointer) : TSynTextFoldAVLNode;
2550var
2551  r : TSynTextFoldAVLNodeData;
2552begin
2553  {$IFDEF SynFoldDebug}debugln(['FOLD-- InsertNewFold ALine:=', ALine, '  AFoldIndex=', AFoldIndex]);{$ENDIF}
2554  r := NewNode;
2555  r.LineOffset := ALine; // 1-based
2556  r.FoldIndex := AFoldIndex;
2557  r.FoldColumn := AColumn;
2558  r.FoldColumnLen := AColumnLen;
2559  r.MergedLineCount := ACount;
2560  r.FullCount  := ACount;
2561  r.LeftCount  := 0;
2562  r.VisibleLines := AVisibleLines;
2563  r.Classification := AClassification;
2564  r.FoldTypeCompatible := AFoldTypeCompatible;
2565
2566  Result{%H-}.Init(r, ALine, 0);
2567  Result.fFoldedBefore := InsertNode(r);
2568end;
2569
2570function TSynTextFoldAVLTree.RemoveFoldForLine(ALine : Integer;
2571  OnlyCol: Integer = -1) : Integer;
2572var
2573  OldFold : TSynTextFoldAVLNode;
2574  lcount: Integer;
2575begin
2576  {$IFDEF SynFoldDebug}debugln(['FOLD-- RemoveFoldForLine ALine:=', ALine, '  OnlyCol=',OnlyCol]);{$ENDIF}
2577  Result := ALine; // - 1; // Return index
2578  OldFold := FindFoldForLine(ALine, False);
2579  if OldFold.StartLine < Result then
2580    Result := OldFold.StartLine;
2581
2582  if (not OldFold.IsInFold) then exit;
2583
2584  if OnlyCol < 0 then
2585    RemoveFoldForNodeAtLine(OldFold, ALine)
2586  else
2587  if OldFold.FoldIndex = OnlyCol then
2588    RemoveFoldForNodeAtLine(OldFold, -1)
2589  else
2590  if OldFold.fData.Nested <> nil then begin
2591    TreeForNestedNode(OldFold.fData, OldFold.StartLine).RemoveFoldForLine
2592      (ALine, OnlyCol);
2593    lcount := max(OldFold.FullCount,
2594                  TreeForNestedNode(OldFold.fData, 0).LastFoldedLine + 1);
2595    if lcount <> OldFold.MergedLineCount then begin
2596      OldFold.fData.MergedLineCount := lcount;
2597      OldFold.fData.AdjustParentLeftCount(OldFold.MergedLineCount - lcount);
2598    end;
2599  end;
2600end;
2601
2602function TSynTextFoldAVLTree.RemoveFoldForNodeAtLine(ANode : TSynTextFoldAVLNode;
2603  ALine : Integer) : Integer;
2604var
2605  NestedNode, MergeNode : TSynTextFoldAVLNodeData;
2606  NestedLine, offs, lcount : Integer;
2607  OnlyNested: Boolean;
2608  Nested: TSynTextFoldAVLNode;
2609begin
2610  {$IFDEF SynFoldDebug}debugln(['FOLD-- RemoveFoldForNodeAtLine: ALine:=', ALine, ' ANode.StartLine=', ANode.StartLine]);{$ENDIF}
2611  OnlyNested := ALine >= ANode.StartLine + ANode.FullCount;
2612  // The cfCollapsed line is one line before the fold
2613  Result := ANode.StartLine-1;  // Return the cfcollapsed that was unfolded
2614  if not OnlyNested then
2615    RemoveNode(ANode.fData);
2616
2617  NestedLine := 0;
2618  If ANode.fData.Nested <> nil then
2619  begin
2620    (*Todo: should we mark the tree as NO balancing needed ???*)
2621    TreeForNestedNode(ANode.fData, ANode.StartLine).RemoveFoldForLine(ALine);
2622
2623    if OnlyNested then begin
2624      NestedLine := ANode.StartLine + ANode.FullCount;
2625      Nested := TreeForNestedNode(ANode.fData, ANode.StartLine).FindLastFold;
2626      while Nested.IsInFold and (Nested.StartLine >= NestedLine) do begin
2627        NestedNode := Nested.fData;
2628        offs := Nested.StartLine;
2629        Nested := Nested.Prev;
2630
2631        lcount := ANode.fData.MergedLineCount;
2632        ANode.fData.MergedLineCount := max(ANode.FullCount,
2633                         Nested.StartLine + Nested.MergedLineCount - ANode.StartLine);
2634        ANode.fData.AdjustParentLeftCount(ANode.MergedLineCount - lcount);
2635
2636        TreeForNestedNode(ANode.fData, ANode.StartLine).RemoveNode(NestedNode);
2637        NestedNode.LineOffset := offs;
2638        InsertNode(NestedNode);
2639      end;
2640      lcount := max(ANode.FullCount,
2641                TreeForNestedNode(ANode.fData, ANode.StartLine).LastFoldedLine
2642                - ANode.StartLine + 1);
2643      if lcount <> ANode.MergedLineCount then begin
2644        ANode.fData.MergedLineCount := lcount;
2645        ANode.fData.AdjustParentLeftCount(ANode.MergedLineCount - lcount);
2646      end;
2647    end
2648    else begin
2649      // merge the remaining nested into current
2650      NestedNode := ANode.fData.Nested;
2651      if NestedNode <> nil
2652      then NestedLine := ANode.fStartLine + NestedNode.LineOffset;
2653
2654      while NestedNode <> nil do begin
2655        while NestedNode.Left <> nil do begin
2656          NestedNode := NestedNode.Left;
2657          NestedLine := NestedLine + NestedNode.LineOffset;
2658        end;
2659
2660        if NestedNode.Right <> nil then begin
2661          NestedNode := NestedNode.Right;
2662          NestedLine := NestedLine + NestedNode.LineOffset;
2663          continue;
2664        end;
2665
2666        // leaf node
2667        // Anything that is still nested (MergeNode.Nested), will stay nested
2668        MergeNode := NestedNode;
2669
2670        NestedLine := NestedLine - NestedNode.LineOffset;
2671        NestedNode := NestedNode.Parent;
2672
2673        MergeNode.LineOffset := MergeNode.LineOffset + NestedLine;
2674        if NestedNode <> nil then begin
2675          NestedNode.ReplaceChild(MergeNode, nil);
2676          MergeNode.FParent := nil;
2677        end;
2678        MergeNode.LeftCount := 0;
2679        MergeNode.FBalance   := 0;
2680        if MergeNode.FullCount <= 0 then begin
2681          MergeNode.FreeAllChildrenAndNested;
2682          MergeNode.Free;
2683        end
2684        else
2685          InsertNode(MergeNode);
2686      end;
2687    end;
2688
2689  end;
2690
2691  if not OnlyNested then
2692    DisposeNode(TSynSizedDifferentialAVLNode(ANode.fData));
2693end;
2694
2695function TSynTextFoldAVLTree.InsertNode(ANode : TSynTextFoldAVLNodeData) : Integer;
2696var
2697  rStartLine, NestStartLine : Integer;
2698  rFoldedBefore, NestFoldedBefore : Integer;
2699
2700  current, Nest : TSynTextFoldAVLNodeData;
2701  ALine, AEnd, ACount : Integer;
2702
2703  (* ANode.StartLine < Current.StartLine // ANode goes into tree  *)
2704  procedure NestCurrentIntoNewBlock; inline;
2705  var
2706    diff, start2, before2 : Integer;
2707    p : TSynTextFoldAVLNodeData;
2708  begin
2709    current.AdjustParentLeftCount(ACount-current.MergedLineCount); // -RecursiveFoldCount(current));
2710    rStartLine := rStartLine - current.LineOffset;  // rStarteLine is now current.Parent
2711    p := current.Parent;
2712    if p <> nil
2713    then p.ReplaceChild(current, ANode, -rStartLine)
2714    else SetRoot(ANode, -rStartLine);
2715
2716    diff := current.LineOffset - ANode.LineOffset;
2717    ANode.Nested  := current;
2718    ANode.FBalance := current.FBalance;
2719    current.LineOffset := diff; // offset to ANode (via Nested)
2720    current.FParent := nil;
2721    current.FBalance := 0;
2722
2723    ANode.SetLeftChild(current.Left, diff, current.LeftCount);
2724    current.FLeft := nil;
2725    current.LeftCount := 0;
2726    ANode.SetRightChild(current.Right, diff);
2727    current.FRight := nil;
2728
2729    start2 := ALine; before2 := rFoldedBefore;
2730    p := ANode.Successor(start2, before2);
2731    while (p <> nil) and (start2 <= AEnd) do begin
2732      RemoveNode(p);
2733      p.LineOffset := start2- ALine;
2734      TreeForNestedNode(Anode, 0).InsertNode(p);
2735
2736      start2 := ALine; before2 := rFoldedBefore;
2737      p := ANode.Successor(start2, before2);
2738    end;
2739    // check only after loop, if we gre, we did so by existing nodes, so no new overlaps
2740    start2 := TreeForNestedNode(Anode, 0).LastFoldedLine;
2741    if start2 > ANode.FullCount - 1 then begin
2742      ANode.AdjustParentLeftCount(start2 + 1 - ANode.MergedLineCount);
2743      ANode.MergedLineCount := start2 + 1;
2744    end;
2745  end;
2746
2747  (* ANode.StartLine > Current.StartLine // Current remains in tree  *)
2748  procedure NestNewBlockIntoCurrent; //inline;
2749  var
2750    end2, start2, before2: Integer;
2751    p: TSynTextFoldAVLNodeData;
2752  begin
2753    // Check if current.LineCount needs extension
2754    ANode.LineOffset := ALine - rStartLine;
2755    if current.Nested <> nil
2756    then TreeForNestedNode(current, 0).InsertNode(ANode)
2757    else current.Nested := ANode;
2758
2759    end2 := TreeForNestedNode(current, 0).LastFoldedLine;
2760    if end2 > current.FullCount -1 then begin
2761      end2 := rStartLine + end2;
2762
2763      start2 := rStartLine; before2 := rFoldedBefore;
2764      p := current.Successor(start2, before2);
2765      while (p <> nil) and (start2 <= end2) do begin
2766        RemoveNode(p);
2767        p.LineOffset := start2 - rStartLine;
2768        TreeForNestedNode(current, 0).InsertNode(p);
2769
2770        start2 := rStartLine; before2 := rFoldedBefore;
2771        p := current.Successor(start2, before2);
2772      end;
2773      end2 := TreeForNestedNode(current, 0).LastFoldedLine;
2774      if end2 > current.FullCount -1 then begin
2775        current.AdjustParentLeftCount(end2 + 1 - current.MergedLineCount);
2776        current.MergedLineCount := end2 + 1;
2777      end;
2778    end;
2779  end;
2780
2781begin
2782  Result := 0;
2783  if fRoot = nil then begin
2784    SetRoot(ANode, -fRootOffset);
2785    exit;
2786  end;
2787  ALine := ANode.LineOffset;
2788  ACount := ANode.MergedLineCount;
2789  AEnd := ALine + ACount - 1;
2790  current := TSynTextFoldAVLNodeData(fRoot);
2791  rStartLine := fRootOffset;
2792  rFoldedBefore := 0;
2793  Nest := nil;
2794  NestFoldedBefore := 0;
2795  NestStartLine := 0;
2796
2797  while (current <> nil) do begin
2798    rStartLine := rStartLine + current.LineOffset;
2799
2800    if ALine < rStartLine then begin
2801      (* *** New block goes to the left *** *)
2802      // remember possible nesting, continue scan for nesting with precessor
2803      if (AEnd >= rStartLine) then begin
2804        Nest := current;
2805        NestFoldedBefore := rFoldedBefore;
2806        NestStartLine := rStartLine;
2807      end;
2808
2809      if current.Left <> nil Then begin
2810        current := current.Left;
2811        continue;
2812      end
2813      else if Nest = nil then begin // insert as Left - no nesting
2814        current.AdjustParentLeftCount(ACount);
2815        current.SetLeftChild(ANode, -rStartLine, ANode.MergedLineCount);
2816        BalanceAfterInsert(ANode);
2817      end
2818      else begin // nest
2819        current := Nest;
2820        rStartLine := NestStartLine;
2821        rFoldedBefore := NestFoldedBefore;
2822        NestCurrentIntoNewBlock;
2823      end;
2824      break;
2825    end;
2826
2827    rFoldedBefore := rFoldedBefore + current.LeftCount;
2828    if ALine = rStartLine then begin
2829      if ANode.FoldIndex > current.FoldIndex then
2830        (* *** New Block will be nested in current *** *)
2831        NestNewBlockIntoCurrent
2832      else
2833      if ANode.FoldIndex < current.FoldIndex then
2834        (* *** current will be nested in New Block *** *)
2835        NestCurrentIntoNewBlock
2836      else begin
2837        debugln(['Droping Foldnode / Already exists. Startline=', rStartLine,' LineCount=',ACount]);
2838        FreeAndNil(ANode);
2839      end;
2840    end
2841    else begin
2842      If ALine <= rStartLine + current.MergedLineCount - 1
2843      (* *** New Block will be nested in current *** *)
2844      then NestNewBlockIntoCurrent
2845      (* *** New block goes to the right *** *)
2846      else begin
2847        rFoldedBefore := rFoldedBefore + current.MergedLineCount;
2848        if current.Right <> nil then begin
2849          current := current.Right;
2850          continue;
2851        end
2852        else  if Nest=nil then Begin  // insert to the right - no nesting
2853          current.AdjustParentLeftCount(ACount);
2854          current.SetRightChild(ANode, -rStartLine);
2855          BalanceAfterInsert(ANode);
2856        end
2857        else begin // nest
2858          current := Nest;
2859          rStartLine := NestStartLine;
2860          rFoldedBefore := NestFoldedBefore;
2861          NestCurrentIntoNewBlock;
2862        end;
2863
2864      end;
2865    end;
2866
2867    break;
2868  end; // while
2869
2870  Result := rFoldedBefore;
2871end;
2872
2873function TSynTextFoldAVLTree.TreeForNestedNode(ANode: TSynTextFoldAVLNodeData; aOffset : Integer) : TSynTextFoldAVLTree;
2874begin
2875  if fNestedNodesTree = nil then fNestedNodesTree := TSynTextFoldAVLTree.Create;
2876  Result := fNestedNodesTree;
2877  Result.fRoot := ANode.Nested;
2878  Result.fNestParent := ANode; // TODO: this is dangerous, this is never cleaned up, even if ANode is Destroyed
2879  Result.fRootOffset := aOffset;
2880end;
2881
2882constructor TSynTextFoldAVLTree.Create;
2883begin
2884  inherited;
2885  fNestParent := nil;
2886  fNestedNodesTree := nil;
2887end;
2888
2889{ TSynEditFoldProvider }
2890
2891function TSynEditFoldProvider.GetLineCapabilities(ALineIdx: Integer): TSynEditFoldLineCapabilities;
2892var
2893  c: Integer;
2894begin
2895  Result := [];
2896  if not FEnabled then
2897    exit;
2898  if (FEdit <> nil) and (FEdit.SelAvail) then begin
2899    if (FEdit.BlockBegin.Y < ALineIdx+1) and
2900       (FEdit.BlockEnd.Y  > ALineIdx+1)
2901    then Result := [cfFoldBody];
2902    if (FEdit.BlockEnd.Y  = ALineIdx+1) then Result := [cfFoldEnd];
2903    if (FEdit.BlockBegin.Y = ALineIdx+1) then Result := [cfHideStart];
2904    if (FEdit.BlockBegin.Y = ALineIdx+1) and
2905       (FEdit.BlockEnd.Y  = ALineIdx+1) then Result := [cfHideStart, cfSingleLineHide];
2906  end;
2907  if (FHighlighter = nil) or (ALineIdx < 0) then
2908    exit;
2909
2910  FHighlighter.CurrentLines := FLines;
2911  if FHighlighter.FoldBlockEndLevel(ALineIdx - 1) > 0 then Result := Result + [cfFoldBody];
2912  if FHighlighter.FoldBlockClosingCount(ALineIdx) > 0    then Result := Result + [cfFoldEnd, cfFoldBody];
2913
2914  c := FHighlighter.FoldNodeInfo[ALineIdx].CountEx([]);
2915  if c > 0 then begin
2916    c := FHighlighter.FoldNodeInfo[ALineIdx].CountEx([sfaOpenFold, sfaFoldFold]);
2917    if c > 0 then
2918      include(Result, cfFoldStart);
2919    c := FHighlighter.FoldNodeInfo[ALineIdx].CountEx([sfaOpenFold, sfaFoldHide]);
2920    if c > 0 then
2921      include(Result, cfHideStart);
2922    c := FHighlighter.FoldNodeInfo[ALineIdx].CountEx([sfaOneLineOpen, sfaFoldHide]); // TODO: Include scftFoldEnd ?
2923    // Todo: cfSingleLineHide only, if there is no other hide
2924    if c > 0 then
2925      Result := Result + [cfHideStart, cfSingleLineHide];
2926  end
2927  else
2928    if FHighlighter.FoldBlockOpeningCount(ALineIdx) > 0 then include(Result, cfFoldStart);
2929end;
2930
2931function TSynEditFoldProvider.GetLineClassification(ALineIdx: Integer): TFoldNodeClassifications;
2932begin
2933  Result := [];
2934  if (FEdit <> nil) and FEdit.SelAvail and (FEdit.BlockBegin.Y = ALineIdx+1) then
2935    Result := [fncBlockSelection];
2936end;
2937
2938function TSynEditFoldProvider.GetNestedFoldsList: TLazSynEditNestedFoldsList;
2939begin
2940  if FNestedFoldsList = nil then
2941    FNestedFoldsList := TLazSynEditNestedFoldsList.Create(FLines, FHighlighter);
2942  Result := FNestedFoldsList;
2943end;
2944
2945function TSynEditFoldProvider.GetFoldsAvailable: Boolean;
2946begin
2947  Result := FEnabled and (
2948              (FHighlighter <> nil) or
2949              ((FEdit <> nil) and FEdit.SelAvail)
2950            );
2951end;
2952
2953function TSynEditFoldProvider.GetHighLighterWithLines: TSynCustomFoldHighlighter;
2954begin
2955  Result := FHighlighter;
2956  if (Result = nil) then
2957    exit;
2958  Result.CurrentLines := FLines;
2959end;
2960
2961procedure TSynEditFoldProvider.SetHighLighter(const AValue: TSynCustomFoldHighlighter);
2962begin
2963  if FHighlighter = AValue then exit;
2964  FHighlighter := AValue;
2965  if FNestedFoldsList <> nil then
2966    FNestedFoldsList.HighLighter := FHighlighter;
2967end;
2968
2969procedure TSynEditFoldProvider.SetLines(AValue: TSynEditStrings);
2970begin
2971  if FLines = AValue then Exit;
2972  FLines := AValue;
2973  if FNestedFoldsList <> nil then
2974    FNestedFoldsList.Lines := FLines;
2975end;
2976
2977constructor TSynEditFoldProvider.Create;
2978begin
2979  FEnabled := True;
2980  inherited Create;
2981end;
2982
2983destructor TSynEditFoldProvider.Destroy;
2984begin
2985  inherited Destroy;
2986  FreeAndNil(FNestedFoldsList);
2987end;
2988
2989function TSynEditFoldProvider.FoldOpenCount(ALineIdx: Integer; AType: Integer = 0): Integer;
2990begin
2991  if not FEnabled then
2992    exit(0);
2993
2994  if (FHighlighter = nil) or (ALineIdx < 0) then begin
2995    if (AType=0) and (FEdit <> nil) and FEdit.SelAvail and (FEdit.BlockBegin.Y=ALineIdx+1) then exit(1);
2996    exit(0);
2997  end;
2998  // Need to check alll nodes with FoldNodeInfoCount
2999  // Hide-able nodes can open and close on the same line "(* comment *)"
3000  FHighlighter.CurrentLines := FLines;
3001  Result := FHighlighter.FoldNodeInfo[ALineIdx].CountEx([sfaOpenFold, sfaFold], AType);
3002  // fallback for HL without GetFoldNodeInfoCountEx
3003  if Result < 0 then
3004    Result := FHighlighter.FoldBlockOpeningCount(ALineIdx, AType);
3005  if (AType=0) and (FEdit <> nil) and FEdit.SelAvail and (FEdit.BlockBegin.Y=ALineIdx+1) then
3006    inc(Result);
3007end;
3008
3009function TSynEditFoldProvider.FoldOpenInfo(ALineIdx, AFoldIdx: Integer;
3010  AType: Integer = 0): TSynFoldNodeInfo;
3011
3012  function BlockSelInfo(NIdx: Integer): TSynFoldNodeInfo;
3013  begin
3014    Result.LineIndex    := ALineIdx;
3015    Result.NodeIndex    := NIdx;
3016    Result.LogXStart    := FEdit.BlockBegin.x;
3017    Result.LogXEnd      := FEdit.BlockBegin.x;
3018    Result.FoldLvlStart := 0;
3019    Result.NestLvlStart := 0;
3020    Result.NestLvlEnd   := 1;
3021    Result.FoldLvlEnd   := 1;
3022    Result.FoldAction   := [sfaOpen, sfaOpenFold, sfaFold, sfaFoldHide];
3023    Result.FoldType           := nil;
3024    Result.FoldTypeCompatible := nil;
3025    Result.FoldGroup := -1;
3026  end;
3027
3028begin
3029  Result.FoldAction := [sfaInvalid];
3030  if (FHighlighter = nil) or (ALineIdx < 0) then begin
3031    if (AType=0) and (FEdit <> nil) and FEdit.SelAvail and (FEdit.BlockBegin.Y=ALineIdx+1) then
3032      exit(BlockSelInfo(0));
3033    exit;
3034  end;
3035
3036  FHighlighter.CurrentLines := FLines;
3037  if (AType = 0) and (FEdit <> nil) and FEdit.SelAvail and
3038     (FEdit.BlockBegin.Y=ALineIdx+1) and
3039     (AFoldIdx = FoldOpenCount(ALineIdx, AType)-1)
3040  then
3041    Result := BlockSelInfo(AFoldIdx)
3042  else
3043    Result := FHighlighter.FoldNodeInfo[ALineIdx].NodeInfoEx(AFoldIdx, [sfaOpen, sfaFold], AType);
3044end;
3045
3046function TSynEditFoldProvider.FoldLineLength(ALine, AFoldIndex: Integer): integer;
3047begin
3048  if (FEdit <> nil) and FEdit.SelAvail and (FEdit.BlockBegin.Y=ALine+1) and
3049    (AFoldIndex = FoldOpenCount(ALine, 0)-1)
3050  then
3051    exit(FEdit.BlockEnd.y - FEdit.BlockBegin.y);
3052
3053  FHighlighter.CurrentLines := FLines;
3054  Result := FHighlighter.FoldLineLength(ALine, AFoldIndex);
3055end;
3056
3057function TSynEditFoldProvider.InfoForFoldAtTextIndex(ALine, AFoldIndex: Integer;
3058  HideLen: Boolean; NeedLen: Boolean = True): TSynEditFoldProviderNodeInfo;
3059var
3060  nd: TSynFoldNodeInfo;
3061begin
3062  Result.LineCount := 0;
3063  Result.Column := 0;
3064  Result.ColumnLen := 0;
3065  Result.DefaultCollapsed := False;
3066  Result.Classification := fncInvalid;
3067  if not FoldsAvailable then
3068    exit;
3069
3070  if NeedLen then begin
3071    Result.LineCount := FoldLineLength(ALine, AFoldIndex);
3072    if HideLen then
3073      inc(Result.LineCount);
3074  end
3075  else
3076    Result.LineCount := -1;
3077  nd := FoldOpenInfo(ALine, AFoldIndex, 0);
3078  Result.Column := nd.LogXStart+1;
3079  Result.ColumnLen := nd.LogXEnd - nd.LogXStart;
3080  Result.DefaultCollapsed := (sfaDefaultCollapsed in nd.FoldAction);
3081  Result.FoldTypeCompatible := nd.FoldTypeCompatible;
3082  Result.FoldGroup := nd.FoldGroup;
3083  if Result.FoldGroup = -1 then
3084    Result.Classification := fncBlockSelection
3085  else
3086    Result.Classification := fncHighlighter;
3087end;
3088
3089function TSynEditFoldProvider.InfoListForFoldsAtTextIndex(ALine: Integer;
3090  NeedLen: Boolean): TSynEditFoldProviderNodeInfoList;
3091var
3092  i: Integer;
3093begin
3094  i := FoldOpenCount(ALine);
3095  SetLength(Result, i);
3096  while i > 0 do begin
3097    dec(i);
3098    Result[i] := InfoForFoldAtTextIndex(ALine, i, False, NeedLen);
3099  end;
3100end;
3101
3102{ TSynEditFoldedView }
3103
3104constructor TSynEditFoldedView.Create(AOwner: TSynEditBase;
3105  ACaret: TSynEditCaret);
3106begin
3107  FOwner := AOwner;
3108  inherited Create;
3109  fTopLine := 0;
3110  fLinesInWindow := -1;
3111  fCaret := ACaret;
3112  fCaret.AddChangeHandler(@DoCaretChanged);
3113  fFoldTree := TSynTextFoldAVLTree.Create;
3114  FFoldProvider := TSynEditFoldProvider.Create;
3115  FFoldProvider.FEdit := FOwner;
3116  // TODO: if NextLineChanges, update FFoldProvider //     DoSynStringsChanged
3117  FDisplayView := TLazSynDisplayFold.Create(Self);
3118
3119  FMarkupInfoFoldedCode := TSynSelectedColor.Create;
3120  FMarkupInfoFoldedCode.Background := clNone;
3121  FMarkupInfoFoldedCode.Foreground := clDkGray;
3122  FMarkupInfoFoldedCode.FrameColor := clDkGray;
3123
3124  FMarkupInfoFoldedCodeLine := TSynSelectedColor.Create;
3125  FMarkupInfoFoldedCodeLine.Background := clNone;
3126  FMarkupInfoFoldedCodeLine.Foreground := clNone;
3127  FMarkupInfoFoldedCodeLine.FrameColor := clNone;
3128
3129  FMarkupInfoHiddenCodeLine := TSynSelectedColor.Create;
3130  FMarkupInfoHiddenCodeLine.Background := clNone;
3131  FMarkupInfoHiddenCodeLine.Foreground := clNone;
3132  FMarkupInfoHiddenCodeLine.FrameColor := clNone;
3133
3134  FOwner.RegisterStatusChangedHandler(@DoBlockSelChanged, [scSelection]);
3135  FOwner.RegisterCommandHandler(@ProcessMySynCommand, nil, [hcfPreExec]);
3136  FOwner.TextViewsManager.AddTextView(Self);
3137end;
3138
3139destructor TSynEditFoldedView.Destroy;
3140begin
3141  FOwner.UnregisterCommandHandler(@ProcessMySynCommand);
3142  NextLines := nil;
3143  fCaret.RemoveChangeHandler(@DoCaretChanged);
3144  FreeAndNil(FDisplayView);
3145  fFoldTree.Free;
3146  fTextIndexList := nil;
3147  fFoldTypeList := nil;
3148  FMarkupInfoFoldedCode.Free;
3149  FMarkupInfoFoldedCodeLine.Free;
3150  FMarkupInfoHiddenCodeLine.Free;
3151  FreeAndNil(FFoldProvider);
3152  inherited Destroy;
3153end;
3154
3155function TSynEditFoldedView.TextToViewIndex(aTextIndex: TLineIdx): TLineIdx;
3156begin
3157  aTextIndex := InternTextToViewIndex(aTextIndex);
3158  Result := inherited TextToViewIndex(aTextIndex);
3159end;
3160
3161function TSynEditFoldedView.ViewToTextIndex(aViewIndex: TLineIdx): TLineIdx;
3162begin
3163  aViewIndex := inherited ViewToTextIndex(aViewIndex);
3164  Result := InternViewToTextIndex(aViewIndex);
3165end;
3166
3167function TSynEditFoldedView.TextXYToViewXY(APhysTextXY: TPhysPoint): TPhysPoint;
3168begin
3169  Result := inherited TextXYToViewXY(APhysTextXY);
3170  Result.y := ToPos(InternTextToViewIndex(ToIdx(Result.y)));
3171end;
3172
3173function TSynEditFoldedView.ViewXYToTextXY(APhysViewXY: TPhysPoint): TPhysPoint;
3174begin
3175  APhysViewXY.y := ToPos(InternViewToTextIndex(ToIdx(APhysViewXY.y)));
3176  Result := inherited ViewXYToTextXY(APhysViewXY);
3177end;
3178
3179procedure TSynEditFoldedView.LinesInsertedAtTextIndex(AStartIndex, ALineCount, ABytePos: Integer; SkipFixFolding : Boolean);
3180var top : Integer;
3181begin
3182  if ALineCount = 0 then exit;
3183  top := TopTextIndex;
3184  fFoldTree.AdjustForLinesInserted(AStartIndex+1, ALineCount, ABytePos);
3185  if AStartIndex < top then
3186    TopTextIndex := top + ALineCount;
3187  if not(SkipFixFolding) then FixFoldingAtTextIndex(AStartIndex, AStartIndex+ALineCount+1)
3188  else
3189  if AStartIndex < top + ALineCount then CalculateMaps;
3190end;
3191
3192//procedure TSynEditFoldedView.LinesInsertedAtViewPos(AStartPos, ALineCount : Integer; SkipFixFolding : Boolean);
3193//begin
3194//  LinesInsertedAtTextIndex(InternViewToTextIndex(ToIdx(AStartPos)), ALineCount, SkipFixFolding);
3195//end;
3196
3197procedure TSynEditFoldedView.LinesDeletedAtTextIndex(AStartIndex, ALineCount, ABytePos: Integer; SkipFixFolding : Boolean);
3198var top : Integer;
3199begin
3200  top := TopTextIndex;
3201  // topline may get out of sync => synedit is always going to change it back
3202  fFoldTree.AdjustForLinesDeleted(AStartIndex+1, ALineCount, ABytePos);
3203  if not(SkipFixFolding) then
3204    FixFoldingAtTextIndex(AStartIndex, AStartIndex+ALineCount+1)
3205  else
3206  if AStartIndex < top - ALineCount then CalculateMaps;
3207end;
3208
3209//procedure TSynEditFoldedView.LinesDeletedAtViewPos(AStartPos, ALineCount : Integer; SkipFixFolding : Boolean);
3210//begin
3211//  LinesDeletedAtTextIndex(InternViewToTextIndex(ToIdx(AStartPos)), ALineCount, SkipFixFolding);
3212//end;
3213
3214function TSynEditFoldedView.InternTextToViewIndex(aTextIndex: TLineIdx): TLineIdx;
3215var
3216  n: TSynTextFoldAVLNode;
3217begin
3218  n := fFoldTree.FindFoldForLine(aTextIndex + 1);
3219  if n.IsInFold then
3220    Result := ToIdx(n.StartLine) - 1 - n.FoldedBefore
3221  else
3222    Result := aTextIndex - n.FoldedBefore;
3223end;
3224
3225function TSynEditFoldedView.TextIndexToScreenLine(aTextIndex : Integer) : Integer;
3226begin
3227  Result := InternTextToViewIndex(aTextIndex) - TopLine + 1;
3228end;
3229
3230function TSynEditFoldedView.InternViewToTextIndex(aViewIndex: TLineIdx): TLineIdx;
3231begin
3232  result := aViewIndex + fFoldTree.FindFoldForFoldedLine(ToPos(aViewIndex)).FoldedBefore;
3233end;
3234
3235function TSynEditFoldedView.ScreenLineToTextIndex(aLine : Integer) : Integer;
3236begin
3237  Result := InternViewToTextIndex(aLine + TopLine - 1);
3238end;
3239
3240function TSynEditFoldedView.TextIndexAddLines(aTextIndex, LineOffset : Integer) : Integer;
3241var
3242  node : TSynTextFoldAVLNode;
3243  boundary : integer;
3244begin
3245  node := fFoldTree.FindFoldForLine(aTextIndex+1, True);
3246  result := aTextIndex;
3247  if LineOffset < 0 then begin
3248    boundary := Max(0, InternViewToTextIndex(0));
3249    if node.IsInFold
3250    then node := node.Prev
3251    else node := fFoldTree.FindLastFold;
3252    while LineOffset < 0 do begin
3253      dec(Result);
3254      if Result <= boundary then exit(boundary);
3255      while node.IsInFold and (Result+1 < node.StartLine + node.MergedLineCount) do begin
3256        Result := Result - node.MergedLineCount;
3257        if Result <= boundary then exit(boundary);
3258        node := node.Prev;
3259      end;
3260      inc(LineOffset);
3261    end;
3262  end else begin
3263    boundary := NextLines.Count;
3264    while LineOffset > 0 do begin
3265      if Result >= boundary then exit(boundary);
3266      inc(Result);
3267      while node.IsInFold and (Result+1 >= node.StartLine) do begin
3268        Result := Result + node.MergedLineCount;
3269        if Result >= boundary then exit(boundary);
3270        if Result >= boundary then exit(boundary-node.MergedLineCount-1);
3271        node := node.Next;
3272      end;
3273      dec(LineOffset);
3274    end;
3275  end;
3276end;
3277
3278function TSynEditFoldedView.AddVisibleOffsetToTextIndex(aTextIndex: TLineIdx;
3279  LineOffset: Integer): TLineIdx;
3280begin
3281  //TODO: Modify LineOffset then call inherited;
3282  Result := TextIndexAddLines(aTextIndex, LineOffset);
3283//  Result := inherited AddVisibleOffsetToTextIndex(aTextIndex, LineOffset);
3284end;
3285
3286function TSynEditFoldedView.IsTextIdxVisible(aTextIndex: TLineIdx): Boolean;
3287begin
3288  Result := not FoldedAtTextIndex[aTextIndex];
3289  if Result then
3290    Result := inherited IsTextIdxVisible(aTextIndex);
3291end;
3292
3293procedure TSynEditFoldedView.Lock;
3294begin
3295  if fLockCount=0 then begin
3296    fNeedFixFrom := -1;
3297    fNeedFixMinEnd := -1;
3298  end;
3299  inc(fLockCount);
3300end;
3301
3302procedure TSynEditFoldedView.UnLock;
3303begin
3304  dec(fLockCount);
3305  if (fLockCount=0) then begin
3306    if (fNeedFixFrom >= 0) then
3307      FixFolding(fNeedFixFrom, fNeedFixMinEnd, fFoldTree);
3308    if fvfNeedCaretCheck in FFlags then
3309      DoCaretChanged(fCaret);
3310    if fvfNeedCalcMaps in FFlags then
3311      CalculateMaps;
3312  end;
3313end;
3314
3315(* Count *)
3316function TSynEditFoldedView.GetViewedCount : integer;
3317begin
3318  Result := NextLines.ViewedCount - fFoldTree.FindLastFold.FoldedBefore;
3319end;
3320
3321function TSynEditFoldedView.GetDisplayView: TLazSynDisplayView;
3322begin
3323  Result := FDisplayView;
3324end;
3325
3326procedure TSynEditFoldedView.InternalGetInfoForViewedXY(AViewedXY: TPhysPoint;
3327  AFlags: TViewedXYInfoFlags; out AViewedXYInfo: TViewedXYInfo;
3328  ALogPhysConvertor: TSynLogicalPhysicalConvertor);
3329var
3330  OldY: LongInt;
3331begin
3332  OldY := AViewedXY.y;
3333  AViewedXY.y := ToPos(InternViewToTextIndex(ToIdx(AViewedXY.y)));
3334  OldY := OldY - AViewedXY.y;
3335  inherited InternalGetInfoForViewedXY(AViewedXY, AFlags, AViewedXYInfo,
3336    ALogPhysConvertor);
3337  AViewedXYInfo.CorrectedViewedXY.y := AViewedXYInfo.CorrectedViewedXY.y + OldY;
3338end;
3339
3340function TSynEditFoldedView.GetFoldClasifications(index : Integer): TFoldNodeClassifications;
3341begin
3342  if (index < -1) or (index > fLinesInWindow + 1) then exit([]);
3343  Result := fFoldTypeList[index+1].Classifications;
3344end;
3345
3346function TSynEditFoldedView.GetHighLighter: TSynCustomHighlighter;
3347begin
3348  Result := FFoldProvider.HighLighter;
3349  if assigned(Result) then
3350    Result.CurrentLines := NextLines;
3351end;
3352
3353(* Topline *)
3354procedure TSynEditFoldedView.SetTopLine(const ALine : integer);
3355begin
3356  if fTopLine = ALine then exit;
3357  FInTopLineChanged := True;
3358  fTopLine := ALine;
3359  CalculateMaps;
3360  FInTopLineChanged := False;
3361end;
3362
3363function TSynEditFoldedView.GetTopTextIndex : integer;
3364begin
3365  Result := fTopLine + fFoldTree.FindFoldForFoldedLine(fTopLine).FoldedBefore - 1;
3366end;
3367
3368procedure TSynEditFoldedView.SetTopTextIndex(const AIndex : integer);
3369begin
3370  TopLine := AIndex + 1 - fFoldTree.FindFoldForLine(AIndex+1).FoldedBefore;
3371end;
3372
3373(* LinesInWindow*)
3374procedure TSynEditFoldedView.SetLinesInWindow(const AValue : integer);
3375begin
3376  if fLinesInWindow = AValue then exit;
3377  fLinesInWindow := AValue;
3378  SetLength(fTextIndexList, AValue + 3);
3379  SetLength(fFoldTypeList, AValue + 3); // start 1 before topline
3380  CalculateMaps;
3381end;
3382
3383procedure TSynEditFoldedView.DoFoldChanged(AnIndex: Integer);
3384begin
3385  SendNotification(senrLineMappingChanged, Self, AnIndex, 0);
3386end;
3387
3388procedure TSynEditFoldedView.SetManager(AManager: TSynTextViewsManager);
3389begin
3390  if Manager <> nil then begin
3391    RemoveChangeHandler(senrLineCount, @LineCountChanged);
3392    RemoveNotifyHandler(senrCleared, @LinesCleared);
3393    RemoveEditHandler(@LineEdited);
3394  end;
3395  inherited SetManager(AManager);
3396  if Manager <> nil then begin
3397    AddChangeHandler(senrLineCount, @LineCountChanged);
3398    AddNotifyHandler(senrCleared, @LinesCleared);
3399    AddEditHandler(@LineEdited);
3400  end;
3401end;
3402
3403procedure TSynEditFoldedView.SetSynStrings(AValue: TSynEditStrings);
3404begin
3405  inherited SetSynStrings(AValue);
3406  FFoldProvider.FLines := AValue;
3407end;
3408
3409procedure TSynEditFoldedView.DoBlockSelChanged(Sender: TObject;
3410  Changes: TSynStatusChanges);
3411begin
3412  CalculateMaps;
3413end;
3414
3415procedure TSynEditFoldedView.CalculateMaps;
3416var
3417  i, tpos, cnt  : Integer;
3418  node, tmpnode: TSynTextFoldAVLNode;
3419  FirstChanged, LastChanged: Integer;
3420  NewCapability: TSynEditFoldLineCapabilities;
3421  NewClassifications :TFoldNodeClassifications;
3422begin
3423  if fLinesInWindow < 0 then exit;
3424  if (fLockCount > 0) and
3425     ((not FInTopLineChanged) or (fvfNeedCalcMaps in FFlags)) // TODO: Scan now, to avoid invalidate later
3426  then begin
3427    Include(FFlags, fvfNeedCalcMaps);
3428    exit;
3429  end;
3430  Exclude(FFlags, fvfNeedCalcMaps);
3431
3432  node := fFoldTree.FindFoldForFoldedLine(fTopLine, true);
3433  // ftopline is not a folded line
3434  // so node.FoldedBefore(next node after ftopl) does apply
3435  tpos  := fTopLine + node.FoldedBefore - 1;
3436  if node.IsInFold then
3437    tmpnode := node.Prev
3438  else
3439    tmpnode := fFoldTree.FindLastFold;
3440  if tmpnode.IsInFold and (tmpnode.StartLine + tmpnode.MergedLineCount = tpos + 1) then begin
3441    node := tmpnode;
3442    tpos := tpos - node.MergedLineCount;
3443  end;
3444  {$IFDEF SynFoldDebug}debugln(['FOLD-- CalculateMaps fTopLine:=', fTopLine, '  tpos=',tpos]);{$ENDIF}
3445  cnt := NextLines.Count;
3446  FirstChanged := -1;
3447  LastChanged := -1;
3448  for i := 0 to fLinesInWindow + 2 do begin
3449    if (tpos > cnt) or (tpos < 0) then begin
3450      // Past end of Text
3451      fTextIndexList[i] := -1;
3452      NewCapability := [];
3453      NewClassifications := [];
3454    end else begin
3455      fTextIndexList[i] := tpos - 1; // TextIndex is 0-based
3456      NewCapability := FFoldProvider.LineCapabilities[tpos - 1];
3457      NewClassifications := FFoldProvider.LineClassification[tpos - 1];
3458      if (node.IsInFold) then begin
3459        if (tpos = node.SourceLine) then begin
3460          include(NewCapability, cfCollapsedFold);
3461          include(NewClassifications, node.fData.Classification);
3462        end
3463        else if node.IsHide and (tpos + 1 = node.SourceLine) then begin
3464          include(NewCapability, cfCollapsedHide);
3465          include(NewClassifications, node.fData.Classification);
3466        end;
3467      end;
3468
3469      inc(tpos);
3470      while (node.IsInFold) and (tpos >= node.StartLine) do begin
3471        tpos := tpos + node.MergedLineCount;
3472        node := node.Next;
3473      end;
3474    end;
3475
3476    if (fFoldTypeList[i].Capability <> NewCapability) or
3477       (fFoldTypeList[i].Classifications <> NewClassifications)
3478    then begin
3479      if FirstChanged < 0 then FirstChanged := tpos - 1;
3480      LastChanged := tpos;
3481    end;
3482    fFoldTypeList[i].Capability := NewCapability;
3483    fFoldTypeList[i].Classifications := NewClassifications;
3484  end;
3485  if (not FInTopLineChanged) and (FirstChanged > 0) then
3486    FOwner.InvalidateGutterLines(FirstChanged, LastChanged + 1);
3487end;
3488
3489(* Lines *)
3490function TSynEditFoldedView.GetViewedLines(index : Integer) : String;
3491begin
3492  if (index < -1) or (index > fLinesInWindow + 1) then
3493    exit(NextLines.ViewedLines[ScreenLineToTextIndex(Index)]);
3494  Result := NextLines.ViewedLines[fTextIndexList[index+1]];
3495end;
3496
3497function TSynEditFoldedView.GetDisplayNumber(index : Integer) : Integer;
3498begin
3499  if (index < -1) or (index > fLinesInWindow + 1)
3500  or (fTextIndexList[index+1] < 0) then exit(-1);
3501  Result := fTextIndexList[index+1]+1;
3502end;
3503
3504function TSynEditFoldedView.GetTextIndex(index : Integer) : Integer;
3505begin
3506  if (index < -1) or (index > fLinesInWindow + 1) then
3507    exit(ScreenLineToTextIndex(Index));
3508  Result := fTextIndexList[index+1];
3509end;
3510
3511function TSynEditFoldedView.GetFoldType(index : Integer) : TSynEditFoldLineCapabilities;
3512begin
3513  if (index < -1) or (index > fLinesInWindow + 1) then exit([]);
3514  Result := fFoldTypeList[index+1].Capability;
3515end;
3516
3517function TSynEditFoldedView.IsFolded(index : integer) : Boolean;
3518begin
3519  Result := fFoldTree.FindFoldForLine(index+1).IsInFold;
3520end;
3521
3522procedure TSynEditFoldedView.ProcessMySynCommand(Sender: TObject;
3523  AfterProcessing: boolean; var Handled: boolean;
3524  var Command: TSynEditorCommand; var AChar: TUTF8Char; Data: pointer;
3525  HandlerData: pointer);
3526var
3527  CY: Integer;
3528begin
3529  if Handled then
3530    exit;
3531
3532  case Command of
3533    EcFoldLevel1..EcFoldLevel9:
3534      begin
3535        FoldAll(Command - EcFoldLevel1);
3536        FCaret.Touch;
3537        Handled := True;
3538      end;
3539    EcFoldLevel0:
3540      begin
3541        UnfoldAll;
3542        FCaret.Touch;
3543        Handled := True;
3544      end;
3545    EcFoldCurrent:
3546      begin
3547        CY := ExpandedLineForBlockAtLine(FCaret.LinePos);
3548        if CY > 0 then begin
3549          FoldAtTextIndex(CY-1);
3550          FCaret.ChangeOnTouch; // setting the caret always clears selection (even setting to current pos / no change)
3551          FCaret.LineCharPos:= Point(1, CY);
3552        end;
3553        Handled := True;
3554      end;
3555    EcUnFoldCurrent:
3556      begin
3557        UnFoldAtTextIndex(FCaret.LinePos-1);
3558        FCaret.Touch;
3559        Handled := True;
3560      end;
3561  end;
3562end;
3563
3564procedure TSynEditFoldedView.SetHighLighter(AValue: TSynCustomHighlighter);
3565begin
3566  if not(AValue is TSynCustomFoldHighlighter) then
3567    AValue := nil;
3568  FFoldProvider.HighLighter := TSynCustomFoldHighlighter(AValue);
3569  UnfoldAll;
3570end;
3571
3572(* Folding *)
3573
3574procedure TSynEditFoldedView.FoldAtLine(AStartLine : Integer;
3575  ColIndex : Integer = -1; ColCount : Integer = 1; Skip: Boolean = False;
3576  AVisibleLines: Integer = 1);
3577begin
3578  FoldAtViewPos(AStartLine + fTopLine, ColIndex, ColCount, Skip, AVisibleLines);
3579end;
3580
3581procedure TSynEditFoldedView.FoldAtViewPos(AStartPos : Integer;
3582  ColIndex : Integer = -1; ColCount : Integer = 1; Skip: Boolean = False;
3583  AVisibleLines: Integer = 1);
3584begin
3585  FoldAtTextIndex(AStartPos - 1 + fFoldTree.FindFoldForFoldedLine(AStartPos).FoldedBefore,
3586                  ColIndex, ColCount, Skip, AVisibleLines);
3587end;
3588
3589function TSynEditFoldedView.FoldNodeAtTextIndex(AStartIndex,
3590  ColIndex: Integer): TSynTextFoldAVLNode;
3591var
3592  tree: TSynTextFoldAVLTree;
3593begin
3594  Result := fFoldTree.FindFoldForLine(AStartIndex + 1, True);
3595
3596  tree := fFoldTree;
3597  while (not Result.IsInFold) or (Result.SourceLine <> AStartIndex + 1) do begin
3598    if (not Result.IsInFold) then
3599      Result := tree.FindLastFold;
3600    while Result.IsInFold and (Result.SourceLine > AStartIndex + 1) do
3601      Result := Result.Prev;
3602    if not Result.IsInFold then break;
3603
3604    if Result.IsInFold and (Result.SourceLine < AStartIndex + 1) then begin
3605      if Result.fData.Nested = nil then break;
3606      tree := fFoldTree.TreeForNestedNode(Result.fData, Result.StartLine);
3607      Result := tree.FindFirstFold;
3608      while Result.IsInFold and (Result.SourceLine < AStartIndex + 1) do
3609        Result := Result.Next;
3610    end
3611    else
3612      break;
3613  end;
3614
3615  while Result.IsInFold and (Result.SourceLine = AStartIndex + 1) do begin
3616    if Result.FoldIndex = ColIndex then
3617      exit;
3618    if Result.fData.Nested = nil then break;
3619    Result := fFoldTree.TreeForNestedNode(Result.fData, Result.StartLine).FindFirstFold;
3620  end;
3621  Result.fData := nil;
3622end;
3623
3624function TSynEditFoldedView.IsFoldedAtTextIndex(AStartIndex, ColIndex: Integer): Boolean;
3625begin
3626  Result := FoldNodeAtTextIndex(AStartIndex, ColIndex).IsInFold;
3627end;
3628
3629function TSynEditFoldedView.LogicalPosToNodeIndex(AStartIndex: Integer; LogX: Integer;
3630  Previous: Boolean): Integer;
3631var
3632  hl: TSynCustomFoldHighlighter;
3633  c, i: Integer;
3634  nd: TSynFoldNodeInfo;
3635begin
3636  hl := TSynCustomFoldHighlighter(HighLighter);
3637  if not assigned(hl) then
3638    exit(0);
3639  // AStartIndex is 0-based
3640  // FoldTree is 1-based AND first line remains visble
3641  c := hl.FoldNodeInfo[AStartIndex].CountEx([sfaOpen, sfaFold]);
3642  if c = 0 then
3643    exit(-1);
3644  i := 0;
3645  while i < c do begin
3646    nd := hl.FoldNodeInfo[aStartIndex].NodeInfoEx(i, [sfaOpen, sfaFold]);
3647    if (nd.LogXStart >= LogX) then begin
3648      dec(i);
3649      if not Previous then
3650        i := -1;
3651      break;
3652    end;
3653    if (nd.LogXEnd >= LogX) then
3654      break;
3655    inc(i);
3656  end;
3657  Result := i;
3658end;
3659
3660procedure TSynEditFoldedView.CollapseDefaultFolds;
3661var
3662  i, j, c: Integer;
3663  hl: TSynCustomFoldHighlighter;
3664  fldinf: TSynEditFoldProviderNodeInfo;
3665begin
3666  hl := TSynCustomFoldHighlighter(HighLighter);
3667  if not assigned(hl) then
3668    exit;
3669
3670  i := 0;
3671  while i < NextLines.Count do begin
3672     // Todo: Highlighter should return a list of types that can return default folded
3673     // Currently PascalHl Type 2 = Region
3674    c := hl.FoldBlockOpeningCount(i, 2);
3675    if c > 0 then begin
3676      c := hl.FoldNodeInfo[i].CountEx([sfaOpen, sfaFold]);
3677      j := 0;
3678      while j < c do begin
3679        fldinf := FoldProvider.InfoForFoldAtTextIndex(i, j);
3680        if (fldinf.DefaultCollapsed) and (not IsFoldedAtTextIndex(i, j))
3681        then begin
3682          // TODO: detect default hide too
3683          // currently always VisibleLines=1 => since region only folds
3684          fFoldTree.InsertNewFold(i+2, j, fldinf.Column, fldinf.ColumnLen, fldinf.LineCount, 1,
3685                                  fldinf.Classification, fldinf.FoldTypeCompatible);
3686          DoFoldChanged(i);
3687        end;
3688      inc(j);
3689      end;
3690    end;
3691    inc(i);
3692  end;
3693  CalculateMaps;
3694end;
3695
3696function TSynEditFoldedView.GetFoldDescription(AStartIndex, AStartCol, AEndIndex,
3697  AEndCol: Integer; AsText: Boolean = False; Extended: Boolean = False): String;
3698var
3699  FoldCoders: Array of TSynEditFoldExportCoder;
3700
3701  function FoldCoderForType(AType: Pointer): TSynEditFoldExportCoder;
3702  var
3703    i, j: Integer;
3704  begin
3705    i := 0;
3706    j := length(FoldCoders);
3707    while (i < j) and (FoldCoders[i].FoldType <> AType) do
3708      inc(i);
3709    if (i = j) then begin
3710      SetLength(FoldCoders, i + 1);
3711      FoldCoders[i] := TSynEditFoldExportCoder.Create(AType);
3712    end;
3713    Result := FoldCoders[i];
3714  end;
3715
3716var
3717  hl: TSynCustomFoldHighlighter;
3718  FoldHelper: TSynEditFoldExportStream;
3719  NodeIterator: TSynTextFoldAVLNodeNestedIterator;
3720  NdiHelper1: TSynFoldNodeInfoHelper;
3721  Node: TSynTextFoldAVLNode;
3722  NdInfo, NdInfo2: TSynFoldNodeInfo;
3723  entry: TFoldExportEntry;
3724  i: Integer;
3725  NodeFoldType: TSynEditFoldType;
3726begin
3727  Result := '';
3728  hl := TSynCustomFoldHighlighter(HighLighter);
3729  if not assigned(hl) then exit;
3730
3731  if AEndIndex < 0 then AEndIndex := MaxInt;
3732  if AEndCol   < 0 then AEndCol   := MaxInt;
3733
3734  Node := fFoldTree.FindFoldForLine(AStartIndex + 1, True);
3735  NodeIterator := TSynTextFoldAVLNodeNestedIterator.Create(Node);
3736  FoldHelper := TSynEditFoldExportStream.Create;
3737  NdiHelper1 := TSynFoldNodeInfoHelper.Create(hl);
3738  try
3739    if (AStartCol > 1) then
3740      while Node.IsInFold and (Node.StartLine = AStartIndex + 2) do begin
3741        NdInfo := NdiHelper1.GotoNodeOpenPos(Node);
3742        if (sfaInvalid in NdInfo.FoldAction) or (ndinfo.LogXStart >= AStartCol) then
3743          break;
3744        Node := NodeIterator.Next;
3745      end;
3746    dec(AStartCol);
3747    if not node.IsInFold then
3748      exit;
3749
3750    (* Text stores fold length according to AVLNode
3751       Binary stores line-diff between highlighter open and close line
3752    *)
3753    if AsText then
3754    begin           (* *** Encode as Text for XML *** *)
3755      {$IFDEF SynFoldSaveDebug}
3756      DebugLnEnter(['TSynEditFoldedView.GetFoldDescription as Text']);
3757      {$ENDIF}
3758      while Node.IsInFold and (Node.fData.Classification <> fncHighlighter) do
3759        Node := NodeIterator.Next;
3760      if not node.IsInFold then
3761        exit;
3762
3763      NdInfo := NdiHelper1.GotoNodeOpenPos(Node);
3764      while Node.IsInFold and (Node.StartLine-2 <= AEndIndex) do
3765      begin
3766        if (node.StartLine > AStartIndex + 2) then AStartCol := 0;
3767
3768        NodeFoldType := scftFold;
3769        if Node.SourceLineOffset = 0 then
3770          NodeFoldType := scftHide;
3771        if (NdInfo.FoldAction * [sfaInvalid, sfaDefaultCollapsed] = []) then // Currently skip default nodes
3772          FoldCoderForType(NdInfo.FoldType).AddNode
3773                     (NdInfo.LogXStart, NdInfo.LineIndex, Node.FullCount, NodeFoldType);
3774
3775        Node := NodeIterator.Next;
3776        while Node.IsInFold and (Node.fData.Classification <> fncHighlighter) do
3777          Node := NodeIterator.Next;
3778        if not Node.IsInFold then
3779          break;
3780
3781        NdInfo := NdiHelper1.Next;
3782        while NdiHelper1.IsValid and (not NdiHelper1.IsAtNodeOpenPos(Node)) do begin
3783          // Add unfolded nodes
3784          if (NdInfo.FoldAction * [sfaInvalid, sfaDefaultCollapsed] = []) then // Currently skip default nodes
3785            FoldCoderForType(NdInfo.FoldType).AddNode
3786                                 (NdInfo.LogXStart, NdInfo.LineIndex, 0, scftOpen);
3787          NdInfo := NdiHelper1.Next;
3788        end;
3789      end;
3790
3791      for i := 0 to length(FoldCoders) - 1 do begin
3792        FoldCoders[i].Finish;
3793        FoldHelper.AppendMem(FoldCoders[i].Stream.Mem, FoldCoders[i].Stream.Len);
3794      end;
3795      FoldHelper.AddChecksum;
3796      FoldHelper.Compress;
3797      {$IFDEF SynFoldSaveDebug}
3798      DebugLnExit(['TSynEditFoldedView.GetFoldDescription as Text']);
3799      {$ENDIF}
3800    end             (* *** END: Encode as Text for XML *** *)
3801    else
3802    begin           (* *** Encode as Binary *** *)
3803      while Node.IsInFold and (Node.StartLine-2 <= AEndIndex) do
3804      begin
3805        if (node.StartLine > AStartIndex + 2) then
3806          AStartCol := 0;
3807
3808        NdInfo2 := NdiHelper1.GotoNodeClosePos(Node);
3809        if (sfaInvalid in NdInfo2.FoldAction) or
3810           (NdInfo2.LineIndex > AEndIndex) or
3811           ((NdInfo2.LineIndex = AEndIndex) and (ndinfo2.LogXEnd > AEndCol))
3812        then begin
3813          node := NodeIterator.Next;
3814          continue;
3815        end;
3816
3817        NdInfo := NdiHelper1.GotoNodeOpenPos(Node);
3818
3819        with entry do begin
3820          LogX   := NdInfo.LogXStart - AStartCol;
3821          LogX2  := NdInfo.LogXEnd - ndinfo.LogXStart + (ndinfo.LogXStart - AStartCol);
3822          Line   := NdInfo.LineIndex - AStartIndex;
3823          ELogX  := NdInfo2.LogXStart;
3824          ELogX2 := NdInfo2.LogXEnd;
3825          ELine  := NdInfo2.LineIndex - AStartIndex;
3826          //if sfaLastLineClose in NdInfo2.FoldAction then
3827          //  ELine := -1; // unfinished fold
3828          FType  := PtrUInt(NdInfo.FoldType);
3829          LinesFolded := node.FullCount;
3830        end;
3831        FoldHelper.AppendMem(@entry, SizeOf(TFoldExportEntry));
3832
3833        Node := NodeIterator.Next;
3834      end;
3835    end;            (* *** END: Encode as Binary *** *)
3836
3837    Result := FoldHelper.Text;
3838  finally
3839    FoldHelper.Free;
3840    for i := 0 to length(FoldCoders) - 1 do
3841      FoldCoders[i].Free;
3842    NodeIterator.Free;
3843    NdiHelper1.Free;
3844  end;
3845end;
3846
3847procedure TSynEditFoldedView.ApplyFoldDescription(AStartIndex, AStartCol, AEndIndex,
3848  AEndCol: Integer; FoldDesc: PChar; FoldDescLen: Integer; IsText: Boolean = False);
3849var
3850  FoldCoders: Array of TSynEditFoldExportCoder;
3851
3852  function FoldCoderForType(AType: Pointer): TSynEditFoldExportCoder;
3853  var
3854    j: Integer;
3855  begin
3856    j := length(FoldCoders) - 1;
3857    while (j >= 0) and (FoldCoders[j] <> nil) and (FoldCoders[j].FoldType <> AType) do
3858      dec(j);
3859    if (j < 0) then
3860      Result := nil
3861    else
3862      Result := FoldCoders[j];
3863  end;
3864
3865  procedure RemoveCoderForType(AType: Pointer);
3866  var
3867    j: Integer;
3868  begin
3869    j := length(FoldCoders) - 1;
3870    while (j >= 0) and (FoldCoders[j] <> nil) and (FoldCoders[j].FoldType <> AType) do
3871      dec(j);
3872    if (j >= 0) then begin
3873      debugln(['FoldState loading removed data for foldtype: ', PtrUInt(AType)]);
3874      FreeAndNil(FoldCoders[j]);
3875    end;
3876  end;
3877
3878
3879var
3880  hl: TSynCustomFoldHighlighter;
3881  FoldHelper: TSynEditFoldExportStream;
3882  NdiHelper1: TSynFoldNodeInfoHelper;
3883  NdInfo, ndinfo2: TSynFoldNodeInfo;
3884  i: Integer;
3885  Line, FL: Integer;
3886  entry: TFoldExportEntry;
3887  Coder: TSynEditFoldExportCoder;
3888  IsFold, IsHide: Boolean;
3889begin
3890  hl := TSynCustomFoldHighlighter(HighLighter);
3891  if not assigned(hl) then
3892    exit;
3893  if (FoldDesc = nil) or (FoldDescLen = 0) then exit;
3894
3895  NdiHelper1 := TSynFoldNodeInfoHelper.Create(hl);
3896  FoldHelper := TSynEditFoldExportStream.Create;
3897  try
3898    FoldHelper.Mem := FoldDesc;
3899    FoldHelper.Len := FoldDescLen;
3900
3901    if IsText then
3902    begin           (* *** Decode from Text for XML *** *)
3903      try
3904        FoldHelper.Decompress;
3905      except
3906        exit;
3907      end;
3908      if not FoldHelper.VerifyChecksum then
3909        exit; //raise ESynEditError.Create('fold checksum error');
3910
3911      i := 0;
3912      while not FoldHelper.EOF do begin
3913        SetLength(FoldCoders, i + 1);
3914        FoldCoders[i] := TSynEditFoldExportCoder.Create(FoldHelper);
3915        if not FoldCoders[i].ReadIsValid then
3916          break;
3917        inc(i);
3918      end;
3919
3920      NdInfo := NdiHelper1.FirstOpen;
3921
3922      while NdiHelper1.IsValid do begin
3923        if (sfaDefaultCollapsed in NdInfo.FoldAction) then begin // Currently skip default nodes
3924          NdInfo := NdiHelper1.Next;
3925          continue;
3926        end;
3927        Coder := FoldCoderForType(NdInfo.FoldType);
3928        if coder <> nil then begin
3929          i := FoldProvider.InfoForFoldAtTextIndex(NdInfo.LineIndex, NdInfo.NodeIndex).LineCount;
3930          case coder.ReadNode(NdInfo.LogXStart, NdInfo.LineIndex, i) of
3931            scftFold:  FoldAtTextIndex(NdInfo.LineIndex, NdInfo.NodeIndex);
3932            scftHide:  FoldAtTextIndex(NdInfo.LineIndex, NdInfo.NodeIndex, 1, False, 0);
3933            scftInvalid: RemoveCoderForType(NdInfo.FoldType);
3934          end;
3935        end;
3936        NdInfo := NdiHelper1.Next;
3937      end;
3938    end             (* *** END: Encode as Text for XML *** *)
3939    else
3940    begin           (* *** Decode from Binary *** *)
3941      entry.Line := 0;
3942      if AStartCol > 0 then
3943        dec(AStartCol);
3944      while not FoldHelper.EOF do begin
3945        if not FoldHelper.ReadMem(@entry, sizeof(TFoldExportEntry)) then
3946          break;
3947        if entry.Line > 0 then AStartCol := 0;
3948
3949        Line := AStartIndex + entry.Line;
3950        if Line >= NextLines.Count then
3951          continue;
3952
3953        ndinfo :=NdiHelper1.GotoOpenAtChar(Line, entry.LogX);
3954        Fl := FoldProvider.InfoForFoldAtTextIndex(Line, ndinfo.NodeIndex).LineCount;
3955        IsFold := (sfaFoldFold in NdInfo.FoldAction) and (entry.LinesFolded = FL);
3956        IsHide := (sfaFoldHide in NdInfo.FoldAction) and (entry.LinesFolded = FL + 1);
3957        if (sfaInvalid in ndinfo.FoldAction) or
3958           (ndinfo.LogXStart <> entry.LogX + AStartCol) or
3959           (ndinfo.LogXEnd <> entry.LogX2 + AStartCol)  or
3960           //(ndinfo.FoldType <> entry.FType) or
3961           (not (IsHide or IsFold))
3962        then
3963          continue;
3964
3965        ndinfo2 := NdiHelper1.FindClose;
3966        if (sfaInvalid in ndinfo2.FoldAction) or
3967           (ndinfo2.LogXStart <> entry.ELogX) or
3968           (ndinfo2.LogXEnd <> entry.ELogX2)
3969        then
3970          continue;
3971
3972        i := 1;
3973        if IsHide then i := 0;;
3974        FoldAtTextIndex(Line, NdInfo.NodeIndex, 1, False, i);
3975      end;
3976    end;            (* *** END: Encode as Binary *** *)
3977  finally
3978    for i := 0 to length(FoldCoders) - 1 do
3979      FoldCoders[i].Free;
3980    FreeAndNil(FoldHelper);
3981    FreeAndNil(NdiHelper1);
3982  end;
3983end;
3984
3985procedure TSynEditFoldedView.FoldAtTextIndex(AStartIndex : Integer;
3986  ColIndex : Integer = -1; ColCount : Integer = 1; Skip: Boolean = False;
3987  AVisibleLines: Integer = 1);
3988var
3989  NodeCount, top: Integer;
3990  down: Boolean;
3991  NFolded: TSynTextFoldAVLNode;
3992  IsHide: Boolean;
3993  fldinf: TSynEditFoldProviderNodeInfo;
3994begin
3995  if not FoldProvider.FoldsAvailable then exit;
3996  top := TopTextIndex;
3997
3998  // AStartIndex is 0-based
3999  // FoldTree is 1-based AND first line remains visble
4000  NodeCount := FoldProvider.FoldOpenCount(AStartIndex);
4001  if ColCount = 0 then
4002    ColCount := NodeCount;
4003
4004  down := ColIndex < 0;
4005  if down then
4006    ColIndex := NodeCount + ColIndex;
4007
4008  IsHide := AVisibleLines = 0;
4009
4010  while ColCount > 0 do begin
4011    if (ColIndex < 0) or (ColIndex >= NodeCount) then break;
4012    NFolded := FoldNodeAtTextIndex(AStartIndex, ColIndex);
4013    // TODO: Check if position can Hide or fold
4014    if skip and (   ( (AVisibleLines=0) and NFolded.IsHide  ) or
4015                    ( (AVisibleLines>0) and NFolded.IsInFold  )   )
4016    then begin
4017      if down
4018      then dec(ColIndex)
4019      else inc(ColIndex);
4020      continue;
4021    end;
4022
4023    // TODO: missing check, that hl-node is hideable
4024    fldinf := FoldProvider.InfoForFoldAtTextIndex(AStartIndex, ColIndex, IsHide);
4025    if not NFolded.IsInFold then begin
4026      if fldinf.LineCount > 0 then
4027        fFoldTree.InsertNewFold(AStartIndex+1+AVisibleLines, ColIndex,
4028                                fldinf.Column, fldinf.ColumnLen, fldinf.LineCount,
4029                                AVisibleLines,
4030                                fldinf.Classification, fldinf.FoldTypeCompatible)
4031    end
4032    else begin
4033      if (AVisibleLines=0) and (not NFolded.IsHide) and (fldinf.LineCount > 0) then begin
4034        // upgrade to hide
4035        fFoldTree.RemoveFoldForNodeAtLine(NFolded, -1);
4036        fFoldTree.InsertNewFold(AStartIndex+1, ColIndex,
4037                                fldinf.Column, fldinf.ColumnLen, fldinf.LineCount,
4038                                AVisibleLines,
4039                                fldinf.Classification, fldinf.FoldTypeCompatible);
4040      end;
4041    end;
4042    if down
4043    then dec(ColIndex)
4044    else inc(ColIndex);
4045    dec(ColCount);
4046  end;
4047
4048  fTopLine := -1;  // make sure seting TopLineTextIndex, will do CalculateMaps;
4049  TopTextIndex := top;
4050  DoFoldChanged(AStartIndex);
4051end;
4052
4053procedure TSynEditFoldedView.UnFoldAtLine(AStartLine : Integer;
4054  ColIndex : Integer = -1; ColCount : Integer = 0; Skip: Boolean = False;
4055  AVisibleLines: Integer = 1);
4056begin
4057  UnFoldAtViewPos(AStartLine + fTopLine, ColIndex, ColCount, Skip, AVisibleLines);
4058end;
4059
4060procedure TSynEditFoldedView.UnFoldAtViewPos(AStartPos : Integer;
4061  ColIndex : Integer = -1; ColCount : Integer = 0; Skip: Boolean = False;
4062  AVisibleLines: Integer = 1);
4063begin
4064  UnFoldAtTextIndex(AStartPos - 1 + fFoldTree.FindFoldForFoldedLine(AStartPos).FoldedBefore,
4065                    ColIndex, ColCount, Skip, AVisibleLines);
4066end;
4067
4068procedure TSynEditFoldedView.UnFoldAtTextIndex(AStartIndex : Integer;
4069  ColIndex : Integer = -1; ColCount : Integer = 0; Skip: Boolean = False;
4070  AVisibleLines: Integer = 1);
4071var
4072  top, c, r, r2 : Integer;
4073  down: Boolean;
4074  NFolded: TSynTextFoldAVLNode;
4075begin
4076  top := TopTextIndex;
4077  c := FoldProvider.FoldOpenCount(AStartIndex);
4078
4079  //TODO move to FoldProvider
4080  NFolded := fFoldTree.FindFoldForLine(AStartIndex+1, True);
4081  while NFolded.IsInFold and (NFolded.StartLine = AStartIndex+1) do begin
4082    if NFolded.FoldIndex + 1 > c then c := NFolded.FoldIndex + 1;
4083    NFolded := fFoldTree.TreeForNestedNode(NFolded.fData, NFolded.StartLine).FindFoldForLine(AStartIndex, True);
4084  end;
4085
4086  if c < 1 then begin
4087    // TODO: foldprovider to return all folded nodes, for hte line
4088    ColCount := 0;
4089  end;
4090
4091  r := -1;
4092  if ColCount = 0 then begin
4093    r := fFoldTree.RemoveFoldForLine(AStartIndex+AVisibleLines+1); // r is 1-based num of first (ex-)hidden line
4094  end
4095  else begin
4096    down := ColIndex < 0;
4097    if down then
4098      ColIndex := c + ColIndex ;
4099    while ColCount > 0 do begin
4100      if (ColIndex < 0) or (ColIndex >= c) then break;
4101      NFolded := FoldNodeAtTextIndex(AStartIndex, ColIndex);
4102      if skip and (   ( (AVisibleLines=0) and not NFolded.IsHide  ) or
4103                      ( (AVisibleLines>0) and not NFolded.IsInFold  )   )
4104      then begin
4105        if down
4106        then dec(ColIndex)
4107        else inc(ColIndex);
4108        continue;
4109      end;
4110      r2 := fFoldTree.RemoveFoldForLine(AStartIndex+1+AVisibleLines, ColIndex);
4111      if r2 > 0 then dec(r2);
4112      if (r < 0) or (r2 < r) then r := r2;
4113      if down
4114      then dec(ColIndex)
4115      else inc(ColIndex);
4116      dec(ColCount);
4117    end;
4118  end;
4119
4120  fTopLine := -1;  // make sure seting TopLineTextIndex, will do CalculateMaps;
4121  TopTextIndex := top;
4122  if (r >= 0) then
4123    DoFoldChanged(Max(0, r - 2));
4124end;
4125
4126procedure TSynEditFoldedView.UnFoldAtTextIndexCollapsed(AStartIndex: Integer);
4127var
4128  top, r: Integer;
4129begin
4130  top := TopTextIndex;
4131  r := fFoldTree.RemoveFoldForLine(AStartIndex+1) - 1;
4132  fTopLine := -1;  // make sure seting TopLineTextIndex, will do CalculateMaps;
4133  TopTextIndex := top;
4134  DoFoldChanged(r);
4135end;
4136
4137procedure TSynEditFoldedView.UnfoldAll;
4138var
4139  top : Integer;
4140begin
4141  top := TopTextIndex;
4142  fFoldTree.Clear;
4143  fTopLine := -1;  // make sure seting TopLineTextIndex, will do CalculateMaps;
4144  TopTextIndex := top;
4145  DoFoldChanged(0);
4146end;
4147
4148procedure TSynEditFoldedView.FoldAll(StartLevel : Integer = 0; IgnoreNested : Boolean = False);
4149var
4150  c, i, top, t: Integer;
4151  hl: TSynCustomFoldHighlighter;
4152  fldinf: TSynEditFoldProviderNodeInfo;
4153begin
4154  hl := TSynCustomFoldHighlighter(HighLighter);
4155  if not assigned(hl) then
4156    exit;
4157
4158  t := 1; // TODO: Highlighter default type; or iterate through all types
4159  top := TopTextIndex;
4160  fFoldTree.Clear;
4161  i := 0;
4162  while i < NextLines.Count do begin
4163    if (hl.FoldBlockOpeningCount(i, t) > 0)
4164    and (hl.FoldBlockEndLevel(i, t) > StartLevel) then begin
4165      c := hl.FoldBlockOpeningCount(i) -1;
4166      fldinf := FoldProvider.InfoForFoldAtTextIndex(i, c);
4167      // i is 0-based
4168      // FoldTree is 1-based AND first line remains visble
4169      fFoldTree.InsertNewFold(i+2, c, fldinf.Column, fldinf.ColumnLen, fldinf.LineCount, 1,
4170                              fldinf.Classification, fldinf.FoldTypeCompatible); // TODO: hide too? currently VisibleLines=1
4171      if IgnoreNested then
4172        i := i + fldinf.LineCount;
4173    end;
4174    inc(i);
4175  end;
4176  fTopLine := -1;
4177  TopTextIndex := top;
4178  DoFoldChanged(0);
4179end;
4180
4181function TSynEditFoldedView.FixFolding(AStart: Integer; AMinEnd: Integer;
4182  aFoldTree: TSynTextFoldAVLTree): Boolean;
4183var
4184  FirstchangedLine, MaxCol: Integer;
4185  SrcLineForFldInfos: Integer;
4186  FldInfos: TSynEditFoldProviderNodeInfoList;
4187
4188  function DoFixFolding(doStart: Integer; doMinEnd, AtColumn: Integer;
4189    doFoldTree: TSynTextFoldAVLTree; node: TSynTextFoldAVLNode) : Boolean;
4190
4191    Procedure DoRemoveNode(var theNode: TSynTextFoldAVLNode);
4192    var
4193      tmpnode: TSynTextFoldAVLNode;
4194      l: Integer;
4195    begin
4196      Result := True;
4197      tmpnode := theNode.Prev;
4198      l := theNode.SourceLine;
4199      doFoldTree.RemoveFoldForNodeAtLine(theNode, -1); // Don't touch any nested node
4200      if tmpnode.IsInFold then theNode := tmpnode.Next
4201      else theNode := doFoldTree.FindFirstFold;
4202      if (FirstchangedLine < 0) or (l < FirstchangedLine) then
4203        FirstchangedLine := l;
4204    end;
4205
4206  var
4207    FldSrcLine, FldSrcIndex, FLdNodeLine, FldLen, FndLen: Integer;
4208    i, j, CurLen: Integer;
4209    SubTree: TSynTextFoldAVLTree;
4210  begin
4211    {$IFDEF SynFoldDebug}try DebugLnEnter(['>>FOLD-- DoFixFolding: doStart=', doStart, '  AMinEnd=',AMinEnd]);{$ENDIF}
4212    {$IFDEF SynFoldDebug}aFoldTree.Debug;{$ENDIF}
4213    Result := False;
4214    FldSrcLine := doStart;
4215    while node.IsInFold do begin
4216      {$IFDEF SynFoldDebug}debugln(['>>FOLD-- Node StartLine=', node.StartLine, ' FoldColumn=', node.FoldColumn, ' FoldIndex=', node.FoldIndex, ' FullCount=', node.FullCount, ' Classification=', dbgs(node.Classification)]);{$ENDIF}
4217      FldSrcLine := node.SourceLine; // the 1-based cfCollapsed (last visible) Line (or 1st hidden)
4218      FLdNodeLine := node.StartLine; // the 1 based, first hidden line
4219      FldSrcIndex := FldSrcLine - 1;
4220      FldLen := node.FullCount;
4221      if (FldLen <= 0) then begin
4222        {$IFDEF SynFoldDebug}debugln(['>>FOLD-- FixFolding: Remove node with len<0 FldSrcLine=', FldSrcLine]);{$ENDIF}
4223        DoRemoveNode(node);
4224        continue;
4225      end;
4226
4227      //{$IFDEF SynAssertFold}
4228      //With mixed fold/hide => line goes up/down
4229      //SynAssert(FldSrcLine >= SrcLineForFldInfos, 'TSynEditFoldedView.FixFolding: FoldLine went backwards now %d was %d', [FldSrcLine, SrcLineForFldInfos]);
4230      //{$ENDIF}
4231      if (FldSrcLine <> SrcLineForFldInfos) then begin
4232        // Next Line
4233        SrcLineForFldInfos := FldSrcLine;
4234        AtColumn := 0;
4235                  // AtColumn is used for nodes, behing the HLs index-range (fncHighlighterEx, fncBlockSelection)
4236                  // TODO: At Colum may be wrong for mixed fold/hide
4237        FldInfos := FoldProvider.InfoListForFoldsAtTextIndex(FldSrcIndex, False);
4238        MaxCol := length(FldInfos)-1;
4239        {$IFDEF SynFoldDebug}debugln(['>>FOLD-- Got FldInfos for FldSrcIndex=', FldSrcIndex, ' MaxCol=', MaxCol]);{$ENDIF}
4240      end;
4241
4242      if node.fData.Classification in [fncHighlighter, fncHighlighterEx] then begin
4243        // find node in list
4244        i := -1;
4245        while (i < MaxCol) do begin
4246          inc(i);
4247          if (FldInfos[i].Classification <> fncHighlighter) or
4248             (FldInfos[i].FoldTypeCompatible <> node.fData.FoldTypeCompatible)
4249          then
4250            continue;
4251          FndLen := -1;
4252          j := abs(FldInfos[i].Column - node.FoldColumn);
4253          if (j > 0) and (j < node.FoldColumnLen) then begin
4254            //maybe
4255            FndLen := FoldProvider.FoldLineLength(FldSrcIndex, i);
4256            if node.IsHide then inc(FndLen);
4257            if FndLen <> node.FullCount then Continue;
4258            {$IFDEF SynFoldDebug}debugln('******** FixFolding: Adjusting x pos');{$ENDIF}
4259            //FldInfos[i].Column := node.FoldColumn;
4260          end;
4261          if (FndLen > 0) or (FldInfos[i].Column = node.FoldColumn) then begin
4262            if FndLen < 0 then begin
4263              FndLen := FoldProvider.FoldLineLength(FldSrcIndex, i);
4264              if node.IsHide then inc(FndLen);
4265            end;
4266            if abs(FndLen - node.FullCount) > 1 then continue;
4267            if (node.fData.Classification <> fncHighlighter) or
4268               (node.FoldColumn <> FldInfos[i].Column) or
4269               (node.FoldIndex <> i)
4270            then
4271              Result := true;
4272            {$IFDEF SynFoldDebug}if (node.fData.Classification <> fncHighlighter) then debugln(['>>FOLD-- FixFolding: set Node to fncHighlighter (FOUND) FldSrcLine=', FldSrcLine]);{$ENDIF}
4273            node.fData.Classification :=  fncHighlighter;
4274            node.FoldColumn := FldInfos[i].Column;
4275            node.fData.FoldIndex := i;
4276            i := -1;
4277            break;
4278          end;
4279        end;
4280        if i = MaxCol then begin
4281          {$IFDEF SynFoldDebug}debugln(['>>FOLD-- FixFolding: set Node to fncHighlighterEx (NOT FOUND) FldSrcLine=', FldSrcLine]);{$ENDIF}
4282          node.fData.Classification :=  fncHighlighterEx;
4283          node.fData.FoldIndex := MaxCol + AtColumn;
4284          inc(AtColumn);
4285          Result := True;
4286        end;
4287      end
4288      else begin
4289        if node.fData.FoldIndex <> MaxCol + AtColumn then
4290          Result := True;
4291        node.fData.FoldIndex := MaxCol + AtColumn;
4292        inc(AtColumn);
4293      end;
4294
4295      if (node.fData.Nested <> nil) then begin
4296        SubTree := doFoldTree.TreeForNestedNode(node.fData, FLdNodeLine);
4297        CurLen := node.MergedLineCount;
4298        if DoFixFolding(FldSrcLine, FLdNodeLine + CurLen, AtColumn, SubTree, SubTree.FindFirstFold)
4299        then begin
4300          if CurLen > FldLen then begin
4301            node.fData.MergedLineCount:= max(node.FullCount,
4302              doFoldTree.TreeForNestedNode(node.fData, 0).LastFoldedLine + 1);
4303            if CurLen <> node.MergedLineCount then
4304              node.fData.AdjustParentLeftCount(node.MergedLineCount - CurLen);
4305          end;
4306        end;
4307      end;
4308
4309      // the node was ok
4310      if node.StartLine >= doMinEnd then break;
4311      node := node.Next;
4312    end;
4313    {$IFDEF SynFoldDebug}finally DebugLnExit(['<<FOLD-- DoFixFolding: DONE=', Result]); end{$ENDIF}
4314  end;
4315
4316var
4317  node, tmpnode: TSynTextFoldAVLNode;
4318begin
4319  {$IFDEF SynFoldDebug}try DebugLnEnter(['>>FOLD-- FixFolding: Start=', AStart, '  AMinEnd=',AMinEnd]);{$ENDIF}
4320  Result := false;
4321  if fLockCount > 0 then begin
4322    Include(FFlags, fvfNeedCaretCheck);
4323    if fNeedFixFrom < 0 then fNeedFixFrom := AStart
4324    else fNeedFixFrom := Min(fNeedFixFrom, AStart);
4325    fNeedFixMinEnd := Max(fNeedFixMinEnd, AMinEnd);
4326    exit;
4327  end;
4328
4329  node := aFoldTree.FindFoldForLine(aStart, true);
4330  if not node.IsInFold then node:= aFoldTree.FindLastFold;
4331  if not node.IsInFold then begin
4332    CalculateMaps;
4333    exit;
4334  end;
4335  If aMinEnd < node.StartLine then aMinEnd := node.StartLine; // XXX SourceLine
4336
4337  // FullCount is allowed to be -1
4338  while node.IsInFold and (node.StartLine + node.FullCount + 1 >= aStart) do begin
4339    tmpnode := node.Prev;
4340    if tmpnode.IsInFold
4341    then node := tmpnode
4342    else break; // first node
4343  end;
4344
4345  FirstchangedLine := -1;
4346  FldInfos := nil;
4347  MaxCol := -1;
4348  SrcLineForFldInfos := -1;
4349  Result := DoFixFolding(-1, AMinEnd, 0, aFoldTree, node);
4350  CalculateMaps;
4351  if (FirstchangedLine >= 0) then
4352    DoFoldChanged(FirstchangedLine);
4353  {$IFDEF SynFoldDebug}finally DebugLnExit(['<<FOLD-- FixFolding: DONE=', Result]); end{$ENDIF}
4354end;
4355
4356procedure TSynEditFoldedView.DoCaretChanged(Sender : TObject);
4357var
4358  i: Integer;
4359begin
4360  if fLockCount > 0 then begin
4361    Include(FFlags, fvfNeedCaretCheck);
4362    exit;
4363  end;
4364  Exclude(FFlags, fvfNeedCaretCheck);
4365  i := TSynEditCaret(Sender).LinePos-1;
4366  {$IFDEF SynFoldDebug}if FoldedAtTextIndex[i] then debugln(['FOLD-- DoCaretChanged  about to unfold at Index=', i]);{$ENDIF}
4367  if FoldedAtTextIndex[i] then
4368    UnFoldAtTextIndexCollapsed(i);
4369end;
4370
4371procedure TSynEditFoldedView.LineCountChanged(Sender: TSynEditStrings; AIndex, ACount : Integer);
4372begin
4373  {$IFDEF SynFoldDebug}try DebugLnEnter(['>> FOLD-- LineCountChanged AIndex=', AIndex, '  Acount=',ACount]);{$ENDIF}
4374  // no need for fix folding => synedit will be called, and scanlines will call fixfolding
4375  {TODO: a "need fix folding" flag => to ensure it will be called if synedit doesnt
4376         SynEdit.ScanRanges, calls Fixfolding as workaroound => review
4377  }
4378  if (fLockCount > 0) and (AIndex < max(fNeedFixFrom, fNeedFixMinEnd)) then begin
4379    // adapt the fixfold range. Could be done smarter, but it doesn't matter if the range gets bigger than needed.
4380    if (ACount < 0) and (AIndex < fNeedFixFrom) then inc(fNeedFixFrom, ACount);
4381    if (ACount > 0) and (AIndex < fNeedFixMinEnd) then inc(fNeedFixMinEnd, ACount);
4382  end;
4383  if NextLines.IsInEditAction then exit;
4384  if ACount<0
4385  then LinesDeletedAtTextIndex(AIndex+1, -ACount, 1, true)
4386  else LinesInsertedAtTextIndex(AIndex+1, ACount, 1, true);
4387  {$IFDEF SynFoldDebug}finally DebugLnExit(['<< FOLD-- LineCountChanged']); end;{$ENDIF}
4388end;
4389
4390procedure TSynEditFoldedView.LinesCleared(Sender: TObject);
4391begin
4392  UnfoldAll;
4393end;
4394
4395procedure TSynEditFoldedView.LineEdited(Sender: TSynEditStrings; aLinePos, aBytePos, aCount,
4396  aLineBrkCnt: Integer; aText: String);
4397begin
4398  {$IFDEF SynFoldDebug}try DebugLnEnter(['>> FOLD-- LineEditied aLinePos=', aLinePos, ' aBytePos=', aBytePos, '  Acount=',ACount, ' aLineBrkCnt=',aLineBrkCnt]);{$ENDIF}
4399  if aLineBrkCnt<0
4400  then LinesDeletedAtTextIndex(aLinePos, -aLineBrkCnt, ABytePos, true)
4401  else if aLineBrkCnt > 0
4402  then LinesInsertedAtTextIndex(aLinePos, aLineBrkCnt, ABytePos, true)
4403  else begin
4404    fFoldTree.AdjustColumn(aLinePos, aBytePos, aCount);
4405    //if not(SkipFixFolding) then FixFoldingAtTextIndex(AStartIndex, AStartIndex+ALineCount+1)
4406    //else
4407    //if aLinePos < top + ALineCount then CalculateMaps;
4408  end;
4409  {$IFDEF SynFoldDebug}finally DebugLnExit(['<< FOLD-- LineEditied']); end;{$ENDIF}
4410end;
4411
4412procedure TSynEditFoldedView.FixFoldingAtTextIndex(AStartIndex: Integer; AMinEndLine : Integer);
4413begin
4414  FixFolding(AStartIndex + 1, AMinEndLine, fFoldTree);
4415end;
4416
4417function TSynEditFoldedView.OpenFoldCount(aStartIndex: Integer; AType: Integer = 0): Integer;
4418// Todo: move entirely to FoldProvider
4419var
4420  hl: TSynCustomFoldHighlighter;
4421begin
4422  hl := TSynCustomFoldHighlighter(HighLighter);
4423  if not assigned(hl) then
4424    exit(-1);
4425  Result := hl.FoldBlockEndLevel(AStartIndex-1, AType) + FoldProvider.FoldOpenCount(AStartIndex);
4426end;
4427
4428function TSynEditFoldedView.OpenFoldInfo(aStartIndex, ColIndex: Integer; AType: Integer = 0): TFoldViewNodeInfo;
4429var
4430  hl: TSynCustomFoldHighlighter;
4431  TypeCnt, Lvl: Integer;
4432  EndLvl, CurLvl: Array of integer;
4433  i, c, t, n, o: Integer;
4434  nd: TSynFoldNodeInfo;
4435  FN: TSynTextFoldAVLNode;
4436
4437  procedure GetEndLvl(l: Integer);
4438  var i: integer;
4439  begin
4440    if AType = 0 then begin;
4441      for i := 1 to TypeCnt do begin
4442        EndLvl[i] := hl.FoldBlockEndLevel(l-1, i);
4443        EndLvl[i] := EndLvl[i] + FoldProvider.FoldOpenCount(l, i);
4444        CurLvl[i] := EndLvl[i];
4445      end;
4446    end
4447    else begin
4448      EndLvl[0] := hl.FoldBlockEndLevel(l-1, AType);
4449      EndLvl[0] := EndLvl[0] + FoldProvider.FoldOpenCount(l, AType);
4450      CurLvl[0] := EndLvl[0];
4451    end;
4452  end;
4453
4454begin
4455  hl := TSynCustomFoldHighlighter(HighLighter);
4456  if not assigned(hl) then
4457    exit;  // ToDo: Initialize Result
4458
4459  nd.LogXStart := 0;
4460  nd.LogXEnd := 0;
4461  nd.FoldAction := [];
4462  nd.FoldType := Nil;
4463  nd.FoldGroup := 0;
4464  n := 0;
4465  if AType <> 0 then
4466    TypeCnt := 1
4467  else
4468    TypeCnt := hl.FoldTypeCount;
4469  Lvl := hl.FoldBlockEndLevel(AStartIndex-1, AType);
4470  if ColIndex >= Lvl then begin
4471    n := ColIndex - Lvl;
4472    if AType = 0 then begin
4473      o :=  hl.FoldNodeInfo[aStartIndex].CountEx([sfaOpen, sfaFold]);
4474      nd := hl.FoldNodeInfo[aStartIndex].NodeInfoEx(n, [sfaOpen, sfaFold]);
4475    end else begin
4476      // no sfaFold
4477      o :=  hl.FoldNodeInfo[aStartIndex].CountEx([sfaOpenFold],AType);
4478      nd := hl.FoldNodeInfo[aStartIndex].NodeInfoEx(n, [sfaOpenFold], AType);
4479    end;
4480  end
4481  else begin
4482    SetLength(EndLvl, TypeCnt+1);
4483    SetLength(CurLvl, TypeCnt+1);
4484    GetEndLvl(aStartIndex);
4485    aStartIndex := aStartIndex;
4486    while (ColIndex < Lvl) and (aStartIndex > 0) do begin
4487      dec(aStartIndex);
4488      o := hl.FoldBlockOpeningCount(AStartIndex, AType);
4489      if (o > 0) or (hl.FoldBlockClosingCount(aStartIndex, AType) > 0) then begin
4490        n := o;
4491        c := hl.FoldNodeInfo[aStartIndex].CountEx([], AType) - 1;
4492        for i := c downto 0 do begin
4493          nd := hl.FoldNodeInfo[aStartIndex].NodeInfoEx(i, [], AType);
4494          if (AType = 0) and not(sfaFold in nd.FoldAction) then
4495            continue;
4496          if AType = 0 then
4497            t := nd.FoldGroup
4498          else
4499            t := 0;
4500          if sfaOpenFold in nd.FoldAction then begin
4501            dec(n);
4502            dec(CurLvl[t]);
4503            if CurLvl[t] < EndLvl[t] then begin
4504              dec(EndLvl[t]);
4505              dec(Lvl);
4506              if ColIndex = Lvl then begin
4507                break;
4508              end;
4509            end;
4510          end else
4511          if sfaCloseFold in nd.FoldAction then begin
4512            inc(CurLvl[t]);
4513          end;
4514        end;
4515      end
4516      else
4517      if hl.FoldBlockEndLevel(AStartIndex-1, AType) = 0 then break;
4518    end;
4519  end;
4520  Result.HNode := nd;
4521  Result.OpenCount := o;
4522  Result.Text := NextLines[aStartIndex];
4523  if not(sfaInvalid in nd.FoldAction) then
4524    Result.Keyword := copy(Result.Text, 1 + nd.LogXStart, nd.LogXEnd-nd.LogXStart);
4525  Result.LineNum := aStartIndex + 1;
4526  Result.ColIndex := n;
4527  FN := FoldNodeAtTextIndex(aStartIndex, n);
4528  Result.IsFold := FN.IsInFold;
4529  Result.IsHide := fn.IsHide;
4530end;
4531
4532function TSynEditFoldedView.ExpandedLineForBlockAtLine(ALine : Integer;
4533  HalfExpanded: Boolean = True) : Integer;
4534var
4535  i, l : Integer;
4536  node: TSynTextFoldAVLNode;
4537  hl: TSynCustomFoldHighlighter;
4538begin
4539  Result := -1;
4540  hl := TSynCustomFoldHighlighter(HighLighter);
4541  if not assigned(hl) then
4542    exit;
4543
4544  i := ALine;
4545  l := hl.FoldBlockOpeningCount(i - 1);
4546  if l > 0 then begin
4547    node := fFoldTree.FindFoldForLine(ALine, true);
4548    if node.IsInFold and (node.StartLine = ALine +1) then begin
4549      dec(l);
4550      if HalfExpanded then while (l >= 0) do begin
4551        if not IsFoldedAtTextIndex(ALine-1, l) then exit(ALine);
4552        dec(l);
4553      end;
4554      dec(i);
4555    end
4556    else
4557      exit(ALine);
4558  end
4559  else if hl.FoldBlockClosingCount(i - 1) > 0 then
4560    dec(i);
4561  if (i < 0) or (hl.FoldBlockEndLevel(i-1) = 0) then
4562    exit;
4563
4564  l := 0;
4565  while (i > 0) and (l >= 0) do begin // (FoldMinLevel[i] >= l) do
4566    dec(i);
4567    l := l - hl.FoldBlockOpeningCount(i);
4568    if l >= 0 then
4569      l := l + hl.FoldBlockClosingCount(i);
4570  end;
4571  if (hl.FoldBlockEndLevel(i) > 0) then // TODO, check for collapsed at index = 0
4572    Result := i + 1;
4573end;
4574
4575function TSynEditFoldedView.GetPhysicalCharWidths(Index: Integer): TPhysicalCharWidths;
4576begin
4577  Result := NextLines.GetPhysicalCharWidths(InternViewToTextIndex(Index));
4578end;
4579
4580function TSynEditFoldedView.CollapsedLineForFoldAtLine(ALine : Integer) : Integer;
4581// for hides => line before the hide
4582var
4583  node, tmpnode: TSynTextFoldAVLNode;
4584begin
4585  Result := -1;
4586  node := fFoldTree.FindFoldForLine(ALine, false);
4587  if node.IsInFold then begin
4588    tmpnode := node.Prev;
4589    while tmpnode.IsInFold and
4590          (tmpnode.StartLine + tmpnode.MergedLineCount = node.StartLine)
4591    do begin
4592      node := tmpnode;
4593      tmpnode := node.Prev;
4594    end;
4595    Result := node.StartLine-1;
4596    // Can be 0, if lines are hiden at begin of file
4597  end;
4598end;
4599
4600function dbgs(AClassification: TFoldNodeClassification): String;
4601begin
4602  WriteStr(Result{%H-}, AClassification);
4603end;
4604
4605{$IFDEF SynDebug}
4606procedure TSynEditFoldedView.debug;
4607begin
4608  fFoldTree.debug;
4609end;
4610{$ENDIF}
4611
4612initialization
4613  InitNumEncodeValues;
4614  //SYN_FOLD_DEBUG := DebugLogger.RegisterLogGroup('SynFoldDebug' {$IFDEF SynFoldDebug} , True {$ENDIF} );
4615
4616end.
4617
4618