{------------------------------------------------------------------------------- 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+} {$coperators on} {$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 LCLProc, LazLoggerBase, Graphics, Classes, SysUtils, LazSynEditText, SynEditTypes, SynEditMiscClasses, SynEditMiscProcs, SynEditPointClasses, SynEditHighlighter, SynEditHighlighterFoldBase; type TFoldNodeClassification = (fncInvalid, fncHighlighter, fncHighlighterEx, fncBlockSelection); TFoldNodeClassifications = set of TFoldNodeClassification; { TSynTextFoldAVLNodeData } TSynTextFoldAVLNodeData = class(TSynSizedDifferentialAVLNode) protected function Left: TSynTextFoldAVLNodeData; function Parent: TSynTextFoldAVLNodeData; function Right: TSynTextFoldAVLNodeData; procedure FreeAllChildrenAndNested; public (* Position / Size *) (* FullCount: Amount of lines in source for this fold only (excluding overlaps) *) FullCount : Integer; (* 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; (* LeftCount: Lines folded in left tree. Used to calculate how many lines are folded up to a specified line *) property LeftCount: Integer read FLeftSizeSum write FLeftSizeSum; (* MergedLineCount: Amount of lines folded away by this fold, FullCount + Lines covered by overlaps *) property MergedLineCount: Integer read FSize write FSize; 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) *) function RecursiveFoldCount : Integer; (* Amount of lines covered by this and all child nodes *) function Precessor : TSynTextFoldAVLNodeData; reintroduce; function Successor : TSynTextFoldAVLNodeData; reintroduce; function Precessor(var aStartPosition, aSizesBeforeSum : Integer) : TSynTextFoldAVLNodeData; reintroduce; function Successor(var aStartPosition, aSizesBeforeSum : Integer) : TSynTextFoldAVLNodeData; reintroduce; end; { TSynTextFoldAVLNode } TSynTextFoldAVLNode = object private function GetClassification: TFoldNodeClassification; function GetFoldColumn: Integer; function GetFoldColumnLen: Integer; function GetFoldIndex: Integer; function GetMergedLineCount : Integer; function GetFullCount : Integer; function GetSourceLine: integer; function GetSourceLineOffset: integer; procedure SetFoldColumn(const AValue: Integer); protected fData : TSynTextFoldAVLNodeData; // nil if unfolded fStartLine : Integer; // start of folded fFoldedBefore : Integer; public procedure Init(aData : TSynTextFoldAVLNodeData; aStartLine, aFoldedBefore: Integer); function IsInFold : Boolean; function Next : TSynTextFoldAVLNode; function Prev : TSynTextFoldAVLNode; property MergedLineCount: Integer read GetMergedLineCount; // Zero, if Not in a fold property FullCount: Integer read GetFullCount; // Zero, if Not in a fold property StartLine: Integer read fStartLine; // 1st Line of Current Fold property FoldedBefore: Integer read fFoldedBefore; // Count of Lines folded before Startline 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(TSynSizedDifferentialAVLTree) protected fNestParent: TSynTextFoldAVLNodeData; fNestedNodesTree: TSynTextFoldAVLTree; // FlyWeight Tree used for any nested subtree. 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 FindLastFold : TSynTextFoldAVLNode; Function FindFirstFold : TSynTextFoldAVLNode; 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 FNode: TSynTextFoldAVLNode; // AvlFoldNode 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; 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 FHighlighter: TSynCustomFoldHighlighter; FLines : TSynEditStrings; FSelection: TSynEditSelection; FFoldTree : TSynTextFoldAVLTree; 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(aTextView : TSynEditStrings; AFoldTree : TSynTextFoldAVLTree); 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; 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); override; function GetNextHighlighterToken(out ATokenInfo: TLazSynDisplayTokenInfo): Boolean; override; function GetLinesCount: Integer; override; function TextToViewIndex(AIndex: TLineIdx): TLineRange; override; function ViewToTextIndex(AIndex: TLineIdx): TLineIdx; override; end; { TSynTextFoldedView *Line = Line (0-based) on Screen (except TopLine which should be TopViewPos) *ViewPos = Line (1-based) in the array of viewable/visible lines *TextIndex = Line (0-based) in the complete text(folded and unfolded) } TSynEditFoldedViewFlag = (fvfNeedCaretCheck, fvfNeedCalcMaps); TSynEditFoldedViewFlags = set of TSynEditFoldedViewFlag; { TSynEditFoldedView } TSynEditFoldedView = class private fCaret: TSynEditCaret; FBlockSelection: TSynEditSelection; FFoldProvider: TSynEditFoldProvider; fLines : TSynEditStrings; fFoldTree : TSynTextFoldAVLTree; // Folds are stored 1-based (the 1st line is 1) FMarkupInfoFoldedCode: TSynSelectedColor; FMarkupInfoFoldedCodeLine: TSynSelectedColor; FMarkupInfoHiddenCodeLine: TSynSelectedColor; FOnLineInvalidate: TInvalidateLineProc; fTopLine : Integer; fLinesInWindow : Integer; // there may be an additional part visible line fTextIndexList : Array of integer; (* Map each Screen line into a line in textbuffer *) fFoldTypeList : Array of TSynEditFoldLineMapInfo; fOnFoldChanged : TFoldChangedEvent; fLockCount : Integer; fNeedFixFrom, fNeedFixMinEnd : Integer; FFlags: TSynEditFoldedViewFlags; FInTopLineChanged: Boolean; FDisplayView: TLazSynDisplayFold; FFoldChangedHandlerList: TFoldChangedHandlerList; function GetCount : integer; function GetDisplayView: TLazSynDisplayView; function GetFoldClasifications(index : Integer): TFoldNodeClassifications; function GetHighLighter: TSynCustomHighlighter; function GetLines(index : Integer) : String; function GetDisplayNumber(index : Integer) : Integer; function GetTextIndex(index : Integer) : Integer; function GetFoldType(index : Integer) : TSynEditFoldLineCapabilities; function IsFolded(index : integer) : Boolean; // TextIndex procedure SetBlockSelection(const AValue: TSynEditSelection); procedure SetHighLighter(AValue: TSynCustomHighlighter); procedure SetTopLine(const ALine : integer); function GetTopTextIndex : integer; procedure SetTopTextIndex(const AIndex : integer); procedure SetLinesInWindow(const AValue : integer); procedure DoFoldChanged(AnIndex: Integer); protected procedure DoBlockSelChanged(Sender: TObject); 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 LinesInsertedAtViewPos(AStartPos, ALineCount : Integer; // SkipFixFolding : Boolean = False); Procedure LinesDeletedAtTextIndex(AStartIndex, ALineCount, ABytePos: Integer; SkipFixFolding : Boolean = False); //Procedure LinesDeletedAtViewPos(AStartPos, ALineCount : Integer; // SkipFixFolding : Boolean = False); property FoldTree: TSynTextFoldAVLTree read fFoldTree; public constructor Create(aTextView : TSynEditStrings; ACaret: TSynEditCaret); destructor Destroy; override; // Converting between Folded and Unfolded Lines/Indexes function TextIndexToViewPos(aTextIndex : Integer) : Integer; (* Convert TextIndex (0-based) to ViewPos (1-based) *) function TextIndexToScreenLine(aTextIndex : Integer) : Integer; (* Convert TextIndex (0-based) to Screen (0-based) *) function ViewPosToTextIndex(aViewPos : Integer) : Integer; (* Convert ViewPos (1-based) to TextIndex (0-based) *) function ScreenLineToTextIndex(aLine : Integer) : Integer; (* Convert Screen (0-based) to TextIndex (0-based) *) function TextIndexAddLines(aTextIndex, LineOffset : Integer) : Integer; (* Add/Sub to/from TextIndex (0-based) skipping folded *) function TextPosAddLines(aTextpos, LineOffset : Integer) : Integer; (* Add/Sub to/from TextPos (1-based) skipping folded *) property BlockSelection: TSynEditSelection write SetBlockSelection; // Attributes for Visible-Lines-On-screen property Lines[index : Integer] : String (* Lines on screen / 0 = TopLine *) read GetLines; default; property DisplayNumber[index : Integer] : Integer (* LineNumber for display in Gutter / result is 1-based *) read GetDisplayNumber; 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; // maybe writable // Define Visible Area property TopLine : integer (* refers to visible (unfolded) lines / 1-based *) read fTopLine write SetTopLine; property TopTextIndex : integer (* refers to TextIndex (folded + unfolded lines) / 1-based *) read GetTopTextIndex write SetTopTextIndex; property LinesInWindow : integer (* Fully Visible lines in Window; There may be one half visible line *) read fLinesInWindow write SetLinesInWindow; property Count : integer read GetCount; (* refers to visible (unfolded) lines *) property 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; procedure AddFoldChangedHandler(AHandler: TFoldChangedEvent); procedure RemoveFoldChangedHandler(AHandler: TFoldChangedEvent); 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 OnFoldChanged: TFoldChangedEvent (* reports 1-based line *) {TODO: synedit expects 0 based } read fOnFoldChanged write fOnFoldChanged; property OnLineInvalidate: TInvalidateLineProc(* reports 1-based line *) {TODO: synedit expects 0 based } read FOnLineInvalidate write FOnLineInvalidate; property HighLighter: TSynCustomHighlighter read GetHighLighter write SetHighLighter; property FoldProvider: TSynEditFoldProvider read FFoldProvider; property DisplayView: TLazSynDisplayView read GetDisplayView; end; function dbgs(AClassification: TFoldNodeClassification): 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); begin FLineState := 0; CurrentTokenLine := ALine; FLineFlags := FFoldView.FoldType[CurrentTokenLine + 1 - FFoldView.TopLine] * [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; inherited SetHighlighterTokensLine(FFoldView.ViewPosToTextIndex(ALine + 1), ARealLine); 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 ( (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; 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; 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.Count; end; function TLazSynDisplayFold.TextToViewIndex(AIndex: TLineIdx): TLineRange; begin Result := inherited TextToViewIndex(AIndex); if Result.Top = Result.Bottom then begin Result.Top := FFoldView.TextIndexToViewPos(Result.Top) - 1; Result.Bottom := Result.Top; end else begin; Result.Top := FFoldView.TextIndexToViewPos(Result.Top) - 1; Result.Bottom := FFoldView.TextIndexToViewPos(Result.Bottom) - 1; end; end; function TLazSynDisplayFold.ViewToTextIndex(AIndex: TLineIdx): TLineIdx; begin Result := FFoldView.ViewPosToTextIndex(inherited ViewToTextIndex(AIndex)+1); 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(BestPos2 - 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 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 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( 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] ' T' [type] [yo] ( * ' p' [sum] [yo] )* * (' P' [sum] [yo] )? ////////////////////////// // Version info V1 - no entries V2 July 2010 0.9.29 - added fold-hide ////////////////////////// = { }; = " T" ; [* Stores all folds for the given type (eg cfbtBeginEnd) *] = ord(cfbtBeginEnd) or similar = [], , [ [,] [{ , , [] }] ], [ ]; = [{ , }], , ; [* 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. *] = ; = = = = = = ' p', ; [* FoldListEndCont is mandotory, if another block of , is coming *] = ' P' , , ; [* FoldListEnd is optional. It is expected if the previous has more than 10 folded lines*] = [* The sum of all lines folded by folds in . Not including the fold in , which has it's own len. *] = bigger numbers = for numbers expected below 467; specially 0..80 = ' 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 ) 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 after 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 CntSum := 0; LinesSum := 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 (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 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] ' T' [type] [yo] ( * ' p' [sum] [yo] )* * (' P' [sum] [yo] )? *) 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 FReadCount := 0; FReadState := sfecAtPoint; end; 'P': begin // end marker isnt expected? there were no 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); 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 } function TSynTextFoldAVLNodeData.Left: TSynTextFoldAVLNodeData; begin Result := TSynTextFoldAVLNodeData(FLeft); end; function TSynTextFoldAVLNodeData.Parent: TSynTextFoldAVLNodeData; begin Result := TSynTextFoldAVLNodeData(FParent); end; function TSynTextFoldAVLNodeData.Right: TSynTextFoldAVLNodeData; begin Result := TSynTextFoldAVLNodeData(FRight); end; 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; function TSynTextFoldAVLNodeData.RecursiveFoldCount : Integer; var ANode: TSynTextFoldAVLNodeData; begin Result := 0; ANode := self; while ANode <> nil do begin Result := Result + ANode.MergedLineCount + ANode.LeftCount; ANode := ANode.Right; end; end; function TSynTextFoldAVLNodeData.Precessor: TSynTextFoldAVLNodeData; begin Result := TSynTextFoldAVLNodeData(inherited Precessor); end; function TSynTextFoldAVLNodeData.Successor: TSynTextFoldAVLNodeData; begin Result := TSynTextFoldAVLNodeData(inherited Successor); end; function TSynTextFoldAVLNodeData.Precessor(var aStartPosition, aSizesBeforeSum: Integer): TSynTextFoldAVLNodeData; begin Result := TSynTextFoldAVLNodeData(inherited Precessor(aStartPosition, aSizesBeforeSum)); end; function TSynTextFoldAVLNodeData.Successor(var aStartPosition, aSizesBeforeSum: Integer): TSynTextFoldAVLNodeData; begin Result := TSynTextFoldAVLNodeData(inherited Successor(aStartPosition, aSizesBeforeSum)); end; { TSynTextFoldAVLNode } function TSynTextFoldAVLNode.GetClassification: TFoldNodeClassification; begin if fData = nil then Result := fncInvalid else Result := fData.Classification; end; function TSynTextFoldAVLNode.GetFoldColumn: Integer; begin if fData = nil then Result := -1 else Result := fData.FoldColumn; end; function TSynTextFoldAVLNode.GetFoldColumnLen: Integer; begin if fData = nil then Result := -1 else Result := fData.FoldColumnLen; end; function TSynTextFoldAVLNode.GetFoldIndex: Integer; begin if fData = nil then Result := -1 else Result := fData.FoldIndex; end; function TSynTextFoldAVLNode.GetMergedLineCount : Integer; begin if fData = nil then Result := 0 else Result := fData.MergedLineCount; end; function TSynTextFoldAVLNode.GetFullCount: Integer; begin if fData = nil then Result := -1 else Result := fData.FullCount; end; function TSynTextFoldAVLNode.GetSourceLine: integer; begin if fData = nil then Result := -1 else Result := StartLine - fData.VisibleLines; end; function TSynTextFoldAVLNode.GetSourceLineOffset: integer; begin if fData = nil then Result := 0 else Result := fData.VisibleLines; end; procedure TSynTextFoldAVLNode.SetFoldColumn(const AValue: Integer); begin if fData <> nil then fData.FoldColumn := AValue; end; procedure TSynTextFoldAVLNode.Init(aData: TSynTextFoldAVLNodeData; aStartLine, aFoldedBefore: Integer); begin fData := aData; fStartLine := aStartLine; fFoldedBefore := aFoldedBefore; end; function TSynTextFoldAVLNode.IsInFold : Boolean; begin Result := fData <> nil; end; function TSynTextFoldAVLNode.Next : TSynTextFoldAVLNode; var aStart, aBefore : Integer; begin if fData <> nil then begin aStart := StartLine; aBefore := FoldedBefore; Result.fData := fData.Successor(aStart, aBefore); Result.fStartLine := aStart; Result.fFoldedBefore := aBefore; end else Result.fData := nil; end; function TSynTextFoldAVLNode.Prev : TSynTextFoldAVLNode; var aStart, aBefore : Integer; begin if fData <> nil then begin aStart := StartLine; aBefore := FoldedBefore; Result.fData := fData.Precessor(aStart, aBefore); Result.fStartLine := aStart; Result.fFoldedBefore := aBefore; end else Result.fData := nil; end; function TSynTextFoldAVLNode.IsHide: Boolean; begin Result := (fData <> nil) and (fData.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.fData.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.fData.Nested; FCurrentNode.fData := NewData; FCurrentNode.FStartLine := FCurrentNode.FStartLine + 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.fData.Nested <> nil) do begin SetLength(FOuterNodes, i + 1); FOuterNodes[i] := FCurrentNode; NewData := FCurrentNode.fData.Nested; FCurrentNode.fData := NewData; FCurrentNode.FStartLine := FCurrentNode.FStartLine + 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.LeftCount; 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.MergedLineCount; 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.LeftCount => "FoldedBefore" if ALine + r.LeftCount < rStartLine then begin if FindNextNode and (r.Left = nil) then break; r := r.Left; // rStartLine points to r, so if r.Left is nil then it is pointing to the next fold; continue; end; ALine := ALine + r.LeftCount + r.MergedLineCount; rFoldedBefore := rFoldedBefore + r.LeftCount; if FindNextNode and (r.Right = nil) then begin r := r.Successor(rStartLine, rFoldedBefore); break; end; rFoldedBefore := rFoldedBefore + r.MergedLineCount; 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.FullCount - 1 t := Current.FullCount; if AStartLine <= CurrentLine + t - 1 then Current.FullCount := t + ALineCount; Current.MergedLineCount:= Current.MergedLineCount+ ALineCount; Current.AdjustParentLeftCount(ALineCount); 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, t : 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 t := Current.MergedLineCount; Current.FullCount := Max(Current.FullCount - LinesInside, -1); Current.MergedLineCount := Max(Current.MergedLineCount - LinesInside, 0); Current.AdjustParentLeftCount(Current.MergedLineCount - t); // If LineCount = -1; LeftCount 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 t := Current.MergedLineCount; Current.MergedLineCount := Current.MergedLineCount- LinesInside; if Current.FullCount > Current.MergedLineCount then Current.FullCount := Current.MergedLineCount; Current.AdjustParentLeftCount(Current.MergedLineCount - t); // If MergedLineCount = -1; LeftCount 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.fData, node.StartLine).AdjustColumn(ALine, ABytePos, ACount); end; function TSynTextFoldAVLTree.FindLastFold : 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.LeftCount + r.MergedLineCount; 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.FindFirstFold : TSynTextFoldAVLNode; var r : TSynTextFoldAVLNodeData; rStartLine : Integer; begin r := TSynTextFoldAVLNodeData(fRoot); rStartLine := fRootOffset; while (r <> nil) do begin rStartLine := rStartLine + r.LineOffset; if r.Left = nil then break; r := r.Left; end; Result{%H-}.Init(r, rStartLine, 0); end; function TSynTextFoldAVLTree.LastFoldedLine: integer; var n: TSynTextFoldAVLNode; begin n := FindFirstFold; 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.FullCount -1, offset + ANode.LineOffset + ANode.MergedLineCount-1, ANode.FoldIndex, ANode.FoldColumn, ANode.FoldColumnLen, MergedLineCount, FullCount]), ind, typ, ' (',LineOffset, ') LeftCount: ', LeftCount, ' Balance: ',FBalance]); if ANode.Parent <> AParent then DebugLn([ind,'* Bad parent']); Result := debug2(ind+' ', 'L', ANode.Left, ANode, offset+ANode.LineOffset); If Result <> ANode.LeftCount then debugln([ind,' ***** Leftcount was ',Result, ' but should be ', ANode.LeftCount]); Result := 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.FullCount := ACount; r.LeftCount := 0; r.VisibleLines := AVisibleLines; r.Classification := AClassification; r.FoldTypeCompatible := AFoldTypeCompatible; 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.fData.Nested <> nil then begin TreeForNestedNode(OldFold.fData, OldFold.StartLine).RemoveFoldForLine (ALine, OnlyCol); lcount := max(OldFold.FullCount, TreeForNestedNode(OldFold.fData, 0).LastFoldedLine + 1); if lcount <> OldFold.MergedLineCount then begin OldFold.fData.MergedLineCount := lcount; OldFold.fData.AdjustParentLeftCount(OldFold.MergedLineCount - lcount); 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.FullCount; // 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.fData); NestedLine := 0; If ANode.fData.Nested <> nil then begin (*Todo: should we mark the tree as NO balancing needed ???*) TreeForNestedNode(ANode.fData, ANode.StartLine).RemoveFoldForLine(ALine); if OnlyNested then begin NestedLine := ANode.StartLine + ANode.FullCount; Nested := TreeForNestedNode(ANode.fData, ANode.StartLine).FindLastFold; while Nested.IsInFold and (Nested.StartLine >= NestedLine) do begin NestedNode := Nested.fData; offs := Nested.StartLine; Nested := Nested.Prev; lcount := ANode.fData.MergedLineCount; ANode.fData.MergedLineCount := max(ANode.FullCount, Nested.StartLine + Nested.MergedLineCount - ANode.StartLine); ANode.fData.AdjustParentLeftCount(ANode.MergedLineCount - lcount); TreeForNestedNode(ANode.fData, ANode.StartLine).RemoveNode(NestedNode); NestedNode.LineOffset := offs; InsertNode(NestedNode); end; lcount := max(ANode.FullCount, TreeForNestedNode(ANode.fData, ANode.StartLine).LastFoldedLine - ANode.StartLine + 1); if lcount <> ANode.MergedLineCount then begin ANode.fData.MergedLineCount := lcount; ANode.fData.AdjustParentLeftCount(ANode.MergedLineCount - lcount); end; end else begin // merge the remaining nested into current NestedNode := ANode.fData.Nested; if NestedNode <> nil then NestedLine := ANode.fStartLine + NestedNode.LineOffset; while NestedNode <> nil do begin while NestedNode.Left <> nil do begin NestedNode := NestedNode.Left; NestedLine := NestedLine + NestedNode.LineOffset; end; if NestedNode.Right <> nil then begin NestedNode := NestedNode.Right; NestedLine := NestedLine + NestedNode.LineOffset; continue; end; // leaf node // Anything that is still nested (MergeNode.Nested), will stay nested MergeNode := NestedNode; NestedLine := NestedLine - NestedNode.LineOffset; NestedNode := NestedNode.Parent; MergeNode.LineOffset := MergeNode.LineOffset + NestedLine; if NestedNode <> nil then begin NestedNode.ReplaceChild(MergeNode, nil); MergeNode.FParent := nil; end; MergeNode.LeftCount := 0; MergeNode.FBalance := 0; if MergeNode.FullCount <= 0 then begin MergeNode.FreeAllChildrenAndNested; MergeNode.Free; end else InsertNode(MergeNode); end; end; end; if not OnlyNested then DisposeNode(TSynSizedDifferentialAVLNode(ANode.fData)); end; function TSynTextFoldAVLTree.InsertNode(ANode : TSynTextFoldAVLNodeData) : Integer; var rStartLine, NestStartLine : Integer; rFoldedBefore, NestFoldedBefore : Integer; current, Nest : TSynTextFoldAVLNodeData; ALine, AEnd, ACount : Integer; (* ANode.StartLine < Current.StartLine // ANode goes into tree *) procedure NestCurrentIntoNewBlock; inline; var diff, start2, before2 : Integer; p : TSynTextFoldAVLNodeData; begin current.AdjustParentLeftCount(ACount-current.MergedLineCount); // -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.LeftCount); current.FLeft := nil; current.LeftCount := 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 gre, we did so by existing nodes, so no new overlaps start2 := TreeForNestedNode(Anode, 0).LastFoldedLine; if start2 > ANode.FullCount - 1 then begin ANode.AdjustParentLeftCount(start2 + 1 - ANode.MergedLineCount); ANode.MergedLineCount := start2 + 1; 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.FullCount -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.FullCount -1 then begin current.AdjustParentLeftCount(end2 + 1 - current.MergedLineCount); current.MergedLineCount := end2 + 1; end; end; end; begin Result := 0; if fRoot = nil then begin SetRoot(ANode, -fRootOffset); exit; end; ALine := ANode.LineOffset; ACount := ANode.MergedLineCount; 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(ACount); current.SetLeftChild(ANode, -rStartLine, ANode.MergedLineCount); BalanceAfterInsert(ANode); end else begin // nest current := Nest; rStartLine := NestStartLine; rFoldedBefore := NestFoldedBefore; NestCurrentIntoNewBlock; end; break; end; rFoldedBefore := rFoldedBefore + current.LeftCount; if ALine = rStartLine then begin if 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.MergedLineCount; if current.Right <> nil then begin current := current.Right; continue; end else if Nest=nil then Begin // insert to the right - no nesting current.AdjustParentLeftCount(ACount); current.SetRightChild(ANode, -rStartLine); BalanceAfterInsert(ANode); end else begin // nest current := Nest; rStartLine := NestStartLine; rFoldedBefore := NestFoldedBefore; NestCurrentIntoNewBlock; end; end; end; break; end; // while Result := rFoldedBefore; end; 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 (FSelection <> nil) and (FSelection.SelAvail) then begin if (FSelection.FirstLineBytePos.Y < ALineIdx+1) and (FSelection.LastLineBytePos.Y > ALineIdx+1) then Result := [cfFoldBody]; if (FSelection.LastLineBytePos.Y = ALineIdx+1) then Result := [cfFoldEnd]; if (FSelection.FirstLineBytePos.Y = ALineIdx+1) then Result := [cfHideStart]; if (FSelection.FirstLineBytePos.Y = ALineIdx+1) and (FSelection.LastLineBytePos.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 (FSelection <> nil) and FSelection.SelAvail and (FSelection.FirstLineBytePos.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 := (FHighlighter <> nil) or ((FSelection <> nil) and FSelection.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; FNestedFoldsList.Lines := FLines; end; constructor TSynEditFoldProvider.Create(aTextView: TSynEditStrings; AFoldTree : TSynTextFoldAVLTree); begin FLines := aTextView; FFoldTree := AFoldTree; end; destructor TSynEditFoldProvider.Destroy; begin inherited Destroy; FreeAndNil(FNestedFoldsList); end; function TSynEditFoldProvider.FoldOpenCount(ALineIdx: Integer; AType: Integer = 0): Integer; begin if (FHighlighter = nil) or (ALineIdx < 0) then begin if (AType=0) and (FSelection <> nil) and FSelection.SelAvail and (FSelection.FirstLineBytePos.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 (FSelection <> nil) and FSelection.SelAvail and (FSelection.FirstLineBytePos.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 := FSelection.FirstLineBytePos.x; Result.LogXEnd := FSelection.FirstLineBytePos.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 (FSelection <> nil) and FSelection.SelAvail and (FSelection.FirstLineBytePos.Y=ALineIdx+1) then exit(BlockSelInfo(0)); exit; end; FHighlighter.CurrentLines := FLines; if (AType = 0) and (FSelection <> nil) and FSelection.SelAvail and (FSelection.FirstLineBytePos.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 (FSelection <> nil) and FSelection.SelAvail and (FSelection.FirstLineBytePos.Y=ALine+1) and (AFoldIndex = FoldOpenCount(ALine, 0)-1) then exit(FSelection.LastLineBytePos.y - FSelection.FirstLineBytePos.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); SetLength(Result, i); while i > 0 do begin dec(i); Result[i] := InfoForFoldAtTextIndex(ALine, i, False, NeedLen); end; end; { TSynEditFoldedView } constructor TSynEditFoldedView.Create(aTextView : TSynEditStrings; ACaret: TSynEditCaret); begin fTopLine := 0; fLinesInWindow := -1; fLines := aTextView; fCaret := ACaret; fCaret.AddChangeHandler(@DoCaretChanged); fFoldTree := TSynTextFoldAVLTree.Create; FFoldProvider := TSynEditFoldProvider.Create(aTextView, fFoldTree); // TODO: if NextLineChanges, update FFoldProvider // DoSynStringsChanged FDisplayView := TLazSynDisplayFold.Create(Self); FFoldChangedHandlerList := TFoldChangedHandlerList.Create; 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; fLines.AddChangeHandler(senrLineCount, @LineCountChanged); fLines.AddNotifyHandler(senrCleared, @LinesCleared); fLines.AddEditHandler(@LineEdited); end; destructor TSynEditFoldedView.Destroy; begin fLines.RemoveChangeHandler(senrLineCount, @LineCountChanged); fLines.RemoveNotifyHandler(senrCleared, @LinesCleared); fLines.RemoveEditHandler(@LineEdited); fCaret.RemoveChangeHandler(@DoCaretChanged); FreeAndNil(FDisplayView); FreeAndNil(FFoldChangedHandlerList); fFoldTree.Free; fTextIndexList := nil; fFoldTypeList := nil; FMarkupInfoFoldedCode.Free; FMarkupInfoFoldedCodeLine.Free; FMarkupInfoHiddenCodeLine.Free; FreeAndNil(FFoldProvider); inherited Destroy; 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.LinesInsertedAtViewPos(AStartPos, ALineCount : Integer; SkipFixFolding : Boolean); //begin // LinesInsertedAtTextIndex(ViewPosToTextIndex(AStartPos), ALineCount, SkipFixFolding); //end; procedure TSynEditFoldedView.LinesDeletedAtTextIndex(AStartIndex, ALineCount, ABytePos: Integer; SkipFixFolding : Boolean); var top : Integer; begin top := TopTextIndex; // topline 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; //procedure TSynEditFoldedView.LinesDeletedAtViewPos(AStartPos, ALineCount : Integer; SkipFixFolding : Boolean); //begin // LinesDeletedAtTextIndex(ViewPosToTextIndex(AStartPos), ALineCount, SkipFixFolding); //end; function TSynEditFoldedView.TextIndexToViewPos(aTextIndex : Integer) : Integer; var n: TSynTextFoldAVLNode; begin n := fFoldTree.FindFoldForLine(aTextIndex + 1); if n.IsInFold then Result := n.StartLine - 1 - n.FoldedBefore else Result := aTextIndex + 1 - n.FoldedBefore; end; function TSynEditFoldedView.TextIndexToScreenLine(aTextIndex : Integer) : Integer; begin Result := TextIndexToViewPos(aTextIndex) - TopLine; end; function TSynEditFoldedView.ViewPosToTextIndex(aViewPos : Integer) : Integer; begin result := aViewPos - 1 + fFoldTree.FindFoldForFoldedLine(aViewPos).FoldedBefore; end; function TSynEditFoldedView.ScreenLineToTextIndex(aLine : Integer) : Integer; begin Result := ViewPosToTextIndex(aLine + TopLine); end; function TSynEditFoldedView.TextIndexAddLines(aTextIndex, LineOffset : Integer) : Integer; var node : TSynTextFoldAVLNode; boundary : integer; begin node := fFoldTree.FindFoldForLine(aTextIndex+1, True); result := aTextIndex; if LineOffset < 0 then begin boundary := Max(0, ViewPosToTextIndex(1)); if node.IsInFold then node := node.Prev else node := fFoldTree.FindLastFold; 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 := fLines.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; function TSynEditFoldedView.TextPosAddLines(aTextpos, LineOffset : Integer) : Integer; begin Result := TextIndexAddLines(aTextpos-1, LineOffset)+1; end; procedure TSynEditFoldedView.Lock; begin if fLockCount=0 then begin fNeedFixFrom := -1; fNeedFixMinEnd := -1; 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.GetCount : integer; begin Result := fLines.Count - fFoldTree.FindLastFold.FoldedBefore; end; function TSynEditFoldedView.GetDisplayView: TLazSynDisplayView; begin Result := FDisplayView; 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 := fLines; end; (* Topline *) procedure TSynEditFoldedView.SetTopLine(const ALine : integer); begin if fTopLine = ALine then exit; FInTopLineChanged := True; fTopLine := ALine; CalculateMaps; FInTopLineChanged := False; end; function TSynEditFoldedView.GetTopTextIndex : integer; begin Result := fTopLine + fFoldTree.FindFoldForFoldedLine(fTopLine).FoldedBefore - 1; end; procedure TSynEditFoldedView.SetTopTextIndex(const AIndex : integer); begin TopLine := AIndex + 1 - fFoldTree.FindFoldForLine(AIndex+1).FoldedBefore; end; (* LinesInWindow*) procedure TSynEditFoldedView.SetLinesInWindow(const AValue : integer); begin if fLinesInWindow = AValue then exit; fLinesInWindow := AValue; SetLength(fTextIndexList, AValue + 3); SetLength(fFoldTypeList, AValue + 3); // start 1 before topline CalculateMaps; end; procedure TSynEditFoldedView.DoFoldChanged(AnIndex: Integer); begin if Assigned(fOnFoldChanged) then fOnFoldChanged(AnIndex); FFoldChangedHandlerList.CallFoldChangedEvents(AnIndex); end; procedure TSynEditFoldedView.DoBlockSelChanged(Sender: TObject); begin CalculateMaps; end; procedure TSynEditFoldedView.CalculateMaps; var i, tpos, cnt : Integer; node, tmpnode: TSynTextFoldAVLNode; FirstChanged, LastChanged: Integer; NewCapability: TSynEditFoldLineCapabilities; 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 then begin Include(FFlags, fvfNeedCalcMaps); exit; end; Exclude(FFlags, fvfNeedCalcMaps); node := fFoldTree.FindFoldForFoldedLine(fTopLine, true); // ftopline is not a folded line // so node.FoldedBefore(next node after ftopl) does apply tpos := fTopLine + node.FoldedBefore - 1; if node.IsInFold then tmpnode := node.Prev else tmpnode := fFoldTree.FindLastFold; if tmpnode.IsInFold and (tmpnode.StartLine + tmpnode.MergedLineCount = tpos + 1) then begin node := tmpnode; tpos := tpos - node.MergedLineCount; end; {$IFDEF SynFoldDebug}debugln(['FOLD-- CalculateMaps fTopLine:=', fTopLine, ' tpos=',tpos]);{$ENDIF} cnt := fLines.Count; FirstChanged := -1; LastChanged := -1; for i := 0 to fLinesInWindow + 2 do begin if (tpos > cnt) or (tpos < 0) then begin // Past end of Text fTextIndexList[i] := -1; NewCapability := []; NewClassifications := []; end else begin fTextIndexList[i] := tpos - 1; // TextIndex is 0-based NewCapability := FFoldProvider.LineCapabilities[tpos - 1]; NewClassifications := FFoldProvider.LineClassification[tpos - 1]; if (node.IsInFold) then begin if (tpos = node.SourceLine) then begin include(NewCapability, cfCollapsedFold); include(NewClassifications, node.fData.Classification); end else if node.IsHide and (tpos + 1 = node.SourceLine) then begin include(NewCapability, cfCollapsedHide); include(NewClassifications, node.fData.Classification); end; end; inc(tpos); while (node.IsInFold) and (tpos >= node.StartLine) do begin tpos := tpos + node.MergedLineCount; node := node.Next; end; end; if (fFoldTypeList[i].Capability <> NewCapability) or (fFoldTypeList[i].Classifications <> NewClassifications) then begin if FirstChanged < 0 then FirstChanged := tpos - 1; LastChanged := tpos; end; fFoldTypeList[i].Capability := NewCapability; fFoldTypeList[i].Classifications := NewClassifications; end; if (not FInTopLineChanged) and assigned(FOnLineInvalidate) and (FirstChanged > 0) then FOnLineInvalidate(FirstChanged, LastChanged + 1); end; (* Lines *) function TSynEditFoldedView.GetLines(index : Integer) : String; begin if (index < -1) or (index > fLinesInWindow + 1) then exit(fLines[ScreenLineToTextIndex(Index)]); Result := fLines[fTextIndexList[index+1]]; end; function TSynEditFoldedView.GetDisplayNumber(index : Integer) : Integer; begin if (index < -1) or (index > fLinesInWindow + 1) or (fTextIndexList[index+1] < 0) then exit(-1); Result := fTextIndexList[index+1]+1; end; function TSynEditFoldedView.GetTextIndex(index : Integer) : Integer; begin if (index < -1) or (index > fLinesInWindow + 1) then exit(ScreenLineToTextIndex(Index)); Result := fTextIndexList[index+1]; 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.SetBlockSelection(const AValue: TSynEditSelection); begin if FBlockSelection <> nil then FBlockSelection.RemoveChangeHandler(@DoBlockSelChanged); FBlockSelection := AValue; if FBlockSelection <> nil then FBlockSelection.AddChangeHandler(@DoBlockSelChanged); FoldProvider.FSelection := AValue; 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 + fTopLine, 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.FindLastFold; 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.fData.Nested = nil then break; tree := fFoldTree.TreeForNestedNode(Result.fData, Result.StartLine); Result := tree.FindFirstFold; 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.fData.Nested = nil then break; Result := fFoldTree.TreeForNestedNode(Result.fData, Result.StartLine).FindFirstFold; end; Result.fData := nil; 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 visble 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 < fLines.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.fData.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.FullCount, NodeFoldType); Node := NodeIterator.Next; while Node.IsInFold and (Node.fData.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.FullCount; 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 >= FLines.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 visble 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; fTopLine := -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 + fTopLine, 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.fData, 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; fTopLine := -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; fTopLine := -1; // make sure seting TopLineTextIndex, will do CalculateMaps; TopTextIndex := top; DoFoldChanged(r); end; procedure TSynEditFoldedView.UnfoldAll; var top : Integer; begin top := TopTextIndex; fFoldTree.Clear; fTopLine := -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 < fLines.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 visble 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; fTopLine := -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.FindFirstFold; 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, ' FullCount=', node.FullCount, ' 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.FullCount; 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.fData.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.fData.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.FullCount 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.FullCount) > 1 then continue; if (node.fData.Classification <> fncHighlighter) or (node.FoldColumn <> FldInfos[i].Column) or (node.FoldIndex <> i) then Result := true; {$IFDEF SynFoldDebug}if (node.fData.Classification <> fncHighlighter) then debugln(['>>FOLD-- FixFolding: set Node to fncHighlighter (FOUND) FldSrcLine=', FldSrcLine]);{$ENDIF} node.fData.Classification := fncHighlighter; node.FoldColumn := FldInfos[i].Column; node.fData.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.fData.Classification := fncHighlighterEx; node.fData.FoldIndex := MaxCol + AtColumn; inc(AtColumn); Result := True; end; end else begin if node.fData.FoldIndex <> MaxCol + AtColumn then Result := True; node.fData.FoldIndex := MaxCol + AtColumn; inc(AtColumn); end; if (node.fData.Nested <> nil) then begin SubTree := doFoldTree.TreeForNestedNode(node.fData, FLdNodeLine); CurLen := node.MergedLineCount; if DoFixFolding(FldSrcLine, FLdNodeLine + CurLen, AtColumn, SubTree, SubTree.FindFirstFold) then begin if CurLen > FldLen then begin node.fData.MergedLineCount:= max(node.FullCount, doFoldTree.TreeForNestedNode(node.fData, 0).LastFoldedLine + 1); if CurLen <> node.MergedLineCount then node.fData.AdjustParentLeftCount(node.MergedLineCount - CurLen); end; end; end; // the node was ok if node.StartLine >= doMinEnd then break; node := node.Next; end; {$IFDEF SynFoldDebug}finally DebugLnExit(['<>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.FindLastFold; if not node.IsInFold then begin CalculateMaps; exit; end; If aMinEnd < node.StartLine then aMinEnd := node.StartLine; // XXX SourceLine // FullCount is allowed to be -1 while node.IsInFold and (node.StartLine + node.FullCount + 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-- 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 fLines.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; 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 hl := TSynCustomFoldHighlighter(HighLighter); if not assigned(hl) then exit; // ToDo: Initialize Result 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 := fLines[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; Result.FNode := FoldNodeAtTextIndex(aStartIndex, n); 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; procedure TSynEditFoldedView.AddFoldChangedHandler(AHandler: TFoldChangedEvent); begin FFoldChangedHandlerList.Add(TMethod(AHandler)); end; procedure TSynEditFoldedView.RemoveFoldChangedHandler( AHandler: TFoldChangedEvent); begin FFoldChangedHandlerList.Remove(TMethod(AHandler)); end; function TSynEditFoldedView.GetPhysicalCharWidths(Index: Integer): TPhysicalCharWidths; begin Result := fLines.GetPhysicalCharWidths(ScreenLineToTextIndex(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; {$IFDEF SynDebug} procedure TSynEditFoldedView.debug; begin fFoldTree.debug; end; {$ENDIF} initialization InitNumEncodeValues; //SYN_FOLD_DEBUG := DebugLogger.RegisterLogGroup('SynFoldDebug' {$IFDEF SynFoldDebug} , True {$ENDIF} ); end.