lazarus/components/synedit/syneditfoldedview.pp
paul 0faae3351b synedit: add markup info for folded code
git-svn-id: trunk@18153 -
2009-01-06 07:18:50 +00:00

1977 lines
68 KiB
ObjectPascal

{-------------------------------------------------------------------------------
The contents of this file are subject to the Mozilla Public License
Version 1.1 (the "License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.mozilla.org/MPL/
Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
the specific language governing rights and limitations under the License.
Alternatively, the contents of this file may be used under the terms of the
GNU General Public License Version 2 or later (the "GPL"), in which case
the provisions of the GPL are applicable instead of those above.
If you wish to allow use of your version of this file only under the terms
of the GPL and not to allow others to use your version of this file
under the MPL, indicate your decision by deleting the provisions above and
replace them with the notice and other provisions required by the GPL.
If you do not delete the provisions above, a recipient may use your version
of this file under either the MPL or the GPL.
-------------------------------------------------------------------------------}
(* some parts (AdjustBalance...) of this unit are based on the AVLTree unit *)
(* TODO: Implement node.eof / node.bof *)
unit SynEditFoldedView;
{$mode objfpc}{$H+}
{$IFDEF CPUPOWERPC} {$INLINE OFF} {$ENDIF} (* Workaround for bug 12576 (fpc) see bugs.freepascal.org/view.php?id=12576 *)
interface
uses
LCLProc, Graphics,
Classes, SysUtils, SynEditTypes, SynEditTextBuffer, SynEditTextBase,
SynEditMiscClasses, SynEditPointClasses;
type
TReplacedChildSite = (rplcLeft, rplcRight);
{ TSynTextFoldAVLNodeData }
TSynTextFoldAVLNodeData = Class
public
Parent, Left, Right : TSynTextFoldAVLNodeData; (* AVL Links *)
Balance : shortint; (* AVL Balance *)
Nested : TSynTextFoldAVLNodeData; (* Nested folds (folds within this fold) do not need to be part of the searchable tree
They will be restored, if the outer fold (this fold) is unfolded
Nested points to a standalone tree, the root node in the nested tree, does *not* point back to this node *)
LineOffset : Integer; (* Line-Number Offset to parent node
All line numbers are stored as offsets, for faster updates if lines are inserted/deleted *)
LeftCount : Integer; (* Lines folded in left tree. Used to calculate how many lines are folded up to a specified line *)
LineCount : Integer; (* Amount of lines covered by this fold only *)
function TreeDepth: integer; (* longest WAY down. Only one node => 1! *)
function RecursiveFoldCount : Integer; (* Amount of lines covered by this and all child nodes *)
procedure SetLeftChild(ANode : TSynTextFoldAVLNodeData); overload; inline;
procedure SetLeftChild(ANode : TSynTextFoldAVLNodeData; anAdjustChildLineOffset : Integer); overload; inline;
procedure SetLeftChild(ANode : TSynTextFoldAVLNodeData; anAdjustChildLineOffset, aLeftCount : Integer); overload; inline;
procedure SetRightChild(ANode : TSynTextFoldAVLNodeData); overload; inline;
procedure SetRightChild(ANode : TSynTextFoldAVLNodeData; anAdjustChildLineOffset : Integer); overload; inline;
function ReplaceChild(OldNode, ANode : TSynTextFoldAVLNodeData) : TReplacedChildSite; overload; inline;
function ReplaceChild(OldNode, ANode : TSynTextFoldAVLNodeData; anAdjustChildLineOffset : Integer) : TReplacedChildSite; overload; inline;
procedure AdjustLeftCount(AValue : Integer);
procedure AdjustParentLeftCount(AValue : Integer);
function Precessor : TSynTextFoldAVLNodeData;
function Successor : TSynTextFoldAVLNodeData;
function Precessor(var aStartLine, aLinesBefore : Integer) : TSynTextFoldAVLNodeData;
function Successor(var aStartLine, aLinesBefore : Integer) : TSynTextFoldAVLNodeData;
end;
{ TSynTextFoldAVLNode }
TSynTextFoldAVLNode = object
private
function GetLineCount : Integer;
protected
fData : TSynTextFoldAVLNodeData; // nil if unfolded
fStartLine : Integer; // start of folded
fFoldedBefore : Integer;
public
function IsInFold : Boolean;
function Next : TSynTextFoldAVLNode;
function Prev : TSynTextFoldAVLNode;
property LineCount : Integer read GetLineCount; // Zero, if Not in a fold
property StartLine : Integer read fStartLine; // 1st Line of Current Fold
property FoldedBefore : Integer read fFoldedBefore; // Count of Lines folded before Startline
end;
{ TSynTextFoldAVLTree
Nodes in the tree cover the folded lines only
the cfCollapsed line at the start of a fold, is *not* part of a node.
RemoveFoldForline has special precaution for this.
}
TSynTextFoldAVLTree = class
protected
fRoot: TSynTextFoldAVLNodeData;
fNestParent: TSynTextFoldAVLNodeData;
fNestedNodesTree: TSynTextFoldAVLTree; // FlyWeight Tree used for any nested subtree.
fRootOffset : Integer;
function NewNode : TSynTextFoldAVLNodeData; inline;
procedure DisposeNode(var ANode : TSynTextFoldAVLNodeData); inline;
Function RemoveFoldForNodeAtLine(ANode: TSynTextFoldAVLNode; ALine : Integer;
IgnoreFirst : Boolean = False) : Integer; overload; // Line is for Nested Nodes
procedure SetRoot(ANode : TSynTextFoldAVLNodeData); overload; inline;
procedure SetRoot(ANode : TSynTextFoldAVLNodeData; anAdjustChildLineOffset : Integer); overload; inline;
Function InsertNode(ANode : TSynTextFoldAVLNodeData) : Integer; // returns FoldedBefore // ANode may not have children
procedure RemoveNode(ANode: TSynTextFoldAVLNodeData);
procedure BalanceAfterInsert(ANode: TSynTextFoldAVLNodeData);
procedure BalanceAfterDelete(ANode: TSynTextFoldAVLNodeData);
function TreeForNestedNode(ANode: TSynTextFoldAVLNodeData; aOffset : Integer) : TSynTextFoldAVLTree;
public
constructor Create;
destructor Destroy; override;
procedure Clear;
(* Find Fold by Line in Real Text *)
Function FindFoldForLine(ALine : Integer; FindNextNode : Boolean = False) : TSynTextFoldAVLNode;
(* Find Fold by Line in Folded Text // always returns unfolded, unless next=true *)
Function FindFoldForFoldedLine(ALine : Integer; FindNextNode : Boolean = False) : TSynTextFoldAVLNode;
Function InsertNewFold(ALine, ACount : Integer) : TSynTextFoldAVLNode;
(* This will unfold the block which either contains tALine, or has Aline as its cgColapsed line
If IgnoreFirst, the cfCollapsed will *not* unfold => Hint: IgnoreFirst = Make folded visible
Returns the pos(1-based) of the cfCollapsed Line that was expanded; or ALine, if nothing was done
*)
Function RemoveFoldForLine(ALine : Integer;
IgnoreFirst : Boolean = False) : Integer; overload;
Procedure AdjustForLinesInserted(AStartLine, ALineCount : Integer);
Procedure AdjustForLinesDeleted(AStartLine, ALineCount : Integer);
Function FindLastFold : TSynTextFoldAVLNode;
Function FindFirstFold : TSynTextFoldAVLNode;
procedure debug;
end;
TSynEditCodeFoldType = (
cfNone, // line is not in a block
cfCollapsed, // line is start of collapsed block
cfExpanded, // line is start of expanded block
cfContinue, // line is middle part of block(s)
cfEnd // line is end of block(s)
);
TFoldChangedEvent = procedure(aLine: Integer) of object;
const
SynEditCodeFoldTypeNames: array[TSynEditCodeFoldType] of string = (
'cfNone',
'cfCollapsed',
'cfExpanded',
'cfContinue',
'cfEnd'
);
type
{ TSynTextFoldedView
*Line = Line (0-based) on Screen (except TopLine which should be TopViewPos)
*ViewPos = Line (1-based) in the array of viewable/visible lines
*TextIndex = Line (0-based) in the complete text(folded and unfolded)
}
TSynEditFoldMinClass = class end; // For RegisterAttribute
TSynEditFoldEndClass = class end; // For RegisterAttribute
{ TSynEditFoldedView }
TSynEditFoldedView = class // TODO: class(TSynEditStringsLinked)
private
fCaret: TSynEditCaret;
fLines : TSynEditStrings;
fFoldTree : TSynTextFoldAVLTree; // Folds are stored 1-based (the 1st line is 1)
FMarkupInfoFoldedCode: TSynSelectedColor;
fTopLine : Integer;
fLinesInWindow : Integer; // there may be an additional part visible line
fTextIndexList : Array of integer; (* Map each Screen line into a line in textbuffer *)
fFoldTypeList : Array of TSynEditCodeFoldType;
fOnFoldChanged : TFoldChangedEvent;
fCFDividerDrawLevel: Integer;
fLockCount : Integer;
fNeedFixFrom, fNeedFixMinEnd : Integer;
fNeedCaretCheck : Boolean;
function GetCount : integer;
function GetDrawDivider(Index : integer) : Boolean;
function GetFoldEndLevel(Index: integer): integer;
function GetFoldMinLevel(Index: integer): integer;
function GetFoldNestLevel(index : Integer): integer;
function GetLines(index : Integer) : String;
function GetDisplayNumber(index : Integer) : Integer;
function GetRange(Index : integer) : TSynEditRange;
function GetTextIndex(index : Integer) : Integer;
function GetFoldType(index : Integer) : TSynEditCodeFoldType;
function IsFolded(index : integer) : Boolean; // TextIndex
procedure PutRange(Index : integer; const AValue : TSynEditRange);
procedure SetFoldEndLevel(Index: integer; const AValue: integer);
procedure SetFoldMinLevel(Index: integer; const AValue: integer);
procedure SetTopLine(const ALine : integer);
function GetTopTextIndex : integer;
procedure SetTopTextIndex(const AIndex : integer);
procedure SetLinesInWindow(const AValue : integer);
protected
Procedure CalculateMaps;
function LengthForFoldAtTextIndex(ALine : Integer) : Integer;
function FixFolding(AStart : Integer; AMinEnd : Integer; aFoldTree : TSynTextFoldAVLTree) : Boolean;
procedure DoCaretChanged(Sender : TObject);
Procedure LineCountChanged(Sender: TSynEditStrings; AIndex, ACount : Integer);
Procedure LinesInsertedAtTextIndex(AStartIndex, ALineCount : Integer;
SkipFixFolding : Boolean = False);
Procedure LinesInsertedAtViewPos(AStartPos, ALineCount : Integer;
SkipFixFolding : Boolean = False);
Procedure LinesDeletedAtTextIndex(AStartIndex, ALineCount : Integer;
SkipFixFolding : Boolean = False);
Procedure LinesDeletedAtViewPos(AStartPos, ALineCount : Integer;
SkipFixFolding : Boolean = False);
public
constructor Create(aTextView : TSynEditStrings; ACaret: TSynEditCaret);
destructor Destroy; override;
// Converting between Folded and Unfolded Lines/Indexes
function TextIndexToViewPos(aTextIndex : Integer) : Integer; (* Convert TextIndex (0-based) to ViewPos (1-based) *)
function TextIndexToScreenLine(aTextIndex : Integer) : Integer; (* Convert TextIndex (0-based) to Screen (0-based) *)
function ViewPosToTextIndex(aViewPos : Integer) : Integer; (* Convert ViewPos (1-based) to TextIndex (0-based) *)
function ScreenLineToTextIndex(aLine : Integer) : Integer; (* Convert Screen (0-based) to TextIndex (0-based) *)
function TextIndexAddLines(aTextIndex, LineOffset : Integer) : Integer; (* Add/Sub to/from TextIndex (0-based) skipping folded *)
function TextPosAddLines(aTextpos, LineOffset : Integer) : Integer; (* Add/Sub to/from TextPos (1-based) skipping folded *)
// Attributes for Visible-Lines-On-screen
property Lines[index : Integer] : String (* Lines on screen / 0 = TopLine *)
read GetLines; default;
property Ranges[Index: integer]: TSynEditRange
read GetRange write PutRange;
property DisplayNumber[index : Integer] : Integer (* LineNumber for display in Gutter / result is 1-based *)
read GetDisplayNumber;
property FoldType[index : Integer] : TSynEditCodeFoldType (* FoldIcon / State *)
read GetFoldType;
property FoldNestLvl[index : Integer] : integer (* FoldIcon / Deep/Level of nesting; 1 for top-lvl *)
read GetFoldNestLevel;
property DrawDivider[Index: integer]: Boolean
read GetDrawDivider;
property TextIndex[index : Integer] : Integer (* Position in SynTextBuffer / result is 0-based *)
read GetTextIndex; // maybe writable
// Define Visible Area
property TopLine : integer (* refers to visible (unfolded) lines / 1-based *)
read fTopLine write SetTopLine;
property TopTextIndex : integer (* refers to TextIndex (folded + unfolded lines) / 1-based *)
read GetTopTextIndex write SetTopTextIndex;
property LinesInWindow : integer (* Fully Visible lines in Window; There may be one half visible line *)
read fLinesInWindow write SetLinesInWindow;
property Count : integer read GetCount; (* refers to visible (unfolded) lines *)
property CFDividerDrawLevel: Integer read fCFDividerDrawLevel write fCFDividerDrawLevel;
property MarkupInfoFoldedCode: TSynSelectedColor read FMarkupInfoFoldedCode;
public
procedure Lock;
procedure UnLock;
procedure debug;
// Action Fold/Unfold
procedure FoldAtLine(AStartLine: Integer); (* Folds at ScreenLine / 0-based *)
procedure FoldAtViewPos(AStartPos: Integer); (* Folds at nth visible/unfolded Line / 1-based *)
procedure FoldAtTextIndex(AStartIndex: Integer); (* Folds at nth TextIndex (all lines in buffer) / 1-based *)
procedure UnFoldAtLine(AStartLine: Integer; IgnoreFirst : Boolean = False); (* UnFolds at ScreenLine / 0-based *)
procedure UnFoldAtViewPos(AStartPos: Integer; IgnoreFirst : Boolean = False); (* UnFolds at nth visible/unfolded Line / 1-based *)
procedure UnFoldAtTextIndex(AStartIndex: Integer; IgnoreFirst : Boolean = False); (* UnFolds at nth TextIndex (all lines in buffer) / 1-based *)
procedure UnfoldAll;
procedure FoldAll(StartLevel : Integer = 0; IgnoreNested : Boolean = False);
procedure FixFoldingAtTextIndex(AStartIndex: Integer; AMinEndLine: Integer = 0); // Real/All lines
// Find the visible first line of the fold at ALine. Returns -1 if Aline is not folded
function CollapsedLineForFoldAtLine(ALine : Integer) : Integer;
function ExpandedLineForBlockAtLine(ALine : Integer) : Integer;
property FoldedAtTextIndex [index : integer] : Boolean read IsFolded;
property OnFoldChanged: TFoldChangedEvent (* reports 1-based line *) {TODO: synedit expects 0 based }
read fOnFoldChanged write fOnFoldChanged;
public
// TextIndex
property FoldMinLevel[Index: integer]: integer read GetFoldMinLevel
write SetFoldMinLevel;
property FoldEndLevel[Index: integer]: integer read GetFoldEndLevel
write SetFoldEndLevel;
end;
implementation
uses
SynEditMiscProcs;
{ TSynTextFoldAVLNodeData }
function TSynTextFoldAVLNodeData.TreeDepth : integer;
var t: integer;
begin
Result := 1;
if Left<>nil then Result := Left.TreeDepth+1;
if Right<>nil then t := Right.TreeDepth+1 else t := 0;
if t> Result then Result := t;
end;
function TSynTextFoldAVLNodeData.RecursiveFoldCount : Integer;
var ANode : TSynTextFoldAVLNodeData;
begin
Result := 0;
ANode := self;
while ANode <> nil do begin
Result := Result + ANode.LineCount + ANode.LeftCount;
ANode := ANode.Right;
end;
end;
procedure TSynTextFoldAVLNodeData.SetLeftChild(ANode : TSynTextFoldAVLNodeData); inline;
begin
Left := ANode;
if ANode <> nil then ANode.Parent := self;
end;
procedure TSynTextFoldAVLNodeData.SetLeftChild(ANode : TSynTextFoldAVLNodeData; anAdjustChildLineOffset : Integer); inline;
begin
Left := ANode;
if ANode <> nil then begin
ANode.Parent := self;
ANode.LineOffset := ANode.LineOffset + anAdjustChildLineOffset;
end;
end;
procedure TSynTextFoldAVLNodeData.SetLeftChild(ANode : TSynTextFoldAVLNodeData; anAdjustChildLineOffset, aLeftCount : Integer); inline;
begin
Left := ANode;
LeftCount := aLeftCount;
if ANode <> nil then begin
ANode.Parent := self;
ANode.LineOffset := ANode.LineOffset + anAdjustChildLineOffset;
end
end;
procedure TSynTextFoldAVLNodeData.SetRightChild(ANode : TSynTextFoldAVLNodeData); inline;
begin
Right := ANode;
if ANode <> nil then ANode.Parent := self;
end;
procedure TSynTextFoldAVLNodeData.SetRightChild(ANode : TSynTextFoldAVLNodeData; anAdjustChildLineOffset : Integer); inline;
begin
Right := ANode;
if ANode <> nil then begin
ANode.Parent := self;
ANode.LineOffset := ANode.LineOffset + anAdjustChildLineOffset;
end;
end;
function TSynTextFoldAVLNodeData.ReplaceChild(OldNode, ANode : TSynTextFoldAVLNodeData) : TReplacedChildSite; inline;
begin
if Left = OldNode then begin
SetLeftChild(ANode);
exit(rplcLeft);
end;
SetRightChild(ANode);
result := rplcRight;
end;
function TSynTextFoldAVLNodeData.ReplaceChild(OldNode, ANode : TSynTextFoldAVLNodeData; anAdjustChildLineOffset : Integer) : TReplacedChildSite;
inline;
begin
if Left = OldNode then begin
SetLeftChild(ANode, anAdjustChildLineOffset);
exit(rplcLeft);
end;
SetRightChild(ANode, anAdjustChildLineOffset);
result := rplcRight;
end;
procedure TSynTextFoldAVLNodeData.AdjustLeftCount(AValue : Integer);
begin
LeftCount := LeftCount + AValue;
AdjustParentLeftCount(AValue);
end;
procedure TSynTextFoldAVLNodeData.AdjustParentLeftCount(AValue : Integer);
var
node, pnode : TSynTextFoldAVLNodeData;
begin
node := self;
pnode := node.Parent;
while pnode <> nil do begin
if node = pnode.Left
then pnode.LeftCount := pnode.LeftCount + AValue;
node := pnode;
pnode := node.Parent;
end;
end;
function TSynTextFoldAVLNodeData.Precessor : TSynTextFoldAVLNodeData;
begin
Result := Left;
if Result<>nil then begin
while (Result.Right<>nil) do Result := Result.Right;
end else begin
Result := self;
while (Result.Parent<>nil) and (Result.Parent.Left=Result) do
Result := Result.Parent;
Result := Result.Parent;
end;
end;
function TSynTextFoldAVLNodeData.Successor : TSynTextFoldAVLNodeData;
begin
Result := Right;
if Result<>nil then begin
while (Result.Left<>nil) do Result := Result.Left;
end else begin
Result := self;
while (Result.Parent<>nil) and (Result.Parent.Right=Result) do
Result := Result.Parent;
Result := Result.Parent;
end;
end;
function TSynTextFoldAVLNodeData.Precessor(var aStartLine, aLinesBefore : Integer) : TSynTextFoldAVLNodeData;
begin
Result := Left;
if Result<>nil then begin
aStartLine := aStartLine + Result.LineOffset;
while (Result.Right<>nil) do begin
Result := Result.Right;
aStartLine := aStartLine + Result.LineOffset;
end;
end else begin
Result := self;
while (Result.Parent<>nil) and (Result.Parent.Left=Result) do begin
aStartLine := aStartLine - Result.LineOffset;
Result := Result.Parent;
end;
// result is now a right son
aStartLine := aStartLine - Result.LineOffset;
Result := Result.Parent;
end;
if result <> nil then
aLinesBefore := aLinesBefore - result.LineCount
else
aLinesBefore := 0;
end;
function TSynTextFoldAVLNodeData.Successor(var aStartLine, aLinesBefore : Integer) : TSynTextFoldAVLNodeData;
begin
aLinesBefore := aLinesBefore + LineCount;
Result := Right;
if Result<>nil then begin
aStartLine := aStartLine + Result.LineOffset;
while (Result.Left<>nil) do begin
Result := Result.Left;
aStartLine := aStartLine + Result.LineOffset;
end;
end else begin
Result := self;
while (Result.Parent<>nil) and (Result.Parent.Right=Result) do begin
aStartLine := aStartLine - Result.LineOffset;
Result := Result.Parent;
end;
// Result is now a left son; result has a negative LineOffset
aStartLine := aStartLine - Result.LineOffset;
Result := Result.Parent;
end;
end;
{ TSynTextFoldAVLNode }
function TSynTextFoldAVLNode.GetLineCount : Integer;
begin
if fData = nil
then Result := 0
else Result := fData.LineCount;
end;
function TSynTextFoldAVLNode.IsInFold : Boolean;
begin
Result := fData <> nil;
end;
function TSynTextFoldAVLNode.Next : TSynTextFoldAVLNode;
var aStart, aBefore : Integer;
begin
if fData <> nil then begin
aStart := StartLine;
aBefore := FoldedBefore;
Result.fData := fData.Successor(aStart, aBefore);
Result.fStartLine := aStart;
Result.fFoldedBefore := aBefore;
end
else Result.fData := nil;
end;
function TSynTextFoldAVLNode.Prev : TSynTextFoldAVLNode;
var aStart, aBefore : Integer;
begin
if fData <> nil then begin
aStart := StartLine;
aBefore := FoldedBefore;
Result.fData := fData.Precessor(aStart, aBefore);
Result.fStartLine := aStart;
Result.fFoldedBefore := aBefore;
end
else Result.fData := nil;
end;
{ TSynTextFoldAVLTree }
function TSynTextFoldAVLTree.NewNode : TSynTextFoldAVLNodeData;
begin
Result := TSynTextFoldAVLNodeData.Create;
end;
procedure TSynTextFoldAVLTree.DisposeNode(var ANode : TSynTextFoldAVLNodeData);
begin
FreeAndNil(ANode);
end;
destructor TSynTextFoldAVLTree.Destroy;
begin
Clear;
if fNestedNodesTree <> nil then begin
fNestedNodesTree.fRoot := nil; //was freed in self.Clear
fNestedNodesTree.fNestParent := nil; // Or Destroy will access invalid memory
fNestedNodesTree.Free;
end;
inherited Destroy;
end;
procedure TSynTextFoldAVLTree.Clear;
procedure DeleteNode(var ANode: TSynTextFoldAVLNodeData);
begin
if ANode.Left <>nil then DeleteNode(ANode.Left);
if ANode.Right <>nil then DeleteNode(ANode.Right);
if ANode.Nested <>nil then DeleteNode(ANode.Nested);
DisposeNode(ANode);
end;
begin
if fRoot <> nil then DeleteNode(fRoot);
SetRoot(nil);
end;
procedure TSynTextFoldAVLTree.SetRoot(ANode : TSynTextFoldAVLNodeData); inline;
begin
fRoot := ANode;
if fNestParent <> nil then fNestParent.Nested := ANode;
if ANode <> nil then ANode.Parent := nil;
end;
procedure TSynTextFoldAVLTree.SetRoot(ANode : TSynTextFoldAVLNodeData; anAdjustChildLineOffset : Integer);
inline;
begin
fRoot := ANode;
if fNestParent <> nil then fNestParent.Nested := ANode;
if ANode <> nil then begin
ANode.Parent := nil;
ANode.LineOffset := ANode.LineOffset + anAdjustChildLineOffset;
end;
end;
function TSynTextFoldAVLTree.FindFoldForLine(ALine : Integer; FindNextNode : Boolean = False) : TSynTextFoldAVLNode;
var
r : TSynTextFoldAVLNodeData;
rStartLine : Integer;
rFoldedBefore : Integer;
begin
r := fRoot;
rStartLine := fRootOffset;
rFoldedBefore := 0;
while (r <> nil) do begin
rStartLine := rStartLine + r.LineOffset;
if ALine < rStartLine then begin
if FindNextNode and (r.Left = nil) then break;
r := r.Left; // rStartLine points to r, so if r.Left is nil then it is pointing to the next fold;
continue;
end;
rFoldedBefore := rFoldedBefore + r.LeftCount;
if ALine < rStartLine + r.LineCount
then break;
if FindNextNode and (r.Right = nil) then begin
r := r.Successor(rStartLine, rFoldedBefore);
break;
end;
rFoldedBefore := rFoldedBefore + r.LineCount;
r := r.Right; // rStartLine points to r, which now is the start of the previous fold;
end;
Result.fData := r;
Result.fStartLine := rStartLine; // only IF r <> nil
Result.fFoldedBefore := rFoldedBefore; // always ok
end;
function TSynTextFoldAVLTree.FindFoldForFoldedLine(ALine : Integer; FindNextNode : Boolean) : TSynTextFoldAVLNode;
var
r : TSynTextFoldAVLNodeData;
rStartLine : Integer;
rFoldedBefore : Integer;
begin
r := fRoot;
rStartLine := fRootOffset;
rFoldedBefore := 0;
while (r <> nil) do begin
rStartLine := rStartLine + r.LineOffset;
if ALine + r.LeftCount < rStartLine then begin
if FindNextNode and (r.Left = nil) then break;
r := r.Left; // rStartLine points to r, so if r.Left is nil then it is pointing to the next fold;
continue;
end;
ALine := ALine + r.LeftCount + r.LineCount;
rFoldedBefore := rFoldedBefore + r.LeftCount;
if FindNextNode and (r.Right = nil) then begin
r := r.Successor(rStartLine, rFoldedBefore);
break;
end;
rFoldedBefore := rFoldedBefore + r.LineCount;
r := r.Right; // rStartLine points to r, which now is the start of the previous fold;
end;
Result.fData := r;
Result.fStartLine := rStartLine; // only IF r <> nil
Result.fFoldedBefore := rFoldedBefore; // always ok
end;
procedure TSynTextFoldAVLTree.AdjustForLinesInserted(AStartLine, ALineCount : Integer);
var
Current : TSynTextFoldAVLNodeData;
CurrentLine : Integer;
begin
Current := fRoot;
CurrentLine := fRootOffset;
while (Current <> nil) do begin
CurrentLine := CurrentLine + Current.LineOffset;
if AStartLine < CurrentLine then begin
// move current node
Current.LineOffset := Current.LineOffset + ALineCount;
CurrentLine := CurrentLine + ALineCount;
if Current.Left <> nil then
Current.Left.LineOffset := Current.Left.LineOffset - ALineCount;
Current := Current.Left;
end
else if AStartLine > CurrentLine + Current.LineCount - 1 then begin
// The new lines are entirly behind the current node
Current := Current.Right;
end
else begin
// grow current node
Current.LineCount := Current.LineCount + ALineCount;
Current.AdjustParentLeftCount(ALineCount);
TreeForNestedNode(Current, CurrentLine).AdjustForLinesInserted(AStartLine, ALineCount);
if Current.Right <> nil then // and move entire right
Current.Right.LineOffset := Current.Right.LineOffset + ALineCount;
break;
end;
end;
end;
procedure TSynTextFoldAVLTree.AdjustForLinesDeleted(AStartLine, ALineCount : Integer);
Procedure UnfoldForRange(Current : TSynTextFoldAVLNodeData; CurrentLine, StartLine, LineCount : Integer);
begin
// unfold any node, that has either start or end line in the range
end;
Procedure AdjustNodeForLinesDeleted(Current : TSynTextFoldAVLNodeData; CurrentLine, StartLine, LineCount : Integer);
var
EndLine, LinesBefore, LinesInside, LinesAfter, t : Integer;
begin
EndLine := StartLine + LineCount - 1; // only valid for delete; LineCount < 0
while (Current <> nil) do begin
CurrentLine := CurrentLine + Current.LineOffset;
if StartLine < CurrentLine then begin
// move current node
if EndLine >= CurrentLine then begin
// overlap => shrink
LinesBefore := CurrentLine - StartLine;
LinesInside := LineCount - LinesBefore;
// shrink
t := Current.LineCount;
Current.LineCount := Max(Current.LineCount - LinesInside, -1);
Current.AdjustParentLeftCount(Current.LineCount - t); // If LineCount = -1; LeftCount will be correctd on delete node
TreeForNestedNode(Current, CurrentLine).AdjustForLinesDeleted(CurrentLine, LinesInside);
if (Current.Right <> nil) and (LinesInside > 0) then begin
// move right // Calculate from the new curent.LineOffset, as below
AdjustNodeForLinesDeleted(Current.Right, CurrentLine - LinesBefore,
StartLine, LinesInside);
end;
end
else LinesBefore := LineCount;
// move current node (includes right subtree / left subtree needs eval)
Current.LineOffset := Current.LineOffset - LinesBefore;
CurrentLine := CurrentLine - LinesBefore;
if Current.Left <> nil then
Current.Left.LineOffset := Current.Left.LineOffset + LinesBefore;
Current := Current.Left;
end
else if StartLine > CurrentLine + Current.LineCount - 1 then begin
// The deleted lines are entirly behind the current node
Current := Current.Right;
end
else begin // (StartLine >= CurrentLine) AND (StartLine < CurrentLine - Current.LineCount);
LinesAfter := EndLine - (CurrentLine + Current.LineCount - 1);
if LinesAfter < 0 then LinesAfter := 0;
LinesInside := LineCount - LinesAfter;
// shrink current node
t := Current.LineCount;
Current.LineCount := Current.LineCount - LinesInside;
Current.AdjustParentLeftCount(Current.LineCount - t);
TreeForNestedNode(Current, CurrentLine).AdjustForLinesDeleted(StartLine, LinesInside);
Current := Current.Right;
end;
end;
end;
begin
AdjustNodeForLinesDeleted(fRoot, fRootOffset, AStartLine, ALineCount);
end;
function TSynTextFoldAVLTree.FindLastFold : TSynTextFoldAVLNode;
var
r : TSynTextFoldAVLNodeData;
rStartLine : Integer;
rFoldedBefore : Integer;
begin
r := fRoot;
rStartLine := fRootOffset;
rFoldedBefore := 0;
while (r <> nil) do begin
rStartLine := rStartLine + r.LineOffset;
rFoldedBefore := rFoldedBefore + r.LeftCount + r.LineCount;
if r.Right = nil then break;
r := r.Right; // rStartLine points to r, which now is the start of the previous fold;
end;
Result.fData := r;
Result.fStartLine := rStartLine; // only IF r <> nil
Result.fFoldedBefore := rFoldedBefore; // always ok
end;
function TSynTextFoldAVLTree.FindFirstFold : TSynTextFoldAVLNode;
var
r : TSynTextFoldAVLNodeData;
rStartLine : Integer;
begin
r := fRoot;
rStartLine := fRootOffset;
while (r <> nil) do begin
rStartLine := rStartLine + r.LineOffset;
if r.Left = nil then break;
r := r.Left;
end;
Result.fData := r;
Result.fStartLine := rStartLine; // only IF r <> nil
Result.fFoldedBefore := 0; // first fold
end;
procedure TSynTextFoldAVLTree.debug;
function debug2(ind, typ : String; ANode, AParent : TSynTextFoldAVLNodeData; offset : integer) :integer;
begin
result := 0;
if ANode = nil then exit;
with ANode do
DebugLn([ind,typ,' LineOffset: ',LineOffset,', LineCount: ', LineCount,', LeftCount: ',LeftCount,', Balance: ',Balance,' ##Line=', offset+LineOffset]);
if ANode.Parent <> AParent then DebugLn([ind,'* Bad parent']);
Result := debug2(ind+' ', 'L', ANode.Left, ANode, offset+ANode.LineOffset);
If Result <> ANode.LeftCount then debugln([ind,' ***** Leftcount was ',Result, ' but should be ', ANode.LeftCount]);
Result += debug2(ind+' ', 'R', ANode.Right, ANode, offset+ANode.LineOffset);
debug2(ind+' #', 'N', ANode.Nested, nil, offset+ANode.LineOffset);
Result += ANode.LineCount;
end;
begin
debug2('', '-', fRoot, nil, 0);
end;
function TSynTextFoldAVLTree.InsertNewFold(ALine, ACount : Integer) : TSynTextFoldAVLNode;
var
r : TSynTextFoldAVLNodeData;
begin
r := NewNode;
r.LineOffset := ALine;
r.LineCount := ACount;
r.LeftCount := 0;
Result.fData := r;
Result.fStartLine := ALine;
Result.fFoldedBefore := InsertNode(r);
end;
function TSynTextFoldAVLTree.RemoveFoldForLine(ALine : Integer; IgnoreFirst : Boolean = False) : Integer;
var
OldFold : TSynTextFoldAVLNode;
begin
Result := ALine;
OldFold := FindFoldForLine(ALine, true);
if (not OldFold.IsInFold) // behind last node
or (IgnoreFirst and (OldFold.StartLine > ALine))
or ((not IgnoreFirst) and (OldFold.StartLine > ALine+1))
then exit;
RemoveFoldForNodeAtLine(OldFold, ALine, IgnoreFirst);
end;
function TSynTextFoldAVLTree.RemoveFoldForNodeAtLine(ANode : TSynTextFoldAVLNode;
ALine : Integer; IgnoreFirst : Boolean = False) : Integer;
var
NestedNode, MergeNode : TSynTextFoldAVLNodeData;
NestedLine : LongInt;
begin
// The cfCollapsed line is one line before the fold
Result := ANode.StartLine-1; // Return the cfcollapsed that was unfolded
RemoveNode(ANode.fData);
If ANode.fData.Nested <> nil then
begin
(*Todo: should we mark the tree as NO balancing needed ???*)
TreeForNestedNode(ANode.fData, ANode.StartLine).RemoveFoldForLine(ALine, IgnoreFirst);
// merge the remaining nested into current
NestedNode := ANode.fData.Nested;
if NestedNode <> nil
then NestedLine := ANode.fStartLine + NestedNode.LineOffset;
while NestedNode <> nil do begin
while NestedNode.Left <> nil do begin
NestedNode := NestedNode.Left;
NestedLine := NestedLine + NestedNode.LineOffset;
end;
if NestedNode.Right <> nil then begin
NestedNode := NestedNode.Right;
NestedLine := NestedLine + NestedNode.LineOffset;
continue;
end;
// leaf node
// Anything that is still nested (MergeNode.Nested), will stay nested
MergeNode := NestedNode;
NestedLine := NestedLine - NestedNode.LineOffset;
NestedNode := NestedNode.Parent;
MergeNode.LineOffset := MergeNode.LineOffset + NestedLine;
if NestedNode <> nil then begin
NestedNode.ReplaceChild(MergeNode, nil);
MergeNode.Parent := nil;
end;
MergeNode.LeftCount := 0;
MergeNode.Balance := 0;
InsertNode(MergeNode);
end;
end;
DisposeNode(ANode.fData);
end;
function TSynTextFoldAVLTree.InsertNode(ANode : TSynTextFoldAVLNodeData) : Integer;
var
rStartLine, NestStartLine : Integer;
rFoldedBefore, NestFoldedBefore : Integer;
current, Nest : TSynTextFoldAVLNodeData;
ALine, AEnd, ACount : Integer;
procedure NestCurrentIntoNewBlock; inline;
var
diff, start2, before2 : Integer;
p : TSynTextFoldAVLNodeData;
begin
// TODO => Check if LineCount needs to be extended (part overlap/nesting)
// include extension in below AdjustParentLeftCount
current.AdjustParentLeftCount(ACount-current.LineCount); // -RecursiveFoldCount(current));
rStartLine := rStartLine - current.LineOffset; // rStarteLine is now current.Parent
p := current.Parent;
if p <> nil
then p.ReplaceChild(current, ANode, -rStartLine)
else SetRoot(ANode, -rStartLine);
diff := current.LineOffset - ANode.LineOffset;
ANode.Nested := current;
ANode.Balance := current.Balance;
current.LineOffset := diff; // offset to ANode (via Nested)
current.Parent := nil;
current.Balance := 0;
ANode.SetLeftChild(current.Left, diff, current.LeftCount);
current.Left := nil;
current.LeftCount := 0;
ANode.SetRightChild(current.Right, diff);
current.Right := nil;
start2 := ALine; before2 := rFoldedBefore;
p := ANode.Successor(start2, before2);
while (p <> nil) and (start2 <= AEnd) do begin
RemoveNode(p);
p.LineOffset := start2- ALine;
TreeForNestedNode(Anode, 0).InsertNode(p);
start2 := ALine; before2 := rFoldedBefore;
p := ANode.Successor(start2, before2);
end;
end;
procedure NestNewBlockIntoCurrent; inline;
begin
// Check if current.LineCount needs extension
ANode.LineOffset := ALine - rStartLine;
if current.Nested <> nil
then TreeForNestedNode(current, 0).InsertNode(ANode)
else current.Nested := ANode;
// TODO => Find More Nodes (only if acount extended), that need to be nested => May include LineCount/LeftCount Adjustment
// TODO => BalanceAfterInsert (only if more nodes)
end;
begin
if fRoot = nil then begin
SetRoot(ANode);
Result := 0;
exit;
end;
ALine := ANode.LineOffset;
ACount := ANode.LineCount;
AEnd := ALine + ACount - 1;
current := fRoot;
rStartLine := fRootOffset;
rFoldedBefore := 0;
Nest := nil;
NestFoldedBefore := 0;
NestStartLine := 0;
while (current <> nil) do begin
rStartLine := rStartLine + current.LineOffset;
if ALine < rStartLine then begin
(* *** New block goes to the left *** *)
// remember possible nesting, continue scan for nesting with precessor
if (AEnd >= rStartLine) then begin
Nest := current;
NestFoldedBefore := rFoldedBefore;
NestStartLine := rStartLine;
end;
if current.Left <> nil Then begin
current := current.Left;
continue;
end
else if Nest = nil then begin // insert as Left - no nesting
current.AdjustParentLeftCount(ACount);
current.SetLeftChild(ANode, -rStartLine, ANode.LineCount);
BalanceAfterInsert(ANode);
end
else begin // nest
current := Nest;
rStartLine := NestStartLine;
rFoldedBefore := NestFoldedBefore;
NestCurrentIntoNewBlock;
end;
break;
end;
rFoldedBefore := rFoldedBefore + current.LeftCount;
if ALine = rStartLine then begin
if ACount < current.LineCount then
(* *** New Block will be nested in current *** *)
NestNewBlockIntoCurrent
else
if ACount > current.LineCount then
(* *** current will be nested in New Block *** *)
NestCurrentIntoNewBlock
else begin
debugln(['Droping Foldnode / Already exists. Startline=', rStartLine,' LineCount=',ACount]);
ANode.Free;
end;
end
else begin
If ALine <= rStartLine + current.LineCount - 1
(* *** New Block will be nested in current *** *)
then NestNewBlockIntoCurrent
(* *** New block goes to the right *** *)
else begin
rFoldedBefore := rFoldedBefore + current.LineCount;
if current.Right <> nil then begin
current := current.Right;
continue;
end
else if Nest=nil then Begin // insert to the right - no nesting
current.AdjustParentLeftCount(ACount);
current.SetRightChild(ANode, -rStartLine);
BalanceAfterInsert(ANode);
end
else begin // nest
current := Nest;
rStartLine := NestStartLine;
rFoldedBefore := NestFoldedBefore;
NestCurrentIntoNewBlock;
end;
end;
end;
break;
end; // while
Result := rFoldedBefore;
end;
procedure TSynTextFoldAVLTree.RemoveNode(ANode: TSynTextFoldAVLNodeData);
var OldParent, Precessor, PrecOldParent, PrecOldLeft,
OldSubTree: TSynTextFoldAVLNodeData;
OldBalance, PrecOffset, PrecLeftCount: integer;
begin
if ((ANode.Left<>nil) and (ANode.Right<>nil)) then begin
PrecOffset := 0;
// PrecOffset := ANode.LineOffset;
Precessor := ANode.Left;
while (Precessor.Right<>nil) do begin
PrecOffset := PrecOffset + Precessor.LineOffset;
Precessor := Precessor.Right;
end;
(* *OR*
PnL PnL
\ \
Precessor Anode
/ /
* * PnL PnL
/ / \ \
AnL AnR AnL AnR Precessor AnR AnL AnR
\ / \ / \ / \ /
Anode Precessor() Anode Precessor()
*)
OldBalance := ANode.Balance;
ANode.Balance := Precessor.Balance;
Precessor.Balance := OldBalance;
// Successor.Left = nil
PrecOldLeft := Precessor.Left;
PrecOldParent := Precessor.Parent;
if (ANode.Parent<>nil)
then ANode.Parent.ReplaceChild(ANode, Precessor, PrecOffset + ANode.LineOffset)
else SetRoot(Precessor, PrecOffset + ANode.LineOffset);
Precessor.SetRightChild(ANode.Right,
+ANode.LineOffset-Precessor.LineOffset);
PrecLeftCount := Precessor.LeftCount;
// ANode.Right will be empty // ANode.Left will be Succesor.Left
if (PrecOldParent = ANode) then begin
// Precessor is left son of ANode
// set ANode.LineOffset=0 => LineOffset for the Prec-Children is already correct;
Precessor.SetLeftChild(ANode, -ANode.LineOffset,
PrecLeftCount + ANode.LineCount);
ANode.SetLeftChild(PrecOldLeft, 0, PrecLeftCount);
end else begin
// at least one node between ANode and Precessor ==> Precessor = PrecOldParent.Right
Precessor.SetLeftChild(ANode.Left, +ANode.LineOffset - Precessor.LineOffset,
ANode.LeftCount + ANode.LineCount - Precessor.LineCount);
PrecOffset:=PrecOffset + ANode.LineOffset - Precessor.LineOffset;
// Set Anode.LineOffset, so ANode movesinto position of Precessor;
PrecOldParent.SetRightChild(ANode, - ANode.LineOffset - PrecOffset);
ANode.SetLeftChild(PrecOldLeft, 0, PrecLeftCount);
end;
ANode.Right := nil;
end;
if (ANode.Right<>nil) then begin
OldSubTree := ANode.Right;
ANode.Right := nil;
end
else if (ANode.Left<>nil) then begin
OldSubTree := ANode.Left;
ANode.Left := nil;
end
else OldSubTree := nil;
OldParent := ANode.Parent;
ANode.Parent := nil;
ANode.Left := nil;
ANode.Right := nil;
ANode.Balance := 0;
ANode.LeftCount := 0;
// nested???
if (OldParent<>nil) then begin // Node has parent
if OldParent.ReplaceChild(ANode, OldSubTree, ANode.LineOffset) = rplcLeft
then begin
Inc(OldParent.Balance);
OldParent.AdjustLeftCount(-ANode.LineCount);
end
else begin
Dec(OldParent.Balance);
OldParent.AdjustParentLeftCount(-ANode.LineCount);
end;
BalanceAfterDelete(OldParent);
end
else SetRoot(OldSubTree, ANode.LineOffset);
end;
procedure TSynTextFoldAVLTree.BalanceAfterInsert(ANode : TSynTextFoldAVLNodeData);
var OldParent, OldParentParent, OldRight, OldRightLeft, OldRightRight, OldLeft,
OldLeftLeft, OldLeftRight: TSynTextFoldAVLNodeData;
tmp : integer;
begin
OldParent := ANode.Parent;
if (OldParent=nil) then exit;
if (OldParent.Left=ANode) then begin
(* *** Node is left son *** *)
dec(OldParent.Balance);
if (OldParent.Balance=0) then exit;
if (OldParent.Balance=-1) then begin
BalanceAfterInsert(OldParent);
exit;
end;
// OldParent.Balance=-2
if (ANode.Balance=-1) then begin
(* ** single rotate ** *)
(* []
\
[] ORight [] ORight []
\ / \ \ /
ANode(-1) [] => [] OldParent(0)
\ / \ /
OldParent(-2) ANode(0)
*)
OldRight := ANode.Right;
OldParentParent := OldParent.Parent;
(* ANode moves into position of OldParent *)
if (OldParentParent<>nil)
then OldParentParent.ReplaceChild(OldParent, ANode, OldParent.LineOffset)
else SetRoot(ANode, OldParent.LineOffset);
(* OldParent moves under ANode, replacing Anode.Right, which moves under OldParent *)
ANode.SetRightChild(OldParent, -ANode.LineOffset );
OldParent.SetLeftChild(OldRight, -OldParent.LineOffset, OldParent.LeftCount - ANode.LineCount - ANode.LeftCount);
ANode.Balance := 0;
OldParent.Balance := 0;
(* ** END single rotate ** *)
end
else begin // ANode.Balance = +1
(* ** double rotate ** *)
OldParentParent := OldParent.Parent;
OldRight := ANode.Right;
OldRightLeft := OldRight.Left;
OldRightRight := OldRight.Right;
(* OldRight moves into position of OldParent *)
if (OldParentParent<>nil)
then OldParentParent.ReplaceChild(OldParent, OldRight, OldParent.LineOffset + ANode.LineOffset)
else SetRoot(OldRight, OldParent.LineOffset + ANode.LineOffset); // OldParent was root node. new root node
OldRight.SetRightChild(OldParent, -OldRight.LineOffset);
OldRight.SetLeftChild(ANode, OldParent.LineOffset, OldRight.LeftCount + ANode.LeftCount + ANode.LineCount);
ANode.SetRightChild(OldRightLeft, -ANode.LineOffset);
OldParent.SetLeftChild(OldRightRight, -OldParent.LineOffset, OldParent.LeftCount - OldRight.LeftCount - OldRight.LineCount);
// balance
if (OldRight.Balance<=0)
then ANode.Balance := 0
else ANode.Balance := -1;
if (OldRight.Balance=-1)
then OldParent.Balance := 1
else OldParent.Balance := 0;
OldRight.Balance := 0;
(* ** END double rotate ** *)
end;
(* *** END Node is left son *** *)
end
else begin
(* *** Node is right son *** *)
Inc(OldParent.Balance);
if (OldParent.Balance=0) then exit;
if (OldParent.Balance=+1) then begin
BalanceAfterInsert(OldParent);
exit;
end;
// OldParent.Balance = +2
if(ANode.Balance=+1) then begin
(* ** single rotate ** *)
OldLeft := ANode.Left;
OldParentParent := OldParent.Parent;
if (OldParentParent<>nil)
then OldParentParent.ReplaceChild(OldParent, ANode, OldParent.LineOffset)
else SetRoot(ANode, OldParent.LineOffset);
(* OldParent moves under ANode, replacing Anode.Left, which moves under OldParent *)
ANode.SetLeftChild(OldParent, -ANode.LineOffset, ANode.LeftCount + OldParent.LineCount + OldParent.LeftCount);
OldParent.SetRightChild(OldLeft, -OldParent.LineOffset);
ANode.Balance := 0;
OldParent.Balance := 0;
(* ** END single rotate ** *)
end
else begin // Node.Balance = -1
(* ** double rotate ** *)
OldLeft := ANode.Left;
OldParentParent := OldParent.Parent;
OldLeftLeft := OldLeft.Left;
OldLeftRight := OldLeft.Right;
(* OldLeft moves into position of OldParent *)
if (OldParentParent<>nil)
then OldParentParent.ReplaceChild(OldParent, OldLeft, OldParent.LineOffset + ANode.LineOffset)
else SetRoot(OldLeft, OldParent.LineOffset + ANode.LineOffset);
tmp := OldLeft.LeftCount;
OldLeft.SetLeftChild (OldParent, -OldLeft.LineOffset, tmp + OldParent.LeftCount + OldParent.LineCount);
OldLeft.SetRightChild(ANode, OldParent.LineOffset);
OldParent.SetRightChild(OldLeftLeft, -OldParent.LineOffset);
ANode.SetLeftChild(OldLeftRight, -ANode.LineOffset, ANode.LeftCount - tmp - OldLeft.LineCount);
// Balance
if (OldLeft.Balance>=0)
then ANode.Balance := 0
else ANode.Balance := +1;
if (OldLeft.Balance=+1)
then OldParent.Balance := -1
else OldParent.Balance := 0;
OldLeft.Balance := 0;
(* ** END double rotate ** *)
end;
end;
end;
procedure TSynTextFoldAVLTree.BalanceAfterDelete(ANode : TSynTextFoldAVLNodeData);
var OldParent, OldRight, OldRightLeft, OldLeft, OldLeftRight,
OldRightLeftLeft, OldRightLeftRight, OldLeftRightLeft, OldLeftRightRight
: TSynTextFoldAVLNodeData;
tmp : integer;
begin
if (ANode=nil) then exit;
if ((ANode.Balance=+1) or (ANode.Balance=-1)) then exit;
OldParent := ANode.Parent;
if (ANode.Balance=0) then begin
// Treeheight has decreased by one
if (OldParent<>nil) then begin
if(OldParent.Left=ANode) then
Inc(OldParent.Balance)
else
Dec(OldParent.Balance);
BalanceAfterDelete(OldParent);
end;
exit;
end;
if (ANode.Balance=-2) then begin
// Node.Balance=-2
// Node is overweighted to the left
(*
OLftRight
/
OLeft(<=0)
\
ANode(-2)
*)
OldLeft := ANode.Left;
if (OldLeft.Balance<=0) then begin
// single rotate left
OldLeftRight := OldLeft.Right;
if (OldParent<>nil)
then OldParent.ReplaceChild(ANode, OldLeft, ANode.LineOffset)
else SetRoot(OldLeft, ANode.LineOffset);
OldLeft.SetRightChild(ANode, -OldLeft.LineOffset);
ANode.SetLeftChild(OldLeftRight, -ANode.LineOffset, ANode.LeftCount - OldLeft.LineCount - OldLeft.LeftCount);
ANode.Balance := (-1-OldLeft.Balance);
Inc(OldLeft.Balance);
BalanceAfterDelete(OldLeft);
end else begin
// OldLeft.Balance = 1
// double rotate left left
OldLeftRight := OldLeft.Right;
OldLeftRightLeft := OldLeftRight.Left;
OldLeftRightRight := OldLeftRight.Right;
(*
OLR-Left OLR-Right
\ /
OldLeftRight OLR-Left OLR-Right
/ / \
OldLeft OldLeft ANode
\ \ /
ANode OldLeftRight
| |
OldParent OldParent (or root)
*)
if (OldParent<>nil)
then OldParent.ReplaceChild(ANode, OldLeftRight, ANode.LineOffset + OldLeft.LineOffset)
else SetRoot(OldLeftRight, ANode.LineOffset + OldLeft.LineOffset);
OldLeftRight.SetRightChild(ANode, -OldLeftRight.LineOffset);
OldLeftRight.SetLeftChild(OldLeft, ANode.LineOffset, OldLeftRight.LeftCount + OldLeft.LeftCount + OldLeft.LineCount);
OldLeft.SetRightChild(OldLeftRightLeft, -OldLeft.LineOffset);
ANode.SetLeftChild(OldLeftRightRight, -ANode.LineOffset, ANode.LeftCount - OldLeftRight.LeftCount - OldLeftRight.LineCount);
if (OldLeftRight.Balance<=0)
then OldLeft.Balance := 0
else OldLeft.Balance := -1;
if (OldLeftRight.Balance>=0)
then ANode.Balance := 0
else ANode.Balance := +1;
OldLeftRight.Balance := 0;
BalanceAfterDelete(OldLeftRight);
end;
end else begin
// Node is overweighted to the right
OldRight := ANode.Right;
if (OldRight.Balance>=0) then begin
// OldRight.Balance=={0 or -1}
// single rotate right
OldRightLeft := OldRight.Left;
if (OldParent<>nil)
then OldParent.ReplaceChild(ANode, OldRight, ANode.LineOffset)
else SetRoot(OldRight, ANode.LineOffset);
OldRight.SetLeftChild(ANode, -OldRight.LineOffset, OldRight.LeftCount + ANode.LineCount + ANode.LeftCount);
ANode.SetRightChild(OldRightLeft, -ANode.LineOffset);
ANode.Balance := (1-OldRight.Balance);
Dec(OldRight.Balance);
BalanceAfterDelete(OldRight);
end else begin
// OldRight.Balance=-1
// double rotate right left
OldRightLeft := OldRight.Left;
OldRightLeftLeft := OldRightLeft.Left;
OldRightLeftRight := OldRightLeft.Right;
if (OldParent<>nil)
then OldParent.ReplaceChild(ANode, OldRightLeft, ANode.LineOffset + OldRight.LineOffset)
else SetRoot(OldRightLeft, ANode.LineOffset + OldRight.LineOffset);
tmp := OldRightLeft.LeftCount;
OldRightLeft.SetLeftChild(ANode, -OldRightLeft.LineOffset, tmp + ANode.LeftCount + ANode.LineCount);
OldRightLeft.SetRightChild(OldRight, ANode.LineOffset);
ANode.SetRightChild(OldRightLeftLeft, -ANode.LineOffset);
OldRight.SetLeftChild(OldRightLeftRight, -OldRight.LineOffset, OldRight.LeftCount - tmp - OldRightLeft.LineCount);
if (OldRightLeft.Balance<=0)
then ANode.Balance := 0
else ANode.Balance := -1;
if (OldRightLeft.Balance>=0)
then OldRight.Balance := 0
else OldRight.Balance := +1;
OldRightLeft.Balance := 0;
BalanceAfterDelete(OldRightLeft);
end;
end;
end;
function TSynTextFoldAVLTree.TreeForNestedNode(ANode: TSynTextFoldAVLNodeData; aOffset : Integer) : TSynTextFoldAVLTree;
begin
if fNestedNodesTree = nil then fNestedNodesTree := TSynTextFoldAVLTree.Create;
Result := fNestedNodesTree;
Result.fRoot := ANode.Nested;
Result.fNestParent := ANode; // TODO: this is dangerous, this is never cleaned up, even if ANode is Destroyed
Result.fRootOffset := aOffset;
end;
constructor TSynTextFoldAVLTree.Create;
begin
fRoot := nil;
fRootOffset := 0;
fNestParent := nil;
fNestedNodesTree := nil;
end;
{ TSynEditFoldedView }
constructor TSynEditFoldedView.Create(aTextView : TSynEditStrings; ACaret: TSynEditCaret);
begin
fLines := aTextView;
flines.RegisterAttribute(TSynEditFoldMinClass, SizeOf(Integer));
flines.RegisterAttribute(TSynEditFoldEndClass, SizeOf(Integer));
fCaret := ACaret;
fCaret.AddChangeHandler({$IFDEF FPC}@{$ENDIF}DoCaretChanged);
fFoldTree := TSynTextFoldAVLTree.Create;
fTopLine := 0;
fLinesInWindow := -1;
FMarkupInfoFoldedCode := TSynSelectedColor.Create;
FMarkupInfoFoldedCode.Background := clNone;
FMarkupInfoFoldedCode.Foreground := clDkGray;
FMarkupInfoFoldedCode.FrameColor := clDkGray;
fLines.AddChangeHandler(senrLineCount, {$IFDEF FPC}@{$ENDIF}LineCountChanged);
end;
destructor TSynEditFoldedView.Destroy;
begin
fLines.RemoveChangeHandler(senrLineCount, {$IFDEF FPC}@{$ENDIF}LineCountChanged);
fCaret.RemoveChangeHandler({$IFDEF FPC}@{$ENDIF}DoCaretChanged);
fFoldTree.Free;
fTextIndexList := nil;
fFoldTypeList := nil;
FMarkupInfoFoldedCode.Free;
inherited Destroy;
end;
procedure TSynEditFoldedView.LinesInsertedAtTextIndex(AStartIndex, ALineCount : Integer; SkipFixFolding : Boolean);
var top : Integer;
begin
top := TopTextIndex;
fFoldTree.AdjustForLinesInserted(AStartIndex+1, ALineCount);
if AStartIndex < top then
TopTextIndex := top + ALineCount;
if not(SkipFixFolding) then FixFoldingAtTextIndex(AStartIndex, AStartIndex+ALineCount+1)
else
if AStartIndex < top + ALineCount then CalculateMaps;
end;
procedure TSynEditFoldedView.LinesInsertedAtViewPos(AStartPos, ALineCount : Integer; SkipFixFolding : Boolean);
begin
LinesInsertedAtTextIndex(ViewPosToTextIndex(AStartPos), ALineCount, SkipFixFolding);
end;
procedure TSynEditFoldedView.LinesDeletedAtTextIndex(AStartIndex, ALineCount : Integer; SkipFixFolding : Boolean);
var top : Integer;
begin
top := TopTextIndex;
fFoldTree.AdjustForLinesDeleted(AStartIndex+1, ALineCount);
if AStartIndex < top then
TopTextIndex := top - ALineCount; {TODO: this may be to much, if topline is in the middle of the block}
if not(SkipFixFolding) then FixFoldingAtTextIndex(AStartIndex, AStartIndex+ALineCount+1)
else
if AStartIndex < top - ALineCount then CalculateMaps;
end;
procedure TSynEditFoldedView.LinesDeletedAtViewPos(AStartPos, ALineCount : Integer; SkipFixFolding : Boolean);
begin
LinesDeletedAtTextIndex(ViewPosToTextIndex(AStartPos), ALineCount, SkipFixFolding);
end;
function TSynEditFoldedView.TextIndexToViewPos(aTextIndex : Integer) : Integer;
begin
result := aTextIndex + 1 - fFoldTree.FindFoldForLine(aTextIndex+1).FoldedBefore;
end;
function TSynEditFoldedView.TextIndexToScreenLine(aTextIndex : Integer) : Integer;
begin
Result := TextIndexToViewPos(aTextIndex) - TopLine;
end;
function TSynEditFoldedView.ViewPosToTextIndex(aViewPos : Integer) : Integer;
begin
if aViewPos > Count then
aViewPos := Count;
result := aViewPos - 1 + fFoldTree.FindFoldForFoldedLine(aViewPos).FoldedBefore;
end;
function TSynEditFoldedView.ScreenLineToTextIndex(aLine : Integer) : Integer;
begin
Result := ViewPosToTextIndex(aLine + TopLine);
end;
function TSynEditFoldedView.TextIndexAddLines(aTextIndex, LineOffset : Integer) : Integer;
var
node : TSynTextFoldAVLNode;
cnt : integer;
begin
node := fFoldTree.FindFoldForLine(aTextIndex+1, True);
result := aTextIndex;
if LineOffset < 0 then begin
if node.IsInFold
then node := node.Prev
else node := fFoldTree.FindLastFold;
while LineOffset < 0 do begin
if Result <= 0 then exit(0);
dec(Result);
if node.IsInFold and (Result+1 < node.StartLine + node.LineCount) then begin
Result := Result - node.LineCount;
node := node.Prev;
end;
inc(LineOffset);
end;
end else begin
cnt := fLines.Count;
while LineOffset > 0 do begin
if Result >= cnt then exit(cnt);
inc(Result);
if node.IsInFold and (Result+1 >= node.StartLine) then begin
Result := Result + node.LineCount;
if Result >= cnt then exit(cnt-node.LineCount-1);
node := node.Next;
end;
dec(LineOffset);
end;
end;
end;
function TSynEditFoldedView.TextPosAddLines(aTextpos, LineOffset : Integer) : Integer;
begin
Result := TextIndexAddLines(aTextpos-1, LineOffset)+1;
end;
procedure TSynEditFoldedView.Lock;
begin
if fLockCount=0 then begin
fNeedFixFrom := -1;
fNeedFixMinEnd := -1;
fNeedCaretCheck := false;
end;;
inc(fLockCount);
end;
procedure TSynEditFoldedView.UnLock;
begin
dec(fLockCount);
if (fLockCount=0) then begin
if (fNeedFixFrom >= 0) then
FixFolding(fNeedFixFrom, fNeedFixMinEnd, fFoldTree);
if fNeedCaretCheck then
DoCaretChanged(fCaret);
end;
end;
(* Count *)
function TSynEditFoldedView.GetCount : integer;
begin
Result := fLines.Count - fFoldTree.FindLastFold.FoldedBefore;
end;
function TSynEditFoldedView.GetDrawDivider(Index : integer) : Boolean;
begin
result := (FoldType[Index] in [cfEnd])
and (FoldEndLevel[TextIndex[index]] < CFDividerDrawLevel);
end;
function TSynEditFoldedView.GetFoldNestLevel(index : Integer): integer;
begin
if (index < 0) or (index > fLinesInWindow) then exit(-1);
if (fFoldTypeList[index] = cfEnd) and (fTextIndexList[index] > 0) then
Result := FoldEndLevel[fTextIndexList[index]-1]
else
Result := FoldEndLevel[fTextIndexList[index]];
end;
(* Topline *)
procedure TSynEditFoldedView.SetTopLine(const ALine : integer);
begin
if fTopLine = ALine then exit;
fTopLine := ALine;
CalculateMaps;
end;
function TSynEditFoldedView.GetTopTextIndex : integer;
begin
Result := fTopLine + fFoldTree.FindFoldForFoldedLine(fTopLine).FoldedBefore - 1;
end;
procedure TSynEditFoldedView.SetTopTextIndex(const AIndex : integer);
begin
TopLine := AIndex + 1 - fFoldTree.FindFoldForLine(AIndex+1).FoldedBefore;
end;
(* LinesInWindow*)
procedure TSynEditFoldedView.SetLinesInWindow(const AValue : integer);
begin
if fLinesInWindow = AValue then exit;
fLinesInWindow := AValue;
SetLength(fTextIndexList, AValue + 1);
SetLength(fFoldTypeList, AValue + 1);
CalculateMaps;
end;
procedure TSynEditFoldedView.CalculateMaps;
var
i, tpos, cnt : Integer;
node : TSynTextFoldAVLNode;
begin
node := fFoldTree.FindFoldForFoldedLine(fTopLine, true);
// ftopline is not a folded line
// so node.FoldedBefore(next node after ftopl) does apply
tpos := fTopLine + node.FoldedBefore;
cnt := fLines.Count;
for i := 0 to fLinesInWindow do begin
if tpos > cnt then begin
fTextIndexList[i] := -1;
fFoldTypeList[i] := cfNone;
end else begin
fTextIndexList[i] := tpos - 1; // TextIndex is 0-based
if (node.IsInFold) and (tpos+1 = node.StartLine)
then fFoldTypeList[i] := cfCollapsed
else
if FoldEndLevel[tpos-1] > FoldMinLevel[tpos-1]
then fFoldTypeList[i] := cfExpanded
else
if (tpos > 1) and (FoldEndLevel[tpos-2] > FoldMinLevel[tpos-1])
then fFoldTypeList[i] := cfEnd
else
if FoldEndLevel[tpos-1] > 0
then fFoldTypeList[i] := cfContinue
else fFoldTypeList[i] := cfNone;
inc(tpos);
if (node.IsInFold) and (tpos >= node.StartLine) then begin
tpos := tpos + node.LineCount;
node := node.Next;
end;
end;
end;
end;
(* Lines *)
function TSynEditFoldedView.GetLines(index : Integer) : String;
begin
if (index < 0) or (index > fLinesInWindow) then exit('');
Result := fLines[fTextIndexList[index]];
end;
function TSynEditFoldedView.GetDisplayNumber(index : Integer) : Integer;
begin
if (index < 0) or (index > fLinesInWindow)
or (fTextIndexList[index] < 0) then exit(-1);
Result := fTextIndexList[index]+1;
end;
function TSynEditFoldedView.GetRange(Index : integer) : TSynEditRange;
begin
if (index < 0) or (index > fLinesInWindow) then exit(nil);
Result := fLines.Ranges[fTextIndexList[index]];
end;
function TSynEditFoldedView.GetTextIndex(index : Integer) : Integer;
begin
if (index < 0) or (index > fLinesInWindow) then exit(-1);
Result := fTextIndexList[index];
end;
function TSynEditFoldedView.GetFoldType(index : Integer) : TSynEditCodeFoldType;
begin
if (index < 0) or (index > fLinesInWindow) then exit(cfNone);
Result := fFoldTypeList[index];
end;
function TSynEditFoldedView.IsFolded(index : integer) : Boolean;
begin
Result := fFoldTree.FindFoldForLine(index+1).IsInFold;
end;
procedure TSynEditFoldedView.PutRange(Index : integer; const AValue : TSynEditRange);
begin
if (index < 0) or (index > fLinesInWindow) then exit;
fLines.Ranges[fTextIndexList[index]] := AValue;
end;
(* Folding *)
procedure TSynEditFoldedView.SetFoldEndLevel(Index: integer; const AValue: integer);
begin
if (Index >= 0) and (Index < fLines.Count) then
fLines.Attribute[TSynEditFoldEndClass, Index] := Pointer(PtrUInt(AValue));
end;
procedure TSynEditFoldedView.SetFoldMinLevel(Index: integer; const AValue: integer);
begin
if (Index >= 0) and (Index < fLines.Count) then
fLines.Attribute[TSynEditFoldMinClass, Index] := Pointer(PtrUInt(AValue));
end;
function TSynEditFoldedView.GetFoldEndLevel(Index: integer): integer;
begin
if (Index >= 0) and (Index < fLines.Count) then
Result := Integer(fLines.Attribute[TSynEditFoldEndClass, Index])
else
Result := 0;
end;
function TSynEditFoldedView.GetFoldMinLevel(Index: integer): integer;
begin
if (Index >= 0) and (Index < fLines.Count) then
Result := Integer(fLines.Attribute[TSynEditFoldMinClass, Index])
else
Result := 0;
end;
procedure TSynEditFoldedView.FoldAtLine(AStartLine : Integer);
begin
FoldAtViewPos(AStartLine + fTopLine);
end;
procedure TSynEditFoldedView.FoldAtViewPos(AStartPos : Integer);
begin
FoldAtTextIndex(AStartPos - 1 + fFoldTree.FindFoldForFoldedLine(AStartPos).FoldedBefore);
end;
function TSynEditFoldedView.LengthForFoldAtTextIndex(ALine : Integer) : Integer;
var
i, lvl, cnt : Integer;
begin
cnt := fLines.Count;
// AStartLine is 1-based // FoldEndLevel is 0-based
lvl := FoldEndLevel[ALine];
i := ALine+1;
while (i < cnt) and (FoldMinLevel[i] >= lvl) do inc(i);
// check if fold last line of block (not mixed "end begin")
if (i < cnt) and (FoldEndLevel[i] <= FoldMinLevel[i]) then inc(i);
Result := i-ALine-1;
end;
procedure TSynEditFoldedView.FoldAtTextIndex(AStartIndex : Integer);
var
top : Integer;
begin
top := TopTextIndex;
// AStartIndex is 0-based
// FoldTree is 1-based AND first line remains visble
fFoldTree.InsertNewFold(AStartIndex+2, LengthForFoldAtTextIndex(AStartIndex));
fTopLine := -1; // make sure seting TopLineTextIndex, will do CalculateMaps;
TopTextIndex := top;
if Assigned(fOnFoldChanged) then
fOnFoldChanged(AStartIndex);
end;
procedure TSynEditFoldedView.UnFoldAtLine(AStartLine : Integer; IgnoreFirst : Boolean = False);
begin
UnFoldAtViewPos(AStartLine + fTopLine, IgnoreFirst);
end;
procedure TSynEditFoldedView.UnFoldAtViewPos(AStartPos : Integer; IgnoreFirst : Boolean = False);
begin
UnFoldAtTextIndex(AStartPos - 1 + fFoldTree.FindFoldForFoldedLine(AStartPos).FoldedBefore, IgnoreFirst);
end;
procedure TSynEditFoldedView.UnFoldAtTextIndex(AStartIndex : Integer; IgnoreFirst : Boolean = False);
var
top : Integer;
begin
top := TopTextIndex;
// Foldtree needs 1-based
AStartIndex := fFoldTree.RemoveFoldForLine(AStartIndex+1, IgnoreFirst) - 1;
fTopLine := -1; // make sure seting TopLineTextIndex, will do CalculateMaps;
TopTextIndex := top;
if Assigned(fOnFoldChanged) then
fOnFoldChanged(AStartIndex);
end;
procedure TSynEditFoldedView.UnfoldAll;
var
top : Integer;
begin
top := TopTextIndex;
fFoldTree.Clear;
fTopLine := -1; // make sure seting TopLineTextIndex, will do CalculateMaps;
TopTextIndex := top;
if Assigned(fOnFoldChanged) then
fOnFoldChanged(0);
end;
procedure TSynEditFoldedView.FoldAll(StartLevel : Integer = 0; IgnoreNested : Boolean = False);
var
i, l, top: Integer;
begin
top := TopTextIndex;
fFoldTree.Clear;
i := 0;
while i < fLines.Count do begin
if (FoldEndLevel[i] > FoldMinLevel[i])
and (FoldEndLevel[i] > StartLevel) then begin
l := LengthForFoldAtTextIndex(i);
// i is 0-based
// FoldTree is 1-based AND first line remains visble
fFoldTree.InsertNewFold(i+2, l);
if IgnoreNested then
i := i + l;
end;
inc(i);
end;
fTopLine := -1;
TopTextIndex := top;
if Assigned(fOnFoldChanged) then
fOnFoldChanged(0);
end;
function TSynEditFoldedView.FixFolding(AStart : Integer; AMinEnd : Integer; aFoldTree : TSynTextFoldAVLTree) : Boolean;
var
line, cnt, a: Integer;
LastStart, LastCount: Integer;
node, tmpnode: TSynTextFoldAVLNode;
begin
Result := false;
if fLockCount > 0 then begin
fNeedCaretCheck := true; // We may be here as a result of lines deleted/inserted
if fNeedFixFrom < 0 then fNeedFixFrom := AStart
else fNeedFixFrom := Min(fNeedFixFrom, AStart);
fNeedFixMinEnd := Max(fNeedFixMinEnd, AMinEnd);
exit;
end;
node := aFoldTree.FindFoldForLine(AStart, true);
if not node.IsInFold then node:= aFoldTree.FindLastFold;
if not node.IsInFold then Begin
CalculateMaps;
exit;
end;
If AMinEnd < node.StartLine then AMinEnd := node.StartLine;
// LineCount is allowed to be -1
while node.IsInFold and (node.StartLine + node.LineCount + 1 >= AStart) do begin
tmpnode := node.Prev;
if tmpnode.IsInFold then
node := tmpnode
else
break; // first node
end;
LastStart := -1;
LastCount := -2;
while node.IsInFold do begin
line := node.StartLine - 1; // the 1-based cfCollapsed (last visible) Line
cnt := node.LineCount;
if ((LastStart = line) and (LastCount = cnt)) or (cnt < 0) then begin
// Same node as previous or fully deleted
tmpnode := node.Prev;
aFoldTree.RemoveFoldForNodeAtLine(node, -1); // Don't touch any nested node
if tmpnode.IsInFold then node := tmpnode.Next
else node := aFoldTree.FindFirstFold;
continue;
end;
LastStart := line;
LastCount := cnt;
// look at the 0-based cfCollapsed (visible) Line
if not(FoldEndLevel[line -1] > FoldMinLevel[line - 1]) then begin
// the Fold-Begin of this node has gone
tmpnode := node.Prev;
aFoldTree.RemoveFoldForNodeAtLine(node, -1); // Don't touch any nested node
if tmpnode.IsInFold then node := tmpnode.Next
else node := aFoldTree.FindFirstFold;
continue; // catch nested nodes (now unfolded)
end;
a:= LengthForFoldAtTextIndex(line-1);
if not(cnt = a) then begin
// the Fold-End of this node has gone or moved
tmpnode := node.Prev;
aFoldTree.RemoveFoldForNodeAtLine(node, -1); // Don't touch any nested node
if tmpnode.IsInFold then node := tmpnode.Next
else node := aFoldTree.FindFirstFold; // firstnode
continue; // catch nested nodes (now unfolded)
end;
if (node.fData.Nested <> nil)
and (FixFolding(line, line+1+node.LineCount, aFoldTree.TreeForNestedNode(node.fData.Nested, line+1)))
then continue;
// the node was ok
if node.StartLine >= AMinEnd then break;
node := node.Next;
end;
CalculateMaps;
end;
procedure TSynEditFoldedView.DoCaretChanged(Sender : TObject);
var i : Integer;
begin
if fLockCount > 0 then begin
fNeedCaretCheck := true;
exit;
end;
i := TSynEditCaret(Sender).LinePos-1;
if FoldedAtTextIndex[i] then
UnFoldAtTextIndex(i, true);
end;
procedure TSynEditFoldedView.LineCountChanged(Sender: TSynEditStrings; AIndex, ACount : Integer);
begin
// no need for fix folding => synedit will be called, and scanlines will call fixfolding
{TODO: a "need fix folding" flag => to ensure it will be called if synedit doesnt}
if (fLockCount > 0) and (AIndex < max(fNeedFixFrom, fNeedFixMinEnd)) then begin
// adapt the fixfold range. Could be done smarter, but it doesn't matter if the range gets bigger than needed.
if (ACount < 0) and (AIndex < fNeedFixFrom) then inc(fNeedFixFrom, ACount);
if (ACount > 0) and (AIndex < fNeedFixMinEnd) then inc(fNeedFixMinEnd, ACount);
end;
if ACount<0
then LinesDeletedAtTextIndex(AIndex, -ACount, true)
else LinesInsertedAtTextIndex(AIndex, ACount, true);
end;
procedure TSynEditFoldedView.FixFoldingAtTextIndex(AStartIndex: Integer; AMinEndLine : Integer);
begin
FixFolding(AStartIndex + 1, AMinEndLine, fFoldTree);
end;
function TSynEditFoldedView.ExpandedLineForBlockAtLine(ALine : Integer) : Integer;
var
i, l : Integer;
node: TSynTextFoldAVLNode;
begin
Result := -1;
i := ALine-1;
if (i>0) and (FoldMinLevel[i] < FoldEndLevel[i-1])then begin
if FoldMinLevel[i] < FoldEndLevel[i] then begin
// this is a combined "end begin" line
node := fFoldTree.FindFoldForLine(ALine, true);
if node.IsInFold and (node.StartLine = ALine +1) then
dec(i);
if i < 0 then exit;
end else begin
// this is a "end" line
dec(i);
end;
l := FoldEndLevel[i];
end else if FoldEndLevel[i] = 0 then
exit
else begin
// check if current line is cfCollapsed
node := fFoldTree.FindFoldForLine(ALine, true);
if node.IsInFold and (node.StartLine = ALine +1) then
dec(i);
if i < 0 then exit;
l := FoldEndLevel[i]
end;
while (i > 0) and (FoldMinLevel[i] >= l) do
dec(i);
if (FoldEndLevel[i] > 0) then // TODO, check for collapsed at index = 0
Result := i + 1;
end;
function TSynEditFoldedView.CollapsedLineForFoldAtLine(ALine : Integer) : Integer;
var
node: TSynTextFoldAVLNode;
begin
Result := -1;
node := fFoldTree.FindFoldForLine(ALine, false);
if node.IsInFold then Result := node.StartLine-1;
end;
procedure TSynEditFoldedView.debug;
begin
fFoldTree.debug;
end;
end.