mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-04-05 10:37:58 +02:00
4710 lines
159 KiB
ObjectPascal
4710 lines
159 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+}
|
|
{$ModeSwitch advancedrecords}
|
|
{$ModeSwitch typehelpers}
|
|
{$IFDEF CPUPOWERPC} {$INLINE OFF} {$ENDIF} (* Workaround for bug 12576 (fpc) see bugs.freepascal.org/view.php?id=12576 *)
|
|
|
|
{$IFOPT C+}
|
|
{$DEFINE SynAssertFold}
|
|
{$ENDIF}
|
|
{$IFDEF SynAssert}
|
|
{$DEFINE SynAssertFold}
|
|
{$ENDIF}
|
|
|
|
{$IFDEF SynFoldDebug}
|
|
{$DEFINE SynDebug}
|
|
{$DEFINE SynFoldSaveDebug}
|
|
{$ENDIF}
|
|
{$IFDEF SynFoldSaveDebug}
|
|
{$DEFINE SynDebug}
|
|
{$ENDIF}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils,
|
|
// LCL
|
|
Graphics, LCLType,
|
|
// LazUtils
|
|
LazLoggerBase, LazMethodList,
|
|
// SynEdit
|
|
LazSynEditText, SynEditTypes, SynEditMiscClasses, SynEditMiscProcs,
|
|
SynEditPointClasses, SynEditHighlighter, SynEditHighlighterFoldBase,
|
|
SynEditKeyCmds;
|
|
|
|
type
|
|
|
|
TFoldNodeClassification = (
|
|
fncInvalid,
|
|
fncHighlighter, // Fold provided by HL
|
|
fncHighlighterEx, // Fold originally provided by HL, but no longer exists in HL (text edited)
|
|
fncBlockSelection
|
|
);
|
|
TFoldNodeClassifications = set of TFoldNodeClassification;
|
|
|
|
{ TSynTextFoldAVLNodeData }
|
|
|
|
TSynTextFoldAVLNodeData = class;
|
|
TSynTextFoldAVLNodeData = class(specialize TGenericSynSizedDifferentialAVLNode<TSynTextFoldAVLNodeData>)
|
|
protected
|
|
procedure FreeAllChildrenAndNested;
|
|
public (* Position *)
|
|
(* LineOffset: Line-Number Offset to parent node
|
|
All line numbers are stored as offsets,
|
|
for faster updates if lines are inserted/deleted *)
|
|
property _LineOffset: Integer read FPositionOffset write FPositionOffset;
|
|
|
|
public (* Size *)
|
|
(* FoldedCount: Amount of viewed lines in source for this fold only
|
|
(excluding overlaps) *)
|
|
_FoldedCount : Integer;
|
|
(* MergedLineCount: Amount of lines folded away by this fold-node including _Nested,
|
|
FoldedCount + Lines covered by overlaps
|
|
_MergedVirtualLineCount: Virtual lines for _MergedLineCount
|
|
*)
|
|
_MergedLineCount: Integer;
|
|
property _MergedVirtualLineCount: Integer read FSize write FSize;
|
|
|
|
(* LeftVirtualLineCount: Virtual Lines folded in left tree.
|
|
Used to calculate how many lines are folded up to a specified line *)
|
|
property _LeftVirtualLineCount: Integer read FLeftSizeSum write FLeftSizeSum;
|
|
|
|
procedure UdpateVirtualLineCounts(ANextStrings: TSynEditStrings; AStartLinePos: Integer); inline;
|
|
private
|
|
procedure _DoUdpateVirtualLineCounts(ANextStrings: TSynEditStrings; AStartLinePos: Integer);
|
|
public
|
|
(* Sub-Tree *)
|
|
_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 *)
|
|
|
|
|
|
(* Source Info *)
|
|
_FoldIndex: Integer; (* Index of fold in line; if a line has more than one fold starting *)
|
|
_FoldColumn, _FoldColumnLen: Integer; (* The column (1-based) and len of the keywordm which starts this fold *)
|
|
_FoldTypeCompatible: Pointer; (* help identifying in FixFolding *)
|
|
_Classification: TFoldNodeClassification;
|
|
_VisibleLines: Integer; (* Visible Source lines, containing the "fold keyword"
|
|
0: Hiden block (the fold-keyword is inside the fold)
|
|
1: Normal fold (There is *1* visible line with the fold-keyword)
|
|
*)
|
|
end;
|
|
|
|
{ TSynTextFoldAVLNode }
|
|
|
|
TSynTextFoldAVLNode = specialize TGenericSynSizedDifferentialAVLNodeHolder<TSynTextFoldAVLNodeData>;
|
|
|
|
{ TSynTextFoldAVLNodeHelper }
|
|
|
|
TSynTextFoldAVLNodeHelper = record helper for TSynTextFoldAVLNode
|
|
strict private
|
|
function GetClassification: TFoldNodeClassification;
|
|
function GetFoldColumn: Integer;
|
|
function GetFoldColumnLen: Integer;
|
|
function GetFoldedBefore: Integer; inline;
|
|
function GetFoldIndex: Integer;
|
|
function GetMergedLineCount : Integer;
|
|
function GetFoldedCount : Integer;
|
|
function GetSourceLine: integer;
|
|
function GetSourceLineOffset: integer;
|
|
procedure SetFoldColumn(const AValue: Integer);
|
|
public
|
|
procedure UdpateVirtualLineCounts(ANextStrings: TSynEditStrings); inline;
|
|
function IsInFold : Boolean;
|
|
|
|
property MergedLineCount: Integer read GetMergedLineCount; // Zero, if Not in a fold
|
|
property FoldedCount: Integer read GetFoldedCount; // Zero, if Not in a fold
|
|
property FoldedBefore: Integer read GetFoldedBefore; // Count of Lines folded before Startline
|
|
|
|
function IsHide: Boolean;
|
|
property FoldIndex: Integer read GetFoldIndex;
|
|
property FoldColumn: Integer read GetFoldColumn write SetFoldColumn;
|
|
property FoldColumnLen: Integer read GetFoldColumnLen;
|
|
property SourceLine: integer read GetSourceLine; // The SourceLine with the fold-keyword
|
|
property SourceLineOffset: integer read GetSourceLineOffset; // The SourceLine with the fold-keyword
|
|
property Classification: TFoldNodeClassification read GetClassification;
|
|
end;
|
|
|
|
{ TSynTextFoldAVLNodeNestedIterator:
|
|
Iterates included nested nodes
|
|
FoldedBefore is not valid in nested nodes
|
|
}
|
|
|
|
TSynTextFoldAVLNodeNestedIterator = class
|
|
private
|
|
FCurrentNode: TSynTextFoldAVLNode;
|
|
FOuterNodes: Array of TSynTextFoldAVLNode;
|
|
public
|
|
constructor Create(ANode: TSynTextFoldAVLNode);
|
|
destructor Destroy; override;
|
|
function Next: TSynTextFoldAVLNode;
|
|
function Prev: TSynTextFoldAVLNode;
|
|
function EOF: Boolean;
|
|
function BOF: Boolean;
|
|
function IsInFold: Boolean;
|
|
property Node: TSynTextFoldAVLNode read FCurrentNode;
|
|
end;
|
|
|
|
{ TSynTextFoldAVLTree
|
|
- Nodes in the tree cover the folded lines only.
|
|
The (visible) cfCollapsed line at the start of a fold, is *not* part of a node.
|
|
- In the public methods "ALine" indicates the first invisible/hidden line
|
|
- TSynEditFoldedView uses this with 1-based lines (ToDo: make 0-based)
|
|
}
|
|
|
|
TSynTextFoldAVLTree = class(specialize TGenericSynSizedDifferentialAVLTree<TSynTextFoldAVLNode, TSynTextFoldAVLNodeData>)
|
|
protected
|
|
fNestParent: TSynTextFoldAVLNodeData;
|
|
fNestedNodesTree: TSynTextFoldAVLTree; // FlyWeight Tree used for any nested subtree.
|
|
FNextStrings: TSynEditStrings; //TSynEditStringsLinked; // For mapping virtual lines
|
|
|
|
function NewNode : TSynTextFoldAVLNodeData; inline;
|
|
Function RemoveFoldForNodeAtLine(ANode: TSynTextFoldAVLNode;
|
|
ALine : Integer) : Integer; overload; // Line is for Nested Nodes
|
|
|
|
// SetRoot, does not obbey fRootOffset => use SetRoot(node, -fRootOffset)
|
|
procedure SetRoot(ANode : TSynSizedDifferentialAVLNode); overload; override;
|
|
procedure SetRoot(ANode : TSynSizedDifferentialAVLNode; anAdjustChildLineOffset : Integer); overload; override;
|
|
|
|
Function InsertNode(ANode : TSynTextFoldAVLNodeData) : Integer; reintroduce; // returns FoldedBefore // ANode may not have children
|
|
function TreeForNestedNode(ANode: TSynTextFoldAVLNodeData; aOffset : Integer) : TSynTextFoldAVLTree;
|
|
public
|
|
constructor Create;
|
|
destructor Destroy; override;
|
|
procedure Clear; override;
|
|
|
|
(* 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, AFoldIndex, AColumn, AColumnLen, ACount, AVisibleLines: Integer;
|
|
AClassification: TFoldNodeClassification;
|
|
AFoldTypeCompatible: Pointer
|
|
) : 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; OnlyCol: Integer = -1) : Integer; overload;
|
|
Procedure AdjustForLinesInserted(AStartLine, ALineCount, ABytePos: Integer);
|
|
Procedure AdjustForLinesDeleted(AStartLine, ALineCount, ABytePos: Integer);
|
|
procedure AdjustColumn(ALine, ABytePos, ACount: Integer; InLineBreak: boolean = False);
|
|
function LastPage: TSynTextFoldAVLNode; reintroduce; // This is AFTER last page / includes the last page's size in FoldedBefore.
|
|
Function LastFoldedLine : integer; // The actual line; LastNode.StartLine + LastNode.LineCount - 1
|
|
{$IFDEF SynDebug}
|
|
procedure Debug; reintroduce;
|
|
{$ENDIF}
|
|
end;
|
|
|
|
{ TSynFoldNodeInfoHelper }
|
|
|
|
TSynFoldNodeInfoHelper = class
|
|
FCurInfo: TSynFoldNodeInfo;
|
|
FActions: TSynFoldActions;
|
|
FHighlighter: TSynCustomFoldHighlighter;
|
|
protected
|
|
procedure Invalidate;
|
|
public
|
|
constructor Create(AHighlighter: TSynCustomFoldHighlighter);
|
|
|
|
function FirstOpen: TSynFoldNodeInfo;
|
|
function Next: TSynFoldNodeInfo;
|
|
function Prev: TSynFoldNodeInfo;
|
|
function FindClose: TSynFoldNodeInfo;
|
|
function GotoOpenPos(aLineIdx, aNodeIdx: integer): TSynFoldNodeInfo;
|
|
function GotoOpenAtChar(aLineIdx, aXPos: integer): TSynFoldNodeInfo;
|
|
function GotoNodeOpenPos(ANode : TSynTextFoldAVLNode): TSynFoldNodeInfo;
|
|
function GotoNodeClosePos(ANode : TSynTextFoldAVLNode): TSynFoldNodeInfo;
|
|
function IsAtNodeOpenPos(ANode : TSynTextFoldAVLNode): Boolean;
|
|
function IsValid: Boolean;
|
|
function Equals(AnInfo: TSynFoldNodeInfo): Boolean;
|
|
function Equals(AHelper: TSynFoldNodeInfoHelper): Boolean;
|
|
|
|
property Info: TSynFoldNodeInfo read FCurInfo write FCurInfo;
|
|
property Actions: TSynFoldActions read FActions write FActions;
|
|
end;
|
|
|
|
TFoldChangedEvent = procedure(aLine: Integer) of object;
|
|
TInvalidateLineProc = procedure(FirstLine, LastLine: integer) of object;
|
|
|
|
TFoldViewNodeInfo = record
|
|
HNode: TSynFoldNodeInfo; // Highlighter Node
|
|
IsFold, IsHide: Boolean;
|
|
Text, Keyword: String;
|
|
LineNum, ColIndex: Integer;
|
|
OpenCount: Integer; // Highlighter-Nodes opening on this line (limited to the FoldGroup requested)
|
|
end;
|
|
|
|
TSynEditFoldLineCapability = (
|
|
// Capabilities of Line
|
|
cfFoldStart, cfHideStart,
|
|
cfFoldBody,
|
|
cfFoldEnd,
|
|
// State indicators
|
|
cfCollapsedFold,
|
|
cfCollapsedHide, // lines hidden, after this line
|
|
// Special flags
|
|
cfSingleLineHide,
|
|
cfNone
|
|
);
|
|
TSynEditFoldLineCapabilities = set of TSynEditFoldLineCapability;
|
|
TSynEditFoldType = (scftOpen, scftFold, scftHide, scftAll, scftInvalid);
|
|
|
|
TSynEditFoldLineMapInfo = record
|
|
Capability: TSynEditFoldLineCapabilities;
|
|
Classifications :TFoldNodeClassifications;
|
|
FoldedBefore: integer;
|
|
end;
|
|
|
|
{$IFDEF SynFoldSaveDebug}
|
|
const
|
|
SynEditFoldTypeNames: Array [TSynEditFoldType] of string =
|
|
('scftOpen', 'scftFold', 'scftHide', 'scftAll', 'scftInvalid');
|
|
type
|
|
{$ENDIF}
|
|
|
|
{ TSynEditFoldProvider }
|
|
TSynEditFoldProviderNodeInfo = record
|
|
LineCount: Integer;
|
|
Column, ColumnLen: Integer;
|
|
DefaultCollapsed: Boolean;
|
|
FoldTypeCompatible: Pointer; // eg begin, var, procedure
|
|
FoldGroup: Integer; // eg.: pas, region, ifdef
|
|
Classification: TFoldNodeClassification;
|
|
end;
|
|
|
|
TSynEditFoldProviderNodeInfoList = array of TSynEditFoldProviderNodeInfo;
|
|
TSynEditFoldProvider = class;
|
|
|
|
TSynEditFoldProvider = class
|
|
private
|
|
FEnabled: boolean;
|
|
FHighlighter: TSynCustomFoldHighlighter;
|
|
FLines : TSynEditStrings;
|
|
FEdit: TSynEditBase;
|
|
FNestedFoldsList: TLazSynEditNestedFoldsList;
|
|
function GetFoldsAvailable: Boolean;
|
|
function GetHighLighterWithLines: TSynCustomFoldHighlighter;
|
|
function GetLineCapabilities(ALineIdx: Integer): TSynEditFoldLineCapabilities;
|
|
function GetLineClassification(ALineIdx: Integer): TFoldNodeClassifications;
|
|
function GetNestedFoldsList: TLazSynEditNestedFoldsList;
|
|
procedure SetHighLighter(const AValue: TSynCustomFoldHighlighter);
|
|
procedure SetLines(AValue: TSynEditStrings);
|
|
protected
|
|
property HighLighterWithLines: TSynCustomFoldHighlighter read GetHighLighterWithLines;
|
|
public
|
|
constructor Create;
|
|
destructor Destroy; override;
|
|
|
|
// Info about Folds opening on ALineIdx
|
|
function FoldOpenCount(ALineIdx: Integer; AType: Integer = 0): Integer;
|
|
function FoldOpenInfo(ALineIdx, AFoldIdx: Integer; AType: Integer = 0): TSynFoldNodeInfo;
|
|
//property FoldOpenInfo[ALineIdx, AColumnIdx: Integer]: Integer read GetFoldOpenInfo;
|
|
|
|
function FoldLineLength(ALine, AFoldIndex: Integer): integer;
|
|
function InfoForFoldAtTextIndex(ALine, AFoldIndex : Integer;
|
|
HideLen: Boolean = False;
|
|
NeedLen: Boolean = True): TSynEditFoldProviderNodeInfo;
|
|
function InfoListForFoldsAtTextIndex(ALine: Integer; NeedLen: Boolean = False): TSynEditFoldProviderNodeInfoList;
|
|
|
|
property LineCapabilities[ALineIdx: Integer]: TSynEditFoldLineCapabilities
|
|
read GetLineCapabilities;
|
|
property LineClassification[ALineIdx: Integer]: TFoldNodeClassifications
|
|
read GetLineClassification;
|
|
property Lines: TSynEditStrings read FLines write SetLines;
|
|
property HighLighter: TSynCustomFoldHighlighter read FHighlighter write SetHighLighter;
|
|
property FoldsAvailable: Boolean read GetFoldsAvailable;
|
|
property NestedFoldsList: TLazSynEditNestedFoldsList read GetNestedFoldsList;
|
|
property Enabled: boolean read FEnabled write FEnabled;
|
|
end;
|
|
|
|
{ TFoldChangedHandlerList }
|
|
|
|
TFoldChangedHandlerList = class(TMethodList)
|
|
public
|
|
procedure CallFoldChangedEvents(AnIndex: Integer);
|
|
end;
|
|
|
|
TSynEditFoldedView = class;
|
|
|
|
{ TLazSynDisplayFold }
|
|
|
|
TLazSynDisplayFold = class(TLazSynDisplayViewEx)
|
|
private
|
|
FFoldView: TSynEditFoldedView;
|
|
FLineState: integer;
|
|
FTokenAttr: TSynHighlighterAttributesModifier;
|
|
FMarkupLine: TSynSelectedColorMergeResult;
|
|
FLineFlags, FLineFlags2: TSynEditFoldLineCapabilities;
|
|
public
|
|
constructor Create(AFoldView: TSynEditFoldedView);
|
|
destructor Destroy; override;
|
|
procedure SetHighlighterTokensLine(ALine: TLineIdx; out ARealLine: TLineIdx; out ASubLineIdx, AStartBytePos, AStartPhysPos, ALineByteLen: Integer); override;
|
|
function GetNextHighlighterToken(out ATokenInfo: TLazSynDisplayTokenInfo): Boolean; override;
|
|
function GetLinesCount: Integer; override;
|
|
|
|
function TextToViewIndex(ATextIndex: TLineIdx): TLineRange; override;
|
|
function ViewToTextIndex(AViewIndex: TLineIdx): TLineIdx; override;
|
|
function ViewToTextIndexEx(AViewIndex: TLineIdx; out AViewRange: TLineRange): TLineIdx; override;
|
|
end;
|
|
|
|
{ TSynTextFoldedView
|
|
*ViewPos = Line (1-based) in the array of viewable/visible lines
|
|
*TextIndex = Line (0-based) in the complete text(folded and unfolded)
|
|
}
|
|
|
|
TSynEditFoldedViewFlag = (fvfNeedCaretCheck, fvfNeedCalcMaps);
|
|
TSynEditFoldedViewFlags = set of TSynEditFoldedViewFlag;
|
|
|
|
{ TSynEditFoldedView }
|
|
|
|
TSynEditFoldedView = class(TSynEditStringsLinked)
|
|
private
|
|
FOwner: TSynEditBase;
|
|
fCaret: TSynEditCaret;
|
|
FFoldProvider: TSynEditFoldProvider;
|
|
fFoldTree : TSynTextFoldAVLTree; // Folds are stored 1-based (the 1st line is 1)
|
|
FMarkupInfoFoldedCode: TSynSelectedColor;
|
|
FMarkupInfoFoldedCodeLine: TSynSelectedColor;
|
|
FMarkupInfoHiddenCodeLine: TSynSelectedColor;
|
|
fTopViewPos : TLinePos;
|
|
fLinesInWindow : Integer; // there may be an additional part visible line
|
|
fFoldTypeList : Array of TSynEditFoldLineMapInfo; // Index 0 means one line *above* fTopViewPos
|
|
fLockCount : Integer;
|
|
fNeedFixFrom, fNeedFixMinEnd : Integer;
|
|
FFlags: TSynEditFoldedViewFlags;
|
|
FInTopLineChanged: Boolean;
|
|
FDisplayView: TLazSynDisplayFold;
|
|
|
|
function GetFoldClasifications(index : Integer): TFoldNodeClassifications;
|
|
function GetHighLighter: TSynCustomHighlighter;
|
|
function GetDisplayNumber(index : Integer) : Integer; deprecated 'To be removed in 5.99 / Use DisplayView.ViewToTextIndex';
|
|
function GetTextIndex(index : Integer) : Integer; deprecated 'To be removed in 5.99 / Use ScreenLineToTextIndex';
|
|
function GetFoldType(index : Integer) : TSynEditFoldLineCapabilities;
|
|
function IsFolded(index : integer) : Boolean; // TextIndex
|
|
procedure ProcessMySynCommand(Sender: TObject; AfterProcessing: boolean;
|
|
var Handled: boolean; var Command: TSynEditorCommand;
|
|
var AChar: TUTF8Char; Data: pointer; HandlerData: pointer);
|
|
procedure SetHighLighter(AValue: TSynCustomHighlighter);
|
|
procedure SetTopViewPos(const ALine : integer);
|
|
function GetTopTextIndex : integer;
|
|
procedure SetTopTextIndex(AIndex : integer);
|
|
procedure SetLinesInWindow(const AValue : integer);
|
|
procedure DoFoldChanged(AnIndex: Integer);
|
|
function TextIndexAddLines(aTextIndex, LineOffset : Integer) : Integer; (* Add/Sub to/from TextIndex (0-based) skipping folded *)
|
|
procedure VirtualMappingChanged(Sender: TSynEditStrings; aIndex, aCount: Integer);
|
|
protected
|
|
procedure SetManager(AManager: TSynTextViewsManager); override;
|
|
procedure SetSynStrings(AValue: TSynEditStrings); override;
|
|
function GetViewedLines(index : Integer) : String; override; // TODO: not used?
|
|
function GetViewedCount: integer; override;
|
|
function GetDisplayView: TLazSynDisplayView; override;
|
|
procedure InternalGetInfoForViewedXY(AViewedXY: TViewedPoint;
|
|
AFlags: TViewedXYInfoFlags; out AViewedXYInfo: TViewedXYInfo;
|
|
ALogPhysConvertor: TSynLogicalPhysicalConvertor); override;
|
|
procedure DoBlockSelChanged(Sender: TObject; Changes: TSynStatusChanges);
|
|
Procedure CalculateMaps;
|
|
function FoldNodeAtTextIndex(AStartIndex, ColIndex: Integer): TSynTextFoldAVLNode; (* Returns xth Fold at nth TextIndex (all lines in buffer) / 1-based *)
|
|
function FixFolding(AStart : Integer; AMinEnd : Integer; aFoldTree : TSynTextFoldAVLTree) : Boolean;
|
|
|
|
procedure DoCaretChanged(Sender : TObject);
|
|
Procedure LineCountChanged(Sender: TSynEditStrings; AIndex, ACount : Integer);
|
|
Procedure LinesCleared(Sender: TObject);
|
|
Procedure LineEdited(Sender: TSynEditStrings; aLinePos, aBytePos, aCount,
|
|
aLineBrkCnt: Integer; aText: String);
|
|
Procedure LinesInsertedAtTextIndex(AStartIndex, ALineCount, ABytePos: Integer;
|
|
SkipFixFolding : Boolean = False);
|
|
Procedure LinesDeletedAtTextIndex(AStartIndex, ALineCount, ABytePos: Integer;
|
|
SkipFixFolding : Boolean = False);
|
|
property FoldTree: TSynTextFoldAVLTree read fFoldTree;
|
|
public
|
|
constructor Create(AOwner: TSynEditBase; ACaret: TSynEditCaret);
|
|
destructor Destroy; override;
|
|
|
|
// Converting between Folded and Unfolded Lines/Indexes
|
|
function TextToViewIndex(aTextIndex : TLineIdx) : TLineIdx; override; (* Convert TextIndex (0-based) to ViewIndex (0-based) *)
|
|
function ViewToTextIndex(aViewIndex : TLineIdx) : TLineIdx; override; (* Convert ViewIndex (0-based) to TextIndex (0-based) *)
|
|
function TextXYToViewXY(APhysTextXY: TPhysPoint): TViewedPoint; override;
|
|
function ViewXYToTextXY(APhysViewXY: TViewedPoint): TPhysPoint; override;
|
|
|
|
function InternTextToViewIndex(aTextIndex : TLineIdx) : TLineIdx; (* Convert TextIndex (0-based) to ViewIndex (0-based) *)
|
|
function InternTextToViewIndexOffset(var aTextIndex : TLineIdx): integer; (* Offset (subtract) to Convert TextIndex (0-based) to ViewIndex (0-based) *)
|
|
function InternViewToTextIndex(aViewIndex : TLineIdx) : TLineIdx; (* Convert ViewIndex (0-based) to TextIndex (0-based) *)
|
|
function InternViewToTextIndexOffest(aViewIndex : TLineIdx) : integer; (* Offset (add) to Convert ViewIndex (0-based) to TextIndex (0-based) *)
|
|
|
|
function TextIndexToScreenLine(aTextIndex : Integer) : Integer; deprecated 'To be removed in 5.99 / Use TextToViewIndex(index)-TopView or TextXYToScreenXY / no support for use with wrap'; (* Convert TextIndex (0-based) to Screen (0-based) *)
|
|
function ScreenLineToTextIndex(aLine : Integer) : Integer; deprecated 'To be removed in 5.99 / Use ViewToTextIndex(index+TopView) or ScreenXYToTextXY / no support for use with wrap'; (* Convert Screen (0-based) to TextIndex (0-based) *)
|
|
|
|
function AddVisibleOffsetToTextIndex(aTextIndex: TLineIdx; LineOffset: Integer): TLineIdx; override;
|
|
function IsTextIdxVisible(aTextIndex: TLineIdx): Boolean; override;
|
|
|
|
// Attributes for Visible-Lines-On-screen
|
|
property DisplayNumber[index : Integer] : Integer (* LineNumber for display in Gutter / result is 1-based *)
|
|
read GetDisplayNumber; deprecated 'To be removed in 5.99 / Use DisplayView.ViewToTextIndex / no support for use with wrap';
|
|
property FoldType[index : Integer] : TSynEditFoldLineCapabilities (* FoldIcon / State *)
|
|
read GetFoldType;
|
|
property FoldClasifications[index : Integer] : TFoldNodeClassifications (* FoldIcon / State *)
|
|
read GetFoldClasifications;
|
|
property TextIndex[index : Integer] : Integer (* Position in SynTextBuffer / result is 0-based *)
|
|
read GetTextIndex; deprecated 'To be removed in 5.99 / Use ViewToTextIndex(index+TopView) / no support for use with wrap';
|
|
|
|
// Define Visible Area
|
|
property TopViewPos : integer (* refers to visible (unfolded) lines / 1-based *)
|
|
read fTopViewPos write SetTopViewPos;
|
|
property TopLine : integer read fTopViewPos write SetTopViewPos; deprecated 'To be removed in 6.99 / Use TopViewPos)';
|
|
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 MarkupInfoFoldedCode: TSynSelectedColor read FMarkupInfoFoldedCode;
|
|
property MarkupInfoFoldedCodeLine: TSynSelectedColor read FMarkupInfoFoldedCodeLine;
|
|
property MarkupInfoHiddenCodeLine: TSynSelectedColor read FMarkupInfoHiddenCodeLine;
|
|
public
|
|
procedure Lock;
|
|
procedure UnLock;
|
|
{$IFDEF SynDebug}
|
|
procedure debug;
|
|
{$ENDIF}
|
|
(* Arguments for (Un)FoldAt* (Line, ViewPos, TextIndex):
|
|
- ColumnIndex (0-based)
|
|
Can be negative, to access the highest(-1) available, 2nd highest(-2) ...
|
|
If negative, count points downward
|
|
- ColCount = 0 => all
|
|
- Skip => Do not count nodes that are already in the desired state
|
|
(or can not archive the desired state: e.g. can not hide)
|
|
- AVisibleLines: 0 = Hide / 1 = Fold
|
|
*)
|
|
procedure FoldAtLine(AStartLine: Integer; ColIndex : Integer = -1; (* Folds at ScreenLine / 0-based *)
|
|
ColCount : Integer = 1; Skip: Boolean = False;
|
|
AVisibleLines: Integer = 1);
|
|
procedure FoldAtViewPos(AStartPos: Integer; ColIndex : Integer = -1; (* Folds at nth visible/unfolded Line / 1-based *)
|
|
ColCount : Integer = 1; Skip: Boolean = False;
|
|
AVisibleLines: Integer = 1);
|
|
procedure FoldAtTextIndex(AStartIndex: Integer; ColIndex : Integer = -1; (* Folds at nth TextIndex (all lines in buffer) / 1-based *)
|
|
ColCount : Integer = 1; Skip: Boolean = False;
|
|
AVisibleLines: Integer = 1);
|
|
procedure UnFoldAtLine(AStartLine: Integer; ColIndex : Integer = -1; (* UnFolds at ScreenLine / 0-based *)
|
|
ColCount : Integer = 0; Skip: Boolean = False;
|
|
AVisibleLines: Integer = 1);
|
|
procedure UnFoldAtViewPos(AStartPos: Integer; ColIndex : Integer = -1; (* UnFolds at nth visible/unfolded Line / 1-based *)
|
|
ColCount : Integer = 0; Skip: Boolean = False;
|
|
AVisibleLines: Integer = 1);
|
|
procedure UnFoldAtTextIndex(AStartIndex: Integer; ColIndex : Integer = -1; (* UnFolds at nth TextIndex (all lines in buffer) / 1-based *)
|
|
ColCount : Integer = 0; Skip: Boolean = False;
|
|
AVisibleLines: Integer = 1);
|
|
procedure UnFoldAtTextIndexCollapsed(AStartIndex: Integer); (* UnFolds only if Index is in the fold, ignores cfcollapsed line, if unfolded / 1-based *)
|
|
|
|
function LogicalPosToNodeIndex(AStartIndex: Integer; LogX: Integer; (* Returns the index of the node, at the logical char pos *)
|
|
Previous: Boolean = False): Integer;
|
|
|
|
procedure CollapseDefaultFolds;
|
|
// Load/Save folds to string
|
|
// AStartIndex, AEndIndex: (0 based) First/last line (EndIndex = -1 = open end)
|
|
// AStartCol, AEndCol: (1 based) Logical text pos in Line. (AEndCol = -1 = full line)
|
|
function GetFoldDescription(AStartIndex, AStartCol, AEndIndex,
|
|
AEndCol: Integer; AsText: Boolean = False;
|
|
Extended: Boolean = False) :String;
|
|
procedure ApplyFoldDescription(AStartIndex, AStartCol, AEndIndex,
|
|
AEndCol: Integer; FoldDesc: PChar;
|
|
FoldDescLen: Integer; IsText: Boolean = False);
|
|
|
|
procedure UnfoldAll;
|
|
procedure FoldAll(StartLevel : Integer = 0; IgnoreNested : Boolean = False);
|
|
procedure FixFoldingAtTextIndex(AStartIndex: Integer; AMinEndLine: Integer = 0); // Real/All lines
|
|
public
|
|
function OpenFoldCount(aStartIndex: Integer; AType: Integer = 0): Integer;
|
|
function OpenFoldInfo(aStartIndex, ColIndex: Integer; AType: Integer = 0): TFoldViewNodeInfo;
|
|
|
|
public
|
|
// 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; HalfExpanded: Boolean = True) : Integer;
|
|
|
|
function GetPhysicalCharWidths(Index: Integer): TPhysicalCharWidths;
|
|
|
|
function IsFoldedAtTextIndex(AStartIndex, ColIndex: Integer): Boolean; (* Checks xth Fold at nth TextIndex (all lines in buffer) / 1-based *)
|
|
property FoldedAtTextIndex [index : integer] : Boolean read IsFolded;
|
|
|
|
property HighLighter: TSynCustomHighlighter read GetHighLighter
|
|
write SetHighLighter;
|
|
property FoldProvider: TSynEditFoldProvider read FFoldProvider;
|
|
end;
|
|
|
|
function dbgs(AClassification: TFoldNodeClassification): String; overload;
|
|
function dbgs(AFoldLineCapability: TSynEditFoldLineCapability): String; overload;
|
|
|
|
implementation
|
|
|
|
//var
|
|
// SYN_FOLD_DEBUG: PLazLoggerLogGroup;
|
|
|
|
type
|
|
TFoldExportEntry = Record
|
|
// Lines and Pos (o 1st line) are relative to Scan-Start
|
|
Line, LogX, LogX2: Integer; // StartLine and Pos
|
|
ELine, ELogX, ELogX2: Integer; // EndLine and pos
|
|
FType: Integer; // e.g ord(cfbtBeginEnd)
|
|
LinesFolded: Integer; // Lines Folded according to AVL-Node
|
|
end;
|
|
|
|
{ TSynEditFoldExportStream }
|
|
|
|
TSynEditFoldExportStream = class
|
|
private
|
|
FData: String;
|
|
FLen, FPos: Integer;
|
|
FMem: PChar;
|
|
function GetLen: Integer;
|
|
procedure SetLen(const AValue: Integer);
|
|
function GetMem: PChar;
|
|
procedure SetMem(const AValue: PChar);
|
|
function GetText: String;
|
|
procedure SetText(const AValue: String);
|
|
protected
|
|
function GrowData(AppendSize: Integer): PChar;
|
|
function EncodeIntEx(Anum: Integer): String; // base 43, with leading continue bit
|
|
function EncodeIntEx2(Anum: Integer): String; // for numbers expected below 467; specially 0..80
|
|
function InternalReadNum(var APos: Integer): Integer;
|
|
function InternalReadNumEx(var APos: Integer): Integer;
|
|
public
|
|
constructor Create;
|
|
procedure Compress;
|
|
procedure Decompress;
|
|
|
|
procedure AddChecksum;
|
|
function VerifyChecksum: Boolean;
|
|
|
|
// see notes for Compression
|
|
Procedure AppendMem(AMem: Pointer; ALen: Integer);
|
|
Procedure AppendString(ATxt: String);
|
|
Procedure AppendNum(ANum: Integer);
|
|
Procedure AppendNumEx(ANum: Integer);
|
|
|
|
Procedure Reset;
|
|
Procedure Clear;
|
|
function ReadMem(AMem: Pointer; ALen: Integer): Boolean;
|
|
function PeakString(ALen: Integer): String;
|
|
function FindChar(AChar: Char): Integer; // 0 based
|
|
function ReadString(ALen: Integer): String;
|
|
function ReadNum: Integer;
|
|
function ReadNumEx: Integer;
|
|
function EOF: Boolean;
|
|
|
|
property Text: String read GetText write SetText;
|
|
property Mem: PChar read GetMem write SetMem;
|
|
property Len: Integer read GetLen write SetLen;
|
|
property Pos: Integer read FPos;
|
|
end;
|
|
|
|
TSynEditFoldExportCoderEntry = record
|
|
aX, aY, aLen: Integer;
|
|
aFoldType: TSynEditFoldType;
|
|
end;
|
|
TSynEditFoldExportCoderStates =
|
|
(sfecAtBegin, sfecAtPoint, sfecInRepeatCount, sfecInvalid, sfecAtEOF);
|
|
{$IFDEF SynFoldSaveDebug}
|
|
const
|
|
SynEditFoldExportCoderStates: Array [TSynEditFoldExportCoderStates] of String =
|
|
('sfecAtBegin', 'sfecAtPoint', 'sfecInRepeatCount', 'sfecInvalid', 'sfecAtEOF');
|
|
type
|
|
{$ENDIF}
|
|
|
|
{ TSynEditFoldExportCoder }
|
|
|
|
TSynEditFoldExportCoder = class
|
|
private
|
|
FExportStream: TSynEditFoldExportStream;
|
|
FFoldType: Pointer;
|
|
|
|
FReadY, FReadLastY, FReadX, FReadSumLen, FReadCount: Integer;
|
|
FReadType: TSynEditFoldType;
|
|
FReadDefaultType: TSynEditFoldType;
|
|
FReadState: TSynEditFoldExportCoderStates;
|
|
|
|
FWriteCache: Array of TSynEditFoldExportCoderEntry;
|
|
FWriteCacheLen: Integer;
|
|
FWriteCacheTypes: set of TSynEditFoldType;
|
|
function GetReadIsValid: Boolean;
|
|
public
|
|
constructor Create(AFoldType: Pointer);
|
|
constructor Create(AStream: TSynEditFoldExportStream);
|
|
destructor Destroy; override;
|
|
|
|
procedure AddNode(aX, aY, aLen: Integer; aFoldType: TSynEditFoldType);
|
|
procedure Finish;
|
|
|
|
function ReadNode(aX, aY: Integer; aLen: Integer): TSynEditFoldType;
|
|
function EOF: Boolean;
|
|
procedure Reset;
|
|
property ReadIsValid: Boolean read GetReadIsValid;
|
|
|
|
property FoldType: Pointer read FFoldType;
|
|
property Stream: TSynEditFoldExportStream read FExportStream;
|
|
end;
|
|
|
|
const
|
|
// use only xml encode-able ascii
|
|
// do not use [ or ], they are reserved for compression
|
|
// space can be used a special indicator
|
|
NumEncode86Chars: string[86] = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-+;:,.@=*/\!?$%()''^{}~_#';
|
|
|
|
NumEncodeAsOneMax = 80; // Maximum Value to encode as 1 char
|
|
NumEncodeAsTwoMax = 81 + 4*86 + 43; // = 467; Maximum Value to encode as 2 char
|
|
NumEncodeAsThreeMax = 81 + 4*86 + 43 * 43 - 1; // = 2273 Maximum Value to encode as 3 char
|
|
|
|
|
|
SEQMaxNodeCount = 75; // New Full entry at least every 75 folds
|
|
SEQMaxLineDistEach = 500; // New Full entry, if folds startlines are more than 500 appart
|
|
SEQMaxLineDistTotal = 2500; // New Full entry at least every 2500; check position
|
|
|
|
var
|
|
NumEncode86Values: Array [Char] of integer;
|
|
|
|
procedure InitNumEncodeValues;
|
|
var
|
|
i: integer;
|
|
c : Char;
|
|
begin
|
|
for c := low(Char) to high(Char) do begin
|
|
NumEncode86Values[c] := -1;
|
|
end;
|
|
for i := 1 to length(NumEncode86Chars) do
|
|
NumEncode86Values[NumEncode86Chars[i]] := i - 1;
|
|
end;
|
|
|
|
{ TFoldChangedHandlerList }
|
|
|
|
procedure TFoldChangedHandlerList.CallFoldChangedEvents(AnIndex: Integer);
|
|
var
|
|
i: LongInt;
|
|
begin
|
|
i:=Count;
|
|
while NextDownIndex(i) do
|
|
TFoldChangedEvent(Items[i])(AnIndex);
|
|
end;
|
|
|
|
{ TLazSynDisplayFold }
|
|
|
|
constructor TLazSynDisplayFold.Create(AFoldView: TSynEditFoldedView);
|
|
begin
|
|
inherited Create;
|
|
FFoldView := AFoldView;
|
|
FTokenAttr := TSynHighlighterAttributesModifier.Create(nil);
|
|
FMarkupLine := TSynSelectedColorMergeResult.Create(nil);
|
|
end;
|
|
|
|
destructor TLazSynDisplayFold.Destroy;
|
|
begin
|
|
FreeAndNil(FTokenAttr);
|
|
FreeAndNil(FMarkupLine);
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TLazSynDisplayFold.SetHighlighterTokensLine(ALine: TLineIdx; out ARealLine: TLineIdx;
|
|
out ASubLineIdx, AStartBytePos, AStartPhysPos, ALineByteLen: Integer);
|
|
begin
|
|
FLineState := 0;
|
|
CurrentTokenLine := ALine;
|
|
|
|
inherited SetHighlighterTokensLine(FFoldView.InternViewToTextIndex(ALine), ARealLine, ASubLineIdx, AStartBytePos, AStartPhysPos, ALineByteLen);
|
|
|
|
FLineFlags := FFoldView.FoldType[CurrentTokenLine - ASubLineIdx + 1 - FFoldView.TopViewPos] * [cfCollapsedFold, cfCollapsedHide];
|
|
FLineFlags2 := FLineFlags;
|
|
|
|
if not FFoldView.MarkupInfoFoldedCodeLine.IsEnabled then
|
|
Exclude(FLineFlags2, cfCollapsedFold);
|
|
if not FFoldView.MarkupInfoHiddenCodeLine.IsEnabled then
|
|
Exclude(FLineFlags2, cfCollapsedHide);
|
|
|
|
if (FLineFlags2 <> []) then begin
|
|
FFoldView.MarkupInfoFoldedCodeLine.SetFrameBoundsLog(1, MaxInt, 0);
|
|
FFoldView.MarkupInfoHiddenCodeLine.SetFrameBoundsLog(1, MaxInt, 0);
|
|
end;
|
|
end;
|
|
|
|
function TLazSynDisplayFold.GetNextHighlighterToken(out ATokenInfo: TLazSynDisplayTokenInfo): Boolean;
|
|
const
|
|
MarkSpaces: string = ' ';
|
|
MarkDots: string = '...';
|
|
LSTATE_BOL = 0; // at BOL
|
|
LSTATE_TEXT = 1; // in text
|
|
LSTATE_BOL_GAP = 2; // BOL and in Gap (empty line) // must be LSTATE_BOL + 2
|
|
LSTATE_GAP = 3; // In Gap betwen txt and dots // must be LSTATE_TEXT + 2
|
|
LSTATE_DOTS = 4; // In Dots
|
|
LSTATE_EOL = 5; // at start of EOL
|
|
var
|
|
EolAttr: TSynHighlighterAttributes;
|
|
MergeStartX, MergeEndX: TLazSynDisplayTokenBound;
|
|
begin
|
|
case FLineState of
|
|
LSTATE_BOL, LSTATE_TEXT: begin
|
|
Result := inherited GetNextHighlighterToken(ATokenInfo);
|
|
if (ATokenInfo.TokenOrigin = dtoAfterText) and
|
|
( (not Result) or (ATokenInfo.TokenStart = nil)) and (FLineFlags <> [])
|
|
then begin
|
|
inc(FLineState, 2); // LSTATE_BOL_GAP(2), if was at bol // LSTATE_GAP(3) otherwise
|
|
ATokenInfo.TokenStart := PChar(MarkSpaces);
|
|
ATokenInfo.TokenLength := 3;
|
|
ATokenInfo.TokenOrigin := dtoAfterText;
|
|
if Assigned(CurrentTokenHighlighter)
|
|
then EolAttr := CurrentTokenHighlighter.GetEndOfLineAttribute
|
|
else EolAttr := nil;
|
|
if EolAttr <> nil then begin
|
|
FTokenAttr.Assign(EolAttr);
|
|
ATokenInfo.TokenAttr := FTokenAttr;
|
|
end
|
|
else begin
|
|
ATokenInfo.TokenAttr := nil;
|
|
end;
|
|
Result := True;
|
|
end;
|
|
end;
|
|
LSTATE_GAP: begin
|
|
FLineState := LSTATE_DOTS;
|
|
FTokenAttr.Assign(FFoldView.MarkupInfoFoldedCode);
|
|
FTokenAttr.SetAllPriorities(MaxInt);
|
|
ATokenInfo.TokenStart := PChar(MarkDots);
|
|
ATokenInfo.TokenLength := 3;
|
|
ATokenInfo.TokenAttr := FTokenAttr;
|
|
ATokenInfo.TokenOrigin := dtoAfterText;
|
|
Result := True;
|
|
end;
|
|
else begin
|
|
Result := inherited GetNextHighlighterToken(ATokenInfo);
|
|
end;
|
|
end;
|
|
|
|
if (FLineFlags2 <> []) then begin
|
|
FMarkupLine.Clear;
|
|
if ATokenInfo.TokenAttr = nil then begin
|
|
// Text Area does not expect StartX/Endx
|
|
// So we must merge, to eliminate unwanted borders
|
|
// if (cfCollapsedFold in FLineFlags2)
|
|
// then ATokenInfo.TokenAttr := FFoldView.MarkupInfoFoldedCodeLine
|
|
// else ATokenInfo.TokenAttr := FFoldView.MarkupInfoHiddenCodeLine;
|
|
// exit;
|
|
FMarkupLine.Clear;
|
|
end //;
|
|
else
|
|
FMarkupLine.Assign(ATokenInfo.TokenAttr);
|
|
|
|
MergeStartX.Physical := -1;
|
|
MergeStartX.Logical := -1;
|
|
MergeEndX.Physical := -1;
|
|
MergeEndX.Logical := -1;
|
|
if FLineState in [LSTATE_BOL, LSTATE_BOL_GAP] then
|
|
MergeStartX := FFoldView.MarkupInfoFoldedCodeLine.StartX;
|
|
if FLineState = LSTATE_EOL then // LSTATE_GAP; // or result := true
|
|
MergeEndX := FFoldView.MarkupInfoFoldedCodeLine.EndX;
|
|
|
|
// fully expand all frames
|
|
//FMarkupLine.SetFrameBoundsLog(0,0,0);
|
|
//FMarkupLine.CurrentStartX := FMarkupLine.StartX;
|
|
//FMarkupLine.CurrentEndX := FMarkupLine.EndX;
|
|
|
|
if (cfCollapsedFold in FLineFlags2) then
|
|
FMarkupLine.Merge(FFoldView.MarkupInfoFoldedCodeLine, MergeStartX, MergeEndX)
|
|
else
|
|
FMarkupLine.Merge(FFoldView.MarkupInfoHiddenCodeLine, MergeStartX, MergeEndX);
|
|
|
|
ATokenInfo.TokenAttr := FMarkupLine;
|
|
end;
|
|
|
|
if FLineState in [LSTATE_BOL, LSTATE_BOL_GAP, LSTATE_DOTS, LSTATE_EOL] then
|
|
inc(FLineState);
|
|
end;
|
|
|
|
function TLazSynDisplayFold.GetLinesCount: Integer;
|
|
begin
|
|
Result := FFoldView.ViewedCount;
|
|
end;
|
|
|
|
function TLazSynDisplayFold.TextToViewIndex(ATextIndex: TLineIdx): TLineRange;
|
|
var
|
|
offs: Integer;
|
|
begin
|
|
offs := FFoldView.InternTextToViewIndexOffset(ATextIndex);
|
|
Result := inherited TextToViewIndex(ATextIndex);
|
|
Result.Top := Result.Top - offs;
|
|
Result.Bottom := Result.Bottom - offs;
|
|
end;
|
|
|
|
function TLazSynDisplayFold.ViewToTextIndex(AViewIndex: TLineIdx): TLineIdx;
|
|
begin
|
|
Result := FFoldView.InternViewToTextIndex(AViewIndex);
|
|
Result := inherited ViewToTextIndex(Result)
|
|
end;
|
|
|
|
function TLazSynDisplayFold.ViewToTextIndexEx(AViewIndex: TLineIdx; out
|
|
AViewRange: TLineRange): TLineIdx;
|
|
var
|
|
offs: Integer;
|
|
begin
|
|
offs := FFoldView.InternViewToTextIndexOffest(AViewIndex);
|
|
Result := AViewIndex + offs;
|
|
Result := inherited ViewToTextIndexEx(Result, AViewRange);
|
|
AViewRange.Top := AViewRange.Top - offs;
|
|
AViewRange.Bottom := AViewRange.Bottom - offs;
|
|
end;
|
|
|
|
{ TSynEditFoldExportStream }
|
|
|
|
constructor TSynEditFoldExportStream.Create;
|
|
begin
|
|
inherited;
|
|
FPos := 0;
|
|
FLen := 0;
|
|
FMem := nil;
|
|
end;
|
|
|
|
|
|
function TSynEditFoldExportStream.GetLen: Integer;
|
|
begin
|
|
Result := FLen;
|
|
end;
|
|
|
|
procedure TSynEditFoldExportStream.SetLen(const AValue: Integer);
|
|
begin
|
|
FPos := 0;
|
|
FLen:= AValue;
|
|
end;
|
|
|
|
function TSynEditFoldExportStream.GetMem: PChar;
|
|
begin
|
|
if FData <> '' then
|
|
Result := @FData[1]
|
|
else
|
|
Result := FMem;
|
|
end;
|
|
|
|
procedure TSynEditFoldExportStream.SetMem(const AValue: PChar);
|
|
begin
|
|
FData := '';
|
|
FMem := AValue;
|
|
FPos := 0;
|
|
end;
|
|
|
|
function TSynEditFoldExportStream.GetText: String;
|
|
begin
|
|
// only valid for FData
|
|
SetLength(FData, FLen);
|
|
Result := FData;
|
|
end;
|
|
|
|
procedure TSynEditFoldExportStream.SetText(const AValue: String);
|
|
begin
|
|
FData := AValue;
|
|
FMem := nil;
|
|
FPos := 0;
|
|
end;
|
|
|
|
function TSynEditFoldExportStream.GrowData(AppendSize: Integer): PChar;
|
|
var
|
|
l: integer;
|
|
begin
|
|
l := length(FData);
|
|
if l < FLen + AppendSize then
|
|
SetLength(FData, l + AppendSize + Max((l+AppendSize) div 4, 1024));
|
|
Result := @FData[FLen + 1];
|
|
inc(FLen, AppendSize);
|
|
end;
|
|
|
|
function TSynEditFoldExportStream.EncodeIntEx(Anum: Integer): String;
|
|
var
|
|
n: integer;
|
|
begin
|
|
// 0 - 42 => 1 byte
|
|
// 43 - 1848 => 2 byte
|
|
// 1849 - .... => 3 and more
|
|
Result := '';
|
|
if ANum = 0 then Result := NumEncode86Chars[1];
|
|
n := 0;
|
|
while ANum > 0 do begin
|
|
Result := NumEncode86Chars[1 + (Anum mod 43) + n] + Result;
|
|
ANum := ANum div 43;
|
|
n := 43;
|
|
end;
|
|
end;
|
|
|
|
function TSynEditFoldExportStream.EncodeIntEx2(Anum: Integer): String;
|
|
var
|
|
n: Integer;
|
|
begin
|
|
// 0 - 80 => 1 char
|
|
// 81 - 424 => 2 char (80 + 4 * 86)
|
|
// 425 - 467 => 2 char (len(EncodeIntEx) = 1)
|
|
// 468 - 2272 => 3 and more char
|
|
//2273 - .... => 4 and more char
|
|
Result := '';
|
|
if Anum <= 80 then
|
|
Result := NumEncode86Chars[1 + Anum]
|
|
else
|
|
begin
|
|
n := (Anum-81) div 86;
|
|
if n <= 3 then
|
|
Result := NumEncode86Chars[1 + 81 + n] + NumEncode86Chars[1 + (Anum - 81) mod 86]
|
|
else
|
|
Result := NumEncode86Chars[1 + 85] + EncodeIntEx(Anum - 81 - 4*86);
|
|
end;
|
|
end;
|
|
|
|
function TSynEditFoldExportStream.InternalReadNum(var APos: Integer): Integer;
|
|
var
|
|
n: Integer;
|
|
begin
|
|
Result := 0;
|
|
while True do begin
|
|
if FPos >= FLen then exit(-1);
|
|
n := NumEncode86Values[(FMem + APos)^];
|
|
if n < 43 then break;
|
|
dec(n, 43);
|
|
Result := Result * 43 + n;
|
|
inc(APos);
|
|
end;
|
|
Result := Result * 43 + n;
|
|
inc(APos);
|
|
end;
|
|
|
|
function TSynEditFoldExportStream.InternalReadNumEx(var APos: Integer): Integer;
|
|
begin
|
|
if FPos >= FLen then exit(-1);
|
|
Result := NumEncode86Values[(FMem + APos)^];
|
|
inc(APos);
|
|
if Result <= 80 then
|
|
exit;
|
|
if FPos >= FLen then exit(-1);
|
|
if Result < 85 then begin
|
|
Result := 81 + (Result-81)*86 + NumEncode86Values[(FMem + APos)^];
|
|
inc(APos);
|
|
exit;
|
|
end;
|
|
Result := 81 + 4*86 + InternalReadNum(APos);
|
|
end;
|
|
|
|
procedure TSynEditFoldExportStream.Compress;
|
|
(* Known Sequences: XX = Enc64Num (copy sequence from XX chars before)
|
|
NN = ENc22 Num / n = enc22digit (copy n bytes)
|
|
[XXn (up to 21 bytes, from up to 64*64 back)
|
|
[NNXX[ (more then 21 bytes, from up to 64*64 back)
|
|
]X (3 bytes from max 64 back)
|
|
]nx ( reocurring space,x times, ever n pos)
|
|
const
|
|
max_single_len = 22 - 1;
|
|
*)
|
|
var
|
|
CurPos, EndPos, SearchPos: Integer;
|
|
FndLen, FndPos, FndPos2: Integer;
|
|
BestLen, BestPos, BestPos2: Integer;
|
|
s: string;
|
|
begin
|
|
AppendString(#0);
|
|
dec(FLen);
|
|
|
|
EndPos := FLen;
|
|
CurPos := FLen - 3;
|
|
while CurPos >= 4 do begin
|
|
SearchPos := CurPos - 3;
|
|
BestLen := 0;
|
|
while (SearchPos >= 1) do begin
|
|
if CompareMem(@FData[CurPos], @FData[SearchPos], 3) then begin
|
|
FndLen := 3;
|
|
FndPos := SearchPos;
|
|
FndPos2 := CurPos;
|
|
while (SearchPos + FndLen < FndPos2) and
|
|
(FndPos2 + FndLen < EndPos - 1) and
|
|
(FData[SearchPos + FndLen] = FData[CurPos + FndLen])
|
|
do
|
|
inc(FndLen);
|
|
while (FndPos > 1) and (FndPos + FndLen < FndPos2) and
|
|
(FData[FndPos - 1] = FData[FndPos2 - 1]) do
|
|
begin
|
|
dec(FndPos);
|
|
dec(FndPos2);
|
|
inc(FndLen);
|
|
end;
|
|
|
|
if (FndLen > BestLen) and
|
|
((FndPos2 - FndPos <= NumEncodeAsOneMax) or (FndLen >= 4)) and
|
|
((FndPos2 - FndPos <= NumEncodeAsTwoMax) or (FndLen >= 5)) and
|
|
((FndPos2 - FndPos <= NumEncodeAsThreeMax) or (FndLen >= 6))
|
|
then begin
|
|
BestLen := FndLen;
|
|
BestPos := FndPos;
|
|
BestPos2 := FndPos2;
|
|
end;
|
|
end;
|
|
dec(SearchPos);
|
|
end;
|
|
|
|
s := '';
|
|
if (BestLen >= 4) then
|
|
s := '[' + EncodeIntEx2({%H-}BestPos2 - {%H-}BestPos) + EncodeIntEx2(BestLen)
|
|
else
|
|
if (BestLen = 3) and (BestPos2 - BestPos <= NumEncodeAsOneMax) then
|
|
s := ']' + EncodeIntEx2(BestPos2 - BestPos);
|
|
if (s<>'') and (length(s) < BestLen) then begin
|
|
System.Move(s[1], FData[BestPos2], length(s));
|
|
System.Move(FData[BestPos2 + BestLen], FData[BestPos2 + length(s)], FLen + 1 - (BestPos2 + BestLen));
|
|
dec(FLen, BestLen - length(s));
|
|
EndPos := BestPos;
|
|
CurPos := BestPos2 - 3;
|
|
end
|
|
else
|
|
dec(CurPos);
|
|
end;
|
|
end;
|
|
|
|
procedure TSynEditFoldExportStream.Decompress;
|
|
var
|
|
i, j, n: Integer;
|
|
p, p2: PChar;
|
|
NewLen: Integer;
|
|
begin
|
|
// curently assumes that FMem points NOT at FData
|
|
if FLen = 0 then
|
|
exit;
|
|
NewLen := 0;
|
|
i := 0;
|
|
while i < Flen do begin
|
|
case (FMem+i)^ of
|
|
'[' :
|
|
begin
|
|
inc(i);
|
|
j := InternalReadNumEx(i);
|
|
n := InternalReadNumEx(i);
|
|
if (j < n) or (j > NewLen) then raise ESynEditError.Create('fold format error');
|
|
inc(NewLen, n);
|
|
end;
|
|
']' :
|
|
begin
|
|
inc(i, 1);
|
|
j := InternalReadNumEx(i);
|
|
if (j < 3) or (j > NewLen) then raise ESynEditError.Create('fold format error');
|
|
inc(NewLen, 3);
|
|
end;
|
|
else
|
|
begin
|
|
inc(NewLen);
|
|
inc(i);
|
|
end;
|
|
end;
|
|
end;
|
|
SetLength(FData, NewLen);
|
|
|
|
i := 0;
|
|
p := PChar(FData);
|
|
while i < Flen do begin
|
|
case (FMem+i)^ of
|
|
'[' :
|
|
begin
|
|
inc(i);
|
|
j := InternalReadNumEx(i);
|
|
n := InternalReadNumEx(i);
|
|
p2 := p;
|
|
while n > 0 do begin
|
|
p^ := (p2 - j)^;
|
|
inc(p);
|
|
dec(j);
|
|
dec(n);
|
|
end;
|
|
end;
|
|
']' :
|
|
begin
|
|
inc(i);
|
|
j := InternalReadNumEx(i);
|
|
p2 := p;
|
|
for n := 0 to 2 do begin
|
|
p^ := (p2 - j)^;
|
|
inc(p);
|
|
dec(j);
|
|
end;
|
|
end;
|
|
else
|
|
begin
|
|
p^ := (FMem + i)^;
|
|
inc(p);
|
|
inc(i);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
FLen := NewLen;
|
|
FMem := PChar(FData);
|
|
FPos := 0;
|
|
end;
|
|
|
|
procedure TSynEditFoldExportStream.AddChecksum;
|
|
var
|
|
i, c: Integer;
|
|
begin
|
|
if FLen = 0 then
|
|
exit;
|
|
if FMem = nil then
|
|
FMem := @FData[1];
|
|
c := 0;
|
|
for i := 0 to FLen - 1 do
|
|
c := c xor (ord((FMem + i)^) * (i+1));
|
|
c := (c mod 256) xor ((c div 256) mod 256) xor ((c div 65536) mod 256);
|
|
AppendString(NumEncode86Chars[1 + (c mod 86)]);
|
|
end;
|
|
|
|
function TSynEditFoldExportStream.VerifyChecksum: Boolean;
|
|
var
|
|
i, c: Integer;
|
|
begin
|
|
if FLen = 0 then
|
|
exit(True);
|
|
if FMem = nil then
|
|
FMem := @FData[1];
|
|
dec(Flen);
|
|
c := 0;
|
|
for i := 0 to FLen - 1 do
|
|
c := c xor (ord((FMem + i)^) * (i+1));
|
|
c := (c mod 256) xor ((c div 256) mod 256) xor ((c div 65536) mod 256);
|
|
Result := (FMem + FLen)^ = NumEncode86Chars[1 + (c mod 86)];
|
|
end;
|
|
|
|
procedure TSynEditFoldExportStream.AppendMem(AMem: Pointer; ALen: Integer);
|
|
begin
|
|
{$IFDEF SynFoldSaveDebug}
|
|
DebugLn(['TSynEditFoldExportStream.AppendMem len=', ALen]);
|
|
{$ENDIF}
|
|
FMem := nil;
|
|
if ALen > 0 then
|
|
System.Move(AMem^, GrowData(ALen)^, ALen);
|
|
end;
|
|
|
|
procedure TSynEditFoldExportStream.AppendString(ATxt: String);
|
|
var
|
|
l: Integer;
|
|
begin
|
|
{$IFDEF SynFoldSaveDebug}
|
|
DebugLn(['TSynEditFoldExportStream.AppendString ', ATxt]);
|
|
{$ENDIF}
|
|
FMem := nil;
|
|
l := length(ATxt);
|
|
if l > 0 then
|
|
System.Move(ATxt[1], GrowData(l)^, l);
|
|
end;
|
|
|
|
procedure TSynEditFoldExportStream.AppendNum(ANum: Integer);
|
|
begin
|
|
{$IFDEF SynFoldSaveDebug}
|
|
DebugLn(['TSynEditFoldExportStream.AppendNum ', ANum]);
|
|
{$ENDIF}
|
|
FMem := nil;
|
|
AppendString(EncodeIntEx(ANum));
|
|
end;
|
|
|
|
procedure TSynEditFoldExportStream.AppendNumEx(ANum: Integer);
|
|
begin
|
|
{$IFDEF SynFoldSaveDebug}
|
|
DebugLn(['TSynEditFoldExportStream.AppendNumEx ', ANum]);
|
|
{$ENDIF}
|
|
FMem := nil;
|
|
AppendString(EncodeIntEx2(ANum));
|
|
end;
|
|
|
|
procedure TSynEditFoldExportStream.Reset;
|
|
begin
|
|
FPos := 0;
|
|
if (FMem = nil) and (FData <> '') then
|
|
FMem := @FData[1];
|
|
end;
|
|
|
|
procedure TSynEditFoldExportStream.Clear;
|
|
begin
|
|
FLen := 0;
|
|
FMem := nil;
|
|
FPos := 0;
|
|
SetLength(FData, 0);
|
|
end;
|
|
|
|
function TSynEditFoldExportStream.ReadMem(AMem: Pointer; ALen: Integer): Boolean;
|
|
begin
|
|
Result := FPos+ ALen <= FLen;
|
|
If not Result then
|
|
exit;
|
|
System.Move((FMem + FPos)^, AMem^, ALen);
|
|
inc(FPos, ALen);
|
|
end;
|
|
|
|
function TSynEditFoldExportStream.PeakString(ALen: Integer): String;
|
|
begin
|
|
Result := '';
|
|
If not(FPos+ ALen <= FLen) then
|
|
exit;
|
|
SetLength(Result, ALen);
|
|
if ALen > 0 then
|
|
System.Move((FMem + FPos)^, Result[1], ALen);
|
|
end;
|
|
|
|
function TSynEditFoldExportStream.FindChar(AChar: Char): Integer;
|
|
begin
|
|
Result := 0;
|
|
While (FPos + Result < FLen) and ((FMem + FPos + Result)^ <> AChar) do
|
|
inc(Result);
|
|
if FPos + Result = FLen then
|
|
Result := -1;
|
|
end;
|
|
|
|
function TSynEditFoldExportStream.ReadString(ALen: Integer): String;
|
|
begin
|
|
Result := '';
|
|
If not(FPos+ ALen <= FLen) then
|
|
exit;
|
|
SetLength(Result, ALen);
|
|
if ALen > 0 then
|
|
System.Move((FMem + FPos)^, Result[1], ALen);
|
|
inc(FPos, ALen);
|
|
end;
|
|
|
|
function TSynEditFoldExportStream.ReadNum: Integer;
|
|
begin
|
|
Result := InternalReadNum(FPos);
|
|
{$IFDEF SynFoldSaveDebug}
|
|
DebugLn(['TSynEditFoldExportStream.ReadNum ', Result]);
|
|
{$ENDIF}
|
|
end;
|
|
|
|
function TSynEditFoldExportStream.ReadNumEx: Integer;
|
|
begin
|
|
Result := InternalReadNumEx(FPos);
|
|
{$IFDEF SynFoldSaveDebug}
|
|
DebugLn(['TSynEditFoldExportStream.ReadNumEx ', Result]);
|
|
{$ENDIF}
|
|
end;
|
|
|
|
function TSynEditFoldExportStream.EOF: Boolean;
|
|
begin
|
|
Result := FPos >= FLen;
|
|
end;
|
|
|
|
{ TSynEditFoldExportCoder }
|
|
|
|
function TSynEditFoldExportCoder.GetReadIsValid: Boolean;
|
|
begin
|
|
Result := FReadState <> sfecInvalid;
|
|
end;
|
|
|
|
constructor TSynEditFoldExportCoder.Create(AFoldType: Pointer);
|
|
begin
|
|
inherited Create;
|
|
FExportStream := TSynEditFoldExportStream.Create;
|
|
FExportStream.AppendString(' T'); // Type Marker
|
|
FExportStream.AppendNum(PtrUInt(AFoldType));
|
|
FFoldType := AFoldType;
|
|
FWriteCacheLen := 0;
|
|
FWriteCache := nil;
|
|
FWriteCacheTypes := [];
|
|
end;
|
|
|
|
constructor TSynEditFoldExportCoder.Create(AStream: TSynEditFoldExportStream);
|
|
var
|
|
i: Integer;
|
|
begin
|
|
inherited Create;
|
|
FExportStream := TSynEditFoldExportStream.Create;
|
|
FReadState := sfecInvalid;
|
|
if AStream.PeakString(2) <> ' T' then exit;
|
|
|
|
AStream.ReadString(2);
|
|
|
|
FFoldType := Pointer(PtrUInt(AStream.ReadNum));
|
|
while(true) do begin
|
|
i := AStream.FindChar(' ');
|
|
if i < 0 then i := AStream.Len - AStream.Pos;
|
|
FExportStream.AppendString(AStream.ReadString(i));
|
|
if AStream.EOF or (AStream.PeakString(2) = ' T') then
|
|
break;
|
|
FExportStream.AppendString(AStream.ReadString(2));
|
|
end;
|
|
{$IFDEF SynFoldSaveDebug}
|
|
DebugLn(['TSynEditFoldExportCoder.Create(<from input-stream> FType=', dbgs(FFoldType), ' txtLen=', FExportStream.Len, ' Txt="', FExportStream.Text, '"']);
|
|
{$ENDIF}
|
|
Reset;
|
|
end;
|
|
|
|
destructor TSynEditFoldExportCoder.Destroy;
|
|
begin
|
|
FreeAndNil(FExportStream);
|
|
Inherited;
|
|
end;
|
|
|
|
procedure TSynEditFoldExportCoder.AddNode(aX, aY, aLen: Integer; aFoldType: TSynEditFoldType);
|
|
(* Format: [Num] <NumEX>
|
|
' T' [type] [yo] <X> <len> ( <c>* ' p' [sum] [yo] <X> <len> )* <c>* (' P' [sum] [yo] <X> <len>)?
|
|
|
|
//////////////////////////
|
|
// Version info
|
|
V1 - no entries
|
|
V2 July 2010 0.9.29
|
|
- added fold-hide <HideInfo>
|
|
|
|
//////////////////////////
|
|
|
|
<Stream> = { <TypeStream> };
|
|
|
|
<TypeStream> = " T" <TypeId> <TypeData>; [* Stores all folds for the given type (eg cfbtBeginEnd) *]
|
|
|
|
<TypeId> = ord(cfbtBeginEnd) or similar
|
|
<TypeData> = [<HideInfo>],
|
|
<NodePos>,
|
|
[ [<FoldList>,] [{ <FoldListEndCont>, <NodePos>, [<FoldList>] }] ],
|
|
[ <FoldListEnd> ];
|
|
|
|
|
|
<FoldList> = [{ <ConsecutiveFoldedCount>, <ConsecutiveUnFoldedCount> }],
|
|
<ConsecutiveFoldedCount>,
|
|
;
|
|
[* NodePos: is the position of a folded node (of the type matching the current stream)
|
|
ConsecutiveFoldedCount: more folded nodes of the same type, without any
|
|
unfolded node (of this type) inbetween.
|
|
ConsecutiveUnFoldedCount: amount of unfolded nodes (of this type) before the next folded node.
|
|
*]
|
|
|
|
<NodePos> = <YOffset> <XPos> <len>;
|
|
<YOffset> = <Number>
|
|
<XPos> = <ExNumber>
|
|
<len> = <ExNumber>
|
|
<ConsecutiveFoldedCount> = <ExNumber>
|
|
<ConsecutiveUnFoldedCount> = <ExNumber>
|
|
|
|
<FoldListEndCont> = ' p', <SumFoldedLines>;
|
|
[* FoldListEndCont is mandotory, if another block of <NodePos>, <FoldList> is coming *]
|
|
<FoldListEnd> = ' P' <SumFoldedLines>, <EndY>, <EndX>;
|
|
[* FoldListEnd is optional. It is expected if the previous <FoldList> has more than 10 folded lines*]
|
|
|
|
<SumFoldedLines> = <Number>
|
|
[* The sum of all lines folded by folds in <ConsecutiveFoldedCount>.
|
|
Not including the fold in <NodePos>, which has it's own len.
|
|
*]
|
|
|
|
<Number> = bigger numbers
|
|
<ExNumber> = for numbers expected below 467; specially 0..80
|
|
|
|
<HideInfo> = ' h' | ' H'
|
|
not present: all folds, no hides (default)
|
|
' H': all hides, no folds
|
|
' h': mixed hides and folds
|
|
For mixed lists the following applies:
|
|
- XPos is doubled; bit 0 (odd <number>) indicates the first node is a hide
|
|
- ConsecutiveFoldedCount, ConsecutiveUnFoldedCount are doubled;
|
|
bit 0 indicates:
|
|
If last was fold: 1-odd = hide / 0-even = open
|
|
If last was hide: 1-odd = fold / 0-even = open
|
|
If last was open: 1-odd = hide / 0-even = fold
|
|
In the first <ConsecutiveFoldedCount> after <NodePos> the bit is unused, since nodepos is continued.
|
|
*)
|
|
begin
|
|
{$IFDEF SynFoldSaveDebug}
|
|
debugln(['TSynEditFoldExportCoder.AddNode FType=', dbgs(FFoldType),' X=', aX, ' Y=', aY, 'Len=', aLen, 'FType=', SynEditFoldTypeNames[aFoldType], ' WCacheLen=', FWriteCacheLen]);
|
|
{$ENDIF}
|
|
if (FWriteCacheLen = 0) and (aFoldType = scftOpen) then
|
|
exit;
|
|
if FWriteCacheLen >= length(FWriteCache) then
|
|
SetLength(FWriteCache, Max(1000, FWriteCacheLen*2));
|
|
FWriteCache[FWriteCacheLen].aY := aY;
|
|
FWriteCache[FWriteCacheLen].aX := aX;
|
|
FWriteCache[FWriteCacheLen].aLen := aLen;
|
|
FWriteCache[FWriteCacheLen].aFoldType := aFoldType;
|
|
inc(FWriteCacheLen);
|
|
include(FWriteCacheTypes, aFoldType);
|
|
end;
|
|
|
|
procedure TSynEditFoldExportCoder.Finish;
|
|
var
|
|
FirstLine, HideFactor, HideBit: Integer;
|
|
CntSum, LinesSum: Integer;
|
|
LastFoldType: TSynEditFoldType;
|
|
|
|
procedure WriteCachedNode(AIndex: Integer);
|
|
begin
|
|
HideBit := 0;
|
|
LastFoldType := FWriteCache[AIndex].aFoldType;
|
|
if (HideFactor = 2) and (LastFoldType = scftHide) then
|
|
HideBit := 1;
|
|
FExportStream.AppendNum (FWriteCache[AIndex].aY - FirstLine);
|
|
FExportStream.AppendNumEx(FWriteCache[AIndex].aX * HideFactor + HideBit);
|
|
FExportStream.AppendNumEx(FWriteCache[AIndex].aLen);
|
|
FirstLine := FWriteCache[AIndex].aY;
|
|
end;
|
|
|
|
function CountConsecutiveNodes(var AStartIndex: Integer; out ACount, ALines: Integer;
|
|
ASkipFirst: Boolean = True): Boolean;
|
|
var l1, l2: Integer;
|
|
t: TSynEditFoldType;
|
|
begin
|
|
// reset counters for following <FoldList>
|
|
CntSum := 0;
|
|
|
|
HideBit := 0;;
|
|
case LastFoldType of
|
|
scftOpen: if scftHide = FWriteCache[AStartIndex].aFoldType then HideBit := 1;
|
|
scftFold: if scftHide = FWriteCache[AStartIndex].aFoldType then HideBit := 1;
|
|
scftHide: if scftFold = FWriteCache[AStartIndex].aFoldType then HideBit := 1;
|
|
end;
|
|
LastFoldType := FWriteCache[AStartIndex].aFoldType;
|
|
|
|
Result := False;
|
|
ACount := 0;
|
|
ALines := 0;
|
|
|
|
l2 := FirstLine;
|
|
t := FWriteCache[AStartIndex].aFoldType;
|
|
Repeat
|
|
if (AStartIndex >= FWriteCacheLen) then
|
|
exit;
|
|
l1 := FWriteCache[AStartIndex].aY;
|
|
if (ACount > SEQMaxNodeCount) or
|
|
(ALines > SEQMaxNodeCount) or // TODO should this be SEQMaxLineDistEach ?
|
|
(l1 - l2 > SEQMaxLineDistEach) or
|
|
(l1 - FirstLine > SEQMaxLineDistTotal)
|
|
then
|
|
exit;
|
|
|
|
if not ASkipFirst then begin
|
|
ALines := ALines + FWriteCache[AStartIndex].aLen;
|
|
inc(ACount);
|
|
end;
|
|
inc(AStartIndex);
|
|
l2 := l1;
|
|
ASkipFirst := False;
|
|
until FWriteCache[AStartIndex].aFoldType <> t;
|
|
Result := True;
|
|
end;
|
|
|
|
var DeferredZero: Boolean;
|
|
procedure WriteNodeCount(ACount, ALines: Integer; AState: TSynEditFoldType);
|
|
begin
|
|
inc(CntSum, ACount);
|
|
inc(LinesSum, ALines); // non folds are always 0
|
|
if ACount = 0 then begin
|
|
DeferredZero := True;
|
|
exit;
|
|
end;
|
|
if DeferredZero then
|
|
FExportStream.AppendNumEx(0);
|
|
DeferredZero := False;
|
|
FExportStream.AppendNumEx(ACount * HideFactor + HideBit);
|
|
end;
|
|
|
|
function ScanForFold(var AIndex: Integer): Boolean;
|
|
begin
|
|
Result := True;
|
|
while AIndex < FWriteCacheLen do begin
|
|
if FWriteCache[AIndex].aFoldType in [scftFold, scftHide] then exit;
|
|
inc(AIndex);
|
|
end;
|
|
Result := False;
|
|
end;
|
|
var
|
|
i, i2, CntF, CntNF, LinesF, LinesNF: Integer;
|
|
r: boolean;
|
|
begin
|
|
if (FWriteCacheLen = 0) or (FWriteCacheTypes * [scftFold, scftHide] = []) then begin
|
|
FExportStream.Clear;
|
|
exit;
|
|
end;
|
|
{$IFDEF SynFoldSaveDebug}
|
|
DebugLnEnter(['TSynEditFoldExportCoder.Finish FType=', dbgs(FFoldType)]);
|
|
{$ENDIF}
|
|
|
|
FirstLine := 0;
|
|
if (FWriteCacheTypes * [scftFold, scftHide] = [scftFold, scftHide]) then begin
|
|
HideFactor := 2;
|
|
FExportStream.AppendString(' h');
|
|
end
|
|
else begin
|
|
HideFactor := 1; // no bit for hide/fold differentation needed
|
|
if scftHide in FWriteCacheTypes then
|
|
FExportStream.AppendString(' H');
|
|
end;
|
|
i := 0;
|
|
while i < FWriteCacheLen do begin
|
|
LinesSum := 0;
|
|
WriteCachedNode(i);
|
|
|
|
DeferredZero := False; // special case at start, there may be 0 more folded nodes
|
|
r := CountConsecutiveNodes(i, cntF, linesF, True);
|
|
WriteNodeCount(CntF, LinesF, scftFold); // or hide, no matter here
|
|
while r do begin
|
|
r := CountConsecutiveNodes(i, cntNF, linesNF, False);
|
|
if not r then break;
|
|
r := CountConsecutiveNodes(i, cntF, linesF, False);
|
|
WriteNodeCount(CntNF, LinesNF, scftOpen);
|
|
WriteNodeCount(CntF, LinesF, scftFold); // or hide, no matter here
|
|
end;
|
|
|
|
i2 := i;
|
|
ScanForFold(i);
|
|
if (i < FWriteCacheLen) then begin
|
|
// another node will follow, must insert ' p' marker
|
|
FExportStream.AppendString(' p'); // point marker (no marker needed for first entry)
|
|
FExportStream.AppendNum(LinesSum); // Start with sum from last sequence
|
|
end;
|
|
end;
|
|
|
|
if LinesSum > 10 then begin
|
|
// end of data; write ' P' marker if needed
|
|
FExportStream.AppendString(' P'); // point marker (no marker needed for first entry)
|
|
FExportStream.AppendNum (LinesSum); // Start with sum from last sequence
|
|
FExportStream.AppendNum (FWriteCache[i2-1].aY - FirstLine); // Last folded Coords
|
|
FExportStream.AppendNumEx(FWriteCache[i2-1].aX);
|
|
end;
|
|
{$IFDEF SynFoldSaveDebug}
|
|
DebugLnExit(['TSynEditFoldExportCoder.Finish FType=', dbgs(FFoldType), ' txtLen=', FExportStream.Len, ' Txt="', FExportStream.Text, '"']);
|
|
{$ENDIF}
|
|
end;
|
|
|
|
function TSynEditFoldExportCoder.ReadNode(aX, aY: Integer; aLen: Integer): TSynEditFoldType;
|
|
(* Format: [Num] <NumEX>
|
|
' T' [type]
|
|
[yo] <X> <len> ( <c>* ' p' [sum] [yo] <X> <len> )* <c>* (' P' [sum] [yo] <X>)?
|
|
*)
|
|
function GetCommand: Char;
|
|
begin
|
|
Result := #0;
|
|
if (FExportStream.PeakString(1) = ' ') and (FExportStream.Len > FExportStream.Pos+1) then
|
|
Result := FExportStream.ReadString(2)[2];
|
|
end;
|
|
|
|
function Invalidate: TSynEditFoldType;
|
|
begin
|
|
{$IFDEF SynFoldSaveDebug}
|
|
DebugLn(['Invalidate']);
|
|
{$ENDIF}
|
|
FReadState := sfecInvalid;
|
|
Result := scftInvalid;
|
|
end;
|
|
|
|
var
|
|
i: Integer;
|
|
begin
|
|
{$IFDEF SynFoldSaveDebug}
|
|
DebugLnEnter(['TSynEditFoldExportCoder.Readnode X=', aX, ' Y=', aY, ' Len=',aLen,
|
|
' ReadState=',SynEditFoldExportCoderStates[FReadState],
|
|
' FReadCount=', FReadCount, ' FReadY=', FReadY, ' FReadX=', FReadX,
|
|
' FReadSumLen=', FReadSumLen, ' FReadType=', SynEditFoldTypeNames[FReadType]
|
|
]);
|
|
try
|
|
{$ENDIF}
|
|
Result := scftInvalid;
|
|
case FReadState of
|
|
sfecAtBegin, sfecAtPoint:
|
|
begin
|
|
if (FReadState = sfecAtBegin) then begin
|
|
case GetCommand of
|
|
'H': begin
|
|
FReadDefaultType := scftHide;
|
|
FReadType := scftHide;
|
|
end;
|
|
'h': begin
|
|
FReadDefaultType := scftAll;
|
|
end;
|
|
end;
|
|
FReadState := sfecAtPoint;
|
|
end;
|
|
|
|
if FReadCount = 0 then begin
|
|
FReadCount := 1;
|
|
FReadY := FExportStream.ReadNum + FReadLastY;
|
|
FReadX := FExportStream.ReadNumEx;
|
|
FReadSumLen := FExportStream.ReadNumEx;
|
|
if FReadSumLen < 0 then exit(Invalidate);
|
|
|
|
if FReadDefaultType = scftAll then begin
|
|
if (FReadX and 1) = 1 then
|
|
FReadType := scftHide
|
|
else
|
|
FReadType := scftFold;
|
|
FReadX := FReadX div 2;
|
|
end
|
|
else
|
|
FReadType := FReadDefaultType;
|
|
end;
|
|
|
|
// ax may be off by one, since pas highlighter changed to include $ in $IFDEF
|
|
if ((aY < FReadY) or ((aY = FReadY) and (aX+1 < FReadX))) then
|
|
exit(scftOpen); // actually, read before point
|
|
|
|
i := 0;
|
|
if FReadType = scftHide then i := 1; // fold one more than len
|
|
if (aY <> FReadY) or (abs(aX - FReadX) > 1) or (aLen + i <> FReadSumLen) then
|
|
exit(Invalidate);
|
|
|
|
FReadLastY := FReadY;
|
|
FReadSumLen := 0; // was len of current fold, no len remaining => prepare for counting consecutive folds
|
|
Result := FReadType;
|
|
|
|
if FExportStream.EOF then
|
|
FReadState := sfecAtEOF
|
|
else case GetCommand of
|
|
'p':
|
|
begin
|
|
FExportStream.ReadNum; // skip len (must be 0) since there was no <ConsecutiveFoldedCount>
|
|
FReadCount := 0;
|
|
FReadState := sfecAtPoint;
|
|
end;
|
|
'P':
|
|
begin
|
|
// end marker isnt expected? there were no <ConsecutiveFoldedCount>
|
|
FReadState := sfecAtEOF;
|
|
end;
|
|
else
|
|
begin
|
|
FReadState := sfecInRepeatCount;
|
|
FReadCount := FExportStream.ReadNumEx; // count up and check at end
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
sfecInRepeatCount:
|
|
begin
|
|
if FReadCount = 0 then begin
|
|
if FExportStream.EOF then begin
|
|
FReadState := sfecAtEOF;
|
|
exit(scftOpen);
|
|
end
|
|
else case GetCommand of
|
|
'p':
|
|
begin
|
|
if FReadSumLen <> FExportStream.ReadNum then
|
|
exit(Invalidate);
|
|
FReadCount := 0;
|
|
FReadState := sfecAtPoint;
|
|
exit(ReadNode(aX, aY, aLen));
|
|
end;
|
|
'P':
|
|
begin
|
|
if (FReadSumLen <> FExportStream.ReadNum) or
|
|
(FReadY <> FExportStream.ReadNum + FReadLastY) or
|
|
(FReadX <> FExportStream.ReadNumEx)
|
|
then
|
|
exit(Invalidate);
|
|
FReadState := sfecAtEOF;
|
|
exit(scftOpen);
|
|
end;
|
|
else
|
|
begin
|
|
FReadCount := FExportStream.ReadNumEx; // count up and check at end
|
|
if FReadDefaultType = scftAll then begin
|
|
if (FReadCount and 1) = 1 then begin
|
|
case FReadType of
|
|
scftOpen: FReadType := scftHide;
|
|
scftFold: FReadType := scftHide;
|
|
scftHide: FReadType := scftFold;
|
|
end;
|
|
end else begin
|
|
case FReadType of
|
|
scftOpen: FReadType := scftFold;
|
|
scftFold: FReadType := scftOpen;
|
|
scftHide: FReadType := scftOpen;
|
|
end;
|
|
end;
|
|
FReadCount := FReadCount div 2;
|
|
end
|
|
else begin
|
|
if FReadType = scftOpen then
|
|
FReadType := FReadDefaultType
|
|
else
|
|
FReadType := scftOpen;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
dec(FReadCount);
|
|
if FReadType <> scftOpen then
|
|
inc(FReadSumLen, aLen);
|
|
Result := FReadType;
|
|
end;
|
|
|
|
sfecAtEOF:
|
|
begin
|
|
exit(scftOpen);
|
|
end;
|
|
sfecInvalid:
|
|
begin
|
|
exit(scftInvalid);
|
|
end;
|
|
end;
|
|
{$IFDEF SynFoldSaveDebug}
|
|
finally
|
|
DebugLnExit(['TSynEditFoldExportCoder.Readnode << ']);
|
|
end;
|
|
{$ENDIF}
|
|
end;
|
|
|
|
function TSynEditFoldExportCoder.EOF: Boolean;
|
|
begin
|
|
Result := FExportStream.EOF;
|
|
end;
|
|
|
|
procedure TSynEditFoldExportCoder.Reset;
|
|
begin
|
|
FExportStream.Reset;
|
|
FReadY := -1;
|
|
FReadX := -1;
|
|
FReadLastY := 0;
|
|
FReadCount := 0;
|
|
FReadSumLen := 0;
|
|
FReadState := sfecAtBegin;
|
|
if FExportStream.Len = 0 then
|
|
FReadState := sfecInvalid;
|
|
FReadDefaultType := scftFold;
|
|
FReadType := scftFold;
|
|
end;
|
|
|
|
{ TSynTextFoldAVLNodeData }
|
|
|
|
procedure TSynTextFoldAVLNodeData.FreeAllChildrenAndNested;
|
|
begin
|
|
if FLeft <> nil then begin
|
|
Left.FreeAllChildrenAndNested;
|
|
FreeAndNil(FLeft);
|
|
end;
|
|
|
|
if FRight <> nil then begin
|
|
Right.FreeAllChildrenAndNested;
|
|
FreeAndNil(FRight);
|
|
end;
|
|
|
|
if _Nested <> nil then begin
|
|
_Nested.FreeAllChildrenAndNested;
|
|
FreeAndNil(_Nested);
|
|
end;
|
|
end;
|
|
|
|
procedure TSynTextFoldAVLNodeData.UdpateVirtualLineCounts(ANextStrings: TSynEditStrings;
|
|
AStartLinePos: Integer);
|
|
begin
|
|
_DoUdpateVirtualLineCounts(ANextStrings, AStartLinePos);
|
|
end;
|
|
|
|
procedure TSynTextFoldAVLNodeData._DoUdpateVirtualLineCounts(ANextStrings: TSynEditStrings;
|
|
AStartLinePos: Integer);
|
|
var
|
|
NewCnt: TLineIdx;
|
|
begin
|
|
if ANextStrings <> nil then
|
|
NewCnt :=
|
|
ANextStrings.TextToViewIndex(ToIdx(AStartLinePos)+_MergedLineCount) -
|
|
ANextStrings.TextToViewIndex(ToIdx(AStartLinePos))
|
|
else
|
|
NewCnt := 0; // Nested tree
|
|
|
|
AdjustParentLeftCount(NewCnt - _MergedVirtualLineCount);
|
|
_MergedVirtualLineCount := NewCnt;
|
|
end;
|
|
|
|
{ TSynTextFoldAVLNodeHelper }
|
|
|
|
function TSynTextFoldAVLNodeHelper.GetClassification: TFoldNodeClassification;
|
|
begin
|
|
if Page = nil
|
|
then Result := fncInvalid
|
|
else Result := Page._Classification;
|
|
end;
|
|
|
|
function TSynTextFoldAVLNodeHelper.GetFoldColumn: Integer;
|
|
begin
|
|
if Page = nil
|
|
then Result := -1
|
|
else Result := Page._FoldColumn;
|
|
end;
|
|
|
|
function TSynTextFoldAVLNodeHelper.GetFoldColumnLen: Integer;
|
|
begin
|
|
if Page = nil
|
|
then Result := -1
|
|
else Result := Page._FoldColumnLen;
|
|
end;
|
|
|
|
function TSynTextFoldAVLNodeHelper.GetFoldedBefore: Integer;
|
|
begin
|
|
Result := LeftSizeSum;
|
|
end;
|
|
|
|
function TSynTextFoldAVLNodeHelper.GetFoldIndex: Integer;
|
|
begin
|
|
if Page = nil
|
|
then Result := -1
|
|
else Result := Page._FoldIndex;
|
|
end;
|
|
|
|
function TSynTextFoldAVLNodeHelper.GetMergedLineCount : Integer;
|
|
begin
|
|
if Page = nil
|
|
then Result := 0
|
|
else Result := Page._MergedLineCount;
|
|
end;
|
|
|
|
function TSynTextFoldAVLNodeHelper.GetFoldedCount: Integer;
|
|
begin
|
|
if Page = nil
|
|
then Result := -1
|
|
else Result := Page._FoldedCount;
|
|
end;
|
|
|
|
function TSynTextFoldAVLNodeHelper.GetSourceLine: integer;
|
|
begin
|
|
if Page = nil then
|
|
Result := -1
|
|
else
|
|
Result := StartLine - Page._VisibleLines;
|
|
end;
|
|
|
|
function TSynTextFoldAVLNodeHelper.GetSourceLineOffset: integer;
|
|
begin
|
|
if Page = nil then
|
|
Result := 0
|
|
else
|
|
Result := Page._VisibleLines;
|
|
end;
|
|
|
|
procedure TSynTextFoldAVLNodeHelper.SetFoldColumn(const AValue: Integer);
|
|
begin
|
|
if Page <> nil then
|
|
Page._FoldColumn := AValue;
|
|
end;
|
|
|
|
procedure TSynTextFoldAVLNodeHelper.UdpateVirtualLineCounts(ANextStrings: TSynEditStrings);
|
|
begin
|
|
Page.UdpateVirtualLineCounts(ANextStrings, StartLine);
|
|
end;
|
|
|
|
function TSynTextFoldAVLNodeHelper.IsInFold : Boolean;
|
|
begin
|
|
Result := Page <> nil;
|
|
end;
|
|
|
|
function TSynTextFoldAVLNodeHelper.IsHide: Boolean;
|
|
begin
|
|
Result := (Page <> nil) and (Page._VisibleLines = 0);
|
|
end;
|
|
|
|
{ TSynTextFoldAVLNodeNestedIterator }
|
|
|
|
constructor TSynTextFoldAVLNodeNestedIterator.Create(ANode: TSynTextFoldAVLNode);
|
|
begin
|
|
SetLength(FOuterNodes, 0);
|
|
FCurrentNode := ANode;
|
|
end;
|
|
|
|
destructor TSynTextFoldAVLNodeNestedIterator.Destroy;
|
|
begin
|
|
SetLength(FOuterNodes, 0);
|
|
inherited Destroy;
|
|
end;
|
|
|
|
function TSynTextFoldAVLNodeNestedIterator.Next: TSynTextFoldAVLNode;
|
|
var
|
|
NewData: TSynTextFoldAVLNodeData;
|
|
i: Integer;
|
|
PNode: TSynTextFoldAVLNode;
|
|
begin
|
|
i := length(FOuterNodes);
|
|
if FCurrentNode.Page._Nested = nil then begin
|
|
FCurrentNode := FCurrentNode.Next;
|
|
while (not FCurrentNode.IsInFold) and (i > 0) do begin
|
|
dec(i);
|
|
FCurrentNode := FOuterNodes[i];
|
|
SetLength(FOuterNodes, i);
|
|
FCurrentNode := FCurrentNode.Next;
|
|
end;
|
|
end else begin
|
|
SetLength(FOuterNodes, i + 1);
|
|
FOuterNodes[i] := FCurrentNode;
|
|
NewData := FCurrentNode.Page._Nested;
|
|
FCurrentNode.Init(NewData, FCurrentNode.StartLine + NewData._LineOffset, FCurrentNode.LeftSizeSum);
|
|
//FCurrentNode.Page := NewData;
|
|
//FCurrentNode.StartLine := FCurrentNode.StartLine + NewData._LineOffset;
|
|
|
|
PNode := FCurrentNode.Prev;
|
|
while PNode.IsInFold do begin
|
|
FCurrentNode := PNode;
|
|
PNode := FCurrentNode.Prev;
|
|
end;
|
|
end;
|
|
Result := FCurrentNode;
|
|
end;
|
|
|
|
function TSynTextFoldAVLNodeNestedIterator.Prev: TSynTextFoldAVLNode;
|
|
var
|
|
i: Integer;
|
|
NewData: TSynTextFoldAVLNodeData;
|
|
PNode: TSynTextFoldAVLNode;
|
|
begin
|
|
FCurrentNode := FCurrentNode.Prev;
|
|
i := length(FOuterNodes);
|
|
if FCurrentNode.IsInFold then begin
|
|
while (FCurrentNode.Page._Nested <> nil) do begin
|
|
SetLength(FOuterNodes, i + 1);
|
|
FOuterNodes[i] := FCurrentNode;
|
|
NewData := FCurrentNode.Page._Nested;
|
|
FCurrentNode.Init(NewData, FCurrentNode.StartLine + NewData._LineOffset, FCurrentNode.LeftSizeSum);
|
|
//FCurrentNode.Page := NewData;
|
|
//FCurrentNode.StartLine := FCurrentNode.StartLine + NewData._LineOffset;
|
|
|
|
PNode := FCurrentNode.Next;
|
|
while PNode.IsInFold do begin
|
|
FCurrentNode := PNode;
|
|
PNode := FCurrentNode.Next;
|
|
end;
|
|
end;
|
|
end
|
|
else // not IsInFold
|
|
if (i > 0) then begin
|
|
dec(i);
|
|
FCurrentNode := FOuterNodes[i];
|
|
SetLength(FOuterNodes, i);
|
|
end;
|
|
Result := FCurrentNode;
|
|
end;
|
|
|
|
function TSynTextFoldAVLNodeNestedIterator.EOF: Boolean;
|
|
begin
|
|
Result := not FCurrentNode.Next.IsInFold;
|
|
end;
|
|
|
|
function TSynTextFoldAVLNodeNestedIterator.BOF: Boolean;
|
|
begin
|
|
Result := not FCurrentNode.Prev.IsInFold;
|
|
end;
|
|
|
|
function TSynTextFoldAVLNodeNestedIterator.IsInFold: Boolean;
|
|
begin
|
|
Result := FCurrentNode.IsInFold;
|
|
end;
|
|
|
|
{ TSynFoldNodeInfoHelper }
|
|
|
|
constructor TSynFoldNodeInfoHelper.Create(AHighlighter: TSynCustomFoldHighlighter);
|
|
begin
|
|
inherited Create;
|
|
FHighlighter := AHighlighter;
|
|
Invalidate;
|
|
end;
|
|
|
|
function TSynFoldNodeInfoHelper.FirstOpen: TSynFoldNodeInfo;
|
|
begin
|
|
FActions := [sfaOpen, sfaFold];
|
|
FCurInfo.NodeIndex := -1;
|
|
FCurInfo.LineIndex := 0;
|
|
Result := Next;
|
|
end;
|
|
|
|
procedure TSynFoldNodeInfoHelper.Invalidate;
|
|
begin
|
|
FCurInfo.FoldAction := [sfaInvalid];
|
|
end;
|
|
|
|
function TSynFoldNodeInfoHelper.Next: TSynFoldNodeInfo;
|
|
var
|
|
Cnt, Line, Idx: LongInt;
|
|
begin
|
|
Idx := FCurInfo.NodeIndex + 1;
|
|
Line := FCurInfo.LineIndex;
|
|
Cnt := FHighlighter.FoldNodeInfo[Line].CountEx(FActions);
|
|
if Idx >= Cnt then begin
|
|
Idx := 0;
|
|
inc(Line);
|
|
while (Line < FHighlighter.CurrentLines.Count) and
|
|
(FHighlighter.FoldNodeInfo[Line].CountEx(FActions) = 0)
|
|
do
|
|
inc(Line);
|
|
end;
|
|
if (Line < FHighlighter.CurrentLines.Count) then
|
|
FCurInfo := FHighlighter.FoldNodeInfo[Line].NodeInfoEx(Idx, FActions)
|
|
else
|
|
Invalidate;
|
|
Result := FCurInfo;
|
|
end;
|
|
|
|
function TSynFoldNodeInfoHelper.Prev: TSynFoldNodeInfo;
|
|
var
|
|
Line, Idx: LongInt;
|
|
begin
|
|
Idx := FCurInfo.NodeIndex - 1;
|
|
Line := FCurInfo.LineIndex;
|
|
if Idx < 0 then begin
|
|
dec(Line);
|
|
while (Line >= 0) and
|
|
(FHighlighter.FoldNodeInfo[Line].CountEx(FActions) = 0)
|
|
do
|
|
dec(Line);
|
|
Idx := FHighlighter.FoldNodeInfo[Line].CountEx(FActions) - 1;
|
|
end;
|
|
if (Line >= 0) then
|
|
FCurInfo := FHighlighter.FoldNodeInfo[Line].NodeInfoEx(Idx, FActions)
|
|
else
|
|
Invalidate;
|
|
Result := FCurInfo;
|
|
end;
|
|
|
|
function TSynFoldNodeInfoHelper.FindClose: TSynFoldNodeInfo;
|
|
var
|
|
Line, EndLine, Cnt: Integer;
|
|
NdInfo: TSynFoldNodeInfo;
|
|
begin
|
|
Line := FCurInfo.LineIndex;
|
|
EndLine := FHighlighter.FoldEndLine(Line, FCurInfo.NodeIndex);
|
|
FActions := [sfaClose, sfaFold];
|
|
Cnt := FHighlighter.FoldNodeInfo[EndLine].CountEx(FActions) - 1;
|
|
while Cnt >= 0 do begin
|
|
NdInfo := FHighlighter.FoldNodeInfo[EndLine].NodeInfoEx(Cnt, FActions);
|
|
if (NdInfo.FoldLvlStart = FCurInfo.FoldLvlEnd) and
|
|
(NdInfo.FoldType = FCurInfo.FoldType)
|
|
then
|
|
break;
|
|
dec(Cnt);
|
|
end;
|
|
if Cnt < 0 then
|
|
Invalidate
|
|
else
|
|
FCurInfo := NdInfo;
|
|
Result := FCurInfo;
|
|
end;
|
|
|
|
function TSynFoldNodeInfoHelper.GotoOpenPos(aLineIdx, aNodeIdx: integer): TSynFoldNodeInfo;
|
|
begin
|
|
FActions := [sfaOpen, sfaFold];
|
|
FCurInfo := FHighlighter.FoldNodeInfo[aLineIdx].NodeInfoEx(aNodeIdx, FActions);
|
|
Result := FCurInfo;
|
|
end;
|
|
|
|
function TSynFoldNodeInfoHelper.GotoOpenAtChar(aLineIdx, aXPos: integer): TSynFoldNodeInfo;
|
|
var
|
|
Cnt: Integer;
|
|
begin
|
|
FActions := [sfaOpen, sfaFold];
|
|
Cnt := FHighlighter.FoldNodeInfo[aLineIdx].CountEx(FActions) - 1;
|
|
while Cnt >= 0 do begin
|
|
FCurInfo := FHighlighter.FoldNodeInfo[aLineIdx].NodeInfoEx(Cnt, FActions);
|
|
if FCurInfo.LogXStart = aXPos then break;
|
|
dec(Cnt);
|
|
end;
|
|
if Cnt < 0 then
|
|
Invalidate;
|
|
Result := FCurInfo;
|
|
end;
|
|
|
|
function TSynFoldNodeInfoHelper.GotoNodeOpenPos(ANode: TSynTextFoldAVLNode): TSynFoldNodeInfo;
|
|
begin
|
|
FActions := [sfaOpen, sfaFold];
|
|
FCurInfo := FHighlighter.FoldNodeInfo[ANode.StartLine - ANode.SourceLineOffset - 1]
|
|
.NodeInfoEx(ANode.FoldIndex, FActions);
|
|
Result := FCurInfo;
|
|
end;
|
|
|
|
function TSynFoldNodeInfoHelper.GotoNodeClosePos(ANode: TSynTextFoldAVLNode): TSynFoldNodeInfo;
|
|
var
|
|
NdInfo, NdInfo2: TSynFoldNodeInfo;
|
|
Cnt, EndCol, EndLineIdx: Integer;
|
|
begin
|
|
FActions := [sfaClose, sfaFold];
|
|
NdInfo := FHighlighter.FoldNodeInfo[ANode.StartLine - ANode.SourceLineOffset - 1]
|
|
.NodeInfoEx(ANode.FoldIndex, [sfaOpen, sfaFold]);
|
|
if sfaInvalid in NdInfo.FoldAction then exit(NdInfo);
|
|
|
|
EndLineIdx := FHighlighter.FoldEndLine(ANode.StartLine - ANode.SourceLineOffset - 1,
|
|
ANode.FoldIndex);
|
|
{$IFDEF SynAssertFold}
|
|
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]);
|
|
{$ENDIF}
|
|
Cnt := FHighlighter.FoldNodeInfo[EndLineIdx].CountEx([sfaClose, sfaFold]);
|
|
EndCol := 0;
|
|
while EndCol < Cnt do begin
|
|
NdInfo2 := FHighlighter.FoldNodeInfo[EndLineIdx].NodeInfoEx(EndCol, [sfaClose, sfaFold]);
|
|
if (NdInfo2.FoldLvlStart = NdInfo.FoldLvlEnd) and
|
|
(NdInfo2.FoldType = NdInfo.FoldType) then break;
|
|
inc(EndCol);
|
|
end;
|
|
if (EndCol = Cnt) or (sfaInvalid in NdInfo2.FoldAction) then
|
|
Invalidate
|
|
else
|
|
FCurInfo := NdInfo2;
|
|
Result := FCurInfo;
|
|
end;
|
|
|
|
function TSynFoldNodeInfoHelper.IsAtNodeOpenPos(ANode: TSynTextFoldAVLNode): Boolean;
|
|
begin
|
|
Result := (not (sfaInvalid in FCurInfo.FoldAction)) and
|
|
(ANode.IsInFold) and
|
|
(FCurInfo.LineIndex = ANode.StartLine - ANode.SourceLineOffset - 1) and
|
|
(FCurInfo.NodeIndex = ANode.FoldIndex);
|
|
end;
|
|
|
|
function TSynFoldNodeInfoHelper.IsValid: Boolean;
|
|
begin
|
|
Result := (not (sfaInvalid in FCurInfo.FoldAction));
|
|
end;
|
|
|
|
function TSynFoldNodeInfoHelper.Equals(AnInfo: TSynFoldNodeInfo): Boolean;
|
|
begin
|
|
Result := (FCurInfo.LineIndex = AnInfo.LineIndex) and
|
|
(FCurInfo.NodeIndex = AnInfo.NodeIndex) and
|
|
(FCurInfo.LogXStart = AnInfo.LogXStart) and
|
|
(FCurInfo.LogXEnd = AnInfo.LogXEnd) and
|
|
(FCurInfo.FoldLvlStart = AnInfo.FoldLvlStart) and
|
|
(FCurInfo.FoldLvlEnd = AnInfo.FoldLvlEnd) and
|
|
(FCurInfo.FoldAction = AnInfo.FoldAction) and
|
|
(FCurInfo.FoldType = AnInfo.FoldType) and
|
|
(FCurInfo.FoldGroup = AnInfo.FoldGroup);
|
|
end;
|
|
|
|
function TSynFoldNodeInfoHelper.Equals(AHelper: TSynFoldNodeInfoHelper): Boolean;
|
|
begin
|
|
Result := Equals(AHelper.Info);
|
|
end;
|
|
|
|
{ TSynTextFoldAVLTree }
|
|
|
|
function TSynTextFoldAVLTree.NewNode : TSynTextFoldAVLNodeData;
|
|
begin
|
|
Result := TSynTextFoldAVLNodeData.Create;
|
|
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(TSynSizedDifferentialAVLNode(ANode));
|
|
end;
|
|
begin
|
|
if fRoot <> nil then DeleteNode(TSynTextFoldAVLNodeData(fRoot));
|
|
SetRoot(nil);
|
|
end;
|
|
|
|
procedure TSynTextFoldAVLTree.SetRoot(ANode : TSynSizedDifferentialAVLNode);
|
|
begin
|
|
inherited;
|
|
if fNestParent <> nil then fNestParent._Nested := TSynTextFoldAVLNodeData(ANode);
|
|
end;
|
|
|
|
procedure TSynTextFoldAVLTree.SetRoot(ANode : TSynSizedDifferentialAVLNode; anAdjustChildLineOffset : Integer);
|
|
begin
|
|
inherited;
|
|
if fNestParent <> nil then fNestParent._Nested := TSynTextFoldAVLNodeData(ANode);
|
|
end;
|
|
|
|
(* Find Fold by Line in Real Text *)
|
|
function TSynTextFoldAVLTree.FindFoldForLine(ALine : Integer;
|
|
FindNextNode : Boolean = False) : TSynTextFoldAVLNode;
|
|
var
|
|
r : TSynTextFoldAVLNodeData;
|
|
rStartLine : Integer;
|
|
rFoldedBefore : Integer;
|
|
begin
|
|
r := TSynTextFoldAVLNodeData(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._LeftVirtualLineCount;
|
|
if ALine < rStartLine + r._MergedLineCount
|
|
then break;
|
|
|
|
if FindNextNode and (r.Right = nil) then begin
|
|
r := r.Successor(rStartLine, rFoldedBefore);
|
|
break;
|
|
end;
|
|
|
|
rFoldedBefore := rFoldedBefore + r._MergedVirtualLineCount;
|
|
r := r.Right; // rStartLine points to r, which now is the start of the previous fold;
|
|
end;
|
|
|
|
Result{%H-}.Init(r, rStartLine, rFoldedBefore);
|
|
end;
|
|
|
|
(* Find Fold by Line in Folded Text // always returns unfolded, unless next=true *)
|
|
function TSynTextFoldAVLTree.FindFoldForFoldedLine(ALine : Integer;
|
|
FindNextNode : Boolean) : TSynTextFoldAVLNode;
|
|
var
|
|
r : TSynTextFoldAVLNodeData;
|
|
rStartLine : Integer;
|
|
rFoldedBefore : Integer;
|
|
begin
|
|
r := TSynTextFoldAVLNodeData(fRoot);
|
|
rStartLine := fRootOffset;
|
|
rFoldedBefore := 0;
|
|
while (r <> nil) do begin
|
|
rStartLine := rStartLine + r._LineOffset;
|
|
|
|
// r._LeftVirtualLineCount => "FoldedBefore"
|
|
if ALine + r._LeftVirtualLineCount < ToPos(FNextStrings.TextToViewIndex(ToIdx(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._LeftVirtualLineCount + r._MergedVirtualLineCount;
|
|
rFoldedBefore := rFoldedBefore + r._LeftVirtualLineCount;
|
|
|
|
if FindNextNode and (r.Right = nil) then begin
|
|
r := r.Successor(rStartLine, rFoldedBefore);
|
|
break;
|
|
end;
|
|
|
|
rFoldedBefore := rFoldedBefore + r._MergedVirtualLineCount;
|
|
r := r.Right; // rStartLine points to r, which now is the start of the previous fold;
|
|
end;
|
|
|
|
Result{%H-}.Init(r, rStartLine, rFoldedBefore);
|
|
end;
|
|
|
|
procedure TSynTextFoldAVLTree.AdjustForLinesInserted(AStartLine, ALineCount, ABytePos: Integer);
|
|
Procedure DoAdjustForLinesInserted(Current : TSynTextFoldAVLNodeData;
|
|
CurrentLine : Integer);
|
|
var
|
|
t: LongInt;
|
|
begin
|
|
while (Current <> nil) do begin
|
|
CurrentLine := CurrentLine + Current._LineOffset;
|
|
|
|
if (AStartLine <= CurrentLine - Current._VisibleLines) or
|
|
( (AStartLine - 1 = CurrentLine - Current._VisibleLines) and
|
|
(ABytePos <= Current._FoldColumn) )
|
|
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._MergedLineCount- 1 then begin
|
|
// The new lines are entirly behind the current node
|
|
Current := Current.Right;
|
|
end
|
|
else begin
|
|
// grow current node (there is only one node one the line, the others are nested)
|
|
// CurrentLine <= AStartLine <= CurrentLine + Current._FoldedCount - 1
|
|
t := Current._FoldedCount;
|
|
if AStartLine <= CurrentLine + t - 1 then
|
|
Current._FoldedCount := t + ALineCount;
|
|
Current._MergedLineCount:= Current._MergedLineCount+ ALineCount;
|
|
Current.UdpateVirtualLineCounts(FNextStrings, CurrentLine);
|
|
TreeForNestedNode(Current, CurrentLine).AdjustForLinesInserted(AStartLine, ALineCount, ABytePos);
|
|
|
|
if Current.Right <> nil then // and move entire right
|
|
Current.Right._LineOffset := Current.Right._LineOffset + ALineCount;
|
|
break;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
{$IFDEF SynFoldDebug}debugln(['FOLD-- AdjustForLinesInsertedAStartLine:=', AStartLine, ' ALineCount=',ALineCount, ' ABytePos=',ABytePos ]); {$ENDIF}
|
|
DoAdjustForLinesInserted(TSynTextFoldAVLNodeData(fRoot), fRootOffset);
|
|
AdjustColumn(AStartLine+ALineCount-1, ABytePos, -ABytePos+1, True);
|
|
end;
|
|
|
|
procedure TSynTextFoldAVLTree.AdjustForLinesDeleted(AStartLine,
|
|
ALineCount, ABytePos: Integer);
|
|
Procedure AdjustNodeForLinesDeleted(Current : TSynTextFoldAVLNodeData;
|
|
CurrentLine, FirstLineToDelete, CountLinesToDelete : Integer);
|
|
var
|
|
LastLineToDelete, LinesBefore, LinesInside, LinesAfter : Integer;
|
|
begin
|
|
LastLineToDelete := FirstLineToDelete + CountLinesToDelete - 1; // only valid for delete; CountLinesToDelete < 0
|
|
|
|
while (Current <> nil) do begin
|
|
CurrentLine := CurrentLine + Current._LineOffset;
|
|
|
|
if FirstLineToDelete <= CurrentLine - Current._VisibleLines then begin
|
|
// move current node
|
|
if LastLineToDelete > CurrentLine - Current._VisibleLines then begin
|
|
// overlap => shrink
|
|
LinesBefore := CurrentLine - FirstLineToDelete;
|
|
LinesInside := CountLinesToDelete - LinesBefore;
|
|
// shrink
|
|
Current._FoldedCount := Max(Current._FoldedCount - LinesInside, -1);
|
|
Current._MergedLineCount := Max(Current._MergedLineCount - LinesInside, 0);
|
|
Current.UdpateVirtualLineCounts(FNextStrings, CurrentLine); // If LineCount = -1; LeftVirtualLineCount will be correctd on delete node
|
|
TreeForNestedNode(Current, CurrentLine).AdjustForLinesDeleted(CurrentLine, LinesInside, ABytePos);
|
|
|
|
if (Current.Right <> nil) then begin
|
|
// move right // Calculate from the new curent.LineOffset, as below
|
|
AdjustNodeForLinesDeleted(Current.Right, CurrentLine - LinesBefore,
|
|
FirstLineToDelete, LinesInside);
|
|
end;
|
|
end
|
|
else LinesBefore := CountLinesToDelete;
|
|
|
|
// move current node (includes right subtree / left subtree needs eval)
|
|
Current._LineOffset := Current._LineOffset - LinesBefore;
|
|
CurrentLine := CurrentLine - LinesBefore;
|
|
//if AStartLine = CurrentLine then begin
|
|
// Current._FoldColumn := Current._FoldColumn + ABytePos-1;
|
|
// TreeForNestedNode(Current, CurrentLine).AdjustColumn(CurrentLine, 1, ABytePos-1);
|
|
//end;
|
|
if Current.Left <> nil then
|
|
Current.Left._LineOffset := Current.Left._LineOffset + LinesBefore;
|
|
Current := Current.Left;
|
|
end
|
|
else if FirstLineToDelete > CurrentLine + Current._MergedLineCount - 1 then begin
|
|
// The deleted lines are entirly behind the current node
|
|
Current := Current.Right;
|
|
end
|
|
else begin
|
|
// (FirstLineToDelete >= CurrentLine) AND (FirstLineToDelete < CurrentLine + Current.LineCount);
|
|
LinesAfter := LastLineToDelete - (CurrentLine + Current._MergedLineCount - 1);
|
|
if LinesAfter < 0 then LinesAfter := 0;
|
|
LinesInside := CountLinesToDelete - LinesAfter;
|
|
|
|
// shrink current node
|
|
Current._MergedLineCount := Current._MergedLineCount- LinesInside;
|
|
if Current._FoldedCount > Current._MergedLineCount then
|
|
Current._FoldedCount := Current._MergedLineCount;
|
|
Current.UdpateVirtualLineCounts(FNextStrings, CurrentLine); // If MergedLineCount = -1; LeftVirtualLineCount will be correctd on delete node
|
|
|
|
TreeForNestedNode(Current, CurrentLine).AdjustForLinesDeleted(FirstLineToDelete, LinesInside, ABytePos);
|
|
Current := Current.Right;
|
|
end;
|
|
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
{$IFDEF SynFoldDebug}debugln(['FOLD-- AdjustForLinesDeleted AStartLine:=', AStartLine, ' ALineCount=',ALineCount, ' ABytePos=',ABytePos ]); {$ENDIF}
|
|
if ABytePos > 1 then
|
|
AdjustColumn(AStartLine+ALineCount-1, 1, ABytePos-1);
|
|
AdjustNodeForLinesDeleted(TSynTextFoldAVLNodeData(fRoot), fRootOffset, AStartLine, ALineCount);
|
|
end;
|
|
|
|
procedure TSynTextFoldAVLTree.AdjustColumn(ALine, ABytePos, ACount: Integer;
|
|
InLineBreak: boolean = False);
|
|
var
|
|
Node: TSynTextFoldAVLNode;
|
|
begin
|
|
Node := FindFoldForLine(ALine, True);
|
|
{$IFDEF SynFoldDebug}debugln(['FOLD-- AdjustColumn ALine:=', ALine, ' ABytePos=',ABytePos, ' ACount=',ACount, ' // node.srcline=',Node.SourceLine, ' StartLine=', node.StartLine, 'column=',Node.FoldColumn ]); {$ENDIF}
|
|
if (not Node.IsInFold) or (Node.SourceLine > ALine) then exit;
|
|
if (Node.SourceLine = ALine) and (node.FoldColumn >= ABytePos) then begin
|
|
node.FoldColumn := Node.FoldColumn + ACount;
|
|
if (not InLineBreak) and (node.FoldColumn < ABytePos) then node.FoldColumn := ABytePos;
|
|
end;
|
|
TreeForNestedNode(Node.Page, node.StartLine).AdjustColumn(ALine, ABytePos, ACount);
|
|
end;
|
|
|
|
function TSynTextFoldAVLTree.LastPage: TSynTextFoldAVLNode;
|
|
var
|
|
r : TSynTextFoldAVLNodeData;
|
|
rStartLine : Integer;
|
|
rFoldedBefore : Integer;
|
|
begin
|
|
r := TSynTextFoldAVLNodeData(fRoot);
|
|
rStartLine := fRootOffset;
|
|
rFoldedBefore := 0;
|
|
while (r <> nil) do begin
|
|
rStartLine := rStartLine + r._LineOffset;
|
|
rFoldedBefore := rFoldedBefore + r._LeftVirtualLineCount + r._MergedVirtualLineCount;
|
|
if r.Right = nil then break;
|
|
r := r.Right; // rStartLine points to r, which now is the start of the previous fold;
|
|
end;
|
|
|
|
Result{%H-}.Init(r, rStartLine, rFoldedBefore);
|
|
end;
|
|
|
|
function TSynTextFoldAVLTree.LastFoldedLine: integer;
|
|
var
|
|
n: TSynTextFoldAVLNode;
|
|
begin
|
|
n := FirstPage;
|
|
if not n.IsInFold then exit(0);
|
|
Result := n.StartLine + n.MergedLineCount - 1;
|
|
end;
|
|
|
|
{$IFDEF SynDebug}
|
|
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([Format('Lines=%3d-%3d (e=%3d / idx=%d) %2d:%d; Lcnt=%2d / Fcnt=%2d | ',
|
|
[offset + ANode._LineOffset, offset + ANode._LineOffset + ANode._FoldedCount -1,
|
|
offset + ANode._LineOffset + ANode._MergedLineCount-1, ANode._FoldIndex,
|
|
ANode._FoldColumn, ANode._FoldColumnLen,
|
|
_MergedLineCount, _FoldedCount]),
|
|
ind, typ, ' (',_LineOffset, ') LeftVirtualLineCount: ', _LeftVirtualLineCount,
|
|
' Balance: ',FBalance]);
|
|
if ANode.Parent <> AParent then DebugLn([ind,'* Bad parent']);
|
|
Result := debug2(ind+' ', 'L', ANode.Left, ANode, offset+ANode._LineOffset);
|
|
If Result <> ANode._LeftVirtualLineCount then debugln([ind,' ***** LeftVirtualLineCount was ',Result, ' but should be ', ANode._LeftVirtualLineCount]);
|
|
Result := Result + debug2(ind+' ', 'R', ANode.Right, ANode, offset+ANode._LineOffset);
|
|
debug2(ind+' #', 'N', ANode._Nested, nil, offset+ANode._LineOffset);
|
|
Result := Result + ANode._MergedLineCount;
|
|
end;
|
|
begin
|
|
debugln('StartLine, EndLine (MergedEnd, FoldIndex) - Column:Len; MergedLineCnt / FullCCnt | .. (_LineOffset) ...');
|
|
debug2('', ' -', TSynTextFoldAVLNodeData(fRoot), nil, 0);
|
|
end;
|
|
{$ENDIF}
|
|
|
|
function TSynTextFoldAVLTree.InsertNewFold(ALine, AFoldIndex, AColumn, AColumnLen,
|
|
ACount, AVisibleLines: Integer; AClassification: TFoldNodeClassification;
|
|
AFoldTypeCompatible: Pointer) : TSynTextFoldAVLNode;
|
|
var
|
|
r : TSynTextFoldAVLNodeData;
|
|
begin
|
|
{$IFDEF SynFoldDebug}debugln(['FOLD-- InsertNewFold ALine:=', ALine, ' AFoldIndex=', AFoldIndex]);{$ENDIF}
|
|
r := NewNode;
|
|
r._LineOffset := ALine; // 1-based
|
|
r._FoldIndex := AFoldIndex;
|
|
r._FoldColumn := AColumn;
|
|
r._FoldColumnLen := AColumnLen;
|
|
r._MergedLineCount := ACount;
|
|
r._FoldedCount := ACount;
|
|
r._LeftVirtualLineCount := 0;
|
|
r._VisibleLines := AVisibleLines;
|
|
r._Classification := AClassification;
|
|
r._FoldTypeCompatible := AFoldTypeCompatible;
|
|
r.UdpateVirtualLineCounts(FNextStrings, ALine);
|
|
|
|
Result{%H-}.Init(r, ALine, InsertNode(r));
|
|
//Result{%H-}.Init(r, ALine, 0);
|
|
//Result.fFoldedBefore := InsertNode(r);
|
|
end;
|
|
|
|
function TSynTextFoldAVLTree.RemoveFoldForLine(ALine : Integer;
|
|
OnlyCol: Integer = -1) : Integer;
|
|
var
|
|
OldFold : TSynTextFoldAVLNode;
|
|
lcount: Integer;
|
|
begin
|
|
{$IFDEF SynFoldDebug}debugln(['FOLD-- RemoveFoldForLine ALine:=', ALine, ' OnlyCol=',OnlyCol]);{$ENDIF}
|
|
Result := ALine; // - 1; // Return index
|
|
OldFold := FindFoldForLine(ALine, False);
|
|
if OldFold.StartLine < Result then
|
|
Result := OldFold.StartLine;
|
|
|
|
if (not OldFold.IsInFold) then exit;
|
|
|
|
if OnlyCol < 0 then
|
|
RemoveFoldForNodeAtLine(OldFold, ALine)
|
|
else
|
|
if OldFold.FoldIndex = OnlyCol then
|
|
RemoveFoldForNodeAtLine(OldFold, -1)
|
|
else
|
|
if OldFold.Page._Nested <> nil then begin
|
|
TreeForNestedNode(OldFold.Page, OldFold.StartLine).RemoveFoldForLine
|
|
(ALine, OnlyCol);
|
|
lcount := max(OldFold.FoldedCount,
|
|
TreeForNestedNode(OldFold.Page, 0).LastFoldedLine + 1);
|
|
if lcount <> OldFold.MergedLineCount then begin
|
|
OldFold.Page._MergedLineCount := lcount;
|
|
OldFold.Page.UdpateVirtualLineCounts(FNextStrings, OldFold.StartLine);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TSynTextFoldAVLTree.RemoveFoldForNodeAtLine(ANode : TSynTextFoldAVLNode;
|
|
ALine : Integer) : Integer;
|
|
var
|
|
NestedNode, MergeNode : TSynTextFoldAVLNodeData;
|
|
NestedLine, offs, lcount : Integer;
|
|
OnlyNested: Boolean;
|
|
Nested: TSynTextFoldAVLNode;
|
|
begin
|
|
{$IFDEF SynFoldDebug}debugln(['FOLD-- RemoveFoldForNodeAtLine: ALine:=', ALine, ' ANode.StartLine=', ANode.StartLine]);{$ENDIF}
|
|
OnlyNested := ALine >= ANode.StartLine + ANode.FoldedCount;
|
|
// The cfCollapsed line is one line before the fold
|
|
Result := ANode.StartLine-1; // Return the cfcollapsed that was unfolded
|
|
if not OnlyNested then
|
|
RemoveNode(ANode.Page);
|
|
|
|
NestedLine := 0;
|
|
If ANode.Page._Nested <> nil then
|
|
begin
|
|
(*Todo: should we mark the tree as NO balancing needed ???*)
|
|
TreeForNestedNode(ANode.Page, ANode.StartLine).RemoveFoldForLine(ALine);
|
|
|
|
if OnlyNested then begin
|
|
NestedLine := ANode.StartLine + ANode.FoldedCount;
|
|
Nested := TreeForNestedNode(ANode.Page, ANode.StartLine).LastPage;
|
|
while Nested.IsInFold and (Nested.StartLine >= NestedLine) do begin
|
|
NestedNode := Nested.Page;
|
|
offs := Nested.StartLine;
|
|
Nested := Nested.Prev;
|
|
|
|
lcount := ANode.Page._MergedLineCount;
|
|
ANode.Page._MergedLineCount := max(ANode.FoldedCount,
|
|
Nested.StartLine + Nested.MergedLineCount - ANode.StartLine);
|
|
ANode.Page.UdpateVirtualLineCounts(FNextStrings, ANode.StartLine);
|
|
|
|
TreeForNestedNode(ANode.Page, ANode.StartLine).RemoveNode(NestedNode);
|
|
NestedNode._LineOffset := offs;
|
|
InsertNode(NestedNode);
|
|
end;
|
|
lcount := max(ANode.FoldedCount,
|
|
TreeForNestedNode(ANode.Page, ANode.StartLine).LastFoldedLine
|
|
- ANode.StartLine + 1);
|
|
if lcount <> ANode.MergedLineCount then begin
|
|
ANode.Page._MergedLineCount := lcount;
|
|
ANode.Page.UdpateVirtualLineCounts(FNextStrings, ANode.StartLine);
|
|
end;
|
|
end
|
|
else begin
|
|
// merge the remaining nested into current
|
|
NestedNode := ANode.Page._Nested;
|
|
if NestedNode <> nil
|
|
then NestedLine := ANode.StartLine + 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.FParent := nil;
|
|
end;
|
|
MergeNode._LeftVirtualLineCount := 0;
|
|
MergeNode.FBalance := 0;
|
|
if MergeNode._FoldedCount <= 0 then begin
|
|
MergeNode.FreeAllChildrenAndNested;
|
|
MergeNode.Free;
|
|
end
|
|
else
|
|
InsertNode(MergeNode);
|
|
end;
|
|
end;
|
|
|
|
end;
|
|
|
|
if not OnlyNested then
|
|
ANode.DisposeNode(Self);
|
|
end;
|
|
|
|
function TSynTextFoldAVLTree.InsertNode(ANode : TSynTextFoldAVLNodeData) : Integer;
|
|
var
|
|
rStartLine, NestStartLine : Integer;
|
|
rFoldedBefore, NestFoldedBefore : Integer;
|
|
|
|
current, Nest : TSynTextFoldAVLNodeData;
|
|
ALine, AEnd, ACount, AVirtualCount: Integer;
|
|
|
|
(* ANode.StartLine < Current.StartLine // ANode goes into tree *)
|
|
procedure NestCurrentIntoNewBlock; inline;
|
|
var
|
|
diff, start2, before2 : Integer;
|
|
p : TSynTextFoldAVLNodeData;
|
|
begin
|
|
current.AdjustParentLeftCount(AVirtualCount-current._MergedVirtualLineCount); // -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.FBalance := current.FBalance;
|
|
current._LineOffset := diff; // offset to ANode (via Nested)
|
|
current.FParent := nil;
|
|
current.FBalance := 0;
|
|
|
|
ANode.SetLeftChild(current.Left, diff, current._LeftVirtualLineCount);
|
|
current.FLeft := nil;
|
|
current._LeftVirtualLineCount := 0;
|
|
ANode.SetRightChild(current.Right, diff);
|
|
current.FRight := 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;
|
|
// check only after loop, if we did grow, we did so by existing nodes, so no new overlaps
|
|
start2 := TreeForNestedNode(Anode, 0).LastFoldedLine;
|
|
if start2 > ANode._FoldedCount - 1 then begin
|
|
ANode._MergedLineCount := start2 + 1;
|
|
ANode.UdpateVirtualLineCounts(FNextStrings, ALine);
|
|
end;
|
|
end;
|
|
|
|
(* ANode.StartLine > Current.StartLine // Current remains in tree *)
|
|
procedure NestNewBlockIntoCurrent; //inline;
|
|
var
|
|
end2, start2, before2: Integer;
|
|
p: TSynTextFoldAVLNodeData;
|
|
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;
|
|
|
|
end2 := TreeForNestedNode(current, 0).LastFoldedLine;
|
|
if end2 > current._FoldedCount -1 then begin
|
|
end2 := rStartLine + end2;
|
|
|
|
start2 := rStartLine; before2 := rFoldedBefore;
|
|
p := current.Successor(start2, before2);
|
|
while (p <> nil) and (start2 <= end2) do begin
|
|
RemoveNode(p);
|
|
p._LineOffset := start2 - rStartLine;
|
|
TreeForNestedNode(current, 0).InsertNode(p);
|
|
|
|
start2 := rStartLine; before2 := rFoldedBefore;
|
|
p := current.Successor(start2, before2);
|
|
end;
|
|
end2 := TreeForNestedNode(current, 0).LastFoldedLine;
|
|
if end2 > current._FoldedCount -1 then begin
|
|
current._MergedLineCount := end2 + 1;
|
|
current.UdpateVirtualLineCounts(FNextStrings, rStartLine);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
Result := 0;
|
|
if fRoot = nil then begin
|
|
SetRoot(ANode, -fRootOffset);
|
|
exit;
|
|
end;
|
|
ALine := ANode._LineOffset;
|
|
ACount := ANode._MergedLineCount;
|
|
AVirtualCount := ANode._MergedVirtualLineCount;
|
|
AEnd := ALine + ACount - 1;
|
|
current := TSynTextFoldAVLNodeData(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(AVirtualCount);
|
|
current.SetLeftChild(ANode, -rStartLine, ANode._MergedVirtualLineCount);
|
|
BalanceAfterInsert(ANode);
|
|
end
|
|
else begin // nest
|
|
current := Nest;
|
|
rStartLine := NestStartLine;
|
|
rFoldedBefore := NestFoldedBefore;
|
|
NestCurrentIntoNewBlock;
|
|
end;
|
|
break;
|
|
end;
|
|
|
|
rFoldedBefore := rFoldedBefore + current._LeftVirtualLineCount;
|
|
if ALine = rStartLine then begin
|
|
if ANode._FoldIndex > current._FoldIndex then
|
|
(* *** New Block will be nested in current *** *)
|
|
NestNewBlockIntoCurrent
|
|
else
|
|
if ANode._FoldIndex < current._FoldIndex then
|
|
(* *** current will be nested in New Block *** *)
|
|
NestCurrentIntoNewBlock
|
|
else begin
|
|
debugln(['Droping Foldnode / Already exists. Startline=', rStartLine,' LineCount=',ACount]);
|
|
FreeAndNil(ANode);
|
|
end;
|
|
end
|
|
else begin
|
|
If ALine <= rStartLine + current._MergedLineCount - 1
|
|
(* *** New Block will be nested in current *** *)
|
|
then NestNewBlockIntoCurrent
|
|
(* *** New block goes to the right *** *)
|
|
else begin
|
|
rFoldedBefore := rFoldedBefore + current._MergedVirtualLineCount;
|
|
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(AVirtualCount);
|
|
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;
|
|
|
|
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
|
|
inherited;
|
|
fNestParent := nil;
|
|
fNestedNodesTree := nil;
|
|
end;
|
|
|
|
{ TSynEditFoldProvider }
|
|
|
|
function TSynEditFoldProvider.GetLineCapabilities(ALineIdx: Integer): TSynEditFoldLineCapabilities;
|
|
var
|
|
c: Integer;
|
|
begin
|
|
Result := [];
|
|
if not FEnabled then
|
|
exit;
|
|
if (FEdit <> nil) and (FEdit.SelAvail) then begin
|
|
if (FEdit.BlockBegin.Y < ALineIdx+1) and
|
|
(FEdit.BlockEnd.Y > ALineIdx+1)
|
|
then Result := [cfFoldBody];
|
|
if (FEdit.BlockEnd.Y = ALineIdx+1) then Result := [cfFoldEnd];
|
|
if (FEdit.BlockBegin.Y = ALineIdx+1) then Result := [cfHideStart];
|
|
if (FEdit.BlockBegin.Y = ALineIdx+1) and
|
|
(FEdit.BlockEnd.Y = ALineIdx+1) then Result := [cfHideStart, cfSingleLineHide];
|
|
end;
|
|
if (FHighlighter = nil) or (ALineIdx < 0) then
|
|
exit;
|
|
|
|
FHighlighter.CurrentLines := FLines;
|
|
if FHighlighter.FoldBlockEndLevel(ALineIdx - 1) > 0 then Result := Result + [cfFoldBody];
|
|
if FHighlighter.FoldBlockClosingCount(ALineIdx) > 0 then Result := Result + [cfFoldEnd, cfFoldBody];
|
|
|
|
c := FHighlighter.FoldNodeInfo[ALineIdx].CountEx([]);
|
|
if c > 0 then begin
|
|
c := FHighlighter.FoldNodeInfo[ALineIdx].CountEx([sfaOpenFold, sfaFoldFold]);
|
|
if c > 0 then
|
|
include(Result, cfFoldStart);
|
|
c := FHighlighter.FoldNodeInfo[ALineIdx].CountEx([sfaOpenFold, sfaFoldHide]);
|
|
if c > 0 then
|
|
include(Result, cfHideStart);
|
|
c := FHighlighter.FoldNodeInfo[ALineIdx].CountEx([sfaOneLineOpen, sfaFoldHide]); // TODO: Include scftFoldEnd ?
|
|
// Todo: cfSingleLineHide only, if there is no other hide
|
|
if c > 0 then
|
|
Result := Result + [cfHideStart, cfSingleLineHide];
|
|
end
|
|
else
|
|
if FHighlighter.FoldBlockOpeningCount(ALineIdx) > 0 then include(Result, cfFoldStart);
|
|
end;
|
|
|
|
function TSynEditFoldProvider.GetLineClassification(ALineIdx: Integer): TFoldNodeClassifications;
|
|
begin
|
|
Result := [];
|
|
if (FEdit <> nil) and FEdit.SelAvail and (FEdit.BlockBegin.Y = ALineIdx+1) then
|
|
Result := [fncBlockSelection];
|
|
end;
|
|
|
|
function TSynEditFoldProvider.GetNestedFoldsList: TLazSynEditNestedFoldsList;
|
|
begin
|
|
if FNestedFoldsList = nil then
|
|
FNestedFoldsList := TLazSynEditNestedFoldsList.Create(FLines, FHighlighter);
|
|
Result := FNestedFoldsList;
|
|
end;
|
|
|
|
function TSynEditFoldProvider.GetFoldsAvailable: Boolean;
|
|
begin
|
|
Result := FEnabled and (
|
|
(FHighlighter <> nil) or
|
|
((FEdit <> nil) and FEdit.SelAvail)
|
|
);
|
|
end;
|
|
|
|
function TSynEditFoldProvider.GetHighLighterWithLines: TSynCustomFoldHighlighter;
|
|
begin
|
|
Result := FHighlighter;
|
|
if (Result = nil) then
|
|
exit;
|
|
Result.CurrentLines := FLines;
|
|
end;
|
|
|
|
procedure TSynEditFoldProvider.SetHighLighter(const AValue: TSynCustomFoldHighlighter);
|
|
begin
|
|
if FHighlighter = AValue then exit;
|
|
FHighlighter := AValue;
|
|
if FNestedFoldsList <> nil then
|
|
FNestedFoldsList.HighLighter := FHighlighter;
|
|
end;
|
|
|
|
procedure TSynEditFoldProvider.SetLines(AValue: TSynEditStrings);
|
|
begin
|
|
if FLines = AValue then Exit;
|
|
FLines := AValue;
|
|
if FNestedFoldsList <> nil then
|
|
FNestedFoldsList.Lines := FLines;
|
|
end;
|
|
|
|
constructor TSynEditFoldProvider.Create;
|
|
begin
|
|
FEnabled := True;
|
|
inherited Create;
|
|
end;
|
|
|
|
destructor TSynEditFoldProvider.Destroy;
|
|
begin
|
|
inherited Destroy;
|
|
FreeAndNil(FNestedFoldsList);
|
|
end;
|
|
|
|
function TSynEditFoldProvider.FoldOpenCount(ALineIdx: Integer; AType: Integer = 0): Integer;
|
|
begin
|
|
if not FEnabled then
|
|
exit(0);
|
|
|
|
if (FHighlighter = nil) or (ALineIdx < 0) then begin
|
|
if (AType=0) and (FEdit <> nil) and FEdit.SelAvail and (FEdit.BlockBegin.Y=ALineIdx+1) then exit(1);
|
|
exit(0);
|
|
end;
|
|
// Need to check alll nodes with FoldNodeInfoCount
|
|
// Hide-able nodes can open and close on the same line "(* comment *)"
|
|
FHighlighter.CurrentLines := FLines;
|
|
Result := FHighlighter.FoldNodeInfo[ALineIdx].CountEx([sfaOpenFold, sfaFold], AType);
|
|
// fallback for HL without GetFoldNodeInfoCountEx
|
|
if Result < 0 then
|
|
Result := FHighlighter.FoldBlockOpeningCount(ALineIdx, AType);
|
|
if (AType=0) and (FEdit <> nil) and FEdit.SelAvail and (FEdit.BlockBegin.Y=ALineIdx+1) then
|
|
inc(Result);
|
|
end;
|
|
|
|
function TSynEditFoldProvider.FoldOpenInfo(ALineIdx, AFoldIdx: Integer;
|
|
AType: Integer = 0): TSynFoldNodeInfo;
|
|
|
|
function BlockSelInfo(NIdx: Integer): TSynFoldNodeInfo;
|
|
begin
|
|
Result.LineIndex := ALineIdx;
|
|
Result.NodeIndex := NIdx;
|
|
Result.LogXStart := FEdit.BlockBegin.x;
|
|
Result.LogXEnd := FEdit.BlockBegin.x;
|
|
Result.FoldLvlStart := 0;
|
|
Result.NestLvlStart := 0;
|
|
Result.NestLvlEnd := 1;
|
|
Result.FoldLvlEnd := 1;
|
|
Result.FoldAction := [sfaOpen, sfaOpenFold, sfaFold, sfaFoldHide];
|
|
Result.FoldType := nil;
|
|
Result.FoldTypeCompatible := nil;
|
|
Result.FoldGroup := -1;
|
|
end;
|
|
|
|
begin
|
|
Result.FoldAction := [sfaInvalid];
|
|
if (FHighlighter = nil) or (ALineIdx < 0) then begin
|
|
if (AType=0) and (FEdit <> nil) and FEdit.SelAvail and (FEdit.BlockBegin.Y=ALineIdx+1) then
|
|
exit(BlockSelInfo(0));
|
|
exit;
|
|
end;
|
|
|
|
FHighlighter.CurrentLines := FLines;
|
|
if (AType = 0) and (FEdit <> nil) and FEdit.SelAvail and
|
|
(FEdit.BlockBegin.Y=ALineIdx+1) and
|
|
(AFoldIdx = FoldOpenCount(ALineIdx, AType)-1)
|
|
then
|
|
Result := BlockSelInfo(AFoldIdx)
|
|
else
|
|
Result := FHighlighter.FoldNodeInfo[ALineIdx].NodeInfoEx(AFoldIdx, [sfaOpen, sfaFold], AType);
|
|
end;
|
|
|
|
function TSynEditFoldProvider.FoldLineLength(ALine, AFoldIndex: Integer): integer;
|
|
begin
|
|
if (FEdit <> nil) and FEdit.SelAvail and (FEdit.BlockBegin.Y=ALine+1) and
|
|
(AFoldIndex = FoldOpenCount(ALine, 0)-1)
|
|
then
|
|
exit(FEdit.BlockEnd.y - FEdit.BlockBegin.y);
|
|
|
|
FHighlighter.CurrentLines := FLines;
|
|
Result := FHighlighter.FoldLineLength(ALine, AFoldIndex);
|
|
end;
|
|
|
|
function TSynEditFoldProvider.InfoForFoldAtTextIndex(ALine, AFoldIndex: Integer;
|
|
HideLen: Boolean; NeedLen: Boolean = True): TSynEditFoldProviderNodeInfo;
|
|
var
|
|
nd: TSynFoldNodeInfo;
|
|
begin
|
|
Result.LineCount := 0;
|
|
Result.Column := 0;
|
|
Result.ColumnLen := 0;
|
|
Result.DefaultCollapsed := False;
|
|
Result.Classification := fncInvalid;
|
|
if not FoldsAvailable then
|
|
exit;
|
|
|
|
if NeedLen then begin
|
|
Result.LineCount := FoldLineLength(ALine, AFoldIndex);
|
|
if HideLen then
|
|
inc(Result.LineCount);
|
|
end
|
|
else
|
|
Result.LineCount := -1;
|
|
nd := FoldOpenInfo(ALine, AFoldIndex, 0);
|
|
Result.Column := nd.LogXStart+1;
|
|
Result.ColumnLen := nd.LogXEnd - nd.LogXStart;
|
|
Result.DefaultCollapsed := (sfaDefaultCollapsed in nd.FoldAction);
|
|
Result.FoldTypeCompatible := nd.FoldTypeCompatible;
|
|
Result.FoldGroup := nd.FoldGroup;
|
|
if Result.FoldGroup = -1 then
|
|
Result.Classification := fncBlockSelection
|
|
else
|
|
Result.Classification := fncHighlighter;
|
|
end;
|
|
|
|
function TSynEditFoldProvider.InfoListForFoldsAtTextIndex(ALine: Integer;
|
|
NeedLen: Boolean): TSynEditFoldProviderNodeInfoList;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
i := FoldOpenCount(ALine);
|
|
Result := nil;
|
|
SetLength(Result, i);
|
|
while i > 0 do begin
|
|
dec(i);
|
|
Result[i] := InfoForFoldAtTextIndex(ALine, i, False, NeedLen);
|
|
end;
|
|
end;
|
|
|
|
{ TSynEditFoldedView }
|
|
|
|
constructor TSynEditFoldedView.Create(AOwner: TSynEditBase;
|
|
ACaret: TSynEditCaret);
|
|
begin
|
|
FOwner := AOwner;
|
|
inherited Create;
|
|
fTopViewPos := 0;
|
|
fLinesInWindow := -1;
|
|
fCaret := ACaret;
|
|
fCaret.AddChangeHandler(@DoCaretChanged);
|
|
fFoldTree := TSynTextFoldAVLTree.Create;
|
|
FFoldProvider := TSynEditFoldProvider.Create;
|
|
FFoldProvider.FEdit := FOwner;
|
|
// TODO: if NextLineChanges, update FFoldProvider // DoSynStringsChanged
|
|
FDisplayView := TLazSynDisplayFold.Create(Self);
|
|
|
|
FMarkupInfoFoldedCode := TSynSelectedColor.Create;
|
|
FMarkupInfoFoldedCode.Background := clNone;
|
|
FMarkupInfoFoldedCode.Foreground := clDkGray;
|
|
FMarkupInfoFoldedCode.FrameColor := clDkGray;
|
|
|
|
FMarkupInfoFoldedCodeLine := TSynSelectedColor.Create;
|
|
FMarkupInfoFoldedCodeLine.Background := clNone;
|
|
FMarkupInfoFoldedCodeLine.Foreground := clNone;
|
|
FMarkupInfoFoldedCodeLine.FrameColor := clNone;
|
|
|
|
FMarkupInfoHiddenCodeLine := TSynSelectedColor.Create;
|
|
FMarkupInfoHiddenCodeLine.Background := clNone;
|
|
FMarkupInfoHiddenCodeLine.Foreground := clNone;
|
|
FMarkupInfoHiddenCodeLine.FrameColor := clNone;
|
|
|
|
FOwner.RegisterStatusChangedHandler(@DoBlockSelChanged, [scSelection]);
|
|
FOwner.RegisterCommandHandler(@ProcessMySynCommand, nil, [hcfPreExec]);
|
|
FOwner.TextViewsManager.AddTextView(Self);
|
|
end;
|
|
|
|
destructor TSynEditFoldedView.Destroy;
|
|
begin
|
|
FOwner.UnregisterCommandHandler(@ProcessMySynCommand);
|
|
NextLines := nil;
|
|
fCaret.RemoveChangeHandler(@DoCaretChanged);
|
|
FreeAndNil(FDisplayView);
|
|
fFoldTree.Free;
|
|
fFoldTypeList := nil;
|
|
FMarkupInfoFoldedCode.Free;
|
|
FMarkupInfoFoldedCodeLine.Free;
|
|
FMarkupInfoHiddenCodeLine.Free;
|
|
FreeAndNil(FFoldProvider);
|
|
inherited Destroy;
|
|
end;
|
|
|
|
function TSynEditFoldedView.TextToViewIndex(aTextIndex: TLineIdx): TLineIdx;
|
|
var
|
|
offs: Integer;
|
|
begin
|
|
offs := InternTextToViewIndexOffset(aTextIndex);
|
|
Result := inherited TextToViewIndex(aTextIndex);
|
|
Result := Result - offs;
|
|
end;
|
|
|
|
function TSynEditFoldedView.ViewToTextIndex(aViewIndex: TLineIdx): TLineIdx;
|
|
begin
|
|
Result := InternViewToTextIndex(aViewIndex);
|
|
Result := inherited ViewToTextIndex(Result);
|
|
end;
|
|
|
|
function TSynEditFoldedView.TextXYToViewXY(APhysTextXY: TPhysPoint): TViewedPoint;
|
|
var
|
|
offs: Integer;
|
|
begin
|
|
// TODO: if Y is in fold, and adapted to be the line before => should X be reset to 1 ?
|
|
APhysTextXY.Y := ToIdx(APhysTextXY.Y);
|
|
offs := InternTextToViewIndexOffset(APhysTextXY.Y);
|
|
APhysTextXY.Y := ToPos(APhysTextXY.Y);
|
|
Result := inherited TextXYToViewXY(APhysTextXY);
|
|
Result.Y := Result.Y - offs;
|
|
end;
|
|
|
|
function TSynEditFoldedView.ViewXYToTextXY(APhysViewXY: TViewedPoint): TPhysPoint;
|
|
begin
|
|
APhysViewXY.y := ToPos(InternViewToTextIndex(ToIdx(APhysViewXY.y)));
|
|
Result := inherited ViewXYToTextXY(APhysViewXY);
|
|
end;
|
|
|
|
procedure TSynEditFoldedView.LinesInsertedAtTextIndex(AStartIndex, ALineCount, ABytePos: Integer; SkipFixFolding : Boolean);
|
|
var top : Integer;
|
|
begin
|
|
if ALineCount = 0 then exit;
|
|
top := TopTextIndex;
|
|
fFoldTree.AdjustForLinesInserted(AStartIndex+1, ALineCount, ABytePos);
|
|
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.LinesDeletedAtTextIndex(AStartIndex, ALineCount, ABytePos: Integer; SkipFixFolding : Boolean);
|
|
var top : Integer;
|
|
begin
|
|
top := TopTextIndex;
|
|
// topViewPos may get out of sync => synedit is always going to change it back
|
|
fFoldTree.AdjustForLinesDeleted(AStartIndex+1, ALineCount, ABytePos);
|
|
if not(SkipFixFolding) then
|
|
FixFoldingAtTextIndex(AStartIndex, AStartIndex+ALineCount+1)
|
|
else
|
|
if AStartIndex < top - ALineCount then CalculateMaps;
|
|
end;
|
|
|
|
function TSynEditFoldedView.InternTextToViewIndex(aTextIndex: TLineIdx): TLineIdx;
|
|
var
|
|
n: TSynTextFoldAVLNode;
|
|
begin
|
|
n := fFoldTree.FindFoldForLine(aTextIndex + 1);
|
|
if n.IsInFold then
|
|
Result := ToIdx(n.StartLine) - 1 - n.FoldedBefore
|
|
else
|
|
Result := aTextIndex - n.FoldedBefore;
|
|
end;
|
|
|
|
function TSynEditFoldedView.InternTextToViewIndexOffset(var aTextIndex: TLineIdx): integer;
|
|
var
|
|
n: TSynTextFoldAVLNode;
|
|
begin
|
|
n := fFoldTree.FindFoldForLine(aTextIndex + 1);
|
|
|
|
Result := n.FoldedBefore;
|
|
if n.IsInFold then
|
|
aTextIndex := ToIdx(n.StartLine) - 1;
|
|
end;
|
|
|
|
function TSynEditFoldedView.TextIndexToScreenLine(aTextIndex : Integer) : Integer;
|
|
begin
|
|
Result := InternTextToViewIndex(aTextIndex) - TopViewPos + 1;
|
|
end;
|
|
|
|
function TSynEditFoldedView.InternViewToTextIndex(aViewIndex: TLineIdx): TLineIdx;
|
|
begin
|
|
if (aViewIndex >= ToIdx(fTopViewPos)) and (aViewIndex < ToIdx(fTopViewPos) + Length(fFoldTypeList) - 1) then
|
|
Result := aViewIndex + fFoldTypeList[aViewIndex-ToIdx(fTopViewPos)+1].FoldedBefore
|
|
else
|
|
result := aViewIndex + fFoldTree.FindFoldForFoldedLine(ToPos(aViewIndex)).FoldedBefore;
|
|
end;
|
|
|
|
function TSynEditFoldedView.InternViewToTextIndexOffest(aViewIndex: TLineIdx): integer;
|
|
begin
|
|
if (aViewIndex >= ToIdx(fTopViewPos)) and (aViewIndex < ToIdx(fTopViewPos) + Length(fFoldTypeList) - 1) then
|
|
Result := fFoldTypeList[aViewIndex-ToIdx(fTopViewPos)+1].FoldedBefore
|
|
else
|
|
result := fFoldTree.FindFoldForFoldedLine(ToPos(aViewIndex)).FoldedBefore;
|
|
end;
|
|
|
|
function TSynEditFoldedView.ScreenLineToTextIndex(aLine : Integer) : Integer;
|
|
begin
|
|
Result := InternViewToTextIndex(aLine + TopViewPos - 1);
|
|
end;
|
|
|
|
function TSynEditFoldedView.TextIndexAddLines(aTextIndex, LineOffset : Integer) : Integer;
|
|
var
|
|
node : TSynTextFoldAVLNode;
|
|
boundary : integer;
|
|
begin
|
|
node := fFoldTree.FindFoldForLine(aTextIndex+1, True);
|
|
result := aTextIndex;
|
|
if LineOffset < 0 then begin
|
|
boundary := Max(0, InternViewToTextIndex(0));
|
|
if node.IsInFold
|
|
then node := node.Prev
|
|
else node := fFoldTree.LastPage;
|
|
while LineOffset < 0 do begin
|
|
dec(Result);
|
|
if Result <= boundary then exit(boundary);
|
|
while node.IsInFold and (Result+1 < node.StartLine + node.MergedLineCount) do begin
|
|
Result := Result - node.MergedLineCount;
|
|
if Result <= boundary then exit(boundary);
|
|
node := node.Prev;
|
|
end;
|
|
inc(LineOffset);
|
|
end;
|
|
end else begin
|
|
boundary := NextLines.Count;
|
|
while LineOffset > 0 do begin
|
|
if Result >= boundary then exit(boundary);
|
|
inc(Result);
|
|
while node.IsInFold and (Result+1 >= node.StartLine) do begin
|
|
Result := Result + node.MergedLineCount;
|
|
if Result >= boundary then exit(boundary);
|
|
if Result >= boundary then exit(boundary-node.MergedLineCount-1);
|
|
node := node.Next;
|
|
end;
|
|
dec(LineOffset);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TSynEditFoldedView.VirtualMappingChanged(Sender: TSynEditStrings; aIndex, aCount: Integer
|
|
);
|
|
var
|
|
node: TSynTextFoldAVLNode;
|
|
begin
|
|
if Sender = self then
|
|
exit;
|
|
|
|
node := fFoldTree.FirstPage;
|
|
while node.IsInFold do begin
|
|
node.UdpateVirtualLineCounts(NextLines);
|
|
node := node.Next;
|
|
end;
|
|
CalculateMaps;
|
|
end;
|
|
|
|
function TSynEditFoldedView.AddVisibleOffsetToTextIndex(aTextIndex: TLineIdx;
|
|
LineOffset: Integer): TLineIdx;
|
|
begin
|
|
//TODO: Modify LineOffset then call inherited;
|
|
Result := TextIndexAddLines(aTextIndex, LineOffset);
|
|
// Result := inherited AddVisibleOffsetToTextIndex(aTextIndex, LineOffset);
|
|
end;
|
|
|
|
function TSynEditFoldedView.IsTextIdxVisible(aTextIndex: TLineIdx): Boolean;
|
|
begin
|
|
Result := not FoldedAtTextIndex[aTextIndex];
|
|
if Result then
|
|
Result := inherited IsTextIdxVisible(aTextIndex);
|
|
end;
|
|
|
|
procedure TSynEditFoldedView.Lock;
|
|
begin
|
|
if fLockCount=0 then begin
|
|
fNeedFixFrom := -1;
|
|
fNeedFixMinEnd := -1;
|
|
end;
|
|
inc(fLockCount);
|
|
end;
|
|
|
|
procedure TSynEditFoldedView.UnLock;
|
|
begin
|
|
dec(fLockCount);
|
|
if (fLockCount=0) then begin
|
|
if (fNeedFixFrom >= 0) then
|
|
FixFolding(fNeedFixFrom, fNeedFixMinEnd, fFoldTree);
|
|
if fvfNeedCaretCheck in FFlags then
|
|
DoCaretChanged(fCaret);
|
|
if fvfNeedCalcMaps in FFlags then
|
|
CalculateMaps;
|
|
end;
|
|
end;
|
|
|
|
(* Count *)
|
|
function TSynEditFoldedView.GetViewedCount : integer;
|
|
begin
|
|
Result := NextLines.ViewedCount - fFoldTree.LastPage.FoldedBefore;
|
|
end;
|
|
|
|
function TSynEditFoldedView.GetDisplayView: TLazSynDisplayView;
|
|
begin
|
|
Result := FDisplayView;
|
|
end;
|
|
|
|
procedure TSynEditFoldedView.InternalGetInfoForViewedXY(AViewedXY: TViewedPoint;
|
|
AFlags: TViewedXYInfoFlags; out AViewedXYInfo: TViewedXYInfo;
|
|
ALogPhysConvertor: TSynLogicalPhysicalConvertor);
|
|
var
|
|
OldY: LongInt;
|
|
begin
|
|
OldY := AViewedXY.y;
|
|
AViewedXY.y := ToPos(InternViewToTextIndex(ToIdx(AViewedXY.y)));
|
|
OldY := OldY - AViewedXY.y;
|
|
inherited InternalGetInfoForViewedXY(AViewedXY, AFlags, AViewedXYInfo,
|
|
ALogPhysConvertor);
|
|
AViewedXYInfo.CorrectedViewedXY.y := AViewedXYInfo.CorrectedViewedXY.y + OldY;
|
|
end;
|
|
|
|
function TSynEditFoldedView.GetFoldClasifications(index : Integer): TFoldNodeClassifications;
|
|
begin
|
|
if (index < -1) or (index > fLinesInWindow + 1) then exit([]);
|
|
Result := fFoldTypeList[index+1].Classifications;
|
|
end;
|
|
|
|
function TSynEditFoldedView.GetHighLighter: TSynCustomHighlighter;
|
|
begin
|
|
Result := FFoldProvider.HighLighter;
|
|
if assigned(Result) then
|
|
Result.CurrentLines := NextLines;
|
|
end;
|
|
|
|
(* TopViewPos *)
|
|
procedure TSynEditFoldedView.SetTopViewPos(const ALine : integer);
|
|
begin
|
|
if fTopViewPos = ALine then exit;
|
|
FInTopLineChanged := True;
|
|
fTopViewPos := ALine;
|
|
CalculateMaps;
|
|
FInTopLineChanged := False;
|
|
end;
|
|
|
|
function TSynEditFoldedView.GetTopTextIndex : integer;
|
|
begin
|
|
Result := fTopViewPos + fFoldTree.FindFoldForFoldedLine(fTopViewPos).FoldedBefore - 1;
|
|
end;
|
|
|
|
procedure TSynEditFoldedView.SetTopTextIndex(AIndex: integer);
|
|
var
|
|
n: TSynTextFoldAVLNode;
|
|
begin
|
|
n := fFoldTree.FindFoldForLine(AIndex+1);
|
|
if n.IsInFold then begin
|
|
assert(AIndex >= ToIdx(n.StartLine), 'TSynEditFoldedView.SetTopTextIndex: AIndex > n.StartLine');
|
|
if AIndex < n.StartLine + n.MergedLineCount then
|
|
AIndex := Max(0, ToIdx(n.StartLine) - 1);
|
|
end;
|
|
TopViewPos := AIndex + 1 - n.FoldedBefore;
|
|
end;
|
|
|
|
(* LinesInWindow*)
|
|
procedure TSynEditFoldedView.SetLinesInWindow(const AValue : integer);
|
|
begin
|
|
if fLinesInWindow = AValue then exit;
|
|
fLinesInWindow := AValue;
|
|
SetLength(fFoldTypeList, AValue + 3); // start 1 before topViewPos
|
|
CalculateMaps;
|
|
end;
|
|
|
|
procedure TSynEditFoldedView.DoFoldChanged(AnIndex: Integer);
|
|
begin
|
|
SendNotification(senrLineMappingChanged, Self, AnIndex, 0);
|
|
end;
|
|
|
|
procedure TSynEditFoldedView.SetManager(AManager: TSynTextViewsManager);
|
|
begin
|
|
if Manager <> nil then begin
|
|
RemoveChangeHandler(senrLineCount, @LineCountChanged);
|
|
RemoveNotifyHandler(senrCleared, @LinesCleared);
|
|
RemoveEditHandler(@LineEdited);
|
|
RemoveChangeHandler(senrLineMappingChanged, @VirtualMappingChanged);
|
|
end;
|
|
inherited SetManager(AManager);
|
|
if Manager <> nil then begin
|
|
AddChangeHandler(senrLineCount, @LineCountChanged);
|
|
AddNotifyHandler(senrCleared, @LinesCleared);
|
|
AddEditHandler(@LineEdited);
|
|
AddChangeHandler(senrLineMappingChanged, @VirtualMappingChanged);
|
|
end;
|
|
end;
|
|
|
|
procedure TSynEditFoldedView.SetSynStrings(AValue: TSynEditStrings);
|
|
begin
|
|
inherited SetSynStrings(AValue);
|
|
FFoldProvider.FLines := AValue;
|
|
fFoldTree.FNextStrings := AValue;
|
|
end;
|
|
|
|
procedure TSynEditFoldedView.DoBlockSelChanged(Sender: TObject;
|
|
Changes: TSynStatusChanges);
|
|
begin
|
|
CalculateMaps;
|
|
end;
|
|
|
|
procedure TSynEditFoldedView.CalculateMaps;
|
|
var
|
|
i, cnt : Integer;
|
|
ViewIdx, TxtIdx, TmpTxtIdx, TmpViewIdx: TLineIdx;
|
|
ViewRange, TmpRange: TLineRange;
|
|
node, tmpnode: TSynTextFoldAVLNode;
|
|
FirstChanged, LastChanged: TLineIdx;
|
|
CurFoldedBefore: Integer;
|
|
CurLineCapability, NewCapability: TSynEditFoldLineCapabilities;
|
|
CurLineClassifications, NewClassifications :TFoldNodeClassifications;
|
|
begin
|
|
if fLinesInWindow < 0 then exit;
|
|
if ( (fLockCount > 0) and
|
|
((not FInTopLineChanged) or (fvfNeedCalcMaps in FFlags)) // TODO: Scan now, to avoid invalidate later
|
|
) or
|
|
( (HighLighter <> nil) and HighLighter.NeedScan ) // SynEdit.DoHighlightChanged will call FixFolding, which does CalculateMaps
|
|
// TODO: HighLighter.CurrentRanges.NeedsReScanStartIndex < "last line in windows"
|
|
then begin
|
|
Include(FFlags, fvfNeedCalcMaps);
|
|
exit;
|
|
end;
|
|
Exclude(FFlags, fvfNeedCalcMaps);
|
|
|
|
FirstChanged := -1;
|
|
LastChanged := -1;
|
|
|
|
// Find node at/after virtual fTopViewPos => FoldedBefore must be before topViewPos (Since fTopViewPos is always visible, it can not be in a fold)
|
|
node := fFoldTree.FindFoldForFoldedLine(fTopViewPos, true);
|
|
CurFoldedBefore := node.FoldedBefore;
|
|
ViewIdx := ToIdx(fTopViewPos + CurFoldedBefore);
|
|
TxtIdx := NextLines.DisplayView.ViewToTextIndexEx(ViewIdx, ViewRange);
|
|
cnt := NextLines.Count;
|
|
|
|
// Prepare data for line one above fTopViewPos
|
|
if (TxtIdx >= cnt) or (TxtIdx < -1) then begin
|
|
// Past end of Text
|
|
NewCapability := [];
|
|
NewClassifications := [];
|
|
end
|
|
else begin
|
|
If ViewIdx > ViewRange.Top then begin
|
|
// Need CurLine.... data for wrapped lines
|
|
CurLineCapability := FFoldProvider.LineCapabilities[TxtIdx];
|
|
CurLineClassifications := FFoldProvider.LineClassification[TxtIdx];
|
|
NewCapability := CurLineCapability;
|
|
NewClassifications := CurLineClassifications;
|
|
TmpRange := ViewRange;
|
|
TmpViewIdx := ViewIdx - 1;
|
|
end
|
|
else begin
|
|
TmpTxtIdx := TxtIdx - 1;
|
|
if node.IsInFold then tmpnode := node.Prev
|
|
else tmpnode := fFoldTree.LastPage;
|
|
if tmpnode.IsInFold and (tmpnode.StartLine + tmpnode.MergedLineCount - 1 = ToPos(TmpTxtIdx))
|
|
then TmpTxtIdx := TmpTxtIdx - node.MergedLineCount // Line before is in fold
|
|
else tmpnode.Init(nil, 0, 0);
|
|
NewCapability := FFoldProvider.LineCapabilities[TmpTxtIdx];
|
|
NewClassifications := FFoldProvider.LineClassification[TmpTxtIdx];
|
|
if (tmpnode.IsInFold) then begin
|
|
if tmpnode.IsHide then include(NewCapability, cfCollapsedHide)
|
|
else include(NewCapability, cfCollapsedFold);
|
|
include(NewClassifications, tmpnode.Page._Classification);
|
|
end;
|
|
TmpRange := NextLines.DisplayView.TextToViewIndex(TmpTxtIdx);
|
|
TmpViewIdx := TmpRange.Bottom;
|
|
end;
|
|
if TmpViewIdx <> TmpRange.Bottom then
|
|
exclude(NewCapability, cfFoldEnd);
|
|
if TmpViewIdx <> TmpRange.Top then begin
|
|
if NewCapability * [cfFoldStart, cfHideStart] <> [] then
|
|
NewCapability := NewCapability + [cfFoldBody];
|
|
NewCapability := NewCapability - [cfFoldStart, cfHideStart];
|
|
end;
|
|
end;
|
|
|
|
// Add entry for line one above virtual topViewPos
|
|
if (fFoldTypeList[0].Capability <> NewCapability) or
|
|
(fFoldTypeList[0].Classifications <> NewClassifications)
|
|
then begin
|
|
FirstChanged := 0;
|
|
LastChanged := 0;
|
|
end;
|
|
fFoldTypeList[0].Capability := NewCapability;
|
|
fFoldTypeList[0].Classifications := NewClassifications;
|
|
fFoldTypeList[0].FoldedBefore := -1;
|
|
|
|
|
|
{$IFDEF SynFoldDebug}debugln(['FOLD-- CalculateMaps fTopViewPos:=', fTopViewPos, ' TxtIdx=',TxtIdx]);{$ENDIF}
|
|
for i := 1 to fLinesInWindow + 2 do begin
|
|
if (TxtIdx >= cnt) or (TxtIdx < -1) then begin
|
|
// Past end of Text
|
|
NewCapability := [];
|
|
NewClassifications := [];
|
|
ViewIdx := -1;
|
|
end else begin
|
|
if ViewIdx > ViewRange.Top then begin
|
|
NewCapability := CurLineCapability;
|
|
NewClassifications := CurLineClassifications;
|
|
if NewCapability * [cfFoldStart, cfHideStart] <> [] then
|
|
NewCapability := NewCapability + [cfFoldBody];
|
|
NewCapability := NewCapability - [cfFoldStart, cfHideStart];
|
|
if ViewIdx <> ViewRange.Bottom then
|
|
exclude(NewCapability, cfFoldEnd);
|
|
end
|
|
else begin
|
|
CurLineCapability := FFoldProvider.LineCapabilities[TxtIdx];
|
|
CurLineClassifications := FFoldProvider.LineClassification[TxtIdx];
|
|
NewCapability := CurLineCapability;
|
|
NewClassifications := CurLineClassifications;
|
|
if (node.IsInFold) then begin
|
|
if (ToPos(TxtIdx) = node.SourceLine) then begin
|
|
include(NewCapability, cfCollapsedFold);
|
|
include(NewClassifications, node.Page._Classification);
|
|
end
|
|
else if node.IsHide and (ToPos(TxtIdx) + 1 = node.SourceLine) then begin
|
|
include(NewCapability, cfCollapsedHide);
|
|
include(NewClassifications, node.Page._Classification);
|
|
end;
|
|
end;
|
|
end;
|
|
if ViewIdx <> ViewRange.Bottom then
|
|
exclude(NewCapability, cfFoldEnd);
|
|
|
|
inc(ViewIdx);
|
|
end;
|
|
|
|
if (fFoldTypeList[i].Capability <> NewCapability) or
|
|
(fFoldTypeList[i].Classifications <> NewClassifications) or
|
|
(fFoldTypeList[i].FoldedBefore <> CurFoldedBefore)
|
|
then begin
|
|
if FirstChanged < 0 then FirstChanged := TxtIdx;
|
|
LastChanged := TxtIdx;
|
|
end;
|
|
fFoldTypeList[i].Capability := NewCapability;
|
|
fFoldTypeList[i].Classifications := NewClassifications;
|
|
fFoldTypeList[i].FoldedBefore := CurFoldedBefore;
|
|
|
|
if ViewIdx > ViewRange.Bottom then begin
|
|
inc(TxtIdx);
|
|
while (node.IsInFold) and (ToPos(TxtIdx) >= node.StartLine) do begin
|
|
TxtIdx := TxtIdx + node.MergedLineCount;
|
|
node := node.Next;
|
|
CurFoldedBefore := node.FoldedBefore;
|
|
end;
|
|
ViewRange := NextLines.DisplayView.TextToViewIndex(TxtIdx);
|
|
ViewIdx := ViewRange.Top;
|
|
end;
|
|
end;
|
|
if (not FInTopLineChanged) and (FirstChanged > 0) then
|
|
FOwner.InvalidateGutterLines(ToPos(FirstChanged), ToPos(LastChanged)); // TODO: Introduce InvalidateScreenLine, avoind mapping to textlines?
|
|
end;
|
|
|
|
(* Lines *)
|
|
function TSynEditFoldedView.GetViewedLines(index : Integer) : String;
|
|
begin
|
|
Result := NextLines.ViewedLines[InternViewToTextIndex(index)];
|
|
end;
|
|
|
|
function TSynEditFoldedView.GetDisplayNumber(index : Integer) : Integer;
|
|
begin
|
|
Result := ToPos(ScreenLineToTextIndex(Index));
|
|
end;
|
|
|
|
function TSynEditFoldedView.GetTextIndex(index : Integer) : Integer;
|
|
begin
|
|
Result := ScreenLineToTextIndex(Index);
|
|
end;
|
|
|
|
function TSynEditFoldedView.GetFoldType(index : Integer) : TSynEditFoldLineCapabilities;
|
|
begin
|
|
if (index < -1) or (index > fLinesInWindow + 1) then exit([]);
|
|
Result := fFoldTypeList[index+1].Capability;
|
|
end;
|
|
|
|
function TSynEditFoldedView.IsFolded(index : integer) : Boolean;
|
|
begin
|
|
Result := fFoldTree.FindFoldForLine(index+1).IsInFold;
|
|
end;
|
|
|
|
procedure TSynEditFoldedView.ProcessMySynCommand(Sender: TObject;
|
|
AfterProcessing: boolean; var Handled: boolean;
|
|
var Command: TSynEditorCommand; var AChar: TUTF8Char; Data: pointer;
|
|
HandlerData: pointer);
|
|
|
|
procedure FoldAtCaret;
|
|
var
|
|
CY: Integer;
|
|
begin
|
|
CY := ExpandedLineForBlockAtLine(FCaret.LinePos);
|
|
if CY > 0 then begin
|
|
FoldAtTextIndex(CY-1);
|
|
FCaret.ChangeOnTouch; // setting the caret always clears selection (even setting to current pos / no change)
|
|
FCaret.LineCharPos:= Point(1, CY);
|
|
end;
|
|
end;
|
|
|
|
procedure UnFoldAtCaret;
|
|
begin
|
|
UnFoldAtTextIndex(FCaret.LinePos-1);
|
|
FCaret.Touch;
|
|
end;
|
|
|
|
begin
|
|
if Handled then
|
|
exit;
|
|
|
|
case Command of
|
|
EcFoldLevel1..EcFoldLevel9:
|
|
begin
|
|
FoldAll(Command - EcFoldLevel1);
|
|
FCaret.Touch;
|
|
Handled := True;
|
|
end;
|
|
EcFoldLevel0:
|
|
begin
|
|
UnfoldAll;
|
|
FCaret.Touch;
|
|
Handled := True;
|
|
end;
|
|
EcFoldCurrent:
|
|
begin
|
|
FoldAtCaret;
|
|
Handled := True;
|
|
end;
|
|
EcUnFoldCurrent:
|
|
begin
|
|
UnFoldAtCaret;
|
|
Handled := True;
|
|
end;
|
|
EcFoldToggle:
|
|
begin
|
|
if IsFolded(FCaret.LinePos) then
|
|
UnFoldAtCaret
|
|
else
|
|
FoldAtCaret;
|
|
Handled := True;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TSynEditFoldedView.SetHighLighter(AValue: TSynCustomHighlighter);
|
|
begin
|
|
if not(AValue is TSynCustomFoldHighlighter) then
|
|
AValue := nil;
|
|
FFoldProvider.HighLighter := TSynCustomFoldHighlighter(AValue);
|
|
UnfoldAll;
|
|
end;
|
|
|
|
(* Folding *)
|
|
|
|
procedure TSynEditFoldedView.FoldAtLine(AStartLine : Integer;
|
|
ColIndex : Integer = -1; ColCount : Integer = 1; Skip: Boolean = False;
|
|
AVisibleLines: Integer = 1);
|
|
begin
|
|
FoldAtViewPos(AStartLine + fTopViewPos, ColIndex, ColCount, Skip, AVisibleLines);
|
|
end;
|
|
|
|
procedure TSynEditFoldedView.FoldAtViewPos(AStartPos : Integer;
|
|
ColIndex : Integer = -1; ColCount : Integer = 1; Skip: Boolean = False;
|
|
AVisibleLines: Integer = 1);
|
|
begin
|
|
FoldAtTextIndex(AStartPos - 1 + fFoldTree.FindFoldForFoldedLine(AStartPos).FoldedBefore,
|
|
ColIndex, ColCount, Skip, AVisibleLines);
|
|
end;
|
|
|
|
function TSynEditFoldedView.FoldNodeAtTextIndex(AStartIndex,
|
|
ColIndex: Integer): TSynTextFoldAVLNode;
|
|
var
|
|
tree: TSynTextFoldAVLTree;
|
|
begin
|
|
Result := fFoldTree.FindFoldForLine(AStartIndex + 1, True);
|
|
|
|
tree := fFoldTree;
|
|
while (not Result.IsInFold) or (Result.SourceLine <> AStartIndex + 1) do begin
|
|
if (not Result.IsInFold) then
|
|
Result := tree.LastPage;
|
|
while Result.IsInFold and (Result.SourceLine > AStartIndex + 1) do
|
|
Result := Result.Prev;
|
|
if not Result.IsInFold then break;
|
|
|
|
if Result.IsInFold and (Result.SourceLine < AStartIndex + 1) then begin
|
|
if Result.Page._Nested = nil then break;
|
|
tree := fFoldTree.TreeForNestedNode(Result.Page, Result.StartLine);
|
|
Result := tree.FirstPage;
|
|
while Result.IsInFold and (Result.SourceLine < AStartIndex + 1) do
|
|
Result := Result.Next;
|
|
end
|
|
else
|
|
break;
|
|
end;
|
|
|
|
while Result.IsInFold and (Result.SourceLine = AStartIndex + 1) do begin
|
|
if Result.FoldIndex = ColIndex then
|
|
exit;
|
|
if Result.Page._Nested = nil then break;
|
|
Result := fFoldTree.TreeForNestedNode(Result.Page, Result.StartLine).FirstPage;
|
|
end;
|
|
Result.ClearData;
|
|
end;
|
|
|
|
function TSynEditFoldedView.IsFoldedAtTextIndex(AStartIndex, ColIndex: Integer): Boolean;
|
|
begin
|
|
Result := FoldNodeAtTextIndex(AStartIndex, ColIndex).IsInFold;
|
|
end;
|
|
|
|
function TSynEditFoldedView.LogicalPosToNodeIndex(AStartIndex: Integer; LogX: Integer;
|
|
Previous: Boolean): Integer;
|
|
var
|
|
hl: TSynCustomFoldHighlighter;
|
|
c, i: Integer;
|
|
nd: TSynFoldNodeInfo;
|
|
begin
|
|
hl := TSynCustomFoldHighlighter(HighLighter);
|
|
if not assigned(hl) then
|
|
exit(0);
|
|
// AStartIndex is 0-based
|
|
// FoldTree is 1-based AND first line remains visible
|
|
c := hl.FoldNodeInfo[AStartIndex].CountEx([sfaOpen, sfaFold]);
|
|
if c = 0 then
|
|
exit(-1);
|
|
i := 0;
|
|
while i < c do begin
|
|
nd := hl.FoldNodeInfo[aStartIndex].NodeInfoEx(i, [sfaOpen, sfaFold]);
|
|
if (nd.LogXStart >= LogX) then begin
|
|
dec(i);
|
|
if not Previous then
|
|
i := -1;
|
|
break;
|
|
end;
|
|
if (nd.LogXEnd >= LogX) then
|
|
break;
|
|
inc(i);
|
|
end;
|
|
Result := i;
|
|
end;
|
|
|
|
procedure TSynEditFoldedView.CollapseDefaultFolds;
|
|
var
|
|
i, j, c: Integer;
|
|
hl: TSynCustomFoldHighlighter;
|
|
fldinf: TSynEditFoldProviderNodeInfo;
|
|
begin
|
|
hl := TSynCustomFoldHighlighter(HighLighter);
|
|
if not assigned(hl) then
|
|
exit;
|
|
|
|
i := 0;
|
|
while i < NextLines.Count do begin
|
|
// Todo: Highlighter should return a list of types that can return default folded
|
|
// Currently PascalHl Type 2 = Region
|
|
c := hl.FoldBlockOpeningCount(i, 2);
|
|
if c > 0 then begin
|
|
c := hl.FoldNodeInfo[i].CountEx([sfaOpen, sfaFold]);
|
|
j := 0;
|
|
while j < c do begin
|
|
fldinf := FoldProvider.InfoForFoldAtTextIndex(i, j);
|
|
if (fldinf.DefaultCollapsed) and (not IsFoldedAtTextIndex(i, j))
|
|
then begin
|
|
// TODO: detect default hide too
|
|
// currently always VisibleLines=1 => since region only folds
|
|
fFoldTree.InsertNewFold(i+2, j, fldinf.Column, fldinf.ColumnLen, fldinf.LineCount, 1,
|
|
fldinf.Classification, fldinf.FoldTypeCompatible);
|
|
DoFoldChanged(i);
|
|
end;
|
|
inc(j);
|
|
end;
|
|
end;
|
|
inc(i);
|
|
end;
|
|
CalculateMaps;
|
|
end;
|
|
|
|
function TSynEditFoldedView.GetFoldDescription(AStartIndex, AStartCol, AEndIndex,
|
|
AEndCol: Integer; AsText: Boolean = False; Extended: Boolean = False): String;
|
|
var
|
|
FoldCoders: Array of TSynEditFoldExportCoder;
|
|
|
|
function FoldCoderForType(AType: Pointer): TSynEditFoldExportCoder;
|
|
var
|
|
i, j: Integer;
|
|
begin
|
|
i := 0;
|
|
j := length(FoldCoders);
|
|
while (i < j) and (FoldCoders[i].FoldType <> AType) do
|
|
inc(i);
|
|
if (i = j) then begin
|
|
SetLength(FoldCoders, i + 1);
|
|
FoldCoders[i] := TSynEditFoldExportCoder.Create(AType);
|
|
end;
|
|
Result := FoldCoders[i];
|
|
end;
|
|
|
|
var
|
|
hl: TSynCustomFoldHighlighter;
|
|
FoldHelper: TSynEditFoldExportStream;
|
|
NodeIterator: TSynTextFoldAVLNodeNestedIterator;
|
|
NdiHelper1: TSynFoldNodeInfoHelper;
|
|
Node: TSynTextFoldAVLNode;
|
|
NdInfo, NdInfo2: TSynFoldNodeInfo;
|
|
entry: TFoldExportEntry;
|
|
i: Integer;
|
|
NodeFoldType: TSynEditFoldType;
|
|
begin
|
|
Result := '';
|
|
hl := TSynCustomFoldHighlighter(HighLighter);
|
|
if not assigned(hl) then exit;
|
|
|
|
if AEndIndex < 0 then AEndIndex := MaxInt;
|
|
if AEndCol < 0 then AEndCol := MaxInt;
|
|
|
|
Node := fFoldTree.FindFoldForLine(AStartIndex + 1, True);
|
|
NodeIterator := TSynTextFoldAVLNodeNestedIterator.Create(Node);
|
|
FoldHelper := TSynEditFoldExportStream.Create;
|
|
NdiHelper1 := TSynFoldNodeInfoHelper.Create(hl);
|
|
try
|
|
if (AStartCol > 1) then
|
|
while Node.IsInFold and (Node.StartLine = AStartIndex + 2) do begin
|
|
NdInfo := NdiHelper1.GotoNodeOpenPos(Node);
|
|
if (sfaInvalid in NdInfo.FoldAction) or (ndinfo.LogXStart >= AStartCol) then
|
|
break;
|
|
Node := NodeIterator.Next;
|
|
end;
|
|
dec(AStartCol);
|
|
if not node.IsInFold then
|
|
exit;
|
|
|
|
(* Text stores fold length according to AVLNode
|
|
Binary stores line-diff between highlighter open and close line
|
|
*)
|
|
if AsText then
|
|
begin (* *** Encode as Text for XML *** *)
|
|
{$IFDEF SynFoldSaveDebug}
|
|
DebugLnEnter(['TSynEditFoldedView.GetFoldDescription as Text']);
|
|
{$ENDIF}
|
|
while Node.IsInFold and (Node.Page._Classification <> fncHighlighter) do
|
|
Node := NodeIterator.Next;
|
|
if not node.IsInFold then
|
|
exit;
|
|
|
|
NdInfo := NdiHelper1.GotoNodeOpenPos(Node);
|
|
while Node.IsInFold and (Node.StartLine-2 <= AEndIndex) do
|
|
begin
|
|
if (node.StartLine > AStartIndex + 2) then AStartCol := 0;
|
|
|
|
NodeFoldType := scftFold;
|
|
if Node.SourceLineOffset = 0 then
|
|
NodeFoldType := scftHide;
|
|
if (NdInfo.FoldAction * [sfaInvalid, sfaDefaultCollapsed] = []) then // Currently skip default nodes
|
|
FoldCoderForType(NdInfo.FoldType).AddNode
|
|
(NdInfo.LogXStart, NdInfo.LineIndex, Node.FoldedCount, NodeFoldType);
|
|
|
|
Node := NodeIterator.Next;
|
|
while Node.IsInFold and (Node.Page._Classification <> fncHighlighter) do
|
|
Node := NodeIterator.Next;
|
|
if not Node.IsInFold then
|
|
break;
|
|
|
|
NdInfo := NdiHelper1.Next;
|
|
while NdiHelper1.IsValid and (not NdiHelper1.IsAtNodeOpenPos(Node)) do begin
|
|
// Add unfolded nodes
|
|
if (NdInfo.FoldAction * [sfaInvalid, sfaDefaultCollapsed] = []) then // Currently skip default nodes
|
|
FoldCoderForType(NdInfo.FoldType).AddNode
|
|
(NdInfo.LogXStart, NdInfo.LineIndex, 0, scftOpen);
|
|
NdInfo := NdiHelper1.Next;
|
|
end;
|
|
end;
|
|
|
|
for i := 0 to length(FoldCoders) - 1 do begin
|
|
FoldCoders[i].Finish;
|
|
FoldHelper.AppendMem(FoldCoders[i].Stream.Mem, FoldCoders[i].Stream.Len);
|
|
end;
|
|
FoldHelper.AddChecksum;
|
|
FoldHelper.Compress;
|
|
{$IFDEF SynFoldSaveDebug}
|
|
DebugLnExit(['TSynEditFoldedView.GetFoldDescription as Text']);
|
|
{$ENDIF}
|
|
end (* *** END: Encode as Text for XML *** *)
|
|
else
|
|
begin (* *** Encode as Binary *** *)
|
|
while Node.IsInFold and (Node.StartLine-2 <= AEndIndex) do
|
|
begin
|
|
if (node.StartLine > AStartIndex + 2) then
|
|
AStartCol := 0;
|
|
|
|
NdInfo2 := NdiHelper1.GotoNodeClosePos(Node);
|
|
if (sfaInvalid in NdInfo2.FoldAction) or
|
|
(NdInfo2.LineIndex > AEndIndex) or
|
|
((NdInfo2.LineIndex = AEndIndex) and (ndinfo2.LogXEnd > AEndCol))
|
|
then begin
|
|
node := NodeIterator.Next;
|
|
continue;
|
|
end;
|
|
|
|
NdInfo := NdiHelper1.GotoNodeOpenPos(Node);
|
|
|
|
with entry do begin
|
|
LogX := NdInfo.LogXStart - AStartCol;
|
|
LogX2 := NdInfo.LogXEnd - ndinfo.LogXStart + (ndinfo.LogXStart - AStartCol);
|
|
Line := NdInfo.LineIndex - AStartIndex;
|
|
ELogX := NdInfo2.LogXStart;
|
|
ELogX2 := NdInfo2.LogXEnd;
|
|
ELine := NdInfo2.LineIndex - AStartIndex;
|
|
//if sfaLastLineClose in NdInfo2.FoldAction then
|
|
// ELine := -1; // unfinished fold
|
|
FType := PtrUInt(NdInfo.FoldType);
|
|
LinesFolded := node.FoldedCount;
|
|
end;
|
|
FoldHelper.AppendMem(@entry, SizeOf(TFoldExportEntry));
|
|
|
|
Node := NodeIterator.Next;
|
|
end;
|
|
end; (* *** END: Encode as Binary *** *)
|
|
|
|
Result := FoldHelper.Text;
|
|
finally
|
|
FoldHelper.Free;
|
|
for i := 0 to length(FoldCoders) - 1 do
|
|
FoldCoders[i].Free;
|
|
NodeIterator.Free;
|
|
NdiHelper1.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TSynEditFoldedView.ApplyFoldDescription(AStartIndex, AStartCol, AEndIndex,
|
|
AEndCol: Integer; FoldDesc: PChar; FoldDescLen: Integer; IsText: Boolean = False);
|
|
var
|
|
FoldCoders: Array of TSynEditFoldExportCoder;
|
|
|
|
function FoldCoderForType(AType: Pointer): TSynEditFoldExportCoder;
|
|
var
|
|
j: Integer;
|
|
begin
|
|
j := length(FoldCoders) - 1;
|
|
while (j >= 0) and (FoldCoders[j] <> nil) and (FoldCoders[j].FoldType <> AType) do
|
|
dec(j);
|
|
if (j < 0) then
|
|
Result := nil
|
|
else
|
|
Result := FoldCoders[j];
|
|
end;
|
|
|
|
procedure RemoveCoderForType(AType: Pointer);
|
|
var
|
|
j: Integer;
|
|
begin
|
|
j := length(FoldCoders) - 1;
|
|
while (j >= 0) and (FoldCoders[j] <> nil) and (FoldCoders[j].FoldType <> AType) do
|
|
dec(j);
|
|
if (j >= 0) then begin
|
|
debugln(['FoldState loading removed data for foldtype: ', PtrUInt(AType)]);
|
|
FreeAndNil(FoldCoders[j]);
|
|
end;
|
|
end;
|
|
|
|
|
|
var
|
|
hl: TSynCustomFoldHighlighter;
|
|
FoldHelper: TSynEditFoldExportStream;
|
|
NdiHelper1: TSynFoldNodeInfoHelper;
|
|
NdInfo, ndinfo2: TSynFoldNodeInfo;
|
|
i: Integer;
|
|
Line, FL: Integer;
|
|
entry: TFoldExportEntry;
|
|
Coder: TSynEditFoldExportCoder;
|
|
IsFold, IsHide: Boolean;
|
|
begin
|
|
hl := TSynCustomFoldHighlighter(HighLighter);
|
|
if not assigned(hl) then
|
|
exit;
|
|
if (FoldDesc = nil) or (FoldDescLen = 0) then exit;
|
|
|
|
NdiHelper1 := TSynFoldNodeInfoHelper.Create(hl);
|
|
FoldHelper := TSynEditFoldExportStream.Create;
|
|
try
|
|
FoldHelper.Mem := FoldDesc;
|
|
FoldHelper.Len := FoldDescLen;
|
|
|
|
if IsText then
|
|
begin (* *** Decode from Text for XML *** *)
|
|
try
|
|
FoldHelper.Decompress;
|
|
except
|
|
exit;
|
|
end;
|
|
if not FoldHelper.VerifyChecksum then
|
|
exit; //raise ESynEditError.Create('fold checksum error');
|
|
|
|
i := 0;
|
|
while not FoldHelper.EOF do begin
|
|
SetLength(FoldCoders, i + 1);
|
|
FoldCoders[i] := TSynEditFoldExportCoder.Create(FoldHelper);
|
|
if not FoldCoders[i].ReadIsValid then
|
|
break;
|
|
inc(i);
|
|
end;
|
|
|
|
NdInfo := NdiHelper1.FirstOpen;
|
|
|
|
while NdiHelper1.IsValid do begin
|
|
if (sfaDefaultCollapsed in NdInfo.FoldAction) then begin // Currently skip default nodes
|
|
NdInfo := NdiHelper1.Next;
|
|
continue;
|
|
end;
|
|
Coder := FoldCoderForType(NdInfo.FoldType);
|
|
if coder <> nil then begin
|
|
i := FoldProvider.InfoForFoldAtTextIndex(NdInfo.LineIndex, NdInfo.NodeIndex).LineCount;
|
|
case coder.ReadNode(NdInfo.LogXStart, NdInfo.LineIndex, i) of
|
|
scftFold: FoldAtTextIndex(NdInfo.LineIndex, NdInfo.NodeIndex);
|
|
scftHide: FoldAtTextIndex(NdInfo.LineIndex, NdInfo.NodeIndex, 1, False, 0);
|
|
scftInvalid: RemoveCoderForType(NdInfo.FoldType);
|
|
end;
|
|
end;
|
|
NdInfo := NdiHelper1.Next;
|
|
end;
|
|
end (* *** END: Encode as Text for XML *** *)
|
|
else
|
|
begin (* *** Decode from Binary *** *)
|
|
entry.Line := 0;
|
|
if AStartCol > 0 then
|
|
dec(AStartCol);
|
|
while not FoldHelper.EOF do begin
|
|
if not FoldHelper.ReadMem(@entry, sizeof(TFoldExportEntry)) then
|
|
break;
|
|
if entry.Line > 0 then AStartCol := 0;
|
|
|
|
Line := AStartIndex + entry.Line;
|
|
if Line >= NextLines.Count then
|
|
continue;
|
|
|
|
ndinfo :=NdiHelper1.GotoOpenAtChar(Line, entry.LogX);
|
|
Fl := FoldProvider.InfoForFoldAtTextIndex(Line, ndinfo.NodeIndex).LineCount;
|
|
IsFold := (sfaFoldFold in NdInfo.FoldAction) and (entry.LinesFolded = FL);
|
|
IsHide := (sfaFoldHide in NdInfo.FoldAction) and (entry.LinesFolded = FL + 1);
|
|
if (sfaInvalid in ndinfo.FoldAction) or
|
|
(ndinfo.LogXStart <> entry.LogX + AStartCol) or
|
|
(ndinfo.LogXEnd <> entry.LogX2 + AStartCol) or
|
|
//(ndinfo.FoldType <> entry.FType) or
|
|
(not (IsHide or IsFold))
|
|
then
|
|
continue;
|
|
|
|
ndinfo2 := NdiHelper1.FindClose;
|
|
if (sfaInvalid in ndinfo2.FoldAction) or
|
|
(ndinfo2.LogXStart <> entry.ELogX) or
|
|
(ndinfo2.LogXEnd <> entry.ELogX2)
|
|
then
|
|
continue;
|
|
|
|
i := 1;
|
|
if IsHide then i := 0;;
|
|
FoldAtTextIndex(Line, NdInfo.NodeIndex, 1, False, i);
|
|
end;
|
|
end; (* *** END: Encode as Binary *** *)
|
|
finally
|
|
for i := 0 to length(FoldCoders) - 1 do
|
|
FoldCoders[i].Free;
|
|
FreeAndNil(FoldHelper);
|
|
FreeAndNil(NdiHelper1);
|
|
end;
|
|
end;
|
|
|
|
procedure TSynEditFoldedView.FoldAtTextIndex(AStartIndex : Integer;
|
|
ColIndex : Integer = -1; ColCount : Integer = 1; Skip: Boolean = False;
|
|
AVisibleLines: Integer = 1);
|
|
var
|
|
NodeCount, top: Integer;
|
|
down: Boolean;
|
|
NFolded: TSynTextFoldAVLNode;
|
|
IsHide: Boolean;
|
|
fldinf: TSynEditFoldProviderNodeInfo;
|
|
begin
|
|
if not FoldProvider.FoldsAvailable then exit;
|
|
top := TopTextIndex;
|
|
|
|
// AStartIndex is 0-based
|
|
// FoldTree is 1-based AND first line remains visible
|
|
NodeCount := FoldProvider.FoldOpenCount(AStartIndex);
|
|
if ColCount = 0 then
|
|
ColCount := NodeCount;
|
|
|
|
down := ColIndex < 0;
|
|
if down then
|
|
ColIndex := NodeCount + ColIndex;
|
|
|
|
IsHide := AVisibleLines = 0;
|
|
|
|
while ColCount > 0 do begin
|
|
if (ColIndex < 0) or (ColIndex >= NodeCount) then break;
|
|
NFolded := FoldNodeAtTextIndex(AStartIndex, ColIndex);
|
|
// TODO: Check if position can Hide or fold
|
|
if skip and ( ( (AVisibleLines=0) and NFolded.IsHide ) or
|
|
( (AVisibleLines>0) and NFolded.IsInFold ) )
|
|
then begin
|
|
if down
|
|
then dec(ColIndex)
|
|
else inc(ColIndex);
|
|
continue;
|
|
end;
|
|
|
|
// TODO: missing check, that hl-node is hideable
|
|
fldinf := FoldProvider.InfoForFoldAtTextIndex(AStartIndex, ColIndex, IsHide);
|
|
if not NFolded.IsInFold then begin
|
|
if fldinf.LineCount > 0 then
|
|
fFoldTree.InsertNewFold(AStartIndex+1+AVisibleLines, ColIndex,
|
|
fldinf.Column, fldinf.ColumnLen, fldinf.LineCount,
|
|
AVisibleLines,
|
|
fldinf.Classification, fldinf.FoldTypeCompatible)
|
|
end
|
|
else begin
|
|
if (AVisibleLines=0) and (not NFolded.IsHide) and (fldinf.LineCount > 0) then begin
|
|
// upgrade to hide
|
|
fFoldTree.RemoveFoldForNodeAtLine(NFolded, -1);
|
|
fFoldTree.InsertNewFold(AStartIndex+1, ColIndex,
|
|
fldinf.Column, fldinf.ColumnLen, fldinf.LineCount,
|
|
AVisibleLines,
|
|
fldinf.Classification, fldinf.FoldTypeCompatible);
|
|
end;
|
|
end;
|
|
if down
|
|
then dec(ColIndex)
|
|
else inc(ColIndex);
|
|
dec(ColCount);
|
|
end;
|
|
|
|
fTopViewPos := -1; // make sure seting TopLineTextIndex, will do CalculateMaps;
|
|
TopTextIndex := top;
|
|
DoFoldChanged(AStartIndex);
|
|
end;
|
|
|
|
procedure TSynEditFoldedView.UnFoldAtLine(AStartLine : Integer;
|
|
ColIndex : Integer = -1; ColCount : Integer = 0; Skip: Boolean = False;
|
|
AVisibleLines: Integer = 1);
|
|
begin
|
|
UnFoldAtViewPos(AStartLine + fTopViewPos, ColIndex, ColCount, Skip, AVisibleLines);
|
|
end;
|
|
|
|
procedure TSynEditFoldedView.UnFoldAtViewPos(AStartPos : Integer;
|
|
ColIndex : Integer = -1; ColCount : Integer = 0; Skip: Boolean = False;
|
|
AVisibleLines: Integer = 1);
|
|
begin
|
|
UnFoldAtTextIndex(AStartPos - 1 + fFoldTree.FindFoldForFoldedLine(AStartPos).FoldedBefore,
|
|
ColIndex, ColCount, Skip, AVisibleLines);
|
|
end;
|
|
|
|
procedure TSynEditFoldedView.UnFoldAtTextIndex(AStartIndex : Integer;
|
|
ColIndex : Integer = -1; ColCount : Integer = 0; Skip: Boolean = False;
|
|
AVisibleLines: Integer = 1);
|
|
var
|
|
top, c, r, r2 : Integer;
|
|
down: Boolean;
|
|
NFolded: TSynTextFoldAVLNode;
|
|
begin
|
|
top := TopTextIndex;
|
|
c := FoldProvider.FoldOpenCount(AStartIndex);
|
|
|
|
//TODO move to FoldProvider
|
|
NFolded := fFoldTree.FindFoldForLine(AStartIndex+1, True);
|
|
while NFolded.IsInFold and (NFolded.StartLine = AStartIndex+1) do begin
|
|
if NFolded.FoldIndex + 1 > c then c := NFolded.FoldIndex + 1;
|
|
NFolded := fFoldTree.TreeForNestedNode(NFolded.Page, NFolded.StartLine).FindFoldForLine(AStartIndex, True);
|
|
end;
|
|
|
|
if c < 1 then begin
|
|
// TODO: foldprovider to return all folded nodes, for hte line
|
|
ColCount := 0;
|
|
end;
|
|
|
|
r := -1;
|
|
if ColCount = 0 then begin
|
|
r := fFoldTree.RemoveFoldForLine(AStartIndex+AVisibleLines+1); // r is 1-based num of first (ex-)hidden line
|
|
end
|
|
else begin
|
|
down := ColIndex < 0;
|
|
if down then
|
|
ColIndex := c + ColIndex ;
|
|
while ColCount > 0 do begin
|
|
if (ColIndex < 0) or (ColIndex >= c) then break;
|
|
NFolded := FoldNodeAtTextIndex(AStartIndex, ColIndex);
|
|
if skip and ( ( (AVisibleLines=0) and not NFolded.IsHide ) or
|
|
( (AVisibleLines>0) and not NFolded.IsInFold ) )
|
|
then begin
|
|
if down
|
|
then dec(ColIndex)
|
|
else inc(ColIndex);
|
|
continue;
|
|
end;
|
|
r2 := fFoldTree.RemoveFoldForLine(AStartIndex+1+AVisibleLines, ColIndex);
|
|
if r2 > 0 then dec(r2);
|
|
if (r < 0) or (r2 < r) then r := r2;
|
|
if down
|
|
then dec(ColIndex)
|
|
else inc(ColIndex);
|
|
dec(ColCount);
|
|
end;
|
|
end;
|
|
|
|
fTopViewPos := -1; // make sure seting TopLineTextIndex, will do CalculateMaps;
|
|
TopTextIndex := top;
|
|
if (r >= 0) then
|
|
DoFoldChanged(Max(0, r - 2));
|
|
end;
|
|
|
|
procedure TSynEditFoldedView.UnFoldAtTextIndexCollapsed(AStartIndex: Integer);
|
|
var
|
|
top, r: Integer;
|
|
begin
|
|
top := TopTextIndex;
|
|
r := fFoldTree.RemoveFoldForLine(AStartIndex+1) - 1;
|
|
fTopViewPos := -1; // make sure seting TopLineTextIndex, will do CalculateMaps;
|
|
TopTextIndex := top;
|
|
DoFoldChanged(r);
|
|
end;
|
|
|
|
procedure TSynEditFoldedView.UnfoldAll;
|
|
var
|
|
top : Integer;
|
|
begin
|
|
top := TopTextIndex;
|
|
fFoldTree.Clear;
|
|
fTopViewPos := -1; // make sure seting TopLineTextIndex, will do CalculateMaps;
|
|
TopTextIndex := top;
|
|
DoFoldChanged(0);
|
|
end;
|
|
|
|
procedure TSynEditFoldedView.FoldAll(StartLevel : Integer = 0; IgnoreNested : Boolean = False);
|
|
var
|
|
c, i, top, t: Integer;
|
|
hl: TSynCustomFoldHighlighter;
|
|
fldinf: TSynEditFoldProviderNodeInfo;
|
|
begin
|
|
hl := TSynCustomFoldHighlighter(HighLighter);
|
|
if not assigned(hl) then
|
|
exit;
|
|
|
|
t := 1; // TODO: Highlighter default type; or iterate through all types
|
|
top := TopTextIndex;
|
|
fFoldTree.Clear;
|
|
i := 0;
|
|
while i < NextLines.Count do begin
|
|
if (hl.FoldBlockOpeningCount(i, t) > 0)
|
|
and (hl.FoldBlockEndLevel(i, t) > StartLevel) then begin
|
|
c := hl.FoldBlockOpeningCount(i) -1;
|
|
fldinf := FoldProvider.InfoForFoldAtTextIndex(i, c);
|
|
// i is 0-based
|
|
// FoldTree is 1-based AND first line remains visible
|
|
fFoldTree.InsertNewFold(i+2, c, fldinf.Column, fldinf.ColumnLen, fldinf.LineCount, 1,
|
|
fldinf.Classification, fldinf.FoldTypeCompatible); // TODO: hide too? currently VisibleLines=1
|
|
if IgnoreNested then
|
|
i := i + fldinf.LineCount;
|
|
end;
|
|
inc(i);
|
|
end;
|
|
fTopViewPos := -1;
|
|
TopTextIndex := top;
|
|
DoFoldChanged(0);
|
|
end;
|
|
|
|
function TSynEditFoldedView.FixFolding(AStart: Integer; AMinEnd: Integer;
|
|
aFoldTree: TSynTextFoldAVLTree): Boolean;
|
|
var
|
|
FirstchangedLine, MaxCol: Integer;
|
|
SrcLineForFldInfos: Integer;
|
|
FldInfos: TSynEditFoldProviderNodeInfoList;
|
|
|
|
function DoFixFolding(doStart: Integer; doMinEnd, AtColumn: Integer;
|
|
doFoldTree: TSynTextFoldAVLTree; node: TSynTextFoldAVLNode) : Boolean;
|
|
|
|
Procedure DoRemoveNode(var theNode: TSynTextFoldAVLNode);
|
|
var
|
|
tmpnode: TSynTextFoldAVLNode;
|
|
l: Integer;
|
|
begin
|
|
Result := True;
|
|
tmpnode := theNode.Prev;
|
|
l := theNode.SourceLine;
|
|
doFoldTree.RemoveFoldForNodeAtLine(theNode, -1); // Don't touch any nested node
|
|
if tmpnode.IsInFold then theNode := tmpnode.Next
|
|
else theNode := doFoldTree.FirstPage;
|
|
if (FirstchangedLine < 0) or (l < FirstchangedLine) then
|
|
FirstchangedLine := l;
|
|
end;
|
|
|
|
var
|
|
FldSrcLine, FldSrcIndex, FLdNodeLine, FldLen, FndLen: Integer;
|
|
i, j, CurLen: Integer;
|
|
SubTree: TSynTextFoldAVLTree;
|
|
begin
|
|
{$IFDEF SynFoldDebug}try DebugLnEnter(['>>FOLD-- DoFixFolding: doStart=', doStart, ' AMinEnd=',AMinEnd]);{$ENDIF}
|
|
{$IFDEF SynFoldDebug}aFoldTree.Debug;{$ENDIF}
|
|
Result := False;
|
|
FldSrcLine := doStart;
|
|
while node.IsInFold do begin
|
|
{$IFDEF SynFoldDebug}debugln(['>>FOLD-- Node StartLine=', node.StartLine, ' FoldColumn=', node.FoldColumn, ' FoldIndex=', node.FoldIndex, ' FoldedCount=', node.FoldedCount, ' Classification=', dbgs(node.Classification)]);{$ENDIF}
|
|
FldSrcLine := node.SourceLine; // the 1-based cfCollapsed (last visible) Line (or 1st hidden)
|
|
FLdNodeLine := node.StartLine; // the 1 based, first hidden line
|
|
FldSrcIndex := FldSrcLine - 1;
|
|
FldLen := node.FoldedCount;
|
|
if (FldLen <= 0) then begin
|
|
{$IFDEF SynFoldDebug}debugln(['>>FOLD-- FixFolding: Remove node with len<0 FldSrcLine=', FldSrcLine]);{$ENDIF}
|
|
DoRemoveNode(node);
|
|
continue;
|
|
end;
|
|
|
|
//{$IFDEF SynAssertFold}
|
|
//With mixed fold/hide => line goes up/down
|
|
//SynAssert(FldSrcLine >= SrcLineForFldInfos, 'TSynEditFoldedView.FixFolding: FoldLine went backwards now %d was %d', [FldSrcLine, SrcLineForFldInfos]);
|
|
//{$ENDIF}
|
|
if (FldSrcLine <> SrcLineForFldInfos) then begin
|
|
// Next Line
|
|
SrcLineForFldInfos := FldSrcLine;
|
|
AtColumn := 0;
|
|
// AtColumn is used for nodes, behing the HLs index-range (fncHighlighterEx, fncBlockSelection)
|
|
// TODO: At Colum may be wrong for mixed fold/hide
|
|
FldInfos := FoldProvider.InfoListForFoldsAtTextIndex(FldSrcIndex, False);
|
|
MaxCol := length(FldInfos)-1;
|
|
{$IFDEF SynFoldDebug}debugln(['>>FOLD-- Got FldInfos for FldSrcIndex=', FldSrcIndex, ' MaxCol=', MaxCol]);{$ENDIF}
|
|
end;
|
|
|
|
if node.Page._Classification in [fncHighlighter, fncHighlighterEx] then begin
|
|
// find node in list
|
|
i := -1;
|
|
while (i < MaxCol) do begin
|
|
inc(i);
|
|
if (FldInfos[i].Classification <> fncHighlighter) or
|
|
(FldInfos[i].FoldTypeCompatible <> node.Page._FoldTypeCompatible)
|
|
then
|
|
continue;
|
|
FndLen := -1;
|
|
j := abs(FldInfos[i].Column - node.FoldColumn);
|
|
if (j > 0) and (j < node.FoldColumnLen) then begin
|
|
//maybe
|
|
FndLen := FoldProvider.FoldLineLength(FldSrcIndex, i);
|
|
if node.IsHide then inc(FndLen);
|
|
if FndLen <> node.FoldedCount then Continue;
|
|
{$IFDEF SynFoldDebug}debugln('******** FixFolding: Adjusting x pos');{$ENDIF}
|
|
//FldInfos[i].Column := node.FoldColumn;
|
|
end;
|
|
if (FndLen > 0) or (FldInfos[i].Column = node.FoldColumn) then begin
|
|
if FndLen < 0 then begin
|
|
FndLen := FoldProvider.FoldLineLength(FldSrcIndex, i);
|
|
if node.IsHide then inc(FndLen);
|
|
end;
|
|
if abs(FndLen - node.FoldedCount) > 1 then continue;
|
|
if (node.Page._Classification <> fncHighlighter) or
|
|
(node.FoldColumn <> FldInfos[i].Column) or
|
|
(node.FoldIndex <> i)
|
|
then
|
|
Result := true;
|
|
{$IFDEF SynFoldDebug}if (node.Page._Classification <> fncHighlighter) then debugln(['>>FOLD-- FixFolding: set Node to fncHighlighter (FOUND) FldSrcLine=', FldSrcLine]);{$ENDIF}
|
|
node.Page._Classification := fncHighlighter;
|
|
node.FoldColumn := FldInfos[i].Column;
|
|
node.Page._FoldIndex := i;
|
|
i := -1;
|
|
break;
|
|
end;
|
|
end;
|
|
if i = MaxCol then begin
|
|
{$IFDEF SynFoldDebug}debugln(['>>FOLD-- FixFolding: set Node to fncHighlighterEx (NOT FOUND) FldSrcLine=', FldSrcLine]);{$ENDIF}
|
|
node.Page._Classification := fncHighlighterEx;
|
|
node.Page._FoldIndex := MaxCol + AtColumn;
|
|
inc(AtColumn);
|
|
Result := True;
|
|
end;
|
|
end
|
|
else begin
|
|
if node.Page._FoldIndex <> MaxCol + AtColumn then
|
|
Result := True;
|
|
node.Page._FoldIndex := MaxCol + AtColumn;
|
|
inc(AtColumn);
|
|
end;
|
|
|
|
if (node.Page._Nested <> nil) then begin
|
|
SubTree := doFoldTree.TreeForNestedNode(node.Page, FLdNodeLine);
|
|
CurLen := node.MergedLineCount;
|
|
if DoFixFolding(FldSrcLine, FLdNodeLine + CurLen, AtColumn, SubTree, SubTree.FirstPage)
|
|
then begin
|
|
if CurLen > FldLen then begin
|
|
node.Page._MergedLineCount:= max(node.FoldedCount,
|
|
doFoldTree.TreeForNestedNode(node.Page, 0).LastFoldedLine + 1);
|
|
if CurLen <> node.MergedLineCount then begin
|
|
node.Page.UdpateVirtualLineCounts(NextLines, node.StartLine);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
// the node was ok
|
|
if node.StartLine >= doMinEnd then break;
|
|
node := node.Next;
|
|
end;
|
|
{$IFDEF SynFoldDebug}finally DebugLnExit(['<<FOLD-- DoFixFolding: DONE=', Result]); end{$ENDIF}
|
|
end;
|
|
|
|
var
|
|
node, tmpnode: TSynTextFoldAVLNode;
|
|
begin
|
|
{$IFDEF SynFoldDebug}try DebugLnEnter(['>>FOLD-- FixFolding: Start=', AStart, ' AMinEnd=',AMinEnd]);{$ENDIF}
|
|
Result := false;
|
|
if fLockCount > 0 then begin
|
|
Include(FFlags, fvfNeedCaretCheck);
|
|
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.LastPage;
|
|
if not node.IsInFold then begin
|
|
CalculateMaps;
|
|
exit;
|
|
end;
|
|
If aMinEnd < node.StartLine then aMinEnd := node.StartLine; // XXX SourceLine
|
|
|
|
// FoldedCount is allowed to be -1
|
|
while node.IsInFold and (node.StartLine + node.FoldedCount + 1 >= aStart) do begin
|
|
tmpnode := node.Prev;
|
|
if tmpnode.IsInFold
|
|
then node := tmpnode
|
|
else break; // first node
|
|
end;
|
|
|
|
FirstchangedLine := -1;
|
|
FldInfos := nil;
|
|
MaxCol := -1;
|
|
SrcLineForFldInfos := -1;
|
|
Result := DoFixFolding(-1, AMinEnd, 0, aFoldTree, node);
|
|
CalculateMaps;
|
|
if (FirstchangedLine >= 0) then
|
|
DoFoldChanged(FirstchangedLine);
|
|
{$IFDEF SynFoldDebug}finally DebugLnExit(['<<FOLD-- FixFolding: DONE=', Result]); end{$ENDIF}
|
|
end;
|
|
|
|
procedure TSynEditFoldedView.DoCaretChanged(Sender : TObject);
|
|
var
|
|
i: Integer;
|
|
begin
|
|
if fLockCount > 0 then begin
|
|
Include(FFlags, fvfNeedCaretCheck);
|
|
exit;
|
|
end;
|
|
Exclude(FFlags, fvfNeedCaretCheck);
|
|
i := TSynEditCaret(Sender).LinePos-1;
|
|
{$IFDEF SynFoldDebug}if FoldedAtTextIndex[i] then debugln(['FOLD-- DoCaretChanged about to unfold at Index=', i]);{$ENDIF}
|
|
if FoldedAtTextIndex[i] then
|
|
UnFoldAtTextIndexCollapsed(i);
|
|
end;
|
|
|
|
procedure TSynEditFoldedView.LineCountChanged(Sender: TSynEditStrings; AIndex, ACount : Integer);
|
|
begin
|
|
{$IFDEF SynFoldDebug}try DebugLnEnter(['>> FOLD-- LineCountChanged AIndex=', AIndex, ' Acount=',ACount]);{$ENDIF}
|
|
// 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
|
|
SynEdit.ScanRanges, calls Fixfolding as workaroound => review
|
|
}
|
|
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 NextLines.IsInEditAction then exit;
|
|
if ACount<0
|
|
then LinesDeletedAtTextIndex(AIndex+1, -ACount, 1, true)
|
|
else LinesInsertedAtTextIndex(AIndex+1, ACount, 1, true);
|
|
{$IFDEF SynFoldDebug}finally DebugLnExit(['<< FOLD-- LineCountChanged']); end;{$ENDIF}
|
|
end;
|
|
|
|
procedure TSynEditFoldedView.LinesCleared(Sender: TObject);
|
|
begin
|
|
UnfoldAll;
|
|
end;
|
|
|
|
procedure TSynEditFoldedView.LineEdited(Sender: TSynEditStrings; aLinePos, aBytePos, aCount,
|
|
aLineBrkCnt: Integer; aText: String);
|
|
begin
|
|
{$IFDEF SynFoldDebug}try DebugLnEnter(['>> FOLD-- LineEditied aLinePos=', aLinePos, ' aBytePos=', aBytePos, ' Acount=',ACount, ' aLineBrkCnt=',aLineBrkCnt]);{$ENDIF}
|
|
if aLineBrkCnt<0
|
|
then LinesDeletedAtTextIndex(aLinePos, -aLineBrkCnt, ABytePos, true)
|
|
else if aLineBrkCnt > 0
|
|
then LinesInsertedAtTextIndex(aLinePos, aLineBrkCnt, ABytePos, true)
|
|
else begin
|
|
fFoldTree.AdjustColumn(aLinePos, aBytePos, aCount);
|
|
//if not(SkipFixFolding) then FixFoldingAtTextIndex(AStartIndex, AStartIndex+ALineCount+1)
|
|
//else
|
|
//if aLinePos < top + ALineCount then CalculateMaps;
|
|
end;
|
|
{$IFDEF SynFoldDebug}finally DebugLnExit(['<< FOLD-- LineEditied']); end;{$ENDIF}
|
|
end;
|
|
|
|
procedure TSynEditFoldedView.FixFoldingAtTextIndex(AStartIndex: Integer; AMinEndLine : Integer);
|
|
begin
|
|
FixFolding(AStartIndex + 1, AMinEndLine, fFoldTree);
|
|
end;
|
|
|
|
function TSynEditFoldedView.OpenFoldCount(aStartIndex: Integer; AType: Integer = 0): Integer;
|
|
// Todo: move entirely to FoldProvider
|
|
var
|
|
hl: TSynCustomFoldHighlighter;
|
|
begin
|
|
hl := TSynCustomFoldHighlighter(HighLighter);
|
|
if not assigned(hl) then
|
|
exit(-1);
|
|
Result := hl.FoldBlockEndLevel(AStartIndex-1, AType) + FoldProvider.FoldOpenCount(AStartIndex);
|
|
end;
|
|
|
|
function TSynEditFoldedView.OpenFoldInfo(aStartIndex, ColIndex: Integer; AType: Integer = 0): TFoldViewNodeInfo;
|
|
var
|
|
hl: TSynCustomFoldHighlighter;
|
|
TypeCnt, Lvl: Integer;
|
|
EndLvl, CurLvl: Array of integer;
|
|
i, c, t, n, o: Integer;
|
|
nd: TSynFoldNodeInfo;
|
|
FN: TSynTextFoldAVLNode;
|
|
|
|
procedure GetEndLvl(l: Integer);
|
|
var i: integer;
|
|
begin
|
|
if AType = 0 then begin;
|
|
for i := 1 to TypeCnt do begin
|
|
EndLvl[i] := hl.FoldBlockEndLevel(l-1, i);
|
|
EndLvl[i] := EndLvl[i] + FoldProvider.FoldOpenCount(l, i);
|
|
CurLvl[i] := EndLvl[i];
|
|
end;
|
|
end
|
|
else begin
|
|
EndLvl[0] := hl.FoldBlockEndLevel(l-1, AType);
|
|
EndLvl[0] := EndLvl[0] + FoldProvider.FoldOpenCount(l, AType);
|
|
CurLvl[0] := EndLvl[0];
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
Result := Default(TFoldViewNodeInfo);
|
|
hl := TSynCustomFoldHighlighter(HighLighter);
|
|
if not assigned(hl) then
|
|
exit;
|
|
|
|
nd.LogXStart := 0;
|
|
nd.LogXEnd := 0;
|
|
nd.FoldAction := [];
|
|
nd.FoldType := Nil;
|
|
nd.FoldGroup := 0;
|
|
n := 0;
|
|
if AType <> 0 then
|
|
TypeCnt := 1
|
|
else
|
|
TypeCnt := hl.FoldTypeCount;
|
|
Lvl := hl.FoldBlockEndLevel(AStartIndex-1, AType);
|
|
if ColIndex >= Lvl then begin
|
|
n := ColIndex - Lvl;
|
|
if AType = 0 then begin
|
|
o := hl.FoldNodeInfo[aStartIndex].CountEx([sfaOpen, sfaFold]);
|
|
nd := hl.FoldNodeInfo[aStartIndex].NodeInfoEx(n, [sfaOpen, sfaFold]);
|
|
end else begin
|
|
// no sfaFold
|
|
o := hl.FoldNodeInfo[aStartIndex].CountEx([sfaOpenFold],AType);
|
|
nd := hl.FoldNodeInfo[aStartIndex].NodeInfoEx(n, [sfaOpenFold], AType);
|
|
end;
|
|
end
|
|
else begin
|
|
SetLength(EndLvl, TypeCnt+1);
|
|
SetLength(CurLvl, TypeCnt+1);
|
|
GetEndLvl(aStartIndex);
|
|
aStartIndex := aStartIndex;
|
|
while (ColIndex < Lvl) and (aStartIndex > 0) do begin
|
|
dec(aStartIndex);
|
|
o := hl.FoldBlockOpeningCount(AStartIndex, AType);
|
|
if (o > 0) or (hl.FoldBlockClosingCount(aStartIndex, AType) > 0) then begin
|
|
n := o;
|
|
c := hl.FoldNodeInfo[aStartIndex].CountEx([], AType) - 1;
|
|
for i := c downto 0 do begin
|
|
nd := hl.FoldNodeInfo[aStartIndex].NodeInfoEx(i, [], AType);
|
|
if (AType = 0) and not(sfaFold in nd.FoldAction) then
|
|
continue;
|
|
if AType = 0 then
|
|
t := nd.FoldGroup
|
|
else
|
|
t := 0;
|
|
if sfaOpenFold in nd.FoldAction then begin
|
|
dec(n);
|
|
dec(CurLvl[t]);
|
|
if CurLvl[t] < EndLvl[t] then begin
|
|
dec(EndLvl[t]);
|
|
dec(Lvl);
|
|
if ColIndex = Lvl then begin
|
|
break;
|
|
end;
|
|
end;
|
|
end else
|
|
if sfaCloseFold in nd.FoldAction then begin
|
|
inc(CurLvl[t]);
|
|
end;
|
|
end;
|
|
end
|
|
else
|
|
if hl.FoldBlockEndLevel(AStartIndex-1, AType) = 0 then break;
|
|
end;
|
|
end;
|
|
Result.HNode := nd;
|
|
Result.OpenCount := o;
|
|
Result.Text := NextLines[aStartIndex];
|
|
if not(sfaInvalid in nd.FoldAction) then
|
|
Result.Keyword := copy(Result.Text, 1 + nd.LogXStart, nd.LogXEnd-nd.LogXStart);
|
|
Result.LineNum := aStartIndex + 1;
|
|
Result.ColIndex := n;
|
|
FN := FoldNodeAtTextIndex(aStartIndex, n);
|
|
Result.IsFold := FN.IsInFold;
|
|
Result.IsHide := fn.IsHide;
|
|
end;
|
|
|
|
function TSynEditFoldedView.ExpandedLineForBlockAtLine(ALine : Integer;
|
|
HalfExpanded: Boolean = True) : Integer;
|
|
var
|
|
i, l : Integer;
|
|
node: TSynTextFoldAVLNode;
|
|
hl: TSynCustomFoldHighlighter;
|
|
begin
|
|
Result := -1;
|
|
hl := TSynCustomFoldHighlighter(HighLighter);
|
|
if not assigned(hl) then
|
|
exit;
|
|
|
|
i := ALine;
|
|
l := hl.FoldBlockOpeningCount(i - 1);
|
|
if l > 0 then begin
|
|
node := fFoldTree.FindFoldForLine(ALine, true);
|
|
if node.IsInFold and (node.StartLine = ALine +1) then begin
|
|
dec(l);
|
|
if HalfExpanded then while (l >= 0) do begin
|
|
if not IsFoldedAtTextIndex(ALine-1, l) then exit(ALine);
|
|
dec(l);
|
|
end;
|
|
dec(i);
|
|
end
|
|
else
|
|
exit(ALine);
|
|
end
|
|
else if hl.FoldBlockClosingCount(i - 1) > 0 then
|
|
dec(i);
|
|
if (i < 0) or (hl.FoldBlockEndLevel(i-1) = 0) then
|
|
exit;
|
|
|
|
l := 0;
|
|
while (i > 0) and (l >= 0) do begin // (FoldMinLevel[i] >= l) do
|
|
dec(i);
|
|
l := l - hl.FoldBlockOpeningCount(i);
|
|
if l >= 0 then
|
|
l := l + hl.FoldBlockClosingCount(i);
|
|
end;
|
|
if (hl.FoldBlockEndLevel(i) > 0) then // TODO, check for collapsed at index = 0
|
|
Result := i + 1;
|
|
end;
|
|
|
|
function TSynEditFoldedView.GetPhysicalCharWidths(Index: Integer): TPhysicalCharWidths;
|
|
begin
|
|
Result := NextLines.GetPhysicalCharWidths(InternViewToTextIndex(Index));
|
|
end;
|
|
|
|
function TSynEditFoldedView.CollapsedLineForFoldAtLine(ALine : Integer) : Integer;
|
|
// for hides => line before the hide
|
|
var
|
|
node, tmpnode: TSynTextFoldAVLNode;
|
|
begin
|
|
Result := -1;
|
|
node := fFoldTree.FindFoldForLine(ALine, false);
|
|
if node.IsInFold then begin
|
|
tmpnode := node.Prev;
|
|
while tmpnode.IsInFold and
|
|
(tmpnode.StartLine + tmpnode.MergedLineCount = node.StartLine)
|
|
do begin
|
|
node := tmpnode;
|
|
tmpnode := node.Prev;
|
|
end;
|
|
Result := node.StartLine-1;
|
|
// Can be 0, if lines are hiden at begin of file
|
|
end;
|
|
end;
|
|
|
|
function dbgs(AClassification: TFoldNodeClassification): String;
|
|
begin
|
|
WriteStr(Result{%H-}, AClassification);
|
|
end;
|
|
|
|
function dbgs(AFoldLineCapability: TSynEditFoldLineCapability): String;
|
|
begin
|
|
WriteStr(Result{%H-}, AFoldLineCapability);
|
|
end;
|
|
|
|
{$IFDEF SynDebug}
|
|
procedure TSynEditFoldedView.debug;
|
|
begin
|
|
fFoldTree.debug;
|
|
end;
|
|
{$ENDIF}
|
|
|
|
initialization
|
|
InitNumEncodeValues;
|
|
//SYN_FOLD_DEBUG := DebugLogger.RegisterLogGroup('SynFoldDebug' {$IFDEF SynFoldDebug} , True {$ENDIF} );
|
|
|
|
end.
|
|
|