mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-04-05 13:58:04 +02:00
4124 lines
141 KiB
ObjectPascal
4124 lines
141 KiB
ObjectPascal
(* SynEditMarkupIfDef
|
|
|
|
Provides a framework to high-(low-)light "{$IFDEF }" blocks. This unit is
|
|
directly bound to the pascal Highlighter.
|
|
The evaluation of IFDEF expression must be done by user code.
|
|
|
|
The state of the IFDEF directives (true/false/unknown) is stored in a
|
|
differential AVL tree.
|
|
Each Node represents a line in the text (and may also indicate the number of
|
|
IFDEF free lines following). The nodes contain a list of all IFDEF on their line.
|
|
|
|
This allows to quickly find and insert/delete nodes, as well as move nodes
|
|
(change line), if text is edited.
|
|
|
|
The tree can be accessed, validated, invalidated, adjusted by several "shared"
|
|
SynEdit.
|
|
|
|
*)
|
|
unit SynEditMarkupIfDef;
|
|
|
|
{$mode objfpc}{$H+}
|
|
{$ASSERTIONS on}
|
|
interface
|
|
|
|
uses
|
|
SysUtils, Classes,
|
|
// LCL
|
|
Graphics, LCLProc,
|
|
// LazUtils
|
|
LazUtilities, LazClasses, LazLoggerBase, LazMethodList,
|
|
// SynEdit
|
|
SynEditMiscClasses, SynHighlighterPas, SynEditMarkupHighAll,
|
|
SynEditHighlighterFoldBase, SynEditFoldedView, LazSynEditText, SynEditMiscProcs,
|
|
SynEditMarkup, SynEditPointClasses, SynEditHighlighter;
|
|
|
|
type
|
|
|
|
{ TSynRefCountedDict }
|
|
|
|
TSynRefCountedDict = class(TRefCountedObject)
|
|
private
|
|
FDict: TSynSearchDictionary; // used to check for single line nodes (avoid using highlighter)
|
|
procedure CheckWordEnd(MatchEnd: PChar; MatchIdx: Integer; var IsMatch: Boolean;
|
|
var StopSeach: Boolean);
|
|
public
|
|
constructor Create;
|
|
destructor Destroy; override;
|
|
function GetMatchAtChar(AText: PChar; ATextLen: Integer): Integer;
|
|
|
|
property Dict: TSynSearchDictionary read FDict;
|
|
end;
|
|
|
|
|
|
TSynMarkupHighIfDefLinesNode = class;
|
|
TSynMarkupHighIfDefLinesTree = class;
|
|
|
|
{ TSynMarkupHighIfDefEntry - Information about a single $IfDef/$Else/$EndIf }
|
|
|
|
TSynMarkupIfDefNodeFlag = (
|
|
idnMultiLineTag, // The "{$IFDEF ... }" wraps across lines.
|
|
// Only allowed on last node AND if FLine.FEndLineOffs > 0
|
|
idnStateByUser,
|
|
idnCommented
|
|
);
|
|
SynMarkupIfDefNodeFlags = set of TSynMarkupIfDefNodeFlag;
|
|
|
|
TSynMarkupIfdefNodeType = (
|
|
idnIfdef, idnElseIf, idnElse, idnEndIf,
|
|
idnCommentedNode // Keep Ifdef if commented
|
|
);
|
|
|
|
TSynMarkupIfdefNodeStateEx = (
|
|
idnEnabled, idnDisabled,
|
|
idnTempEnabled, idnTempDisabled,
|
|
idnNotInCode, // in currently inactive outer IfDef
|
|
idnInvalid, // not known for other reasons. Will not ask again
|
|
idnUnknown, // needs requesting
|
|
idnRequested // not used
|
|
);
|
|
TSynMarkupIfdefNodeState = idnEnabled..idnInvalid;
|
|
|
|
TSynMarkupIfdefPeerType = (idpOpeningPeer, idpClosingPeer);
|
|
const
|
|
ReversePeerType: array [TSynMarkupIfdefPeerType] of TSynMarkupIfdefPeerType =
|
|
(idpClosingPeer, idpOpeningPeer);
|
|
|
|
type
|
|
TSynMarkupIfdefStateRequest = function(Sender: TObject; // SynEdit
|
|
LinePos, XStartPos: Integer; // pos of the "{"
|
|
CurrentState: TSynMarkupIfdefNodeStateEx
|
|
): TSynMarkupIfdefNodeState of object;
|
|
|
|
TSynMarkupHighIfDefEntry = class
|
|
private
|
|
FLine: TSynMarkupHighIfDefLinesNode;
|
|
FNodeType: TSynMarkupIfdefNodeType;
|
|
FNodeState, FOpeningPeerNodeState: TSynMarkupIfdefNodeStateEx;
|
|
FNodeFlags: SynMarkupIfDefNodeFlags;
|
|
FPeers: Array [TSynMarkupIfdefPeerType] of TSynMarkupHighIfDefEntry;
|
|
//FRelativeNestDepth: Integer;
|
|
FStartColumn, FEndColumn: Integer;
|
|
|
|
function GetNeedsRequesting: Boolean;
|
|
function GetNodeState: TSynMarkupIfdefNodeStateEx;
|
|
function GetStateByUser: Boolean;
|
|
procedure SetLine(AValue: TSynMarkupHighIfDefLinesNode);
|
|
|
|
function NodeStateForPeer(APeerType: TSynMarkupIfdefNodeType): TSynMarkupIfdefNodeStateEx;
|
|
procedure SetOpeningPeerNodeState(AValueOfPeer, AValueForNode: TSynMarkupIfdefNodeStateEx);
|
|
procedure SetNodeState(AValue: TSynMarkupIfdefNodeStateEx; ASkipLineState: Boolean);
|
|
procedure SetNodeState(AValue: TSynMarkupIfdefNodeStateEx);
|
|
|
|
function GetPeer(APeerType: TSynMarkupIfdefPeerType): TSynMarkupHighIfDefEntry;
|
|
procedure SetPeer(APeerType: TSynMarkupIfdefPeerType; ANewPeer: TSynMarkupHighIfDefEntry);
|
|
procedure ClearPeerField(APeerType: TSynMarkupIfdefPeerType);
|
|
|
|
procedure ApplyNodeStateToLine(ARemove: Boolean = False);
|
|
procedure RemoveNodeStateFromLine;
|
|
procedure SetStartColumn(AValue: Integer);
|
|
procedure SetStateByUser(AValue: Boolean);
|
|
protected
|
|
procedure SetNodeType(ANodeType: TSynMarkupIfdefNodeType);
|
|
function DebugText(Short: Boolean = False): String;
|
|
public
|
|
constructor Create;
|
|
destructor Destroy; override;
|
|
procedure ClearPeers;
|
|
procedure ClearAll;
|
|
public
|
|
procedure MakeDisabled;
|
|
procedure MakeEnabled;
|
|
procedure MakeRequested;
|
|
procedure MakeUnknown;
|
|
|
|
function IsRequested: Boolean;
|
|
function HasKnownState: Boolean; // Opposite of NeedsRequesting (except idnRequested) / BUT ignore NodeType
|
|
property NeedsRequesting: Boolean read GetNeedsRequesting;
|
|
property StateByUser: Boolean read GetStateByUser write SetStateByUser; // only else(if) keep state set by user
|
|
|
|
function IsDisabled: Boolean;
|
|
function HasDisabledOpening: Boolean;
|
|
function IsEnabled: Boolean;
|
|
function HasEnabledOpening: Boolean;
|
|
function IsTemp: Boolean;
|
|
function HasTempOpening: Boolean;
|
|
|
|
function IsOpening: Boolean;
|
|
function IsClosing: Boolean;
|
|
function IsTempOpening: Boolean;
|
|
function IsTempClosing: Boolean;
|
|
function IsDisabledOpening: Boolean;
|
|
function IsDisabledClosing: Boolean;
|
|
|
|
function NodeType: TSynMarkupIfdefNodeType;
|
|
property NodeState: TSynMarkupIfdefNodeStateEx read GetNodeState write SetNodeState;
|
|
property NodeFlags: SynMarkupIfDefNodeFlags read FNodeFlags write FNodeFlags;
|
|
function UncommentedNodeType: TSynMarkupIfdefNodeType;
|
|
procedure MakeCommented;
|
|
procedure MakeUnCommented;
|
|
property Line: TSynMarkupHighIfDefLinesNode read FLine write SetLine;
|
|
property StartColumn: Integer read FStartColumn write SetStartColumn;// FStartColumn;
|
|
property EndColumn: Integer read FEndColumn write FEndColumn;
|
|
// RelativeNestDepth (opening depth)) First node is always 0 // nodes in line can be negative
|
|
////property RelativeNestDepth: Integer read FRelativeNestDepth;
|
|
// COMMENT BEFORE AUTO COMPLETE !!!!!
|
|
property OpeningPeer: TSynMarkupHighIfDefEntry index idpOpeningPeer read GetPeer write SetPeer;
|
|
property ClosingPeer: TSynMarkupHighIfDefEntry index idpClosingPeer read GetPeer write SetPeer;
|
|
end;
|
|
|
|
{ TSynMarkupHighIfDefLinesNode - List of all nodes on the same line }
|
|
|
|
SynMarkupIfDefLineFlag = (
|
|
idlValid, // X start/stop positions are ok
|
|
idlAllNodesCommented, // all nodes are comments
|
|
idlHasUnknownNodes, // need requesting
|
|
idlHasNodesNotInCode,
|
|
idlNotInCodeToUnknown, // treat all idnNotInCode nodes as unknown
|
|
idlNotInCodeToUnknownReq, // Request to set all nested lines to ... during next validate
|
|
idlDisposed, // Node is disposed, may be re-used
|
|
idlInGlobalClear // Skip unlinking Peers
|
|
);
|
|
SynMarkupIfDefLineFlags = set of SynMarkupIfDefLineFlag;
|
|
|
|
TSynMarkupHighIfDefLinesNode = class(TSynSizedDifferentialAVLNode)
|
|
private
|
|
FDisabledEntryCloseCount: Integer;
|
|
FDisabledEntryOpenCount: Integer;
|
|
FEntries: Array of TSynMarkupHighIfDefEntry;
|
|
FEntryCount: Integer;
|
|
|
|
FLastEntryEndLineOffs: Integer;
|
|
FLineFlags: SynMarkupIfDefLineFlags;
|
|
FScanEndOffs: Integer;
|
|
function GetEntry(AIndex: Integer): TSynMarkupHighIfDefEntry;
|
|
function GetEntryCapacity: Integer;
|
|
function GetId: PtrUInt;
|
|
procedure SetEntry(AIndex: Integer; AValue: TSynMarkupHighIfDefEntry);
|
|
procedure SetEntryCapacity(AValue: Integer);
|
|
procedure SetEntryCount(AValue: Integer);
|
|
protected
|
|
procedure AdjustPositionOffset(AnAdjustment: integer); // Caller is responsible for staying between neighbours
|
|
property NextDispose: TSynSizedDifferentialAVLNode read FParent write FParent;
|
|
function DebugText: String;
|
|
public
|
|
constructor Create;
|
|
destructor Destroy; override;
|
|
procedure MakeDisposed; // Also called to clear, on real destroy
|
|
property LineFlags: SynMarkupIfDefLineFlags read FLineFlags;
|
|
// LastEntryEndLineOffs: For last Entry only, if entry closing "}" is on a diff line. (can go one OVER ScanEndOffs)
|
|
property LastEntryEndLineOffs: Integer read FLastEntryEndLineOffs write FLastEntryEndLineOffs;
|
|
// ScanEndOffs: How many (empty) lines were scanned after this node
|
|
property ScanEndOffs: Integer read FScanEndOffs write FScanEndOffs;
|
|
property DisabledEntryOpenCount: Integer read FDisabledEntryOpenCount write FDisabledEntryOpenCount;
|
|
property DisabledEntryCloseCount: Integer read FDisabledEntryCloseCount write FDisabledEntryCloseCount;
|
|
public
|
|
function AddEntry(AIndex: Integer = -1): TSynMarkupHighIfDefEntry;
|
|
procedure DeletEntry(AIndex: Integer; AFree: Boolean = false);
|
|
procedure ReduceCapacity;
|
|
function IndexOf(AEntry: TSynMarkupHighIfDefEntry): Integer;
|
|
property EntryCount: Integer read FEntryCount write SetEntryCount;
|
|
property EntryCapacity: Integer read GetEntryCapacity write SetEntryCapacity;
|
|
property Entry[AIndex: Integer]: TSynMarkupHighIfDefEntry read GetEntry write SetEntry; default;
|
|
end;
|
|
|
|
{ TSynMarkupHighIfDefLinesNodeInfo }
|
|
|
|
TSynMarkupHighIfDefLinesNodeInfo = object
|
|
private
|
|
FAtBOL: Boolean;
|
|
FAtEOL: Boolean;
|
|
FNode: TSynMarkupHighIfDefLinesNode;
|
|
FTree: TSynMarkupHighIfDefLinesTree;
|
|
FStartLine, Index: Integer; // Todo: Indek is not used
|
|
FCacheNestMinimum, FCacheNestStart, FCacheNestEnd: Integer;
|
|
function GetLastEntryEndLine: Integer;
|
|
function GetLastEntryEndLineOffs: Integer;
|
|
function GetEntry(AIndex: Integer): TSynMarkupHighIfDefEntry;
|
|
function GetEntryCount: Integer;
|
|
function GetLineFlags: SynMarkupIfDefLineFlags;
|
|
function GetScanEndLine: Integer;
|
|
function GetScanEndOffs: Integer;
|
|
procedure SetEntry(AIndex: Integer; AValue: TSynMarkupHighIfDefEntry);
|
|
procedure SetEntryCount(AValue: Integer);
|
|
procedure SetLastEntryEndLineOffs(AValue: Integer);
|
|
procedure SetScanEndLine(AValue: Integer);
|
|
procedure SetScanEndOffs(AValue: Integer);
|
|
procedure SetStartLine(AValue: Integer); // Caller is responsible for staying between neighbours
|
|
function DebugText: String;
|
|
public
|
|
procedure ClearInfo;
|
|
procedure InitForNode(ANode: TSynMarkupHighIfDefLinesNode; ALine: Integer);
|
|
function Precessor: TSynMarkupHighIfDefLinesNodeInfo;
|
|
function Successor: TSynMarkupHighIfDefLinesNodeInfo;
|
|
public
|
|
procedure ClearNestCache;
|
|
function NestMinimumDepthAtNode: Integer;
|
|
function NestDepthAtNodeStart: Integer;
|
|
function NestDepthAtNodeEnd: Integer;
|
|
public
|
|
function IsValid: Boolean;
|
|
procedure Invalidate;
|
|
function HasNode: Boolean;
|
|
property StartLine: Integer read FStartLine write SetStartLine;
|
|
property LineFlags: SynMarkupIfDefLineFlags read GetLineFlags;
|
|
property LastEntryEndLineOffs: Integer read GetLastEntryEndLineOffs write SetLastEntryEndLineOffs;
|
|
property LastEntryEndLine: Integer read GetLastEntryEndLine; // write SetLastEntryEndLineOffs;
|
|
property ScanEndOffs: Integer read GetScanEndOffs write SetScanEndOffs;
|
|
property ScanEndLine: Integer read GetScanEndLine write SetScanEndLine;
|
|
function ValidToLine(const ANextNode: TSynMarkupHighIfDefLinesNodeInfo): Integer; // ScanEndLine or next node
|
|
|
|
//function AddEntry: TSynMarkupHighIfDefEntry;
|
|
//procedure DeletEntry(AIndex: Integer; AFree: Boolean = false);
|
|
property EntryCount: Integer read GetEntryCount write SetEntryCount;
|
|
property Entry[AIndex: Integer]: TSynMarkupHighIfDefEntry read GetEntry write SetEntry;
|
|
property AtBOL: Boolean read FAtBOL;
|
|
property AtEOL: Boolean read FAtEOL;
|
|
property Node: TSynMarkupHighIfDefLinesNode read FNode;
|
|
end;
|
|
|
|
{ TSynMarkupHighIfDefLinesNodeInfoList }
|
|
|
|
TSynMarkupHighIfDefLinesNodeInfoList = object
|
|
private
|
|
FCount: Integer;
|
|
FNestOpenNodes: Array of TSynMarkupHighIfDefLinesNodeInfo;
|
|
function GetCapacity: Integer;
|
|
function GetCount: Integer;
|
|
function GetNode(AIndex: Integer): TSynMarkupHighIfDefLinesNodeInfo;
|
|
procedure SetCapacity(AValue: Integer);
|
|
procedure SetCount(AValue: Integer);
|
|
procedure SetNode(AIndex: Integer; AValue: TSynMarkupHighIfDefLinesNodeInfo);
|
|
procedure SetNodes(ALow, AHigh: Integer; const AValue: TSynMarkupHighIfDefLinesNodeInfo);
|
|
public
|
|
property Count: Integer read GetCount write SetCount;
|
|
property Capacity: Integer read GetCapacity write SetCapacity;
|
|
property Node[AIndex: Integer]: TSynMarkupHighIfDefLinesNodeInfo read GetNode write SetNode;
|
|
property Nodes[ALow, AHigh: Integer]: TSynMarkupHighIfDefLinesNodeInfo write SetNodes;
|
|
Procedure PushNodeLine(var ANode: TSynMarkupHighIfDefLinesNodeInfo);
|
|
procedure dbg;
|
|
end;
|
|
|
|
TSynMarkupHighIfDefTreeNotifications = (
|
|
itnUnlocking, // About to unlock, Markup should validate it's range
|
|
itnUnlocked, // Unlocked, markup should update it's matches
|
|
itnChanged // A node was changed, while NOT locked / SetNodeState
|
|
);
|
|
|
|
{ TSynMarkupHighIfDefLinesTree }
|
|
|
|
TSynMarkupHighIfDefLinesTree = class(TSynSizedDifferentialAVLTree)
|
|
private
|
|
FHighlighter: TSynPasSyn;
|
|
FLines: TSynEditStringsLinked;
|
|
FClearing: Boolean;
|
|
FDisposedNodes: TSynSizedDifferentialAVLNode;
|
|
FOnNodeStateRequest: TSynMarkupIfdefStateRequest;
|
|
FRequestingNodeState: Boolean;
|
|
FLockTreeCount: Integer;
|
|
FNotifyLists: Array [TSynMarkupHighIfDefTreeNotifications] of TMethodList;
|
|
FChangeStep: Integer;
|
|
|
|
procedure IncChangeStep;
|
|
procedure SetHighlighter(AValue: TSynPasSyn);
|
|
procedure SetLines(AValue: TSynEditStringsLinked);
|
|
function GetHighLighterWithLines: TSynCustomFoldHighlighter;
|
|
private
|
|
//copied from SynEdit
|
|
//TODO, move to highlighter
|
|
function GetHighlighterAttriAtRowColEx(XY: TPoint; out Token: string;
|
|
out TokenType, Start: Integer;
|
|
out Attri: TSynHighlighterAttributes): boolean; //L505
|
|
|
|
|
|
procedure MaybeRequestNodeStates(var ANode: TSynMarkupHighIfDefLinesNodeInfo);
|
|
procedure MaybeValidateNode(var ANode: TSynMarkupHighIfDefLinesNodeInfo);
|
|
procedure MaybeExtendNodeBackward(var ANode: TSynMarkupHighIfDefLinesNodeInfo;
|
|
AStopAtLine: Integer = 0);
|
|
procedure MaybeExtendNodeForward(var ANode: TSynMarkupHighIfDefLinesNodeInfo;
|
|
var ANextNode: TSynMarkupHighIfDefLinesNodeInfo;
|
|
AStopBeforeLine: Integer = -1);
|
|
|
|
function GetOrInsertNodeAtLine(ALinePos: Integer): TSynMarkupHighIfDefLinesNodeInfo;
|
|
procedure ConnectPeers(var ANode: TSynMarkupHighIfDefLinesNodeInfo;
|
|
var ANestList: TSynMarkupHighIfDefLinesNodeInfoList;
|
|
AOuterLines: TLazSynEditNestedFoldsList = nil);
|
|
function CheckLineForNodes(ALine: Integer): Boolean;
|
|
procedure ScanLine(ALine: Integer; var ANodeForLine: TSynMarkupHighIfDefLinesNode;
|
|
ACheckOverlapOnCreateLine: Boolean = False);
|
|
|
|
procedure DoLinesEdited(Sender: TSynEditStrings; aLinePos, aBytePos, aCount,
|
|
aLineBrkCnt: Integer; aText: String);
|
|
procedure DoHighlightChanged(Sender: TSynEditStrings; AIndex, ACount : Integer);
|
|
protected
|
|
function CreateNode(APosition: Integer): TSynSizedDifferentialAVLNode; override;
|
|
procedure DisposeNode(var ANode: TSynSizedDifferentialAVLNode); override;
|
|
procedure RemoveLine(var ANode: TSynMarkupHighIfDefLinesNode);
|
|
public
|
|
constructor Create;
|
|
destructor Destroy; override;
|
|
procedure Clear; override;
|
|
procedure DebugPrint(Flat: Boolean = False);
|
|
|
|
procedure RegisterNotification(AReason: TSynMarkupHighIfDefTreeNotifications;
|
|
AHandler: TNotifyEvent);
|
|
procedure UnRegisterNotification(AReason: TSynMarkupHighIfDefTreeNotifications;
|
|
AHandler: TNotifyEvent);
|
|
procedure LockTree;
|
|
procedure UnLockTree;
|
|
|
|
function FindNodeAtPosition(ALine: Integer;
|
|
AMode: TSynSizedDiffAVLFindMode): TSynMarkupHighIfDefLinesNodeInfo;
|
|
overload;
|
|
|
|
function CreateOpeningList: TLazSynEditNestedFoldsList;
|
|
procedure DiscardOpeningList(AList: TLazSynEditNestedFoldsList);
|
|
|
|
procedure ValidateRange(AStartLine, AEndLine: Integer;
|
|
OuterLines: TLazSynEditNestedFoldsList);
|
|
procedure SetNodeState(ALinePos, AstartPos: Integer; AState: TSynMarkupIfdefNodeState);
|
|
|
|
property OnNodeStateRequest: TSynMarkupIfdefStateRequest read FOnNodeStateRequest write FOnNodeStateRequest;
|
|
property Highlighter: TSynPasSyn read FHighlighter write SetHighlighter;
|
|
property Lines : TSynEditStringsLinked read FLines write SetLines;
|
|
property ChangeStep: Integer read FChangeStep;
|
|
end;
|
|
|
|
{ TSynEditMarkupIfDefBase }
|
|
|
|
TSynEditMarkupIfDefBase = class(TSynEditMarkupHighlightMatches)
|
|
private
|
|
FScanLastMatchIdx: Integer;
|
|
protected
|
|
function CreateMatchList: TSynMarkupHighAllMatchList; override;
|
|
function HasEnabledMarkup: Boolean; virtual;
|
|
|
|
procedure StartMatchScan;
|
|
procedure EndMatchScan(ALastLine: Integer);
|
|
procedure AddMatch(P1, P2: TPoint; P1AtTop, P2AtBottom: Boolean; AnID: Integer);
|
|
public
|
|
constructor Create(ASynEdit: TSynEditBase);
|
|
function RealEnabled: Boolean; override;
|
|
end;
|
|
|
|
{ TSynEditMarkupIfDefNodes }
|
|
|
|
TSynEditMarkupIfDefNodes = class(TSynEditMarkupIfDefBase)
|
|
private
|
|
FMarkupInfoEnabled: TSynSelectedColor;
|
|
FMarkupInfoTempDisabled: TSynSelectedColor;
|
|
FMarkupInfoTempEnabled: TSynSelectedColor;
|
|
function GetMarkupInfo: TSynSelectedColor;
|
|
protected
|
|
function MarkupIdForMatch(Idx: Integer): Integer; override;
|
|
function HasEnabledMarkup: Boolean; override;
|
|
public
|
|
constructor Create(ASynEdit: TSynEditBase);
|
|
destructor Destroy; override;
|
|
procedure MergeMarkupAttributeAtRowCol(const aRow: Integer; const aStartCol,
|
|
AEndCol: TLazSynDisplayTokenBound; const AnRtlInfo: TLazSynDisplayRtlInfo;
|
|
AMarkup: TSynSelectedColorMergeResult); override;
|
|
|
|
property MarkupInfoEnabled: TSynSelectedColor read FMarkupInfoEnabled;
|
|
property MarkupInfoDisabled: TSynSelectedColor read GetMarkupInfo; // alias for MarkupInfo
|
|
property MarkupInfoTempEnabled: TSynSelectedColor read FMarkupInfoTempEnabled;
|
|
property MarkupInfoTempDisabled: TSynSelectedColor read FMarkupInfoTempDisabled;
|
|
end;
|
|
|
|
{ TSynEditMarkupIfDef }
|
|
|
|
TSynEditMarkupIfDef = class(TSynEditMarkupIfDefBase)
|
|
private
|
|
FFoldView: TSynEditFoldedView;
|
|
FHighlighter: TSynPasSyn;
|
|
FIfDefTree: TSynMarkupHighIfDefLinesTree;
|
|
FOnNodeStateRequest: TSynMarkupIfdefStateRequest;
|
|
FOuterLines: TLazSynEditNestedFoldsList;
|
|
FAdjustedTop: Integer;
|
|
FLastValidTopLine, FLastValidLastLine: Integer;
|
|
FLastValidTreeStep: Integer;
|
|
FMarkOnlyOpeningNodes: Boolean;
|
|
|
|
FMarkupNodes: TSynEditMarkupIfDefNodes;
|
|
FMarkupEnabled: TSynEditMarkupIfDefBase;
|
|
FMarkupTemp, FMarkupEnabledTemp: TSynEditMarkupIfDefBase;
|
|
|
|
function GetMarkupInfoDisabled: TSynSelectedColor;
|
|
function GetMarkupInfoEnabled: TSynSelectedColor;
|
|
function GetMarkupInfoNodeDisabled: TSynSelectedColor;
|
|
function GetMarkupInfoNodeEnabled: TSynSelectedColor;
|
|
function GetMarkupInfoTempDisabled: TSynSelectedColor;
|
|
function GetMarkupInfoTempEnabled: TSynSelectedColor;
|
|
function GetMarkupInfoTempNodeDisabled: TSynSelectedColor;
|
|
function GetMarkupInfoTempNodeEnabled: TSynSelectedColor;
|
|
procedure SetFoldView(AValue: TSynEditFoldedView);
|
|
procedure SetHighlighter(AValue: TSynPasSyn);
|
|
procedure DoBufferChanging(Sender: TObject);
|
|
procedure DoBufferChanged(Sender: TObject);
|
|
function DoNodeStateRequest(Sender: TObject; LinePos, XStartPos: Integer;
|
|
CurrentState: TSynMarkupIfdefNodeStateEx): TSynMarkupIfdefNodeState;
|
|
procedure SetMarkOnlyOpeningNodes(AValue: Boolean);
|
|
|
|
Procedure ValidateMatches;
|
|
procedure DoTreeUnlocking(Sender: TObject);
|
|
procedure DoTreeUnlocked(Sender: TObject);
|
|
procedure DoTreeChanged(Sender: TObject);
|
|
procedure PrepareHighlighter;
|
|
protected
|
|
function HasEnabledMarkup: Boolean; override;
|
|
|
|
procedure DoFoldChanged(aLine: Integer);
|
|
procedure DoTopLineChanged(OldTopLine : Integer); override;
|
|
procedure DoLinesInWindoChanged(OldLinesInWindow : Integer); override;
|
|
procedure DoMarkupChanged(AMarkup: TSynSelectedColor); override;
|
|
procedure DoTextChanged(StartLine, EndLine, ACountDiff: Integer); override; // 1 based
|
|
procedure DoVisibleChanged(AVisible: Boolean); override;
|
|
procedure SetLines(const AValue: TSynEditStringsLinked); override;
|
|
procedure SetInvalidateLinesMethod(const AValue: TInvalidateLines); override;
|
|
property IfDefTree: TSynMarkupHighIfDefLinesTree read FIfDefTree;
|
|
public
|
|
constructor Create(ASynEdit : TSynEditBase);
|
|
destructor Destroy; override;
|
|
procedure IncPaintLock; override;
|
|
procedure DecPaintLock; override;
|
|
|
|
procedure PrepareMarkupForRow(aRow: Integer); override;
|
|
procedure FinishMarkupForRow(aRow: Integer); override;
|
|
procedure EndMarkup; override;
|
|
//GetMarkupAttributeAtRowCol
|
|
procedure GetNextMarkupColAfterRowCol(const aRow: Integer;
|
|
const aStartCol: TLazSynDisplayTokenBound; const AnRtlInfo: TLazSynDisplayRtlInfo; out
|
|
ANextPhys, ANextLog: Integer); override;
|
|
procedure MergeMarkupAttributeAtRowCol(const aRow: Integer; const aStartCol,
|
|
AEndCol: TLazSynDisplayTokenBound; const AnRtlInfo: TLazSynDisplayRtlInfo;
|
|
AMarkup: TSynSelectedColorMergeResult); override;
|
|
|
|
procedure InvalidateAll;
|
|
procedure SetNodeState(ALinePos, AstartPos: Integer; AState: TSynMarkupIfdefNodeState);
|
|
|
|
property FoldView: TSynEditFoldedView read FFoldView write SetFoldView;
|
|
property Highlighter: TSynPasSyn read FHighlighter write SetHighlighter;
|
|
property OnNodeStateRequest: TSynMarkupIfdefStateRequest read FOnNodeStateRequest write FOnNodeStateRequest;
|
|
|
|
property MarkOnlyOpeningNodes: Boolean read FMarkOnlyOpeningNodes write SetMarkOnlyOpeningNodes;
|
|
|
|
property MarkupInfoDisabled: TSynSelectedColor read GetMarkupInfoDisabled; // alias for MarkupInfo
|
|
property MarkupInfoEnabled: TSynSelectedColor read GetMarkupInfoEnabled;
|
|
property MarkupInfoNodeDisabled: TSynSelectedColor read GetMarkupInfoNodeDisabled;
|
|
property MarkupInfoNodeEnabled: TSynSelectedColor read GetMarkupInfoNodeEnabled;
|
|
|
|
property MarkupInfoTempDisabled: TSynSelectedColor read GetMarkupInfoTempDisabled;
|
|
property MarkupInfoTempEnabled: TSynSelectedColor read GetMarkupInfoTempEnabled;
|
|
property MarkupInfoTempNodeDisabled: TSynSelectedColor read GetMarkupInfoTempNodeDisabled;
|
|
property MarkupInfoTempNodeEnabled: TSynSelectedColor read GetMarkupInfoTempNodeEnabled;
|
|
end;
|
|
|
|
function dbgs(AFlag: SynMarkupIfDefLineFlag): String; overload;
|
|
function dbgs(AFlags: SynMarkupIfDefLineFlags): String; overload;
|
|
function dbgs(AFlag: TSynMarkupIfDefNodeFlag): String; overload;
|
|
function dbgs(AFlags: SynMarkupIfDefNodeFlags): String; overload;
|
|
function dbgs(AFlag: TSynMarkupIfdefNodeType): String; overload;
|
|
function dbgs(AFlag: TSynMarkupIfdefNodeStateEx): String; overload;
|
|
function dbgs(APeerType: TSynMarkupIfdefPeerType): String; overload;
|
|
|
|
implementation
|
|
|
|
var
|
|
TheDict: TSynRefCountedDict = nil;
|
|
{$IFDEF WITH_SYN_DEBUG_MARKUP_IFDEF}
|
|
DebugCurTreeForAssert: TSynMarkupHighIfDefLinesTree = nil;
|
|
{$ENDIF}
|
|
|
|
type
|
|
|
|
{ TSynRefCountedDictIfDef }
|
|
|
|
TSynRefCountedDictIfDef = class(TSynRefCountedDict)
|
|
public
|
|
destructor Destroy; override;
|
|
end;
|
|
|
|
const
|
|
MARKUP_DEFAULT = 0;
|
|
// Nodes, bitmask
|
|
MARKUP_DISABLED = 1;
|
|
MARKUP_ENABLED = 2;
|
|
MARKUP_TEMP_DISABLED = 4;
|
|
MARKUP_TEMP_ENABLED = 8;
|
|
|
|
procedure MaybeCreateDict;
|
|
begin
|
|
if TheDict = nil then
|
|
TheDict := TSynRefCountedDictIfDef.Create;
|
|
end;
|
|
|
|
function dbgs(AFlag: SynMarkupIfDefLineFlag): String;
|
|
begin
|
|
Result := '';
|
|
WriteStr(Result, AFlag);
|
|
end;
|
|
|
|
function dbgs(AFlags: SynMarkupIfDefLineFlags): String;
|
|
var
|
|
i: SynMarkupIfDefLineFlag;
|
|
begin
|
|
Result := '';
|
|
for i := low(AFlags) to high(AFlags) do
|
|
if i in AFlags then
|
|
if Result = '' then
|
|
Result := Result + dbgs(i)
|
|
else
|
|
Result := Result + ', ' + dbgs(i);
|
|
Result := '[' + Result + ']';
|
|
end;
|
|
|
|
function dbgs(AFlag: TSynMarkupIfDefNodeFlag): String;
|
|
begin
|
|
Result := '';
|
|
WriteStr(Result, AFlag);
|
|
end;
|
|
|
|
function dbgs(AFlags: SynMarkupIfDefNodeFlags): String;
|
|
var
|
|
i: TSynMarkupIfDefNodeFlag;
|
|
begin
|
|
Result := '';
|
|
for i := low(AFlags) to high(AFlags) do
|
|
if i in AFlags then
|
|
if Result = '' then
|
|
Result := Result + dbgs(i)
|
|
else
|
|
Result := Result + ', ' + dbgs(i);
|
|
Result := '[' + Result + ']';
|
|
end;
|
|
|
|
function dbgs(AFlag: TSynMarkupIfdefNodeType): String;
|
|
begin
|
|
Result := '';
|
|
WriteStr(Result, AFlag);
|
|
end;
|
|
|
|
function dbgs(AFlag: TSynMarkupIfdefNodeStateEx): String;
|
|
begin
|
|
Result := '';
|
|
WriteStr(Result, AFlag);
|
|
end;
|
|
|
|
function dbgs(APeerType: TSynMarkupIfdefPeerType): String;
|
|
begin
|
|
Result := '';
|
|
WriteStr(Result, APeerType);
|
|
end;
|
|
|
|
{ TSynEditMarkupIfDefNodes }
|
|
|
|
function TSynEditMarkupIfDefNodes.GetMarkupInfo: TSynSelectedColor;
|
|
begin
|
|
Result := MarkupInfo;
|
|
end;
|
|
|
|
function TSynEditMarkupIfDefNodes.MarkupIdForMatch(Idx: Integer): Integer;
|
|
begin
|
|
Result := TSynMarkupHighAllMultiMatchList(Matches).MarkupId[Idx];
|
|
end;
|
|
|
|
function TSynEditMarkupIfDefNodes.HasEnabledMarkup: Boolean;
|
|
begin
|
|
Result := (inherited HasEnabledMarkup) or
|
|
FMarkupInfoEnabled.IsEnabled or
|
|
FMarkupInfoTempDisabled.IsEnabled or
|
|
FMarkupInfoTempEnabled.IsEnabled;
|
|
end;
|
|
|
|
constructor TSynEditMarkupIfDefNodes.Create(ASynEdit: TSynEditBase);
|
|
begin
|
|
inherited Create(ASynEdit);
|
|
|
|
FMarkupInfoEnabled := TSynSelectedColor.Create;
|
|
FMarkupInfoTempDisabled := TSynSelectedColor.Create;
|
|
FMarkupInfoTempEnabled := TSynSelectedColor.Create;
|
|
|
|
MarkupInfoDisabled.Clear;
|
|
MarkupInfoEnabled.Clear;
|
|
MarkupInfoTempDisabled.Clear;
|
|
MarkupInfoTempEnabled.Clear;
|
|
|
|
MarkupInfoEnabled.ForePriority := 99999+1;
|
|
MarkupInfoDisabled.ForePriority := 99999+1;
|
|
MarkupInfoTempEnabled.ForePriority := 99999+1;
|
|
MarkupInfoTempDisabled.ForePriority := 99999+1;
|
|
|
|
MarkupInfoDisabled.OnChange := @MarkupChanged;
|
|
MarkupInfoEnabled.OnChange := @MarkupChanged;
|
|
MarkupInfoTempEnabled.OnChange := @MarkupChanged;
|
|
MarkupInfoTempDisabled.OnChange := @MarkupChanged;
|
|
end;
|
|
|
|
destructor TSynEditMarkupIfDefNodes.Destroy;
|
|
begin
|
|
inherited Destroy;
|
|
FreeThenNil(FMarkupInfoEnabled);
|
|
FreeThenNil(FMarkupInfoTempDisabled);
|
|
FreeThenNil(FMarkupInfoTempEnabled);
|
|
end;
|
|
|
|
procedure TSynEditMarkupIfDefNodes.MergeMarkupAttributeAtRowCol(const aRow: Integer;
|
|
const aStartCol, AEndCol: TLazSynDisplayTokenBound; const AnRtlInfo: TLazSynDisplayRtlInfo;
|
|
AMarkup: TSynSelectedColorMergeResult);
|
|
var
|
|
i, s, e: Integer;
|
|
c: TSynSelectedColor;
|
|
begin
|
|
i := GetMarkupAttrIdAtRowCol(aRow, aStartCol, s, e);
|
|
if i < 0 then
|
|
exit;
|
|
|
|
if (i and MARKUP_ENABLED) <> 0 then begin
|
|
c := MarkupInfoEnabled;
|
|
c.SetFrameBoundsLog(s, e);
|
|
AMarkup.Merge(c, aStartCol, AEndCol);
|
|
end;
|
|
if (i and MARKUP_DISABLED) <> 0 then begin
|
|
c := MarkupInfoDisabled;
|
|
c.SetFrameBoundsLog(s, e);
|
|
AMarkup.Merge(c, aStartCol, AEndCol);
|
|
end;
|
|
|
|
if (i and MARKUP_TEMP_ENABLED) <> 0 then begin
|
|
c := MarkupInfoTempEnabled;
|
|
c.SetFrameBoundsLog(s, e);
|
|
AMarkup.Merge(c, aStartCol, AEndCol);
|
|
end;
|
|
if (i and MARKUP_TEMP_DISABLED) <> 0 then begin
|
|
c := MarkupInfoTempDisabled;
|
|
c.SetFrameBoundsLog(s, e);
|
|
AMarkup.Merge(c, aStartCol, AEndCol);
|
|
end;
|
|
end;
|
|
|
|
{ TSynEditMarkupIfDefBase }
|
|
|
|
function TSynEditMarkupIfDefBase.HasEnabledMarkup: Boolean;
|
|
begin
|
|
Result := MarkupInfo.IsEnabled;
|
|
end;
|
|
|
|
function TSynEditMarkupIfDefBase.CreateMatchList: TSynMarkupHighAllMatchList;
|
|
begin
|
|
Result := TSynMarkupHighAllMultiMatchList.Create;
|
|
end;
|
|
|
|
procedure TSynEditMarkupIfDefBase.StartMatchScan;
|
|
begin
|
|
FScanLastMatchIdx := -1;
|
|
end;
|
|
|
|
procedure TSynEditMarkupIfDefBase.EndMatchScan(ALastLine: Integer);
|
|
var
|
|
m: TSynMarkupHighAllMatch;
|
|
begin
|
|
while Matches.Count - 1 > FScanLastMatchIdx do begin
|
|
m := Matches.Match[Matches.Count - 1];
|
|
if (m.EndPoint.y >= TopLine) and (m.StartPoint.y <= ALastLine) then
|
|
InvalidateSynLines(m.StartPoint.y, m.EndPoint.y);
|
|
Matches.Delete(Matches.Count - 1);
|
|
end;
|
|
end;
|
|
|
|
procedure TSynEditMarkupIfDefBase.AddMatch(P1, P2: TPoint; P1AtTop, P2AtBottom: Boolean;
|
|
AnID: Integer);
|
|
var
|
|
Match, OldMatch: TSynMarkupHighAllMatch;
|
|
MList: TSynMarkupHighAllMultiMatchList;
|
|
begin
|
|
if ComparePoints(P1, P2) = 0 then // empty match does not highlight
|
|
exit;
|
|
//if not MarkupInfoForMatch(AnID).IsEnabled then exit;
|
|
|
|
Match.StartPoint := P1;
|
|
Match.EndPoint := P2;
|
|
|
|
MList := Matches as TSynMarkupHighAllMultiMatchList;
|
|
inc(FScanLastMatchIdx);
|
|
if FScanLastMatchIdx >= MList.Count then begin
|
|
MList.Match[FScanLastMatchIdx] := Match;
|
|
MList.MarkupId[FScanLastMatchIdx] := AnID;
|
|
InvalidateSynLines(Match.StartPoint.y, Match.EndPoint.y);
|
|
exit;
|
|
end;
|
|
|
|
OldMatch := MList.Match[FScanLastMatchIdx];
|
|
if ( (ComparePoints(OldMatch.StartPoint, Match.StartPoint) = 0) or
|
|
(P1AtTop and (ComparePoints(OldMatch.StartPoint, Match.StartPoint) <= 0))
|
|
) and
|
|
( (ComparePoints(OldMatch.EndPoint, Match.EndPoint) = 0) or
|
|
(P2AtBottom and (ComparePoints(OldMatch.EndPoint, Match.EndPoint) >= 0))
|
|
)
|
|
then begin // existing match found
|
|
if MList.MarkupId[FScanLastMatchIdx] <> AnID then begin
|
|
InvalidateSynLines(OldMatch.StartPoint.y, OldMatch.EndPoint.y);;
|
|
MList.MarkupId[FScanLastMatchIdx] := AnID;
|
|
end;
|
|
exit;
|
|
end;
|
|
|
|
if ComparePoints(OldMatch.StartPoint, Match.EndPoint) >= 0 then
|
|
MList.Insert(FScanLastMatchIdx, 1)
|
|
else
|
|
InvalidateSynLines(OldMatch.StartPoint.y, OldMatch.EndPoint.y);;
|
|
|
|
MList.Match[FScanLastMatchIdx] := Match;
|
|
MList.MarkupId[FScanLastMatchIdx] := AnID;
|
|
InvalidateSynLines(Match.StartPoint.y, Match.EndPoint.y);
|
|
end;
|
|
|
|
constructor TSynEditMarkupIfDefBase.Create(ASynEdit: TSynEditBase);
|
|
begin
|
|
inherited Create(ASynEdit);
|
|
|
|
MarkupInfo.Clear;
|
|
MarkupInfo.ForePriority := 99999;
|
|
end;
|
|
|
|
function TSynEditMarkupIfDefBase.RealEnabled: Boolean;
|
|
begin
|
|
Result := Enabled and (not IsTempDisabled) and HasEnabledMarkup;
|
|
end;
|
|
|
|
{ TSynRefCountedDictIfDef }
|
|
|
|
destructor TSynRefCountedDictIfDef.Destroy;
|
|
begin
|
|
inherited Destroy;
|
|
TheDict := nil;
|
|
end;
|
|
|
|
{ TSynMarkupHighIfDefLinesNodeInfoList }
|
|
|
|
function TSynMarkupHighIfDefLinesNodeInfoList.GetCapacity: Integer;
|
|
begin
|
|
Result := Length(FNestOpenNodes);
|
|
end;
|
|
|
|
function TSynMarkupHighIfDefLinesNodeInfoList.GetCount: Integer;
|
|
begin
|
|
if Capacity = 0 then
|
|
FCount := 0;
|
|
Result := FCount;
|
|
end;
|
|
|
|
function TSynMarkupHighIfDefLinesNodeInfoList.GetNode(AIndex: Integer): TSynMarkupHighIfDefLinesNodeInfo;
|
|
begin
|
|
Assert((AIndex < Count) and (AIndex >= 0), 'TSynMarkupHighIfDefLinesNodeInfoList.GetNode Index='+IntToStr(AIndex)+' Cnt='+IntToStr(Count));
|
|
Result := FNestOpenNodes[AIndex];
|
|
end;
|
|
|
|
procedure TSynMarkupHighIfDefLinesNodeInfoList.SetCapacity(AValue: Integer);
|
|
begin
|
|
if Capacity = 0 then
|
|
FCount := 0;
|
|
SetLength(FNestOpenNodes, AValue);
|
|
FCount := Min(FCount, AValue);
|
|
end;
|
|
|
|
procedure TSynMarkupHighIfDefLinesNodeInfoList.SetCount(AValue: Integer);
|
|
begin
|
|
if Capacity = 0 then
|
|
FCount := 0;
|
|
if Count = AValue then Exit;
|
|
Capacity := Max(FCount, Capacity);
|
|
FCount := AValue;
|
|
end;
|
|
|
|
procedure TSynMarkupHighIfDefLinesNodeInfoList.SetNode( AIndex: Integer;
|
|
AValue: TSynMarkupHighIfDefLinesNodeInfo);
|
|
begin
|
|
Assert(( AIndex < Count) and ( AIndex >= 0), 'TSynMarkupHighIfDefLinesNodeInfoList.SetNode Index='+IntToStr(AIndex)+' Cnt='+IntToStr(Count));
|
|
FNestOpenNodes[ AIndex] := AValue;
|
|
end;
|
|
|
|
procedure TSynMarkupHighIfDefLinesNodeInfoList.SetNodes(ALow, AHigh: Integer;
|
|
const AValue: TSynMarkupHighIfDefLinesNodeInfo);
|
|
var
|
|
i: Integer;
|
|
begin
|
|
if AHigh >= Count then begin
|
|
Capacity := 1 + AHigh + Min(AHigh div 2, 100);
|
|
Count := AHigh + 1;
|
|
end;
|
|
for i := ALow to AHigh do
|
|
FNestOpenNodes[i] := AValue;
|
|
end;
|
|
|
|
procedure TSynMarkupHighIfDefLinesNodeInfoList.PushNodeLine(var ANode: TSynMarkupHighIfDefLinesNodeInfo);
|
|
begin
|
|
Nodes[ANode.NestMinimumDepthAtNode+1, ANode.NestDepthAtNodeEnd] := ANode;
|
|
end;
|
|
|
|
procedure TSynMarkupHighIfDefLinesNodeInfoList.dbg;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
for i := 0 to Count-1 do begin
|
|
DbgOut(['## ',i, ': ', dbgs(Node[i].HasNode)]);
|
|
if Node[i].HasNode then DbgOut(['(', Node[i].StartLine, ')']);
|
|
end;
|
|
DebugLn();
|
|
end;
|
|
|
|
{ TSynRefCountedDict }
|
|
|
|
procedure TSynRefCountedDict.CheckWordEnd(MatchEnd: PChar; MatchIdx: Integer;
|
|
var IsMatch: Boolean; var StopSeach: Boolean);
|
|
begin
|
|
IsMatch := not ((MatchEnd)^ in ['a'..'z', 'A'..'Z', '0'..'9', '_']);
|
|
end;
|
|
|
|
constructor TSynRefCountedDict.Create;
|
|
begin
|
|
inherited Create;
|
|
FDict := TSynSearchDictionary.Create;
|
|
FDict.Add('{$if', 1);
|
|
FDict.Add('{$ifc', 1);
|
|
FDict.Add('{$ifdef', 1);
|
|
FDict.Add('{$ifndef', 1);
|
|
FDict.Add('{$ifopt', 1);
|
|
|
|
FDict.Add('{$else', 2);
|
|
FDict.Add('{$elsec', 2);
|
|
|
|
FDict.Add('{$elseif', 4);
|
|
FDict.Add('{$elifc', 4);
|
|
|
|
FDict.Add('{$endif', 3);
|
|
FDict.Add('{$ifend', 3);
|
|
FDict.Add('{$endc', 3);
|
|
|
|
end;
|
|
|
|
destructor TSynRefCountedDict.Destroy;
|
|
begin
|
|
FDict.Free;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
function TSynRefCountedDict.GetMatchAtChar(AText: PChar; ATextLen: Integer): Integer;
|
|
begin
|
|
Result := FDict.GetMatchAtChar(AText, ATextLen, @CheckWordEnd);
|
|
end;
|
|
|
|
{ TSynMarkupHighIfDefEntry }
|
|
|
|
function TSynMarkupHighIfDefEntry.NodeType: TSynMarkupIfdefNodeType;
|
|
begin
|
|
Result := FNodeType;
|
|
if idnCommented in NodeFlags then
|
|
Result := idnCommentedNode;
|
|
end;
|
|
|
|
function TSynMarkupHighIfDefEntry.UncommentedNodeType: TSynMarkupIfdefNodeType;
|
|
begin
|
|
Result := FNodeType;
|
|
end;
|
|
|
|
procedure TSynMarkupHighIfDefEntry.MakeCommented;
|
|
begin
|
|
RemoveNodeStateFromLine;
|
|
Include(FNodeFlags, idnCommented);
|
|
ApplyNodeStateToLine;
|
|
end;
|
|
|
|
procedure TSynMarkupHighIfDefEntry.MakeUnCommented;
|
|
begin
|
|
RemoveNodeStateFromLine;
|
|
Exclude(FNodeFlags, idnCommented);
|
|
ApplyNodeStateToLine;
|
|
end;
|
|
|
|
function TSynMarkupHighIfDefEntry.IsDisabled: Boolean;
|
|
begin
|
|
Result := FNodeState in [idnDisabled, idnTempDisabled];
|
|
end;
|
|
|
|
function TSynMarkupHighIfDefEntry.HasDisabledOpening: Boolean;
|
|
begin
|
|
Result := IsClosing and (FOpeningPeerNodeState in [idnDisabled, idnTempDisabled]);
|
|
end;
|
|
|
|
function TSynMarkupHighIfDefEntry.IsEnabled: Boolean;
|
|
begin
|
|
Result := FNodeState in [idnEnabled, idnTempEnabled];
|
|
end;
|
|
|
|
function TSynMarkupHighIfDefEntry.HasEnabledOpening: Boolean;
|
|
begin
|
|
Result := IsClosing and (FOpeningPeerNodeState in [idnEnabled, idnTempEnabled]);
|
|
end;
|
|
|
|
function TSynMarkupHighIfDefEntry.IsRequested: Boolean;
|
|
begin
|
|
Result := FNodeState = idnRequested;
|
|
end;
|
|
|
|
function TSynMarkupHighIfDefEntry.IsTempClosing: Boolean;
|
|
begin
|
|
Result := IsClosing and HasTempOpening;
|
|
end;
|
|
|
|
function TSynMarkupHighIfDefEntry.IsTempOpening: Boolean;
|
|
begin
|
|
Result := IsOpening and IsTemp;
|
|
end;
|
|
|
|
function TSynMarkupHighIfDefEntry.GetNeedsRequesting: Boolean;
|
|
begin
|
|
Result := ( (NodeType = idnIfdef) or
|
|
( (NodeType = idnElseIf) and (FOpeningPeerNodeState in [idnEnabled, idnTempEnabled]) )
|
|
) and
|
|
( (FNodeState in [idnUnknown, idnRequested]) or
|
|
( (FNodeState = idnNotInCode) and (idlNotInCodeToUnknown in Line.LineFlags) )
|
|
);
|
|
end;
|
|
|
|
function TSynMarkupHighIfDefEntry.GetNodeState: TSynMarkupIfdefNodeStateEx;
|
|
begin
|
|
Result := FNodeState
|
|
end;
|
|
|
|
function TSynMarkupHighIfDefEntry.GetStateByUser: Boolean;
|
|
begin
|
|
Result := idnStateByUser in FNodeFlags;
|
|
end;
|
|
|
|
procedure TSynMarkupHighIfDefEntry.SetLine(AValue: TSynMarkupHighIfDefLinesNode);
|
|
begin
|
|
if FLine = AValue then Exit;
|
|
|
|
RemoveNodeStateFromLine;
|
|
|
|
if (FNodeState = idnNotInCode) and (FLine <> nil) and
|
|
(idlNotInCodeToUnknown in FLine.LineFlags)
|
|
then
|
|
SetNodeState(idnUnknown, True);
|
|
|
|
FLine := AValue;
|
|
ApplyNodeStateToLine;
|
|
end;
|
|
|
|
procedure TSynMarkupHighIfDefEntry.SetOpeningPeerNodeState(AValueOfPeer,
|
|
AValueForNode: TSynMarkupIfdefNodeStateEx);
|
|
begin
|
|
RemoveNodeStateFromLine;
|
|
FOpeningPeerNodeState := AValueOfPeer;
|
|
|
|
// ignore invalidated by LineFlags If StateByUser. Keep old (invalid) state until set again
|
|
if (not StateByUser) or (AValueForNode = idnUnknown)
|
|
then begin
|
|
|
|
if NodeType in [idnElse, idnEndIf] then
|
|
SetNodeState(AValueForNode, True)
|
|
else
|
|
if NodeType = idnElseIf then
|
|
case AValueForNode of
|
|
idnEnabled, idnTempEnabled: SetNodeState(idnUnknown, True); // Maybe keep?
|
|
idnDisabled, idnTempDisabled: SetNodeState(AValueForNode, True);
|
|
else SetNodeState(idnUnknown, True);
|
|
end;
|
|
end;
|
|
|
|
ApplyNodeStateToLine;
|
|
end;
|
|
|
|
procedure TSynMarkupHighIfDefEntry.SetNodeState(AValue: TSynMarkupIfdefNodeStateEx);
|
|
begin
|
|
SetNodeState(AValue, False);
|
|
end;
|
|
|
|
procedure TSynMarkupHighIfDefEntry.SetNodeState(AValue: TSynMarkupIfdefNodeStateEx;
|
|
ASkipLineState: Boolean);
|
|
begin
|
|
if not ASkipLineState then
|
|
RemoveNodeStateFromLine;
|
|
FNodeState := AValue;
|
|
if not ASkipLineState then
|
|
ApplyNodeStateToLine;
|
|
|
|
if FNodeState in [idnEnabled, idnTempEnabled] then
|
|
Line.FLineFlags := Line.FLineFlags + [idlNotInCodeToUnknownReq, idlNotInCodeToUnknown];
|
|
|
|
case NodeType of
|
|
idnIfdef, idnElse, idnElseIf: begin
|
|
if (ClosingPeer <> nil) then
|
|
ClosingPeer.SetOpeningPeerNodeState(NodeState, NodeStateForPeer(ClosingPeer.NodeType))
|
|
end;
|
|
idnCommentedNode: Assert(AValue = idnUnknown, 'SetOpeningPeerNodeState for idnCommentedIfdef not possible. '+DebugText);
|
|
end;
|
|
end;
|
|
|
|
function TSynMarkupHighIfDefEntry.GetPeer(APeerType: TSynMarkupIfdefPeerType): TSynMarkupHighIfDefEntry;
|
|
begin
|
|
Result := FPeers[APeerType];
|
|
end;
|
|
|
|
procedure TSynMarkupHighIfDefEntry.SetPeer(APeerType: TSynMarkupIfdefPeerType;
|
|
ANewPeer: TSynMarkupHighIfDefEntry);
|
|
begin
|
|
assert( ((APeerType=idpOpeningPeer) and (NodeType <> idnIfdef)) OR ((APeerType=idpClosingPeer) and (NodeType <> idnEndIf)), 'Invalid peertype ('+dbgs(APeerType)+') for this node'+DebugText+' NEWNODE='+ANewPeer.DebugText(True));
|
|
assert((ANewPeer=nil) OR
|
|
((APeerType=idpOpeningPeer) and (ANewPeer.NodeType <> idnEndIf)) OR ((APeerType=idpClosingPeer) and (ANewPeer.NodeType <> idnIfdef))
|
|
, 'New peer not allowed for peertype ('+dbgs(APeerType)+') Node:'+DebugText+' NEWNODE='+ANewPeer.DebugText(True));
|
|
if FPeers[APeerType] = ANewPeer then begin
|
|
assert((ANewPeer = nil) or (ANewPeer.GetPeer(ReversePeerType[APeerType]) = self), 'Peer does not point back to self. Node:'+DebugText+' NEWNODE='+ANewPeer.DebugText(True));
|
|
assert((NodeType in [idnElse, idnElseIf]) or (FPeers[idpOpeningPeer] = nil) or (FPeers[idpClosingPeer] = nil), 'Only ELSE has 2 peers. Node:'+DebugText+' NEWNODE='+ANewPeer.DebugText(True));
|
|
exit;
|
|
end;
|
|
|
|
ClearPeerField(APeerType);
|
|
|
|
if ANewPeer = nil then begin
|
|
FPeers[APeerType] := ANewPeer;
|
|
|
|
if APeerType = idpOpeningPeer then
|
|
SetOpeningPeerNodeState(idnUnknown, idnUnknown);
|
|
end
|
|
else begin
|
|
// If new peer is part of another pair, disolve that pair. This may set FPeers[APeerType] = nil, if new pair points to this node
|
|
assert(ANewPeer.GetPeer(ReversePeerType[APeerType]) <> self, 'New peer points to this, but was not known by this / link is not bidirectional. Node:'+DebugText+' NEWNODE='+ANewPeer.DebugText(True));
|
|
ANewPeer.ClearPeerField(ReversePeerType[APeerType]);
|
|
|
|
ANewPeer.FPeers[ReversePeerType[APeerType]] := Self;
|
|
FPeers[APeerType] := ANewPeer;
|
|
|
|
if APeerType = idpClosingPeer then
|
|
ANewPeer.SetOpeningPeerNodeState(NodeState, NodeStateForPeer(ANewPeer.NodeType))
|
|
else
|
|
SetOpeningPeerNodeState(FPeers[APeerType].NodeState, FPeers[APeerType].NodeStateForPeer(NodeType));
|
|
end;
|
|
assert((NodeType in [idnElse, idnElseIf]) or (FPeers[idpOpeningPeer] = nil) or (FPeers[idpClosingPeer] = nil), 'Only ELSE has 2 peers. Node:'+DebugText+' NEWNODE='+ANewPeer.DebugText(True));
|
|
end;
|
|
|
|
procedure TSynMarkupHighIfDefEntry.MakeDisabled;
|
|
begin
|
|
NodeState := idnDisabled;
|
|
end;
|
|
|
|
procedure TSynMarkupHighIfDefEntry.MakeEnabled;
|
|
begin
|
|
NodeState := idnEnabled;
|
|
end;
|
|
|
|
procedure TSynMarkupHighIfDefEntry.MakeRequested;
|
|
begin
|
|
NodeState := idnRequested;
|
|
end;
|
|
|
|
procedure TSynMarkupHighIfDefEntry.MakeUnknown;
|
|
begin
|
|
NodeState := idnUnknown;
|
|
end;
|
|
|
|
function TSynMarkupHighIfDefEntry.IsTemp: Boolean;
|
|
begin
|
|
Result := FNodeState in [idnTempEnabled, idnTempDisabled];
|
|
end;
|
|
|
|
function TSynMarkupHighIfDefEntry.HasTempOpening: Boolean;
|
|
begin
|
|
Result := IsClosing and (FOpeningPeerNodeState in [idnTempEnabled, idnTempDisabled]);
|
|
end;
|
|
|
|
function TSynMarkupHighIfDefEntry.IsOpening: Boolean;
|
|
begin
|
|
Result := (NodeType in [idnIfdef, idnElseIf, idnElse]);
|
|
end;
|
|
|
|
function TSynMarkupHighIfDefEntry.IsClosing: Boolean;
|
|
begin
|
|
Result := (NodeType in [idnElse, idnElseIf, idnEndIf]);
|
|
end;
|
|
|
|
function TSynMarkupHighIfDefEntry.HasKnownState: Boolean;
|
|
begin
|
|
Result := FNodeState in [idnEnabled, idnDisabled, idnTempEnabled, idnTempDisabled, idnInvalid];
|
|
end;
|
|
|
|
function TSynMarkupHighIfDefEntry.IsDisabledOpening: Boolean;
|
|
begin
|
|
Result := IsOpening and IsDisabled;
|
|
end;
|
|
|
|
function TSynMarkupHighIfDefEntry.IsDisabledClosing: Boolean;
|
|
begin
|
|
Result := IsClosing and HasDisabledOpening;
|
|
end;
|
|
|
|
procedure TSynMarkupHighIfDefEntry.ClearPeerField(APeerType: TSynMarkupIfdefPeerType);
|
|
begin
|
|
if FPeers[APeerType] = nil then exit;
|
|
assert(FPeers[APeerType].GetPeer(ReversePeerType[APeerType]) = self, 'ClearPeerField('+dbgs(APeerType)+'): Peer does not point back to self. '+DebugText);
|
|
|
|
if APeerType = idpClosingPeer then
|
|
FPeers[APeerType].SetOpeningPeerNodeState(idnUnknown, idnUnknown)
|
|
else
|
|
SetOpeningPeerNodeState(idnUnknown, idnUnknown);
|
|
|
|
FPeers[APeerType].FPeers[ReversePeerType[APeerType]] := nil;
|
|
FPeers[APeerType] := nil;
|
|
end;
|
|
|
|
procedure TSynMarkupHighIfDefEntry.ApplyNodeStateToLine(ARemove: Boolean = False);
|
|
var
|
|
i: Integer;
|
|
begin
|
|
if (FLine <> nil) then begin
|
|
i := 1;
|
|
if ARemove then i := -1;
|
|
case NodeType of
|
|
idnIfdef: begin
|
|
if FNodeState in [idnDisabled, idnTempDisabled] then
|
|
FLine.DisabledEntryOpenCount := FLine.DisabledEntryOpenCount + i;
|
|
end;
|
|
idnElse, idnElseIf: begin
|
|
if FOpeningPeerNodeState in [idnDisabled, idnTempDisabled] then
|
|
FLine.DisabledEntryCloseCount := FLine.DisabledEntryCloseCount + i;
|
|
if FNodeState in [idnDisabled, idnTempDisabled] then
|
|
FLine.DisabledEntryOpenCount := FLine.DisabledEntryOpenCount + i;
|
|
end;
|
|
idnEndIf: begin
|
|
if FOpeningPeerNodeState in [idnDisabled, idnTempDisabled] then
|
|
FLine.DisabledEntryCloseCount := FLine.DisabledEntryCloseCount + i;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
if not ARemove then begin
|
|
if FNodeState = idnNotInCode then
|
|
Include(FLine.FLineFlags, idlHasNodesNotInCode)
|
|
else
|
|
if NeedsRequesting then
|
|
Include(FLine.FLineFlags, idlHasUnknownNodes);
|
|
end;
|
|
end;
|
|
|
|
procedure TSynMarkupHighIfDefEntry.RemoveNodeStateFromLine;
|
|
begin
|
|
ApplyNodeStateToLine(True);
|
|
end;
|
|
|
|
procedure TSynMarkupHighIfDefEntry.SetStartColumn(AValue: Integer);
|
|
begin
|
|
if FStartColumn = AValue then Exit;
|
|
FStartColumn := AValue;
|
|
Assert(AValue>0, 'Startcol negative'+DebugText);
|
|
end;
|
|
|
|
procedure TSynMarkupHighIfDefEntry.SetStateByUser(AValue: Boolean);
|
|
begin
|
|
if AValue then
|
|
Include(FNodeFlags, idnStateByUser)
|
|
else
|
|
Exclude(FNodeFlags, idnStateByUser);
|
|
end;
|
|
|
|
procedure TSynMarkupHighIfDefEntry.SetNodeType(
|
|
ANodeType: TSynMarkupIfdefNodeType);
|
|
begin
|
|
RemoveNodeStateFromLine;
|
|
FNodeType := ANodeType;
|
|
ApplyNodeStateToLine;
|
|
end;
|
|
|
|
function TSynMarkupHighIfDefEntry.DebugText(Short: Boolean): String;
|
|
begin
|
|
If Self = nil then
|
|
exit('NODE IS NIL');
|
|
Result := Format('Line=%s NType=%s State=%s OpenState=%s Flags=%s ' +
|
|
' StartCol=%d EndCol=%d',
|
|
[FLine.DebugText, dbgs(FNodeType), dbgs(FNodeState), dbgs(FOpeningPeerNodeState) ,
|
|
dbgs(FNodeFlags), FStartColumn, FEndColumn]
|
|
);
|
|
if Short or (FPeers[idpOpeningPeer] = nil) then
|
|
Result := Result + ' OpenPeer='+dbgs(FPeers[idpOpeningPeer])
|
|
else
|
|
Result := Result + ' OpenPeer='+FPeers[idpOpeningPeer].DebugText(True);
|
|
|
|
if Short or (FPeers[idpClosingPeer] = nil) then
|
|
Result := Result + ' ClosePeer='+dbgs(FPeers[idpClosingPeer])
|
|
else
|
|
Result := Result + ' OpenPeer='+FPeers[idpClosingPeer].DebugText(True);
|
|
end;
|
|
|
|
function TSynMarkupHighIfDefEntry.NodeStateForPeer(APeerType: TSynMarkupIfdefNodeType): TSynMarkupIfdefNodeStateEx;
|
|
const
|
|
NodeStateMap: array [Boolean] of TSynMarkupIfdefNodeStateEx =
|
|
(idnDisabled, idnEnabled); // False, True
|
|
NodeStateTempMap: array [Boolean] of TSynMarkupIfdefNodeStateEx =
|
|
(idnTempDisabled, idnTempEnabled); // False, True
|
|
begin
|
|
Result := idnUnknown;
|
|
Assert((NodeType <> APeerType) or (NodeType = idnElseIf), 'NodeStateForPeer: NodeType <> APeerType'+dbgs(APeerType)+' Node:'+DebugText);
|
|
case NodeState of
|
|
idnEnabled: begin
|
|
case NodeType of
|
|
idnIfdef: Result := NodeStateMap[APeerType = idnEndIf]; // idnElse[if] will be idnDisabled;
|
|
idnElseIf: Result := NodeStateMap[APeerType = idnEndIf]; // idnElse[if] will be idnDisabled;
|
|
idnElse: Result := NodeStateMap[APeerType = idnEndIf]; // idnIfdef will be idnDisabled;;
|
|
idnEndIf: Result := idnEnabled;
|
|
end;
|
|
end;
|
|
idnDisabled: begin
|
|
case NodeType of
|
|
idnIfdef: Result := NodeStateMap[APeerType <> idnEndIf];
|
|
idnElseIf: Result := NodeStateMap[APeerType <> idnEndIf];
|
|
idnElse: Result := NodeStateMap[APeerType <> idnEndIf];
|
|
idnEndIf: Result := idnDisabled;
|
|
end;
|
|
end;
|
|
idnTempEnabled: begin
|
|
case NodeType of
|
|
idnIfdef: Result := NodeStateTempMap[APeerType = idnEndIf]; // idnElse[if] will be idnDisabled;
|
|
idnElseIf: Result := NodeStateTempMap[APeerType = idnEndIf]; // idnElse[if] will be idnDisabled;
|
|
idnElse: Result := NodeStateTempMap[APeerType = idnEndIf]; // idnIfdef will be idnDisabled;;
|
|
idnEndIf: Result := idnTempEnabled;
|
|
end;
|
|
end;
|
|
idnTempDisabled: begin
|
|
case NodeType of
|
|
idnIfdef: Result := NodeStateTempMap[APeerType <> idnEndIf];
|
|
idnElseIf: Result := NodeStateTempMap[APeerType <> idnEndIf];
|
|
idnElse: Result := NodeStateTempMap[APeerType <> idnEndIf];
|
|
idnEndIf: Result := idnTempDisabled;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
constructor TSynMarkupHighIfDefEntry.Create;
|
|
begin
|
|
FNodeState := idnUnknown;
|
|
FNodeFlags := [];
|
|
end;
|
|
|
|
destructor TSynMarkupHighIfDefEntry.Destroy;
|
|
begin
|
|
if (FLine <> nil) and not(idlInGlobalClear in FLine.LineFlags) then begin
|
|
NodeState := idnUnknown; // RemoveNodeStateFromLine;
|
|
ClearPeers;
|
|
end;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TSynMarkupHighIfDefEntry.ClearPeers;
|
|
begin
|
|
ClearPeerField(idpOpeningPeer);
|
|
ClearPeerField(idpClosingPeer);
|
|
end;
|
|
|
|
procedure TSynMarkupHighIfDefEntry.ClearAll;
|
|
begin
|
|
ClearPeers;
|
|
RemoveNodeStateFromLine;
|
|
FNodeFlags := [];
|
|
ApplyNodeStateToLine;
|
|
end;
|
|
|
|
{ TSynMarkupHighIfDefLinesNode }
|
|
|
|
function TSynMarkupHighIfDefLinesNode.GetEntry(AIndex: Integer): TSynMarkupHighIfDefEntry;
|
|
begin
|
|
Result := FEntries[AIndex];
|
|
end;
|
|
|
|
function TSynMarkupHighIfDefLinesNode.GetEntryCapacity: Integer;
|
|
begin
|
|
Result := Length(FEntries);
|
|
end;
|
|
|
|
function TSynMarkupHighIfDefLinesNode.GetId: PtrUInt;
|
|
begin
|
|
Result := PtrUInt(Self);
|
|
end;
|
|
|
|
procedure TSynMarkupHighIfDefLinesNode.SetEntry(AIndex: Integer;
|
|
AValue: TSynMarkupHighIfDefEntry);
|
|
begin
|
|
FEntries[AIndex] := AValue;
|
|
if AValue <> nil then
|
|
AValue.Line := Self;
|
|
end;
|
|
|
|
procedure TSynMarkupHighIfDefLinesNode.SetEntryCapacity(AValue: Integer);
|
|
var
|
|
i, j: Integer;
|
|
begin
|
|
j := Length(FEntries);
|
|
if j = AValue then Exit;
|
|
for i := j - 1 downto AValue do
|
|
FreeAndNil(FEntries[i]);
|
|
SetLength(FEntries, AValue);
|
|
for i := j to AValue - 1 do
|
|
FEntries[i] := nil;
|
|
end;
|
|
|
|
procedure TSynMarkupHighIfDefLinesNode.SetEntryCount(AValue: Integer);
|
|
begin
|
|
if FEntryCount = AValue then Exit;
|
|
FEntryCount := AValue;
|
|
if EntryCapacity < FEntryCount then
|
|
EntryCapacity := FEntryCount;
|
|
end;
|
|
|
|
procedure TSynMarkupHighIfDefLinesNode.AdjustPositionOffset(AnAdjustment: integer);
|
|
begin
|
|
Assert((Successor = nil) or (GetPosition + AnAdjustment < Successor.GetPosition), 'GetPosition + AnAdjustment < Successor.GetPosition '+DebugText);
|
|
Assert((Precessor = nil) or (GetPosition + AnAdjustment > Precessor.GetPosition), 'GetPosition + AnAdjustment > Precessor.GetPosition '+DebugText);
|
|
FPositionOffset := FPositionOffset + AnAdjustment;
|
|
if FLeft <> nil then
|
|
TSynMarkupHighIfDefLinesNode(FLeft).FPositionOffset :=
|
|
TSynMarkupHighIfDefLinesNode(FLeft).FPositionOffset - AnAdjustment;
|
|
if FRight <> nil then
|
|
TSynMarkupHighIfDefLinesNode(FRight).FPositionOffset :=
|
|
TSynMarkupHighIfDefLinesNode(FRight).FPositionOffset - AnAdjustment;
|
|
end;
|
|
|
|
function TSynMarkupHighIfDefLinesNode.DebugText: String;
|
|
begin
|
|
if self = nil then
|
|
exit('NODE IN NIL');
|
|
Result := Format('Pos=%d Flags=%s ECnt=%d LastEOffs=%d ScanEndOffs=%d ' +
|
|
' DisEOpen=%d DisEClose=%d',
|
|
[GetPosition, dbgs(FLineFlags), FEntryCount, FLastEntryEndLineOffs,
|
|
FScanEndOffs, FDisabledEntryOpenCount, FDisabledEntryCloseCount
|
|
]);
|
|
end;
|
|
|
|
constructor TSynMarkupHighIfDefLinesNode.Create;
|
|
begin
|
|
FSize := 1; // used for index
|
|
FScanEndOffs := 0;
|
|
end;
|
|
|
|
destructor TSynMarkupHighIfDefLinesNode.Destroy;
|
|
begin
|
|
inherited Destroy;
|
|
MakeDisposed;
|
|
end;
|
|
|
|
procedure TSynMarkupHighIfDefLinesNode.MakeDisposed; // Also called to clear, on real destroy
|
|
begin
|
|
FLineFlags := [idlDisposed] + FLineFlags * [idlInGlobalClear];
|
|
while EntryCount > 0 do
|
|
DeletEntry(EntryCount-1, True);
|
|
assert((idlInGlobalClear in LineFlags) or ((FDisabledEntryOpenCount =0) and (FDisabledEntryCloseCount = 0)), 'no close count left over'+DebugText);
|
|
FDisabledEntryOpenCount := 0;
|
|
FDisabledEntryCloseCount := 0;
|
|
end;
|
|
|
|
function TSynMarkupHighIfDefLinesNode.AddEntry(AIndex: Integer): TSynMarkupHighIfDefEntry;
|
|
var
|
|
c: Integer;
|
|
begin
|
|
c := EntryCount;
|
|
EntryCount := c + 1;
|
|
assert(FEntries[c]=nil, 'FEntries[c]=nil Aindex='+IntToStr(AIndex)+' '+DebugText);
|
|
Result := TSynMarkupHighIfDefEntry.Create;
|
|
Result.Line := Self;
|
|
if (AIndex >= 0) then begin
|
|
Assert(AIndex <= c, 'Add node index ('+IntToStr(AIndex)+') <= count c='+IntToStr(c)+' '+DebugText);
|
|
while c > AIndex do begin
|
|
FEntries[c] := FEntries[c - 1];
|
|
dec(c);
|
|
end;
|
|
end;
|
|
FEntries[c] := Result;
|
|
end;
|
|
|
|
procedure TSynMarkupHighIfDefLinesNode.DeletEntry(AIndex: Integer; AFree: Boolean);
|
|
begin
|
|
Assert((AIndex >= 0) and (AIndex < FEntryCount), 'DeletEntry Aindex='+IntToStr(AIndex)+' '+DebugText);
|
|
if AFree then
|
|
FEntries[AIndex].Free
|
|
else
|
|
FEntries[AIndex] := nil;
|
|
while AIndex < FEntryCount - 1 do begin
|
|
FEntries[AIndex] := FEntries[AIndex + 1];
|
|
inc(AIndex);
|
|
end;
|
|
FEntries[AIndex] := nil;
|
|
dec(FEntryCount);
|
|
end;
|
|
|
|
procedure TSynMarkupHighIfDefLinesNode.ReduceCapacity;
|
|
begin
|
|
EntryCapacity := EntryCount;
|
|
end;
|
|
|
|
function TSynMarkupHighIfDefLinesNode.IndexOf(AEntry: TSynMarkupHighIfDefEntry): Integer;
|
|
begin
|
|
Result := EntryCount - 1;
|
|
while (Result >= 0) and (Entry[Result] <> AEntry) do
|
|
dec(Result);
|
|
end;
|
|
|
|
{ TSynMarkupHighIfDefLinesNodeInfo }
|
|
|
|
procedure TSynMarkupHighIfDefLinesNodeInfo.SetStartLine(AValue: Integer);
|
|
begin
|
|
Assert(FNode <> nil, 'TSynMarkupHighIfDefLinesNodeInfo.SetStartLine has node '+DebugText);
|
|
if FStartLine = AValue then Exit;
|
|
FNode.AdjustPositionOffset(AValue - FStartLine);
|
|
FStartLine := AValue;
|
|
end;
|
|
|
|
function TSynMarkupHighIfDefLinesNodeInfo.DebugText: String;
|
|
begin
|
|
Result := ' Startline='+IntToStr(FStartLine);
|
|
if FNode <> nil then
|
|
Result := Result + ' '+FNode.DebugText
|
|
else
|
|
Result := Result + ' Node is nil';
|
|
end;
|
|
|
|
function TSynMarkupHighIfDefLinesNodeInfo.GetLineFlags: SynMarkupIfDefLineFlags;
|
|
begin
|
|
if not HasNode then
|
|
exit([]);
|
|
Result := FNode.LineFlags;
|
|
end;
|
|
|
|
function TSynMarkupHighIfDefLinesNodeInfo.GetLastEntryEndLineOffs: Integer;
|
|
begin
|
|
if not HasNode then
|
|
exit(0);
|
|
Result := FNode.LastEntryEndLineOffs;
|
|
end;
|
|
|
|
function TSynMarkupHighIfDefLinesNodeInfo.GetLastEntryEndLine: Integer;
|
|
begin
|
|
if not HasNode then
|
|
exit(0);
|
|
Result := StartLine + FNode.LastEntryEndLineOffs;
|
|
end;
|
|
|
|
function TSynMarkupHighIfDefLinesNodeInfo.GetEntry(AIndex: Integer): TSynMarkupHighIfDefEntry;
|
|
begin
|
|
Assert(HasNode, 'HasNode for TSynMarkupHighIfDefLinesNodeInfo.GetEntry'+DebugText);
|
|
Result := FNode.Entry[AIndex];
|
|
end;
|
|
|
|
function TSynMarkupHighIfDefLinesNodeInfo.GetEntryCount: Integer;
|
|
begin
|
|
if not HasNode then
|
|
exit(0);
|
|
Result := FNode.EntryCount;
|
|
end;
|
|
|
|
function TSynMarkupHighIfDefLinesNodeInfo.GetScanEndLine: Integer;
|
|
begin
|
|
if not IsValid then
|
|
exit(StartLine);
|
|
if ScanEndOffs >= 0 then
|
|
Result := StartLine + ScanEndOffs
|
|
else
|
|
Result := StartLine;
|
|
end;
|
|
|
|
function TSynMarkupHighIfDefLinesNodeInfo.GetScanEndOffs: Integer;
|
|
begin
|
|
if not HasNode then
|
|
exit(0);
|
|
Result := FNode.ScanEndOffs;
|
|
end;
|
|
|
|
procedure TSynMarkupHighIfDefLinesNodeInfo.SetEntry(AIndex: Integer;
|
|
AValue: TSynMarkupHighIfDefEntry);
|
|
begin
|
|
Assert(HasNode, 'HasNode for TSynMarkupHighIfDefLinesNodeInfo.SetEntry'+DebugText);
|
|
FNode.Entry[AIndex] := AValue;
|
|
end;
|
|
|
|
procedure TSynMarkupHighIfDefLinesNodeInfo.SetEntryCount(AValue: Integer);
|
|
begin
|
|
Assert(HasNode, 'HasNode for TSynMarkupHighIfDefLinesNodeInfo.SetEntryCount'+DebugText);
|
|
FNode.EntryCount := AValue;
|
|
end;
|
|
|
|
procedure TSynMarkupHighIfDefLinesNodeInfo.SetLastEntryEndLineOffs(AValue: Integer);
|
|
begin
|
|
Assert(HasNode, 'HasNode for TSynMarkupHighIfDefLinesNodeInfo.SetEndLineOffs'+DebugText);
|
|
FNode.LastEntryEndLineOffs := AValue;
|
|
end;
|
|
|
|
procedure TSynMarkupHighIfDefLinesNodeInfo.SetScanEndLine(AValue: Integer);
|
|
begin
|
|
Assert(HasNode, 'HasNode for TSynMarkupHighIfDefLinesNodeInfo.SetScanEndLine'+DebugText);
|
|
ScanEndOffs := AValue - StartLine;
|
|
end;
|
|
|
|
procedure TSynMarkupHighIfDefLinesNodeInfo.SetScanEndOffs(AValue: Integer);
|
|
begin
|
|
Assert(HasNode, 'HasNode for TSynMarkupHighIfDefLinesNodeInfo.SetScanEndOffs'+DebugText);
|
|
FNode.ScanEndOffs := AValue;
|
|
end;
|
|
|
|
procedure TSynMarkupHighIfDefLinesNodeInfo.ClearInfo;
|
|
begin
|
|
FStartLine := 0;
|
|
Index := 0;
|
|
FNode := nil;
|
|
FAtBOL := False;
|
|
FAtEOL := False;
|
|
ClearNestCache;
|
|
end;
|
|
|
|
procedure TSynMarkupHighIfDefLinesNodeInfo.InitForNode(ANode: TSynMarkupHighIfDefLinesNode;
|
|
ALine: Integer);
|
|
begin
|
|
ClearInfo;
|
|
FNode := ANode;
|
|
FStartLine := ALine;
|
|
end;
|
|
|
|
function TSynMarkupHighIfDefLinesNodeInfo.Precessor: TSynMarkupHighIfDefLinesNodeInfo;
|
|
begin
|
|
ClearNestCache;
|
|
Result.ClearNestCache;
|
|
Result.FTree := FTree;
|
|
If HasNode then begin
|
|
Result.FStartLine := FStartLine;
|
|
Result.Index := Index;
|
|
Result.FNode := TSynMarkupHighIfDefLinesNode(FNode.Precessor(Result.FStartLine, Result.Index));
|
|
Result.FAtBOL := not Result.HasNode;
|
|
Result.FAtEOL := False;
|
|
end
|
|
else
|
|
if AtEOL then begin
|
|
Result.FNode := TSynMarkupHighIfDefLinesNode(FTree.Last(Result.FStartLine, Result.Index));
|
|
Result.FAtBOL := not Result.HasNode;
|
|
Result.FAtEOL := False;
|
|
end
|
|
else begin
|
|
Result.ClearInfo;
|
|
end;
|
|
end;
|
|
|
|
function TSynMarkupHighIfDefLinesNodeInfo.Successor: TSynMarkupHighIfDefLinesNodeInfo;
|
|
begin
|
|
ClearNestCache;
|
|
Result.ClearNestCache;
|
|
Result.FTree := FTree;
|
|
If HasNode then begin
|
|
Result.FStartLine := FStartLine;
|
|
Result.Index := Index;
|
|
Result.FNode := TSynMarkupHighIfDefLinesNode(FNode.Successor(Result.FStartLine, Result.Index));
|
|
Result.FAtBOL := False;
|
|
Result.FAtEOL := not Result.HasNode;
|
|
end
|
|
else
|
|
if FAtBOL then begin
|
|
Result.FNode := TSynMarkupHighIfDefLinesNode(FTree.First(Result.FStartLine, Result.Index));
|
|
Result.FAtBOL := False;
|
|
Result.FAtEOL := not Result.HasNode;
|
|
end
|
|
else begin
|
|
Result.ClearInfo;
|
|
end;
|
|
end;
|
|
|
|
procedure TSynMarkupHighIfDefLinesNodeInfo.ClearNestCache;
|
|
begin
|
|
FCacheNestMinimum := -1;
|
|
FCacheNestStart := -1;
|
|
FCacheNestEnd := -1;
|
|
end;
|
|
|
|
function TSynMarkupHighIfDefLinesNodeInfo.NestMinimumDepthAtNode: Integer;
|
|
begin
|
|
assert(FTree <> nil, 'NestWinimumDepthAtNode has tree'+DebugText);
|
|
if FCacheNestMinimum < 0 then
|
|
FCacheNestMinimum :=
|
|
FTree.GetHighLighterWithLines.FoldBlockMinLevel(ToIdx(StartLine), FOLDGROUP_IFDEF,
|
|
[sfbIncludeDisabled]);
|
|
Result := FCacheNestMinimum;
|
|
end;
|
|
|
|
function TSynMarkupHighIfDefLinesNodeInfo.NestDepthAtNodeStart: Integer;
|
|
begin
|
|
assert(FTree <> nil, 'NestDepthAtNodeStart has tree'+DebugText);
|
|
if FCacheNestStart < 0 then
|
|
FCacheNestStart :=
|
|
FTree.GetHighLighterWithLines.FoldBlockEndLevel(ToIdx(StartLine)-1, FOLDGROUP_IFDEF,
|
|
[sfbIncludeDisabled]);
|
|
Result := FCacheNestStart;
|
|
end;
|
|
|
|
function TSynMarkupHighIfDefLinesNodeInfo.NestDepthAtNodeEnd: Integer;
|
|
begin
|
|
assert(FTree <> nil, 'NestDepthAtNodeEnd has tree'+DebugText);
|
|
if FCacheNestEnd < 0 then
|
|
FCacheNestEnd :=
|
|
FTree.GetHighLighterWithLines.FoldBlockEndLevel(ToIdx(StartLine), FOLDGROUP_IFDEF,
|
|
[sfbIncludeDisabled]);
|
|
Result := FCacheNestEnd;
|
|
end;
|
|
|
|
function TSynMarkupHighIfDefLinesNodeInfo.ValidToLine(const ANextNode: TSynMarkupHighIfDefLinesNodeInfo): Integer;
|
|
begin
|
|
if not HasNode then
|
|
exit(-1);
|
|
if not IsValid then
|
|
exit(StartLine);
|
|
|
|
if ScanEndOffs >= 0 then begin
|
|
Result := StartLine + ScanEndOffs;
|
|
assert((not ANextNode.HasNode) or (Result<ANextNode.StartLine), '(ANextNode=nil) or (Result<ANextNode.StartLine)'+DebugText);
|
|
end
|
|
else
|
|
if ANextNode.HasNode then
|
|
Result := ANextNode.StartLine - 1
|
|
else
|
|
Result := StartLine;
|
|
end;
|
|
|
|
function TSynMarkupHighIfDefLinesNodeInfo.IsValid: Boolean;
|
|
begin
|
|
Result := HasNode and (idlValid in LineFlags);
|
|
end;
|
|
|
|
procedure TSynMarkupHighIfDefLinesNodeInfo.Invalidate;
|
|
begin
|
|
Exclude(Node.FLineFlags, idlValid);
|
|
end;
|
|
|
|
function TSynMarkupHighIfDefLinesNodeInfo.HasNode: Boolean;
|
|
begin
|
|
Result := FNode <> nil;
|
|
end;
|
|
|
|
{ TSynMarkupHighIfDefLinesTree }
|
|
|
|
procedure TSynMarkupHighIfDefLinesTree.IncChangeStep;
|
|
begin
|
|
if FChangeStep = high(FChangeStep) then
|
|
FChangeStep := low(FChangeStep)
|
|
else
|
|
inc(FChangeStep);
|
|
end;
|
|
|
|
procedure TSynMarkupHighIfDefLinesTree.SetHighlighter(AValue: TSynPasSyn);
|
|
begin
|
|
if FHighlighter = AValue then Exit;
|
|
FHighlighter := AValue;
|
|
Clear;
|
|
end;
|
|
|
|
procedure TSynMarkupHighIfDefLinesTree.SetLines(AValue: TSynEditStringsLinked);
|
|
begin
|
|
if FLines = AValue then Exit;
|
|
|
|
if FLines <> nil then begin
|
|
FLines.RemoveChangeHandler(senrHighlightChanged, @DoHighlightChanged);
|
|
FLines.RemoveEditHandler(@DoLinesEdited);
|
|
end;
|
|
|
|
FLines := AValue;
|
|
Clear;
|
|
|
|
if FLines <> nil then begin
|
|
FLines.AddChangeHandler(senrHighlightChanged, @DoHighlightChanged);
|
|
FLines.AddEditHandler(@DoLinesEdited);
|
|
end;
|
|
end;
|
|
|
|
procedure TSynMarkupHighIfDefLinesTree.MaybeRequestNodeStates(var ANode: TSynMarkupHighIfDefLinesNodeInfo);
|
|
var
|
|
e: TSynMarkupHighIfDefEntry;
|
|
i: Integer;
|
|
NewState: TSynMarkupIfdefNodeState;
|
|
begin
|
|
if (not (idlHasUnknownNodes in ANode.LineFlags)) or
|
|
(not Assigned(FOnNodeStateRequest)) or
|
|
FRequestingNodeState
|
|
then
|
|
exit;
|
|
Exclude(ANode.Node.FLineFlags, idlHasUnknownNodes);
|
|
FRequestingNodeState := True;
|
|
try
|
|
i := 0;
|
|
while i < ANode.EntryCount do begin
|
|
// replace Sender in Markup object
|
|
e := ANode.Entry[i];
|
|
if e.NeedsRequesting then begin
|
|
NewState := FOnNodeStateRequest(nil, ANode.StartLine, e.StartColumn, e.NodeState);
|
|
if e.NodeState <> NewState then
|
|
IncChangeStep;
|
|
e.NodeState := NewState;
|
|
end;
|
|
inc(i);
|
|
end;
|
|
finally
|
|
FRequestingNodeState := False;
|
|
end;
|
|
end;
|
|
|
|
procedure TSynMarkupHighIfDefLinesTree.MaybeValidateNode(var ANode: TSynMarkupHighIfDefLinesNodeInfo);
|
|
begin
|
|
// TODO: search first
|
|
if not ANode.HasNode then
|
|
exit;
|
|
if (not ANode.IsValid) then begin
|
|
//debugln(['Validating existing node ', ANode.StartLine, ' - ', ANode.ScanEndLine]);
|
|
ScanLine(ANode.StartLine, ANode.FNode);
|
|
end;
|
|
MaybeRequestNodeStates(ANode);
|
|
end;
|
|
|
|
procedure TSynMarkupHighIfDefLinesTree.MaybeExtendNodeBackward(var ANode: TSynMarkupHighIfDefLinesNodeInfo;
|
|
AStopAtLine: Integer);
|
|
var
|
|
Line: Integer;
|
|
begin
|
|
Assert(ANode.HasNode, 'ANode.HasNode in MaybeExtendNodeDownwards'+ANode.DebugText+ ' Stopline='+IntToStr(AStopAtLine));
|
|
MaybeValidateNode(ANode);
|
|
if (ANode.EntryCount = 0) then begin
|
|
// ANode is a Scan-Start-Marker and may be extended downto StartLine
|
|
Line := ANode.StartLine;
|
|
while Line > AStopAtLine do begin
|
|
dec(Line);
|
|
if CheckLineForNodes(Line) then begin
|
|
ScanLine(Line, ANode.FNode);
|
|
if ANode.EntryCount > 0 then begin
|
|
MaybeRequestNodeStates(ANode);
|
|
break;
|
|
end;
|
|
end;
|
|
end;
|
|
if ANode.StartLine <> Line then begin
|
|
//debugln(['EXTEND BACK node ', ANode.StartLine, ' - ', ANode.ScanEndLine, ' TO ', Line]);
|
|
ANode.StartLine := Line;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TSynMarkupHighIfDefLinesTree.MaybeExtendNodeForward(var ANode: TSynMarkupHighIfDefLinesNodeInfo;
|
|
var ANextNode: TSynMarkupHighIfDefLinesNodeInfo; AStopBeforeLine: Integer);
|
|
var
|
|
Line: Integer;
|
|
begin
|
|
ANextNode.ClearInfo;
|
|
Assert(ANode.IsValid, 'ANode.IsValid in MaybeExtendNodeForward');
|
|
if AStopBeforeLine < 0 then
|
|
AStopBeforeLine := Lines.Count + 1;
|
|
// ANode is a Scan-Start-Marker and may be extended downto StartLine
|
|
Line := ANode.StartLine + ANode.ScanEndOffs;
|
|
while Line < AStopBeforeLine - 1 do begin
|
|
inc(Line);
|
|
if CheckLineForNodes(Line) then begin
|
|
ScanLine(Line, ANextNode.FNode);
|
|
if ANextNode.HasNode then begin
|
|
ANextNode.FStartLine := Line; // directly to field
|
|
ANode.ScanEndLine := Line - 1;
|
|
MaybeRequestNodeStates(ANextNode);
|
|
exit;
|
|
end;
|
|
end;
|
|
end;
|
|
// Line is empty, include in offs
|
|
if ANode.ScanEndLine <> Line then begin
|
|
//debugln(['EXTEND FORWARD node ', ANode.StartLine, ' - ', ANode.ScanEndLine, ' TO ', Line]);
|
|
ANode.ScanEndLine := Line;
|
|
end;
|
|
end;
|
|
|
|
function TSynMarkupHighIfDefLinesTree.GetOrInsertNodeAtLine(ALinePos: Integer): TSynMarkupHighIfDefLinesNodeInfo;
|
|
begin
|
|
Result := FindNodeAtPosition(ALinePos, afmPrev); // might be multiline
|
|
if (not Result.HasNode) then begin
|
|
Result := FindNodeAtPosition(ALinePos, afmCreate);
|
|
end
|
|
else
|
|
if (Result.StartLine <> ALinePos) then begin
|
|
if Result.ScanEndLine >= ALinePos then
|
|
Result.ScanEndLine := ALinePos - 1;
|
|
if Result.LastEntryEndLine > ALinePos then begin
|
|
Result.LastEntryEndLineOffs := 0;
|
|
Assert(Result.EntryCount > 0, 'Result.EntryCount > 0 : in Outer Nesting');
|
|
Result.Entry[Result.EntryCount-1].MakeUnknown;
|
|
//Result.Entry[Result.EntryCount].MakeUnknown;
|
|
//Result.; xxxxxxxxxxxxxxxxxxxxxxxx TODO invalidate
|
|
end;
|
|
Result := FindNodeAtPosition(ALinePos, afmCreate);
|
|
end;
|
|
end;
|
|
|
|
procedure TSynMarkupHighIfDefLinesTree.ConnectPeers(var ANode: TSynMarkupHighIfDefLinesNodeInfo;
|
|
var ANestList: TSynMarkupHighIfDefLinesNodeInfoList; AOuterLines: TLazSynEditNestedFoldsList);
|
|
var
|
|
PeerList: array of TSynMarkupHighIfDefEntry; // List of Else/Endif in the current line, that where opened in a previous line
|
|
OpenList: array of TSynMarkupHighIfDefEntry; // List of IfDef/Else in the current line
|
|
CurDepth, MaxListIdx, MinOpenDepth, MaxOpenDepth, MaxPeerDepth, MinPeerDepth: Integer;
|
|
i, j, OtherDepth: Integer;
|
|
OtherLine: TSynMarkupHighIfDefLinesNodeInfo;
|
|
PeerChanged: Boolean;
|
|
CurEntry: TSynMarkupHighIfDefEntry;
|
|
|
|
function OpenIdx(AIdx: Integer): Integer; // correct negative idx
|
|
begin
|
|
Result := AIdx;
|
|
if Result >= 0 then exit;
|
|
Result := MaxListIdx + (-AIdx);
|
|
end;
|
|
|
|
begin
|
|
/// Scan for onel line blocks
|
|
PeerChanged := False;
|
|
CurDepth := ANode.NestDepthAtNodeStart;
|
|
MinOpenDepth := MaxInt;
|
|
MaxOpenDepth := -1;
|
|
MinPeerDepth := MaxInt;
|
|
MaxPeerDepth := -1;
|
|
|
|
MaxListIdx := CurDepth + ANode.EntryCount + 1;
|
|
SetLength(OpenList, MaxListIdx + ANode.EntryCount);
|
|
SetLength(PeerList, MaxListIdx);
|
|
|
|
for i := 0 to ANode.EntryCount - 1 do begin
|
|
CurEntry := ANode.Entry[i];
|
|
case CurEntry.NodeType of
|
|
idnIfdef: begin
|
|
inc(CurDepth);
|
|
OpenList[OpenIdx(CurDepth)] := CurEntry; // Store IfDef, with Index at end of IfDef (inside block)
|
|
if CurDepth < MinOpenDepth then MinOpenDepth := CurDepth;
|
|
if CurDepth > MaxOpenDepth then MaxOpenDepth := CurDepth;
|
|
end;
|
|
idnElse, idnElseIf: begin
|
|
If CurDepth <= 0 then begin
|
|
//debugln(['Ignoring node with has no opening at all in line ', ANode.StartLine]);
|
|
end;
|
|
|
|
if (CurDepth >= MinOpenDepth) and (CurDepth <= MaxOpenDepth) then begin
|
|
// Opening Node on this line
|
|
assert(CurDepth = MaxOpenDepth, 'ConnectPeers: Same line peer skips opening node(s)');
|
|
case OpenList[OpenIdx(CurDepth)].NodeType of
|
|
idnIfdef, idnElseIf:
|
|
if OpenList[OpenIdx(CurDepth)].ClosingPeer <> CurEntry then begin
|
|
//Debugln(['New Peer for ',dbgs(OpenList[OpenIdx(CurDepth)].NodeType), ' to else same line']);
|
|
OpenList[OpenIdx(CurDepth)].ClosingPeer := CurEntry;
|
|
PeerChanged := True;
|
|
//dec(MaxOpenDepth); // Will be set with the current entry
|
|
end;
|
|
idnElse: ;//DebugLn('Ignoring invalid double else (on same line)');
|
|
end;
|
|
end
|
|
else
|
|
If CurDepth >= 0 then begin
|
|
// Opening Node in previous line
|
|
PeerList[CurDepth] := CurEntry;
|
|
assert((MaxPeerDepth=-1) or ((MinPeerDepth <= MaxPeerDepth) and (CurDepth = MinPeerDepth-1)), 'ConnectPeers: skipped noeds during line scan');
|
|
if CurDepth < MinPeerDepth then MinPeerDepth := CurDepth;
|
|
if CurDepth > MaxPeerDepth then MaxPeerDepth := CurDepth;
|
|
end;
|
|
|
|
OpenList[OpenIdx(CurDepth)] := CurEntry; // Store IfDef, with Index at end of IfDef (inside block)
|
|
if CurDepth < MinOpenDepth then MinOpenDepth := CurDepth;
|
|
if CurDepth > MaxOpenDepth then MaxOpenDepth := CurDepth;
|
|
end;
|
|
idnEndIf: begin
|
|
If CurDepth <= 0 then begin
|
|
//debugln(['Ignoring node with has no opening at all in line', ANode.StartLine]);
|
|
dec(CurDepth);
|
|
continue; // This node has no opening node
|
|
end;
|
|
|
|
if (CurDepth >= MinOpenDepth) and (CurDepth <= MaxOpenDepth) then begin
|
|
// Opening Node on this line
|
|
assert(CurDepth = MaxOpenDepth, 'ConnectPeers: Same line peer skips opening node(s)');
|
|
if OpenList[OpenIdx(CurDepth)].ClosingPeer <> CurEntry then begin
|
|
//Debugln(['New Peer for ',dbgs(OpenList[OpenIdx(CurDepth)].NodeType), ' to endif same line']);
|
|
OpenList[OpenIdx(CurDepth)].ClosingPeer := CurEntry;
|
|
PeerChanged := True;
|
|
end;
|
|
dec(MaxOpenDepth);
|
|
end
|
|
else begin
|
|
// Opening Node in previous line
|
|
PeerList[CurDepth] := CurEntry;
|
|
assert((MaxPeerDepth=-1) or ((MinPeerDepth <= MaxPeerDepth) and (CurDepth = MinPeerDepth-1)), 'ConnectPeers: skipped noeds during line scan');
|
|
if CurDepth < MinPeerDepth then MinPeerDepth := CurDepth;
|
|
if CurDepth > MaxPeerDepth then MaxPeerDepth := CurDepth;
|
|
end;
|
|
|
|
dec(CurDepth);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
|
|
|
|
// Find peers in previous lines. MinPeerDepth <= 0 have no opening
|
|
// Opening (IfDef) nodes will be connected when there closing node is found.
|
|
for i := MaxPeerDepth downto Max(MinPeerDepth, 1) do begin
|
|
// Todo: MAybe optimize, if it can be known that an existing peer link is correct
|
|
//case PeerList[i].NodeType of
|
|
// idnElse: if PeerList[i].IfDefPeer <> nil then continue;
|
|
// idnEndIf: if PeerList[i].ElsePeer <> nil then continue;
|
|
//end;
|
|
|
|
assert(not (PeerList[i].NodeType in [idnIfdef, idnCommentedNode]), 'multi-line peer valid');
|
|
|
|
// AOuterLines is only set while scanning opening lines in front of the 1st visible screenline
|
|
// (at the start of ValidateRange)
|
|
if (AOuterLines <> nil) then begin
|
|
if PeerList[i].NodeType in [idnElse, idnElseIf] then begin
|
|
// todo: find multiply elseif
|
|
j := ToPos(AOuterLines.NodeLineEx[i-1, 1]);
|
|
if j < 0 then begin
|
|
//debugln(['Skipping peer for ELSE with NO IFDEF at depth ', i-1, ' before line ', ANode.StartLine]);
|
|
continue;
|
|
end;
|
|
OtherLine := GetOrInsertNodeAtLine(j);
|
|
MaybeValidateNode(OtherLine);
|
|
end
|
|
else
|
|
continue; // while scanning outerlines, any EndIf can be ignored
|
|
end
|
|
else
|
|
OtherLine := ANestList.Node[i]; // Todo: keep if same al last loop, and continue at OtherDepth / j
|
|
|
|
OtherDepth := OtherLine.NestDepthAtNodeEnd;
|
|
j := OtherLine.EntryCount;
|
|
while j > 0 do begin
|
|
dec(j);
|
|
if OtherDepth = i then begin
|
|
case OtherLine.Entry[j].NodeType of
|
|
idnIfdef: begin
|
|
assert(PeerList[i].NodeType in [idnElse, idnElseIf, idnEndIf], 'PeerList[i].NodeType in [idnElse, idnEndIf] for other ifdef');
|
|
if PeerList[i].OpeningPeer <> OtherLine.Entry[j] then begin
|
|
//Debugln(['New Peer for ',dbgs(PeerList[i].NodeType), ' to ifdef other line']);
|
|
PeerList[i].OpeningPeer := OtherLine.Entry[j];
|
|
PeerChanged := True;
|
|
end;
|
|
j := -1;
|
|
break;
|
|
end;
|
|
idnElse, idnElseIf: begin
|
|
assert(PeerList[i].NodeType in [idnElse, idnElseIf, idnEndIf], 'PeerList[i].NodeType in [idnElse, idnEndIf] for other else');
|
|
if (PeerList[i].NodeType = idnEndIf) OR
|
|
( (PeerList[i].NodeType in [idnElseIf, idnElse]) and
|
|
(OtherLine.Entry[j].NodeType = idnElseIf) )
|
|
then begin
|
|
if PeerList[i].OpeningPeer <> OtherLine.Entry[j] then begin
|
|
//Debugln(['New Peer for ',dbgs(PeerList[i].NodeType), ' to else other line']);
|
|
PeerList[i].OpeningPeer := OtherLine.Entry[j];
|
|
PeerChanged := True;
|
|
end;
|
|
j := -1;
|
|
end
|
|
else begin
|
|
//DebugLn('Ignoring invalid double else');
|
|
end;
|
|
break;
|
|
end;
|
|
end;
|
|
end;
|
|
case OtherLine.Entry[j].NodeType of
|
|
idnIfdef: dec(OtherDepth);
|
|
idnElse, idnElseIf: ; //
|
|
idnEndIf: inc(OtherDepth);
|
|
end;
|
|
end;
|
|
|
|
if j >= 0 then begin
|
|
// no peer found
|
|
case PeerList[i].NodeType of
|
|
idnIfdef: ;
|
|
idnElse, idnElseIf: begin
|
|
//Debugln(['CLEARING ifdef Peer for ',dbgs(PeerList[i].NodeType)]);
|
|
PeerList[i].OpeningPeer := nil;
|
|
PeerChanged := True;
|
|
//DoModified;
|
|
end;
|
|
idnEndIf: begin
|
|
//Debugln(['CLEARING BOTH Peer for ',dbgs(PeerList[i].NodeType)]);
|
|
PeerList[i].ClearPeers;
|
|
PeerChanged := True;
|
|
//DoModified;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
end;
|
|
|
|
if PeerChanged then
|
|
IncChangeStep;
|
|
end;
|
|
|
|
procedure TSynMarkupHighIfDefLinesTree.DoLinesEdited(Sender: TSynEditStrings; aLinePos,
|
|
aBytePos, aCount, aLineBrkCnt: Integer; aText: String);
|
|
|
|
function IndexOfEntryAfter(ANode: TSynMarkupHighIfDefLinesNode; AXPos: Integer): Integer;
|
|
var
|
|
Cnt: Integer;
|
|
begin
|
|
Result := 0;
|
|
Cnt := aNode.EntryCount;
|
|
while (Result < Cnt) and (ANode.Entry[Result].StartColumn < aBytePos) do
|
|
inc(Result);
|
|
// if LastEntryEndLineOffs = 0, then keep at count. Migth check closing pos of (Result-1)
|
|
if (Result = Cnt) and (ANode.LastEntryEndLineOffs > 0) then
|
|
Result := -1;
|
|
end;
|
|
|
|
procedure AdjustEntryXPos(ANode: TSynMarkupHighIfDefLinesNode; ADiffX: Integer;
|
|
AStartIdx: Integer = 0; ADestNode: TSynMarkupHighIfDefLinesNode = nil;
|
|
AMinXPos : Integer = 1);
|
|
var
|
|
Cnt, SkipEndIdx, DestPos, j: Integer;
|
|
CurEntry: TSynMarkupHighIfDefEntry;
|
|
begin
|
|
Cnt := aNode.EntryCount;
|
|
if AStartIdx >= Cnt then
|
|
exit;
|
|
if (aNode.LastEntryEndLineOffs > 0) then
|
|
SkipEndIdx := Cnt - 1
|
|
else
|
|
SkipEndIdx := -1;
|
|
|
|
if ADestNode = nil then begin
|
|
for j := AStartIdx to Cnt - 1 do begin
|
|
CurEntry := aNode.Entry[j];
|
|
assert(CurEntry.StartColumn >= AMinXPos, 'DoLinesEdited: CurEntry.StartColumn >= AMinXPos');
|
|
CurEntry.StartColumn := Max(AMinXPos, CurEntry.StartColumn + ADiffX);
|
|
if (j <> SkipEndIdx) then
|
|
CurEntry.EndColumn := Max(CurEntry.StartColumn, CurEntry.EndColumn + ADiffX);
|
|
end;
|
|
end
|
|
else begin
|
|
DestPos := ADestNode.EntryCount;
|
|
ADestNode.EntryCount := DestPos + (Cnt - AStartIdx);
|
|
for j := AStartIdx to Cnt - 1 do begin
|
|
CurEntry := aNode.Entry[j];
|
|
aNode.Entry[j] := nil;
|
|
assert(CurEntry.StartColumn >= AMinXPos, 'DoLinesEdited: CurEntry.StartColumn >= AMinXPos');
|
|
CurEntry.StartColumn := Max(AMinXPos, CurEntry.StartColumn + ADiffX);
|
|
if (j <> SkipEndIdx) then
|
|
CurEntry.EndColumn := Max(CurEntry.StartColumn, CurEntry.EndColumn + ADiffX);
|
|
ADestNode.Entry[DestPos] := CurEntry;
|
|
inc(DestPos);
|
|
end;
|
|
ANode.EntryCount := AStartIdx;
|
|
ADestNode.LastEntryEndLineOffs := ANode.LastEntryEndLineOffs;
|
|
ANode.LastEntryEndLineOffs := 0;
|
|
end;
|
|
end;
|
|
|
|
var
|
|
i, c: Integer;
|
|
WorkNode, NextNode, LinePosNode: TSynMarkupHighIfDefLinesNodeInfo;
|
|
WorkLine, LineAfterDelete: Integer;
|
|
|
|
begin
|
|
// Line nodes vill be invalidated in DoHighlightChanged
|
|
{$IFDEF WITH_SYN_DEBUG_MARKUP_IFDEF}
|
|
DebugCurTreeForAssert := self; try
|
|
{$ENDIF}
|
|
|
|
IncChangeStep;
|
|
|
|
if aLineBrkCnt > 0 then begin
|
|
WorkNode := FindNodeAtPosition(aLinePos - 1, afmPrev);
|
|
if WorkNode.HasNode then begin
|
|
WorkLine := WorkNode.StartLine;
|
|
if aLinePos <= WorkNode.LastEntryEndLine then begin
|
|
c := WorkNode.EntryCount - 1;
|
|
assert(c >= 0, 'must have node, if LastEntryOffs > 0 [insert lines]');
|
|
if (aLinePos = WorkNode.LastEntryEndLine) then begin
|
|
if (WorkNode.Entry[c].EndColumn > aBytePos) then begin
|
|
WorkNode.LastEntryEndLineOffs := WorkNode.LastEntryEndLineOffs + aLineBrkCnt;
|
|
WorkNode.Entry[c].EndColumn := WorkNode.Entry[c].EndColumn - aBytePos + 1;
|
|
end;
|
|
end
|
|
else
|
|
WorkNode.LastEntryEndLineOffs := WorkNode.LastEntryEndLineOffs + aLineBrkCnt;
|
|
WorkNode.Entry[c].MakeUnknown;
|
|
end;
|
|
if aLinePos <= WorkLine + WorkNode.ScanEndOffs then begin
|
|
assert(aLinePos > WorkLine);
|
|
WorkNode.ScanEndOffs := aLinePos - WorkLine;
|
|
end;
|
|
end;
|
|
|
|
WorkNode := WorkNode.Successor;
|
|
if (not WorkNode.HasNode) then
|
|
exit;
|
|
WorkLine := WorkNode.StartLine;
|
|
NextNode.ClearInfo;
|
|
|
|
if (WorkLine > aLinePos) or
|
|
( (WorkLine = aLinePos) and
|
|
( (aBytePos <= 1) or (WorkNode.EntryCount = 0) or (WorkNode.Entry[0].StartColumn >= aBytePos) )
|
|
)
|
|
then begin
|
|
// Move Entire Line
|
|
AdjustForLinesInserted(aLinePos, aLineBrkCnt);
|
|
// Adjust the FIELD directly (the real node already moved)
|
|
WorkNode.FStartLine := WorkNode.FStartLine + aLineBrkCnt;
|
|
if (aBytePos > 1) and (WorkLine = aLinePos) then
|
|
AdjustEntryXPos(WorkNode.Node, (-aBytePos) + 1);
|
|
end
|
|
else begin
|
|
// Move part of Line (or nothing)
|
|
AdjustForLinesInserted(aLinePos + 1, aLineBrkCnt);
|
|
if (WorkLine = aLinePos) then begin
|
|
i := IndexOfEntryAfter(WorkNode.Node, aBytePos);
|
|
if i >= 0 then begin
|
|
NextNode := FindNodeAtPosition(aLinePos + aLineBrkCnt, afmCreate);
|
|
AdjustEntryXPos(WorkNode.Node, (-aBytePos) + 1, i, NextNode.Node);
|
|
if (i > 0) and (WorkNode.Entry[i-1].EndColumn > aBytePos) then begin
|
|
WorkNode.LastEntryEndLineOffs := aLineBrkCnt;
|
|
WorkNode.Entry[i-1].EndColumn := WorkNode.Entry[i-1].EndColumn - aBytePos + 1;
|
|
WorkNode.Entry[i-1].MakeUnknown;
|
|
end;
|
|
end
|
|
else begin
|
|
assert(WorkNode.LastEntryEndLineOffs > 0, 'WorkNode.LastEntryEndLineOffs > 0');
|
|
c := WorkNode.EntryCount - 1;
|
|
if (c >= 0) and (WorkNode.Entry[c].StartColumn < aBytePos) then begin
|
|
WorkNode.LastEntryEndLineOffs := WorkNode.LastEntryEndLineOffs + aLineBrkCnt;
|
|
WorkNode.Entry[c].MakeUnknown;
|
|
end;
|
|
end;
|
|
end
|
|
else begin
|
|
if aLinePos <= WorkNode.LastEntryEndLine then begin
|
|
c := WorkNode.EntryCount - 1;
|
|
assert(c >= 0, 'must have node, if LastEntryOffs > 0 [insert lines]');
|
|
if (aLinePos = WorkNode.LastEntryEndLine) then begin
|
|
if (WorkNode.Entry[c].EndColumn > aBytePos) then begin
|
|
WorkNode.LastEntryEndLineOffs := WorkNode.LastEntryEndLineOffs + aLineBrkCnt;
|
|
WorkNode.Entry[c].EndColumn := WorkNode.Entry[c].EndColumn - aBytePos + 1;
|
|
end;
|
|
end
|
|
else
|
|
WorkNode.LastEntryEndLineOffs := WorkNode.LastEntryEndLineOffs + aLineBrkCnt;
|
|
WorkNode.Entry[c].MakeUnknown;
|
|
end;
|
|
if aLinePos <= WorkLine + WorkNode.ScanEndOffs then begin
|
|
assert(aLinePos > WorkLine);
|
|
WorkNode.ScanEndOffs := aLinePos - WorkLine;
|
|
end;
|
|
end;
|
|
end;
|
|
end
|
|
|
|
else
|
|
if aLineBrkCnt < 0 then begin
|
|
aLineBrkCnt := - aLineBrkCnt;
|
|
WorkNode := FindNodeAtPosition(aLinePos - 1, afmPrev);
|
|
if WorkNode.HasNode then begin
|
|
WorkLine := WorkNode.StartLine;
|
|
if aLinePos <= WorkNode.LastEntryEndLine then begin
|
|
c := WorkNode.EntryCount - 1;
|
|
assert(c >= 0, 'must have node, if LastEntryOffs > 0 [insert lines]');
|
|
if (aLinePos < WorkNode.LastEntryEndLine) or (WorkNode.Entry[c].EndColumn > aBytePos) then begin
|
|
if aLinePos + aLineBrkCnt > WorkNode.LastEntryEndLine then
|
|
WorkNode.Entry[c].EndColumn := 1
|
|
else
|
|
if (aLinePos + aLineBrkCnt = WorkNode.LastEntryEndLine) then
|
|
WorkNode.Entry[c].EndColumn := WorkNode.Entry[c].EndColumn + aBytePos - 1;
|
|
WorkNode.LastEntryEndLineOffs := WorkNode.LastEntryEndLineOffs - aLineBrkCnt;
|
|
WorkNode.Entry[c].MakeUnknown;
|
|
end;
|
|
end;
|
|
if aLinePos <= WorkLine + WorkNode.ScanEndOffs then begin
|
|
assert(aLinePos > WorkLine);
|
|
WorkNode.ScanEndOffs := aLinePos - WorkLine;
|
|
end;
|
|
end;
|
|
|
|
WorkNode := WorkNode.Successor;
|
|
if (not WorkNode.HasNode) then
|
|
exit;
|
|
WorkLine := WorkNode.StartLine;
|
|
NextNode.ClearInfo;
|
|
|
|
LinePosNode.ClearInfo;
|
|
if (WorkNode.StartLine = aLinePos) then begin
|
|
LinePosNode := WorkNode;
|
|
WorkNode := WorkNode.Successor;
|
|
end;
|
|
LineAfterDelete := aLinePos + aLineBrkCnt;
|
|
while (WorkNode.HasNode) and (WorkNode.StartLine < LineAfterDelete) do begin
|
|
NextNode := WorkNode.Successor;
|
|
RemoveLine(WorkNode.FNode);
|
|
WorkNode := NextNode;
|
|
end;
|
|
|
|
if LinePosNode.HasNode and (LinePosNode.LastEntryEndLineOffs > 0) then begin
|
|
c := LinePosNode.EntryCount - 1;
|
|
if LinePosNode.LastEntryEndLineOffs < aLineBrkCnt then
|
|
LinePosNode.Entry[c].EndColumn := aBytePos // no known end
|
|
else
|
|
if LinePosNode.LastEntryEndLineOffs = aLineBrkCnt then
|
|
LinePosNode.Entry[c].EndColumn := LinePosNode.Entry[c].EndColumn + aBytePos - 1;
|
|
LinePosNode.LastEntryEndLineOffs := Max(0, LinePosNode.LastEntryEndLineOffs - aLineBrkCnt);
|
|
end;
|
|
if (WorkNode.StartLine = LineAfterDelete) then begin
|
|
if LinePosNode.HasNode then begin
|
|
AdjustEntryXPos(WorkNode.Node, aBytePos - 1, 0, LinePosNode.Node);
|
|
RemoveLine(WorkNode.FNode);
|
|
WorkNode.ClearInfo;
|
|
end
|
|
else begin
|
|
AdjustEntryXPos(WorkNode.Node, aBytePos - 1);
|
|
WorkNode.StartLine := aLinePos;
|
|
end;
|
|
end;
|
|
|
|
AdjustForLinesDeleted(aLinePos + 1, aLineBrkCnt);
|
|
end
|
|
|
|
else begin
|
|
WorkNode := FindNodeAtPosition(aLinePos, afmPrev);
|
|
if (not WorkNode.HasNode) then
|
|
exit;
|
|
WorkLine := WorkNode.StartLine;
|
|
|
|
if aLinePos = WorkLine then begin
|
|
i := IndexOfEntryAfter(WorkNode.Node, aBytePos);
|
|
if i >= 0 then begin
|
|
AdjustEntryXPos(WorkNode.Node, aCount, i, nil, aBytePos);
|
|
if (i > 0) and (WorkNode.Entry[i-1].EndColumn > aBytePos) then begin
|
|
WorkNode.Entry[i-1].EndColumn := Max(aBytePos, WorkNode.Entry[i-1].EndColumn + aCount);
|
|
WorkNode.Entry[i-1].MakeUnknown;
|
|
end;
|
|
if aCount < 0 then begin
|
|
c := WorkNode.EntryCount - 1;
|
|
while ( (i < c) or ((i = c) and (WorkNode.LastEntryEndLineOffs=0)) ) and
|
|
(WorkNode.Entry[i].EndColumn <= WorkNode.Entry[i].StartColumn)
|
|
do begin
|
|
WorkNode.Node.DeletEntry(i, True);
|
|
dec(c);
|
|
end;
|
|
if (i <= c) and (WorkNode.Entry[i].StartColumn < aBytePos) then begin
|
|
WorkNode.Entry[i].StartColumn := aBytePos;
|
|
WorkNode.Entry[i].MakeUnknown;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
WorkNode := WorkNode.Precessor;
|
|
if (not WorkNode.HasNode) then
|
|
exit;
|
|
end;
|
|
|
|
assert(WorkNode.StartLine < aLinePos);
|
|
WorkLine := WorkNode.StartLine + WorkNode.ScanEndOffs;
|
|
if (WorkLine >= aLinePos) then
|
|
WorkNode.ScanEndOffs := aLinePos - 1 - WorkNode.StartLine;
|
|
|
|
WorkLine := WorkNode.StartLine + WorkNode.LastEntryEndLineOffs;
|
|
i := WorkNode.EntryCount - 1;
|
|
if (WorkLine >= aLinePos) and (i >= 0) then begin
|
|
if (WorkLine = aLinePos) and (WorkNode.Entry[i].EndColumn > aBytePos) then
|
|
WorkNode.Entry[i].EndColumn := Max(aBytePos, WorkNode.Entry[i].EndColumn + aCount);
|
|
WorkNode.Entry[i].MakeUnknown;
|
|
end;
|
|
|
|
end;
|
|
|
|
{$IFDEF WITH_SYN_DEBUG_MARKUP_IFDEF}
|
|
finally DebugCurTreeForAssert := nil; end;
|
|
{$ENDIF}
|
|
end;
|
|
|
|
procedure TSynMarkupHighIfDefLinesTree.DoHighlightChanged(Sender: TSynEditStrings; AIndex,
|
|
ACount: Integer);
|
|
var
|
|
LinePos: Integer;
|
|
WorkNode: TSynMarkupHighIfDefLinesNodeInfo;
|
|
begin
|
|
IncChangeStep;
|
|
// Invalidate. The highlighter should only run once, so no need to collect multply calls
|
|
LinePos := ToPos(AIndex);
|
|
WorkNode := FindNodeAtPosition(LinePos, afmPrev);
|
|
if WorkNode.HasNode and (Max(WorkNode.ScanEndLine, WorkNode.LastEntryEndLine) >= LinePos) then
|
|
WorkNode.Invalidate;
|
|
WorkNode := WorkNode.Successor;
|
|
LinePos := LinePos + ACount;
|
|
while WorkNode.HasNode and (WorkNode.StartLine <= LinePos) do begin
|
|
WorkNode.Invalidate;
|
|
WorkNode := WorkNode.Successor;
|
|
end;
|
|
end;
|
|
|
|
function TSynMarkupHighIfDefLinesTree.GetHighLighterWithLines: TSynCustomFoldHighlighter;
|
|
begin
|
|
Result := FHighlighter;
|
|
if (Result = nil) then
|
|
exit;
|
|
Result.CurrentLines := FLines;
|
|
end;
|
|
|
|
function TSynMarkupHighIfDefLinesTree.GetHighlighterAttriAtRowColEx(XY: TPoint; out
|
|
Token: string; out TokenType, Start: Integer; out Attri: TSynHighlighterAttributes): boolean;
|
|
var
|
|
PosX, PosY: integer;
|
|
Line: string;
|
|
begin
|
|
PosY := XY.Y -1;
|
|
if Assigned(Highlighter) and (PosY >= 0) and (PosY < FLines.Count) then
|
|
begin
|
|
Line := FLines[PosY];
|
|
fHighlighter.CurrentLines := FLines;
|
|
Highlighter.StartAtLineIndex(PosY);
|
|
PosX := XY.X;
|
|
if (PosX > 0) and (PosX <= Length(Line)) then begin
|
|
while not Highlighter.GetEol do begin
|
|
Start := Highlighter.GetTokenPos + 1;
|
|
Token := Highlighter.GetToken;
|
|
if (PosX >= Start) and (PosX < Start + Length(Token)) then begin
|
|
Attri := Highlighter.GetTokenAttribute;
|
|
TokenType := Highlighter.GetTokenKind;
|
|
Result := TRUE;
|
|
exit;
|
|
end;
|
|
Highlighter.Next;
|
|
end;
|
|
end;
|
|
end;
|
|
Token := '';
|
|
Attri := nil;
|
|
TokenType := -1;
|
|
Result := FALSE;
|
|
end;
|
|
|
|
function TSynMarkupHighIfDefLinesTree.CreateNode(APosition: Integer): TSynSizedDifferentialAVLNode;
|
|
begin
|
|
IncChangeStep;
|
|
if FDisposedNodes <> nil then begin
|
|
Result := FDisposedNodes;
|
|
FDisposedNodes := TSynMarkupHighIfDefLinesNode(Result).NextDispose;
|
|
TSynMarkupHighIfDefLinesNode(Result).FLineFlags := [];
|
|
end
|
|
else
|
|
Result := TSynMarkupHighIfDefLinesNode.Create;
|
|
end;
|
|
|
|
procedure TSynMarkupHighIfDefLinesTree.DisposeNode(var ANode: TSynSizedDifferentialAVLNode);
|
|
begin
|
|
IncChangeStep;
|
|
if FClearing then begin
|
|
Include(TSynMarkupHighIfDefLinesNode(ANode).FLineFlags, idlInGlobalClear);
|
|
inherited DisposeNode(ANode);
|
|
end
|
|
else begin
|
|
TSynMarkupHighIfDefLinesNode(ANode).MakeDisposed;
|
|
TSynMarkupHighIfDefLinesNode(ANode).NextDispose := FDisposedNodes;
|
|
FDisposedNodes := TSynMarkupHighIfDefLinesNode(ANode);
|
|
end;
|
|
end;
|
|
|
|
procedure TSynMarkupHighIfDefLinesTree.RemoveLine(var ANode: TSynMarkupHighIfDefLinesNode);
|
|
begin
|
|
RemoveNode(TSynSizedDifferentialAVLNode(ANode));
|
|
DisposeNode(TSynSizedDifferentialAVLNode(ANode));
|
|
end;
|
|
|
|
constructor TSynMarkupHighIfDefLinesTree.Create;
|
|
var
|
|
i: TSynMarkupHighIfDefTreeNotifications;
|
|
begin
|
|
inherited Create;
|
|
for i := low(TSynMarkupHighIfDefTreeNotifications) to high(TSynMarkupHighIfDefTreeNotifications) do
|
|
FNotifyLists[i] := TMethodList.Create;
|
|
|
|
FRequestingNodeState := False;
|
|
MaybeCreateDict;
|
|
TheDict.AddReference;
|
|
FChangeStep := 0;
|
|
end;
|
|
|
|
destructor TSynMarkupHighIfDefLinesTree.Destroy;
|
|
var
|
|
i: TSynMarkupHighIfDefTreeNotifications;
|
|
begin
|
|
Lines := nil;
|
|
inherited Destroy;
|
|
TheDict.ReleaseReference;
|
|
for i := low(TSynMarkupHighIfDefTreeNotifications) to high(TSynMarkupHighIfDefTreeNotifications) do
|
|
FreeAndNil(FNotifyLists[i]);
|
|
end;
|
|
|
|
function TSynMarkupHighIfDefLinesTree.CreateOpeningList: TLazSynEditNestedFoldsList;
|
|
begin
|
|
Result := TLazSynEditNestedFoldsList.Create(FLines, FHighlighter);
|
|
Result.ResetFilter;
|
|
Result.Clear;
|
|
//Result.Line :=
|
|
Result.FoldGroup := FOLDGROUP_IFDEF;
|
|
Result.FoldFlags := [sfbIncludeDisabled];
|
|
Result.IncludeOpeningOnLine := False;
|
|
end;
|
|
|
|
procedure TSynMarkupHighIfDefLinesTree.DiscardOpeningList(AList: TLazSynEditNestedFoldsList);
|
|
begin
|
|
AList.Free;
|
|
end;
|
|
|
|
function TSynMarkupHighIfDefLinesTree.CheckLineForNodes(ALine: Integer): Boolean;
|
|
var
|
|
m, e: Integer;
|
|
LineText, LineTextLower: String;
|
|
h: TSynCustomFoldHighlighter;
|
|
begin
|
|
h := GetHighLighterWithLines;
|
|
m := h.FoldBlockMinLevel(ToIdx(ALine), FOLDGROUP_IFDEF, [sfbIncludeDisabled]);
|
|
e := h.FoldBlockEndLevel(ToIdx(ALine), FOLDGROUP_IFDEF, [sfbIncludeDisabled]);
|
|
Result := (m < e);
|
|
|
|
if not Result then begin
|
|
LineText := Lines[ToIdx(ALine)];
|
|
if LineText = '' then
|
|
exit;
|
|
LineTextLower := LowerCase(LineText);
|
|
Result := TheDict.Dict.Search(@LineTextLower[1], Length(LineTextLower), nil) <> nil;
|
|
end;
|
|
end;
|
|
|
|
procedure TSynMarkupHighIfDefLinesTree.ScanLine(ALine: Integer;
|
|
var ANodeForLine: TSynMarkupHighIfDefLinesNode; ACheckOverlapOnCreateLine: Boolean);
|
|
var
|
|
FoldNodeInfoList: TLazSynFoldNodeInfoList;
|
|
LineTextLower: String;
|
|
LineLen, NodesAddedCnt: Integer;
|
|
LineNeedsReq, LineChanged, HasUncommentedNodes: Boolean;
|
|
|
|
function FindCloseCurlyBracket(StartX: Integer; out ALineOffs: Integer;
|
|
ASearchComment: Boolean = False; AMaxLine: Integer = -1): Integer;
|
|
var
|
|
len, i, c, tk, x: Integer;
|
|
dummy: String;
|
|
attr: TSynHighlighterAttributes;
|
|
r: TSynCustomHighlighterRange;
|
|
f: TRangeStates;
|
|
begin
|
|
Result := StartX;
|
|
ALineOffs := 0;
|
|
|
|
c := Lines.Count;
|
|
i := ALine;
|
|
|
|
while (i <= c) do begin
|
|
len := Length(Lines[ToIdx(i)]);
|
|
if len > 0 then begin
|
|
GetHighlighterAttriAtRowColEx(Point(StartX, i), dummy, tk, x, attr);
|
|
Result := x + Length(dummy) - 1;
|
|
if Result < len - 1 then
|
|
exit;
|
|
r := TSynCustomHighlighterRange(Highlighter.GetRange);
|
|
f := TRangeStates(Integer(PtrUInt(r.RangeType)));
|
|
if ASearchComment then begin
|
|
if (f * [rsAnsi, rsBor] = []) or ( (AMaxLine > 0) and (i > AMaxLine) ) then
|
|
exit;
|
|
end
|
|
else
|
|
if not (rsDirective in f) then
|
|
exit;
|
|
end;
|
|
|
|
inc(i);
|
|
inc(ALineOffs);
|
|
StartX := 1;
|
|
end;
|
|
|
|
Result := -1;
|
|
exit;
|
|
end;
|
|
|
|
function IsCommentedIfDef(AEntry: TSynMarkupHighIfDefEntry): Boolean;
|
|
var
|
|
i, j, k, FoundOffs, LastLineOffs: Integer;
|
|
s: String;
|
|
begin
|
|
i := AEntry.StartColumn;
|
|
Result :=
|
|
(i <= length(LineTextLower)) and
|
|
(LineTextLower[i] = '{') and
|
|
( (AEntry.NodeType in [idnIfdef, idnElseIf, idnCommentedNode]) or
|
|
(AEntry.StateByUser)
|
|
) and
|
|
(AEntry.HasKnownState);
|
|
if not Result then
|
|
exit;
|
|
|
|
j := AEntry.EndColumn;
|
|
LastLineOffs := AEntry.Line.LastEntryEndLineOffs;
|
|
if (idnMultiLineTag in AEntry.NodeFlags) then
|
|
s := Lines[ToIdx(ALine + LastLineOffs)]
|
|
else
|
|
s := LineTextLower;
|
|
Result :=
|
|
(j-1 <= length(s)) and (j > 1) and (s[j-1] = '}') and
|
|
(TheDict.GetMatchAtChar(@LineTextLower[i], LineLen + 1 - i) in [1, 4]);
|
|
// Old closing is inside the comment?
|
|
|
|
if Result then begin
|
|
k := FindCloseCurlyBracket(i+1, FoundOffs, True, ALine+LastLineOffs)+1;
|
|
Result := (LastLineOffs < FoundOffs) or
|
|
( (LastLineOffs = FoundOffs) and (j < k) );
|
|
|
|
//if Result then // FoundOffs is evaluated
|
|
// Result :=
|
|
// ((not (idnMultiLineTag in AEntry.NodeFlags)) and (FoundOffs = 0)) or
|
|
// ((idnMultiLineTag in AEntry.NodeFlags) and (o = AEntry.Line.LastEntryEndLineOffs));
|
|
end;
|
|
|
|
end;
|
|
|
|
function GetEntry(ALogStart, ALogEnd, ALineOffs: Integer;
|
|
AType: TSynMarkupIfdefNodeType): TSynMarkupHighIfDefEntry;
|
|
var
|
|
i: Integer;
|
|
e: TSynMarkupHighIfDefEntry;
|
|
begin
|
|
if ANodeForLine = nil then begin
|
|
if ACheckOverlapOnCreateLine then
|
|
ANodeForLine := GetOrInsertNodeAtLine(ALine).Node
|
|
else
|
|
ANodeForLine := FindNodeAtPosition(ALine, afmCreate).FNode;
|
|
ANodeForLine.EntryCapacity := FoldNodeInfoList.Count;
|
|
LineChanged := True;
|
|
end;
|
|
if NodesAddedCnt >= ANodeForLine.EntryCount then begin
|
|
Result := ANodeForLine.AddEntry;
|
|
LineNeedsReq := True;
|
|
LineChanged := True;
|
|
end
|
|
else begin
|
|
Result := nil;
|
|
|
|
// Check for comments
|
|
i := NodesAddedCnt;
|
|
while (i < ANodeForLine.EntryCount-1) do begin
|
|
e := ANodeForLine.Entry[i];
|
|
if e.StartColumn >= ALogStart then
|
|
break;
|
|
if IsCommentedIfDef(e) then begin // commented Ifdef or ElseIf
|
|
//debugln('Found commented node');
|
|
LineChanged := LineChanged or (i-1 >= NodesAddedCnt);
|
|
while i-1 >= NodesAddedCnt do begin
|
|
ANodeForLine.DeletEntry(i-1, True);
|
|
dec(i);
|
|
end;
|
|
inc(NodesAddedCnt);
|
|
e.MakeCommented;
|
|
end;
|
|
inc(i);
|
|
end;
|
|
// Check if existing node matches
|
|
if i < ANodeForLine.EntryCount then begin
|
|
Result := ANodeForLine.Entry[i];
|
|
if ( (Result.NodeType = AType) or
|
|
( (Result.NodeType = idnCommentedNode) and (Result.UncommentedNodeType = AType) )
|
|
) and
|
|
(Result.StartColumn = ALogStart) and
|
|
(Result.EndColumn = ALogEnd) and
|
|
((idnMultiLineTag in Result.NodeFlags) = (ALineOffs > 0)) and
|
|
( (ALineOffs = 0) or (ALineOffs = ANodeForLine.LastEntryEndLineOffs) )
|
|
then begin
|
|
// Does match exactly, keep as is
|
|
//DebugLn(['++++ KEEPING NODE ++++ ', ALine, ' ', dbgs(AType), ': ', ALogStart, ' - ', ALogEnd]);
|
|
Result.MakeUnCommented;
|
|
if not LineNeedsReq then
|
|
LineNeedsReq := Result.NeedsRequesting;
|
|
if i > NodesAddedCnt then begin
|
|
// Delete the skipped notes
|
|
dec(i);
|
|
LineChanged := LineChanged or (i >= NodesAddedCnt);
|
|
while i >= NodesAddedCnt do begin
|
|
ANodeForLine.DeletEntry(i, True);
|
|
dec(i);
|
|
end;
|
|
end;
|
|
end
|
|
else
|
|
Result := nil;
|
|
end;
|
|
|
|
If Result = nil then begin
|
|
// No matching node found
|
|
LineChanged := True;
|
|
if ANodeForLine.Entry[NodesAddedCnt].StartColumn < ALogEnd then
|
|
Result := ANodeForLine.Entry[NodesAddedCnt]
|
|
else
|
|
Result := ANodeForLine.AddEntry(NodesAddedCnt);
|
|
Result.ClearAll;
|
|
Result.SetNodeType(AType);
|
|
LineNeedsReq := True;
|
|
end;
|
|
end;
|
|
if not(idnCommented in Result.FNodeFlags) then
|
|
HasUncommentedNodes := True;
|
|
inc(NodesAddedCnt);
|
|
Result.StartColumn := ALogStart;
|
|
Result.EndColumn := ALogEnd;
|
|
Result.SetNodeType(AType);
|
|
end;
|
|
|
|
var
|
|
fn, fn2: TSynFoldNodeInfo;
|
|
LogStartX, LogEndX, LineOffs: Integer;
|
|
Entry: TSynMarkupHighIfDefEntry;
|
|
i, c: Integer;
|
|
//RelNestDepth, RelNestDepthNext: Integer;
|
|
NType: TSynMarkupIfdefNodeType;
|
|
begin
|
|
//NestComments := Highlighter.NestedComments;
|
|
LineNeedsReq := False;
|
|
LineChanged := False;
|
|
HasUncommentedNodes := False;
|
|
FoldNodeInfoList := GetHighLighterWithLines.FoldNodeInfo[ToIdx(ALine)];
|
|
FoldNodeInfoList.AddReference;
|
|
FoldNodeInfoList.ActionFilter := []; //[sfaOpen, sfaClose];
|
|
FoldNodeInfoList.GroupFilter := FOLDGROUP_IFDEF;
|
|
|
|
if (ANodeForLine <> nil) and (ANodeForLine.EntryCapacity < FoldNodeInfoList.Count) then
|
|
ANodeForLine.EntryCapacity := FoldNodeInfoList.Count;
|
|
NodesAddedCnt := 0;
|
|
LineOffs := 0;
|
|
//RelNestDepthNext := 0;
|
|
|
|
LineTextLower := LowerCase(Lines[ToIdx(ALine)]);
|
|
LineLen := Length(LineTextLower);
|
|
|
|
Entry := nil;
|
|
i := -1;
|
|
LogEndX := 0;
|
|
c := FoldNodeInfoList.Count - 1;
|
|
while i < c do begin
|
|
inc(i);
|
|
fn := FoldNodeInfoList[i];
|
|
if fn.FoldAction * [sfaInvalid, sfaLastLineClose] <> [] then
|
|
continue;
|
|
|
|
LogStartX := ToPos(fn.LogXStart)-1; // LogXStart is at "$", we need "{"
|
|
if (LogStartX < 1) or (LogStartX > LineLen) then begin
|
|
assert(false, '(LogStartX < 1) or (LogStartX > LineLen) LogX='+IntToStr(LogStartX)+ ' Line='+IntToStr(ALine)+' Txt='+LineTextLower);
|
|
continue;
|
|
end;
|
|
// assert(LogStartX >= LogEndX, 'ifdef xpos found before end of previous ifdef');
|
|
|
|
LogEndX := FindCloseCurlyBracket(LogStartX+1, LineOffs) + 1;
|
|
//RelNestDepth := RelNestDepthNext;
|
|
case TheDict.GetMatchAtChar(@LineTextLower[LogStartX], LineLen + 1 - LogStartX) of
|
|
1: // ifdef
|
|
begin
|
|
assert(sfaOpen in fn.FoldAction, 'sfaOpen in fn.FoldAction LogX='+IntToStr(LogStartX)+ ' FldAct='+dbgs(fn.FoldAction)+ ' Line='+IntToStr(ALine)+' Txt='+LineTextLower);
|
|
NType := idnIfdef;
|
|
//inc(RelNestDepthNext);
|
|
end;
|
|
2: // else
|
|
begin
|
|
assert(i < c, '$ELSE i < c LogX='+IntToStr(LogStartX)+ ' FldAct='+dbgs(fn.FoldAction)+ ' Line='+IntToStr(ALine)+' Txt='+LineTextLower);
|
|
inc(i);
|
|
fn2 := FoldNodeInfoList[i];
|
|
assert(sfaClose in fn.FoldAction, 'sfaClose in fn.FoldAction LogX='+IntToStr(LogStartX)+ ' FldAct='+dbgs(fn.FoldAction)+ ' Line='+IntToStr(ALine)+' Txt='+LineTextLower);
|
|
assert(sfaOpen in fn2.FoldAction, 'sfaOpen in fn2.FoldAction LogX='+IntToStr(LogStartX)+ ' FldAct='+dbgs(fn.FoldAction)+ ' Line='+IntToStr(ALine)+' Txt='+LineTextLower);
|
|
assert(fn.LogXStart = fn2.LogXStart, 'sfaOpen in fn2.FoldAction LogX='+IntToStr(LogStartX)+ ' FldAct='+dbgs(fn.FoldAction)+ ' Line='+IntToStr(ALine)+' Txt='+LineTextLower);
|
|
NType := idnElse;
|
|
end;
|
|
3: // endif
|
|
begin
|
|
assert(sfaClose in fn.FoldAction, 'sfaOpen in fn.FoldAction LogX='+IntToStr(LogStartX)+ ' FldAct='+dbgs(fn.FoldAction)+ ' Line='+IntToStr(ALine)+' Txt='+LineTextLower);
|
|
NType := idnEndIf;
|
|
//dec(RelNestDepthNext);
|
|
end;
|
|
4: // ElseIf
|
|
begin
|
|
assert(i < c, '$ELSE i < c LogX='+IntToStr(LogStartX)+ ' FldAct='+dbgs(fn.FoldAction)+ ' Line='+IntToStr(ALine)+' Txt='+LineTextLower);
|
|
inc(i);
|
|
fn2 := FoldNodeInfoList[i];
|
|
assert(sfaClose in fn.FoldAction, 'sfaClose in fn.FoldAction LogX='+IntToStr(LogStartX)+ ' FldAct='+dbgs(fn.FoldAction)+ ' Line='+IntToStr(ALine)+' Txt='+LineTextLower);
|
|
assert(sfaOpen in fn2.FoldAction, 'sfaOpen in fn2.FoldAction LogX='+IntToStr(LogStartX)+ ' FldAct='+dbgs(fn.FoldAction)+ ' Line='+IntToStr(ALine)+' Txt='+LineTextLower);
|
|
assert(fn.LogXStart = fn2.LogXStart, 'sfaOpen in fn2.FoldAction LogX='+IntToStr(LogStartX)+ ' FldAct='+dbgs(fn.FoldAction)+ ' Line='+IntToStr(ALine)+' Txt='+LineTextLower);
|
|
NType := idnElseIf;
|
|
end;
|
|
else
|
|
begin
|
|
assert(false, 'not found ifdef LogX='+IntToStr(LogStartX)+ ' FldAct='+dbgs(fn.FoldAction)+ ' Line='+IntToStr(ALine)+' Txt='+LineTextLower);
|
|
continue;
|
|
end;
|
|
end;
|
|
|
|
Entry := GetEntry(LogStartX, LogEndX, LineOffs, NType);
|
|
//Entry.FRelativeNestDepth := RelNestDepth;
|
|
|
|
if LineOffs > 0 then
|
|
break;
|
|
end;
|
|
|
|
// LineOffs now has the value of the last node / or zero, if there is no node
|
|
if (ANodeForLine <> nil) and (ANodeForLine.LastEntryEndLineOffs <> LineOffs) then begin
|
|
LineNeedsReq := True;
|
|
LineChanged := True;
|
|
ANodeForLine.LastEntryEndLineOffs := LineOffs;
|
|
if Entry <> nil then begin
|
|
if LineOffs > 0 then
|
|
Include(Entry.FNodeFlags, idnMultiLineTag)
|
|
else
|
|
Exclude(Entry.FNodeFlags, idnMultiLineTag);
|
|
end;
|
|
end;
|
|
|
|
FoldNodeInfoList.ReleaseReference;
|
|
if ANodeForLine <> nil then begin
|
|
Include(ANodeForLine.FLineFlags, idlValid);
|
|
if (NodesAddedCnt > 0) and LineNeedsReq then
|
|
Include(ANodeForLine.FLineFlags, idlHasUnknownNodes);
|
|
if HasUncommentedNodes then
|
|
exclude(ANodeForLine.FLineFlags, idlAllNodesCommented)
|
|
else
|
|
include(ANodeForLine.FLineFlags, idlAllNodesCommented);
|
|
// Check for commented ifdef
|
|
i := ANodeForLine.EntryCount - 1;
|
|
while i >= NodesAddedCnt do begin
|
|
if IsCommentedIfDef(ANodeForLine.Entry[i]) then begin
|
|
ANodeForLine.Entry[i].MakeCommented;
|
|
inc(NodesAddedCnt);
|
|
end
|
|
else begin
|
|
ANodeForLine.DeletEntry(i, True);
|
|
LineChanged := True;
|
|
end;
|
|
dec(i);
|
|
end;
|
|
ANodeForLine.EntryCount := NodesAddedCnt;
|
|
ANodeForLine.ReduceCapacity;
|
|
ANodeForLine.ScanEndOffs := Max(0, LineOffs-1);
|
|
end;
|
|
if LineChanged then
|
|
IncChangeStep;
|
|
end;
|
|
|
|
procedure TSynMarkupHighIfDefLinesTree.ValidateRange(AStartLine, AEndLine: Integer;
|
|
OuterLines: TLazSynEditNestedFoldsList);
|
|
var
|
|
NestList: TSynMarkupHighIfDefLinesNodeInfoList;
|
|
NotInCodeLowLevel: Integer;
|
|
|
|
procedure FixNodePeers(var ANode: TSynMarkupHighIfDefLinesNodeInfo);
|
|
begin
|
|
if ANode.EntryCount = 0 then
|
|
exit;
|
|
ConnectPeers(ANode, NestList);
|
|
NestList.PushNodeLine(ANode);
|
|
end;
|
|
|
|
procedure ApplyNotInCodeFlagToNode(var ANode: TSynMarkupHighIfDefLinesNodeInfo);
|
|
begin
|
|
if Anode.HasNode and (ANode.NestDepthAtNodeStart >= NotInCodeLowLevel) then
|
|
Include(ANode.Node.FLineFlags, idlNotInCodeToUnknown);
|
|
end;
|
|
|
|
procedure CheckNodeForAppNotInCodeFlag(var ANode: TSynMarkupHighIfDefLinesNodeInfo);
|
|
begin
|
|
if not ANode.HasNode then
|
|
exit;
|
|
if idlNotInCodeToUnknownReq in ANode.LineFlags then
|
|
NotInCodeLowLevel := Min(NotInCodeLowLevel, ANode.NestMinimumDepthAtNode);
|
|
end;
|
|
|
|
procedure FinishNodeForAppNotInCodeFlag(var ANode: TSynMarkupHighIfDefLinesNodeInfo);
|
|
begin
|
|
if not ANode.HasNode then
|
|
exit;
|
|
if idlNotInCodeToUnknownReq in ANode.LineFlags then
|
|
NotInCodeLowLevel := Min(NotInCodeLowLevel, ANode.NestMinimumDepthAtNode);
|
|
ANode.Node.FLineFlags := ANode.Node.FLineFlags - [idlNotInCodeToUnknown, idlNotInCodeToUnknownReq];
|
|
end;
|
|
|
|
procedure CheckNextNodeForEmpty(var ANode: TSynMarkupHighIfDefLinesNodeInfo;
|
|
ABeforeLineOnly: integer = -1);
|
|
var
|
|
n: TSynMarkupHighIfDefLinesNode;
|
|
begin
|
|
(* TODO: keep state info on commented nodes.*)
|
|
if not ANode.HasNode then
|
|
exit;
|
|
if (ABeforeLineOnly > 0) and (ANode.StartLine >= ABeforeLineOnly) then
|
|
exit;
|
|
MaybeValidateNode(ANode); // maybe skip node states?
|
|
while (ANode.EntryCount = 0) or (idlAllNodesCommented in ANode.LineFlags) do begin
|
|
n := ANode.Node;
|
|
ANode := ANode.Successor;
|
|
while n.EntryCount > 0 do
|
|
n.DeletEntry(0, True);
|
|
RemoveLine(n);
|
|
if not ANode.HasNode then
|
|
exit;
|
|
if (ABeforeLineOnly > 0) and (ANode.StartLine >= ABeforeLineOnly) then
|
|
exit;
|
|
MaybeValidateNode(ANode);
|
|
end;
|
|
end;
|
|
|
|
var
|
|
NextNode, Node, TmpNode: TSynMarkupHighIfDefLinesNodeInfo;
|
|
i, j, NodeValidTo: Integer;
|
|
SkipPeers: Boolean;
|
|
begin
|
|
{$IFDEF WITH_SYN_DEBUG_MARKUP_IFDEF}
|
|
DebugCurTreeForAssert := self; try
|
|
{$ENDIF}
|
|
TmpNode.FTree := Self;
|
|
|
|
NotInCodeLowLevel := MaxInt;
|
|
Node.ClearInfo;
|
|
(*** Outer Nesting ***)
|
|
OuterLines.Line := ToIdx(AStartLine);
|
|
For i := 0 to OuterLines.Count - 1 do begin
|
|
j := ToPos(OuterLines.NodeLine[i]);
|
|
Node := GetOrInsertNodeAtLine(j);
|
|
ApplyNotInCodeFlagToNode(Node);
|
|
Assert(CheckLineForNodes(j), 'CheckLineForNodes(j) : in Outer Nesting');
|
|
MaybeValidateNode(Node);
|
|
// FixNodePeers(Node);
|
|
ConnectPeers(Node, NestList, OuterLines);
|
|
FinishNodeForAppNotInCodeFlag(Node);
|
|
NestList.PushNodeLine(Node);
|
|
end;
|
|
|
|
(*** Find or create a node for StartLine ***)
|
|
// Todo use node from outer, if possible
|
|
//if (not node.HasNode) or (Node.StartLine + Node.ScanEndOffs < AStartLine) then
|
|
Node := FindNodeAtPosition(AStartLine, afmPrev); // might be multiline
|
|
MaybeValidateNode(Node);
|
|
|
|
//debugln(['Validate RANGE ', AStartLine, ' - ', AEndLine,' -- 1st node ', Node.StartLine, ' - ', Node.ScanEndLine]);
|
|
NextNode := Node.Successor;
|
|
CheckNextNodeForEmpty(NextNode, Node.LastEntryEndLine);
|
|
assert((not NextNode.HasNode) or (AStartLine < NextNode.StartLine), 'AStartLine < NextNode.StartLine');
|
|
|
|
if (not Node.HasNode) or (AStartLine > Node.ValidToLine(NextNode)) then begin
|
|
// Node does not cover startline
|
|
if NextNode.HasNode and (NextNode.EntryCount = 0) then begin
|
|
// NextNode is/was a Start-Of-Scan marker
|
|
if (not NextNode.IsValid) then begin
|
|
Node := NextNode;
|
|
Node.StartLine := AStartLine;
|
|
NextNode := Node.Successor;
|
|
end
|
|
else
|
|
if (NextNode.StartLine <= AEndLine) then begin
|
|
//CheckNodeForAppNotInCodeFlag(Node);
|
|
//ApplyNotInCodeFlagToNode(NextNode);
|
|
MaybeExtendNodeBackward(NextNode, AStartLine);
|
|
//FinishNodeForAppNotInCodeFlag(NextNode);
|
|
if NextNode.StartLine = AStartLine then begin
|
|
Node := NextNode;
|
|
NextNode := Node.Successor;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
MaybeValidateNode(Node);
|
|
CheckNextNodeForEmpty(NextNode, Node.LastEntryEndLine);
|
|
if (not Node.HasNode) or (AStartLine > Node.ValidToLine(NextNode)) then begin
|
|
Node := FindNodeAtPosition(AStartLine, afmCreate);
|
|
NextNode := Node.Successor;
|
|
end;
|
|
end;
|
|
// If node starts before AStartLine, then it must be in the OuterNodes
|
|
SkipPeers := Node.StartLine < AStartLine; // NO peer info available, if node starts before startline
|
|
ApplyNotInCodeFlagToNode(Node);
|
|
MaybeValidateNode(Node);
|
|
assert((AStartLine >= Node.StartLine) and (AStartLine <= Node.StartLine + Node.ScanEndLine), 'AStartLine is in Node');
|
|
|
|
(*** Node is at StartLine, NextNode is set --- Scan to Endline ***)
|
|
|
|
CheckNextNodeForEmpty(NextNode, Node.LastEntryEndLine);
|
|
while (Node.HasNode) and (NextNode.HasNode) and
|
|
(Node.ValidToLine(NextNode) < AEndLine) and
|
|
(NextNode.StartLine <= AEndLine)
|
|
do begin
|
|
Assert(Node.IsValid, 'Node.IsValid while "Scan to Endline"');
|
|
if not SkipPeers then
|
|
FixNodePeers(Node);
|
|
SkipPeers := False;
|
|
FinishNodeForAppNotInCodeFlag(Node);
|
|
ApplyNotInCodeFlagToNode(NextNode);
|
|
|
|
MaybeValidateNode(NextNode);
|
|
NodeValidTo := Node.ValidToLine(NextNode);
|
|
MaybeExtendNodeBackward(NextNode, NodeValidTo + 1);
|
|
assert(NextNode.StartLine > NodeValidTo, 'NextNode.StartLine > NodeValidTo');
|
|
|
|
TmpNode.ClearInfo;
|
|
MaybeExtendNodeForward(Node, TmpNode, NextNode.StartLine);
|
|
assert(NextNode.StartLine > Node.ScanEndLine, 'NextNode.StartLine > Node.ScanEndLine');
|
|
|
|
if NextNode.StartLine = Node.ScanEndLine + 1 then begin
|
|
Assert(not TmpNode.HasNode, 'not TmpNode.HasNode');
|
|
if NextNode.EntryCount = 0 then begin
|
|
// Merge nodes
|
|
Node.ScanEndOffs := Node.ScanEndOffs + NextNode.ScanEndOffs + 1;
|
|
RemoveLine(NextNode.FNode);
|
|
NextNode := Node.Successor;
|
|
// Do NOT FixNodePeers again for Node
|
|
SkipPeers := True;
|
|
end
|
|
else begin
|
|
Node := NextNode;
|
|
NextNode := Node.Successor;
|
|
end;
|
|
CheckNextNodeForEmpty(NextNode, Node.LastEntryEndLine);
|
|
continue;
|
|
end;
|
|
|
|
// scan gap
|
|
Assert(TmpNode.HasNode, 'TmpNode.HasNode');
|
|
Assert(NextNode.EntryCount > 0, 'NextNode.EntryCount > 0');
|
|
Node := TmpNode;
|
|
CheckNextNodeForEmpty(NextNode, Node.LastEntryEndLine);
|
|
if not NextNode.HasNode then
|
|
break;
|
|
TmpNode.ClearInfo;
|
|
while Node.ScanEndLine + 1 < NextNode.StartLine do begin
|
|
MaybeExtendNodeForward(Node, TmpNode, NextNode.StartLine);
|
|
if not TmpNode.HasNode then
|
|
break;
|
|
FixNodePeers(Node);
|
|
assert(Node.ScanEndLine + 1 < NextNode.StartLine, 'Scan gap still before next node');
|
|
Node := TmpNode;
|
|
TmpNode.ClearInfo;
|
|
end;
|
|
assert(Node.ScanEndLine + 1 = NextNode.StartLine, 'Scan gap has reached next node');
|
|
assert(NextNode.Node = Node.Successor.Node, 'NextNode = Node.Successor');
|
|
assert(NextNode.StartLine = Node.Successor.StartLine, 'NextNode = Node.Successor / start');
|
|
NextNode := Node.Successor; // TODO: not needed
|
|
CheckNextNodeForEmpty(NextNode, Node.LastEntryEndLine);
|
|
|
|
end;
|
|
|
|
assert(Node.HasNode);
|
|
if not SkipPeers then
|
|
FixNodePeers(Node);
|
|
FinishNodeForAppNotInCodeFlag(Node);
|
|
|
|
|
|
while Node.ScanEndLine < AEndLine do begin
|
|
MaybeExtendNodeForward(Node, TmpNode, AEndLine + 1);
|
|
if not TmpNode.HasNode then
|
|
break;
|
|
assert(Node.ScanEndLine < AEndLine, 'Scan gap still before AEndLine');
|
|
Node := TmpNode;
|
|
TmpNode.ClearInfo;
|
|
|
|
FixNodePeers(Node);
|
|
end;
|
|
assert(Node.ScanEndLine >= AEndLine, 'Scan gap has reached AEndLine');
|
|
|
|
// Check for existing nodes nested in NotInCodeLowLevel
|
|
if NotInCodeLowLevel < MaxInt then begin
|
|
Node := Node.Successor;
|
|
while Node.HasNode do begin
|
|
if Node.NestDepthAtNodeStart >= NotInCodeLowLevel then begin
|
|
ApplyNotInCodeFlagToNode(Node);
|
|
MaybeValidateNode(Node);
|
|
FinishNodeForAppNotInCodeFlag(Node);
|
|
end;
|
|
Node := Node.Successor;
|
|
end;
|
|
end;
|
|
|
|
{$IFDEF WITH_SYN_DEBUG_MARKUP_IFDEF}
|
|
finally DebugCurTreeForAssert := nil; end;
|
|
{$ENDIF}
|
|
end;
|
|
|
|
procedure TSynMarkupHighIfDefLinesTree.SetNodeState(ALinePos, AstartPos: Integer;
|
|
AState: TSynMarkupIfdefNodeState);
|
|
var
|
|
Node: TSynMarkupHighIfDefLinesNodeInfo;
|
|
e, e2: TSynMarkupHighIfDefEntry;
|
|
LineNeedReq: Boolean;
|
|
i: Integer;
|
|
begin
|
|
Node := FindNodeAtPosition(ALinePos, afmNil);
|
|
if not (Node.HasNode and Node.IsValid) then begin
|
|
ScanLine(ALinePos, Node.FNode, True);
|
|
if Node.HasNode then begin
|
|
Node.FStartLine := ALinePos; // directly to field
|
|
end
|
|
else begin
|
|
DebugLn([ 'SetNodeState did not find a node (ScanLine) ', ALinePos, '/', 'AstartPos', AstartPos, ' ', dbgs(AState), ' #',copy(Lines[ALinePos-1],1,20)]);
|
|
//assert(false, 'SetNodeState did not find a node (ScanLine)');
|
|
exit;
|
|
end;
|
|
end;
|
|
|
|
i := Node.EntryCount;
|
|
if i = 0 then begin DebugLn(['SetNodeState did not find a node (zero entries)', ALinePos, '/', 'AstartPos', AstartPos, ' ', dbgs(AState), ' #',copy(Lines[ALinePos-1],1,20)]); exit; end;
|
|
//assert(i > 0, 'SetNodeState did not find a node (zero entries)');
|
|
e := nil;
|
|
LineNeedReq := False;
|
|
repeat
|
|
dec(i);
|
|
e2 := Node.Entry[i];
|
|
if e2.StartColumn = AstartPos then
|
|
e := e2
|
|
else
|
|
LineNeedReq := LineNeedReq or e2.NeedsRequesting;
|
|
until (i = 0) or (e <> nil);
|
|
|
|
//assert(e <> nil, 'SetNodeState did not find a node (no matching entry)');
|
|
if (e = nil) then DebugLn([ 'SetNodeState did not find a matching node ', ALinePos, '/', 'AstartPos',AstartPos, ' ', dbgs(AState), ' #',copy(Lines[ALinePos-1],1,20)]);
|
|
//assert(e.NodeType in [idnIfdef, idnElseIf], 'SetNodeState did not find a node (e.NodeType <> idnIfdef)');
|
|
//if (e = nil) or not(e.NodeType in [idnIfdef, idnElseIf]) then
|
|
if (e <> nil) and (e.NodeType in [idnCommentedNode]) then begin
|
|
DebugLn([ 'SetNodeState did find a COMMENTED node ', ALinePos, '/', 'AstartPos',AstartPos, ' ', dbgs(AState), ' #',copy(Lines[ALinePos-1],1,20)]);
|
|
exit;
|
|
end;
|
|
if (e = nil) then
|
|
exit;
|
|
|
|
if e.NodeState <> AState then begin
|
|
IncChangeStep;
|
|
if FLockTreeCount = 0 then
|
|
FNotifyLists[itnChanged].CallNotifyEvents(Self);
|
|
//DebugLn([ 'SetNodeState ', ALinePos, '/', 'AstartPos', AstartPos, ' ', dbgs(AState)]);
|
|
end;
|
|
e.NodeState := AState;
|
|
e.StateByUser := True; // ignored by ifdef
|
|
|
|
dec(i); // Assume the node, just set does not need requesting
|
|
while (i >= 0) and (not LineNeedReq) do begin
|
|
e2 := Node.Entry[i];
|
|
LineNeedReq := LineNeedReq or e2.NeedsRequesting;
|
|
dec(i);
|
|
end;
|
|
|
|
if not LineNeedReq then
|
|
Exclude(Node.Node.FLineFlags, idlHasUnknownNodes);
|
|
end;
|
|
|
|
procedure TSynMarkupHighIfDefLinesTree.DebugPrint(Flat: Boolean);
|
|
function PeerLine(AEntry: TSynMarkupHighIfDefEntry): string;
|
|
begin
|
|
if AEntry = nil then exit('- ');
|
|
Result := Format('%2d/%2d (%10s)', [AEntry.Line.GetPosition, AEntry.StartColumn, dbgs(PtrUInt(AEntry))]);
|
|
end;
|
|
|
|
function PeerLines(AEntry: TSynMarkupHighIfDefEntry): string;
|
|
begin
|
|
Result := '>>p1: ' + PeerLine(AEntry.FPeers[idpOpeningPeer]) +
|
|
' >>p2: ' + PeerLine(AEntry.FPeers[idpClosingPeer]);
|
|
end;
|
|
|
|
procedure DebugPrintNode(ANode: TSynMarkupHighIfDefLinesNode; PreFix, PreFixOne: String);
|
|
function DbgsLine(ANode: TSynMarkupHighIfDefLinesNode): String;
|
|
begin
|
|
Result := 'nil';
|
|
if ANode <> nil then Result := IntToStr(ANode.GetPosition);
|
|
end;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
DebugLn([PreFixOne, 'Line=', ANode.GetPosition, ' ScannedTo=', ANode.ScanEndOffs,
|
|
' Cnt=', ANode.EntryCount,
|
|
' EndLine=', ANode.LastEntryEndLineOffs,
|
|
' Flags=', dbgs(ANode.LineFlags),
|
|
' D-Open=', ANode.DisabledEntryOpenCount,
|
|
' D-Close=', ANode.DisabledEntryCloseCount
|
|
]);
|
|
for i := 0 to ANode.EntryCount - 1 do
|
|
DebugLn(Format('%s%s%s x1-x2=%2d - %2d %6s %8s flg=%12s %s',
|
|
[PreFix, ' # ', dbgs(PtrUInt(ANode.Entry[i])),
|
|
ANode.Entry[i].StartColumn, ANode.Entry[i].EndColumn,
|
|
dbgs(ANode.Entry[i].NodeType),
|
|
dbgs(ANode.Entry[i].NodeState),
|
|
dbgs(ANode.Entry[i].NodeFlags),
|
|
PeerLines(ANode.Entry[i])
|
|
//' p1=', PeerLine(ANode.Entry[i].FPeer1),
|
|
//' p2=', PeerLine(ANode.Entry[i].FPeer2)
|
|
]));
|
|
if Flat then
|
|
exit;
|
|
if ANode.FLeft <> nil then
|
|
DebugPrintNode(TSynMarkupHighIfDefLinesNode(ANode.FLeft), PreFix+' ', PreFix + 'L: ');
|
|
if ANode.FRight <> nil then
|
|
DebugPrintNode(TSynMarkupHighIfDefLinesNode(ANode.FRight), PreFix+' ', PreFix + 'R: ');
|
|
end;
|
|
var
|
|
i: Integer;
|
|
n: TSynMarkupHighIfDefLinesNode;
|
|
begin
|
|
if Flat then begin
|
|
i := 1;
|
|
n := TSynMarkupHighIfDefLinesNode(First);
|
|
while n <> nil do begin
|
|
DebugPrintNode(n, ' ', format('%3d ', [i]));
|
|
n := TSynMarkupHighIfDefLinesNode(n.Successor);
|
|
inc(i);
|
|
end;
|
|
end
|
|
else
|
|
DebugPrintNode(TSynMarkupHighIfDefLinesNode(FRoot), '', '');
|
|
end;
|
|
|
|
procedure TSynMarkupHighIfDefLinesTree.RegisterNotification(AReason: TSynMarkupHighIfDefTreeNotifications;
|
|
AHandler: TNotifyEvent);
|
|
begin
|
|
FNotifyLists[AReason].Add(TMethod(AHandler));
|
|
end;
|
|
|
|
procedure TSynMarkupHighIfDefLinesTree.UnRegisterNotification(AReason: TSynMarkupHighIfDefTreeNotifications;
|
|
AHandler: TNotifyEvent);
|
|
begin
|
|
FNotifyLists[AReason].Remove(TMethod(AHandler));
|
|
end;
|
|
|
|
procedure TSynMarkupHighIfDefLinesTree.LockTree;
|
|
begin
|
|
inc(FLockTreeCount);
|
|
end;
|
|
|
|
procedure TSynMarkupHighIfDefLinesTree.UnLockTree;
|
|
begin
|
|
assert(FLockTreeCount > 0, 'UnLockTree < 0');
|
|
if FLockTreeCount = 1 then
|
|
FNotifyLists[itnUnlocking].CallNotifyEvents(Self);
|
|
dec(FLockTreeCount);
|
|
if FLockTreeCount = 0 then
|
|
FNotifyLists[itnUnlocked].CallNotifyEvents(Self);
|
|
end;
|
|
|
|
procedure TSynMarkupHighIfDefLinesTree.Clear;
|
|
var
|
|
n: TSynSizedDifferentialAVLNode;
|
|
begin
|
|
FClearing := True;
|
|
inherited Clear;
|
|
while FDisposedNodes <> nil do begin
|
|
n := FDisposedNodes;
|
|
FDisposedNodes := TSynMarkupHighIfDefLinesNode(n).NextDispose;
|
|
n.Free;
|
|
end;
|
|
FClearing := False;
|
|
end;
|
|
|
|
function TSynMarkupHighIfDefLinesTree.FindNodeAtPosition(ALine: Integer;
|
|
AMode: TSynSizedDiffAVLFindMode): TSynMarkupHighIfDefLinesNodeInfo;
|
|
begin
|
|
Result{%H-}.ClearInfo;
|
|
Result.FTree := Self;
|
|
Result.FNode :=
|
|
TSynMarkupHighIfDefLinesNode(FindNodeAtPosition(ALine, AMode,
|
|
Result.FStartLine, Result.Index));
|
|
if Result.FNode = nil then begin
|
|
Result.FAtBOL := AMode = afmPrev;
|
|
Result.FAtEOL := AMode = afmNext;
|
|
end;
|
|
end;
|
|
|
|
{ TSynEditMarkupIfDef }
|
|
|
|
procedure TSynEditMarkupIfDef.DoTreeUnlocking(Sender: TObject);
|
|
var
|
|
LastLine: Integer;
|
|
begin
|
|
Assert(FPaintLock = 0, 'DoTreeUnlocked in paintlock');
|
|
|
|
if (not SynEdit.IsVisible) or (not HasEnabledMarkup) or (Highlighter = nil) then
|
|
exit;
|
|
|
|
LastLine := ScreenRowToRow(LinesInWindow+1);
|
|
FAdjustedTop := ToIdx(TopLine);
|
|
PrepareHighlighter;
|
|
|
|
if FMarkupNodes.HasEnabledMarkup then
|
|
while (FAdjustedTop > 0) and Highlighter.IsLineStartingInDirective(FAdjustedTop) and
|
|
(not (Highlighter.FoldBlockMinLevel(FAdjustedTop, FOLDGROUP_IFDEF, [sfbIncludeDisabled]) <
|
|
Highlighter.FoldBlockEndLevel(FAdjustedTop, FOLDGROUP_IFDEF, [sfbIncludeDisabled]) ))
|
|
do
|
|
dec(FAdjustedTop);
|
|
FAdjustedTop := ToPos(FAdjustedTop);
|
|
if FAdjustedTop <> TopLine then DebugLn(['FAdjustedTop=', FAdjustedTop, ' top=', TopLine]);
|
|
|
|
if (FLastValidTopLine <= TopLine) and (FLastValidLastLine >= LastLine) and
|
|
(FLastValidTreeStep = FIfDefTree.ChangeStep)
|
|
then
|
|
exit;
|
|
|
|
// TODO: assert synedit does not change
|
|
//DebugLn(['TSynEditMarkupIfDef.DoTreeUnlocking', TopLine, ' - ', LastLine]);
|
|
FOuterLines.Clear; // TODO: invalidate acording to actual lines edited
|
|
FIfDefTree.ValidateRange(TopLine, LastLine, FOuterLines);
|
|
end;
|
|
|
|
procedure TSynEditMarkupIfDef.DoTreeUnlocked(Sender: TObject);
|
|
var
|
|
LastLine: Integer;
|
|
|
|
function IndexOfLastNodeAtLevel(ALevel: Integer; ANode: TSynMarkupHighIfDefLinesNode;
|
|
ADownFromIndex: Integer; var ADownFromLevel: Integer): Integer;
|
|
begin
|
|
Result := ADownFromIndex;
|
|
repeat
|
|
case ANode.Entry[Result].NodeType of
|
|
idnIfdef: dec(ADownFromLevel);
|
|
idnEndIf: inc(ADownFromLevel);
|
|
idnElse, idnElseIf:
|
|
if ADownFromLevel <= ALevel + 1 then
|
|
dec(ADownFromLevel);
|
|
end;
|
|
if (ADownFromLevel <= ALevel) then
|
|
exit;
|
|
dec(Result);
|
|
until (Result < 0);
|
|
end;
|
|
|
|
function GetValidClosingPeer(AEntry: TSynMarkupHighIfDefEntry): TSynMarkupHighIfDefEntry;
|
|
begin
|
|
Result := AEntry.ClosingPeer;
|
|
// if the Result line is not valid, then it ends after the visible area (otherwise it would have a valid line)
|
|
If (Result <> nil) and not(idlValid in Result.Line.LineFlags) then
|
|
Result := nil;
|
|
end;
|
|
|
|
function EntryToPointAtBegin(Entry: TSynMarkupHighIfDefEntry; Line: Integer): TPoint;
|
|
begin
|
|
Result.y := Line;
|
|
Result.x := Entry.StartColumn;
|
|
end;
|
|
|
|
function EntryToPointAtEnd(Entry: TSynMarkupHighIfDefEntry; Line: Integer): TPoint;
|
|
begin
|
|
Result.y := Line;
|
|
if idnMultiLineTag in Entry.NodeFlags then
|
|
Result.y := Line + Entry.Line.LastEntryEndLineOffs;
|
|
Result.x := Entry.EndColumn;
|
|
end;
|
|
|
|
var
|
|
ScanNodes, ScanDisNode, ScanEnaNode, ScanTmpDisNode, ScanTmpEnaNode: Boolean;
|
|
|
|
procedure AddNodeMatch(Entry1: TSynMarkupHighIfDefEntry; Line1: Integer);
|
|
var
|
|
P1, P2: TPoint;
|
|
i: Integer;
|
|
begin
|
|
P1 := EntryToPointAtBegin(Entry1, Line1);
|
|
P2 := EntryToPointAtEnd(Entry1, Line1);
|
|
i := 0;
|
|
|
|
if Entry1.IsOpening then begin
|
|
if Entry1.IsDisabled then begin
|
|
if Entry1.IsTemp then begin
|
|
if ScanTmpDisNode then i := i or MARKUP_TEMP_DISABLED;
|
|
end else begin
|
|
if ScanDisNode then i := i or MARKUP_DISABLED;
|
|
end;
|
|
end
|
|
else if Entry1.IsEnabled then begin
|
|
if Entry1.IsTemp then begin
|
|
if ScanTmpEnaNode then i := i or MARKUP_TEMP_ENABLED;
|
|
end else begin
|
|
if ScanEnaNode then i := i or MARKUP_ENABLED;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
if Entry1.IsClosing and (not MarkOnlyOpeningNodes) then begin
|
|
if Entry1.HasDisabledOpening then begin
|
|
if Entry1.IsTempClosing then begin
|
|
if ScanTmpDisNode then i := i or MARKUP_TEMP_DISABLED;
|
|
end else begin
|
|
if ScanDisNode then i := i or MARKUP_DISABLED;
|
|
end;
|
|
end
|
|
else if Entry1.HasEnabledOpening then begin
|
|
if Entry1.IsTempClosing then begin
|
|
if ScanTmpEnaNode then i := i or MARKUP_TEMP_ENABLED;
|
|
end else begin
|
|
if ScanEnaNode then i := i or MARKUP_ENABLED;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
if i <> 0 then
|
|
FMarkupNodes.AddMatch(P1, P2, False, False, i)
|
|
end;
|
|
|
|
procedure AddRangeMatch(Entry1: TSynMarkupHighIfDefEntry; Line1: Integer;
|
|
Entry2: TSynMarkupHighIfDefEntry; Line2: Integer);
|
|
var
|
|
P1, P2: TPoint;
|
|
begin
|
|
P1 := EntryToPointAtEnd(Entry1, Line1);
|
|
if Entry2 <> nil then
|
|
P2 := EntryToPointAtBegin(Entry2, Line2)
|
|
else
|
|
P2 := point(1, LastLine+1);
|
|
|
|
// Entry1 always is an opening node.
|
|
if (Entry1.IsDisabled) then begin
|
|
if (Entry1.IsTemp) then
|
|
FMarkupTemp.AddMatch(P1, P2, FAlse, Entry2=nil, MARKUP_DEFAULT)
|
|
else
|
|
AddMatch(P1, P2, False, Entry2=nil, MARKUP_DEFAULT);
|
|
end
|
|
else if (Entry1.IsEnabled) then begin
|
|
if (Entry1.IsTemp) then
|
|
FMarkupEnabledTemp.AddMatch(P1, P2, FAlse, Entry2=nil, MARKUP_DEFAULT)
|
|
else
|
|
FMarkupEnabled.AddMatch(P1, P2, False, Entry2=nil, MARKUP_DEFAULT);
|
|
end;
|
|
end;
|
|
|
|
procedure AddRangeIfNotNil(AnOpenEntry: TSynMarkupHighIfDefEntry; AnOpenLine: Integer;
|
|
out AClosePeerEntry: TSynMarkupHighIfDefEntry; out AClosePeerLine: Integer);
|
|
begin
|
|
AClosePeerEntry := nil;
|
|
AClosePeerLine := -1;
|
|
if AnOpenEntry = nil then
|
|
exit;
|
|
AClosePeerEntry := GetValidClosingPeer(AnOpenEntry);
|
|
If (AClosePeerEntry <> nil) then // Disabled to end of display or beyond (end of validated)
|
|
AClosePeerLine := AClosePeerEntry.Line.GetPosition;
|
|
assert((AClosePeerEntry = nil) or ((AClosePeerLine >= FAdjustedTop)), 'outernode finish early'); // Should not reach here
|
|
|
|
AddRangeMatch(AnOpenEntry, AnOpenLine, AClosePeerEntry, AClosePeerLine);
|
|
end;
|
|
|
|
var
|
|
ScanDisRange, ScanEnaRange, ScanTmpDisRange, ScanTmpEnaRange: Boolean;
|
|
DisableOpenEntry, DisabledCloseEntry, EnableOpenEntry, EnableCloseEntry: TSynMarkupHighIfDefEntry;
|
|
TmpDisableOpenEntry, TmpDisabledCloseEntry, TmpEnableOpenEntry, TmpEnableCloseEntry: TSynMarkupHighIfDefEntry;
|
|
DisabledCloseLinePos, EnableCloseLinePos, TmpDisabledCloseLinePos, TmpEnableCloseLinePos: Integer;
|
|
|
|
function NeedDisableOpen: Boolean;
|
|
begin
|
|
Result := ScanDisRange and (DisableOpenEntry = nil);
|
|
end;
|
|
function NeedEnableOpen: Boolean;
|
|
begin
|
|
Result := ScanEnaRange and (EnableOpenEntry = nil) and
|
|
(DisableOpenEntry = nil) and (TmpDisableOpenEntry = nil); // Do not show enblade, intside a disabled
|
|
end;
|
|
function NeedTmpDisableOpen: Boolean;
|
|
begin
|
|
Result := ScanTmpDisRange and (TmpDisableOpenEntry = nil);
|
|
end;
|
|
function NeedTmpEnableOpen: Boolean;
|
|
begin
|
|
Result := ScanTmpEnaRange and (TmpEnableOpenEntry = nil) and
|
|
(DisableOpenEntry = nil) and (TmpDisableOpenEntry = nil);
|
|
end;
|
|
|
|
procedure SetNil(var AOpenEntry, ACloseEntry: TSynMarkupHighIfDefEntry;
|
|
var ALine: Integer);
|
|
begin
|
|
AOpenEntry := nil;
|
|
ACloseEntry := nil;
|
|
ALine := 0;
|
|
end;
|
|
|
|
var
|
|
CacheIdx: Array[0..3] of Integer;
|
|
CacheEntry: Array[0..3] of TSynMarkupHighIfDefEntry;
|
|
|
|
procedure ClearIdxCache;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
for i := 0 to 3 do CacheEntry[i] := nil;
|
|
end;
|
|
function IndexOfEntry(AEntry: TSynMarkupHighIfDefEntry; ACache: Integer): Integer;
|
|
begin
|
|
if CacheEntry[ACache] = AEntry then exit(CacheIdx[ACache]);
|
|
Result := AEntry.Line.IndexOf(AEntry);
|
|
CacheEntry[ACache] := AEntry;
|
|
CacheIdx[ACache] := Result;
|
|
end;
|
|
function IndexOfDisabledClose: Integer; // cache 0
|
|
begin
|
|
Result := IndexOfEntry(DisabledCloseEntry, 0);
|
|
end;
|
|
function IndexOfEnableClose: Integer; // cache 1
|
|
begin
|
|
Result := IndexOfEntry(EnableCloseEntry, 1);
|
|
end;
|
|
function IndexOfTmpDisabledClose: Integer; // cache 2
|
|
begin
|
|
Result := IndexOfEntry(TmpDisabledCloseEntry, 2);
|
|
end;
|
|
function IndexOfTmpEnableClose: Integer; // cache 3
|
|
begin
|
|
Result := IndexOfEntry(TmpEnableCloseEntry, 3);
|
|
end;
|
|
|
|
procedure FindNextScanPos(var ANodeInfo: TSynMarkupHighIfDefLinesNodeInfo;
|
|
var AFirstIdx: Integer);
|
|
var
|
|
CurFoundLine: Integer;
|
|
procedure SetResultFor(AEntry: TSynMarkupHighIfDefEntry; ALine: Integer);
|
|
begin
|
|
CurFoundLine := ALine;
|
|
Assert((not ANodeInfo.HasNode) or (ANodeInfo.StartLine <= ALine), 'FindNextScanPos goes backward');
|
|
ANodeInfo.InitForNode(AEntry.Line, ALine);
|
|
AFirstIdx := ANodeInfo.Node.IndexOf(DisabledCloseEntry) + 1;
|
|
end;
|
|
procedure MaybeSetNil(var AOpenEntry, ACloseEntry: TSynMarkupHighIfDefEntry;
|
|
var ALine: Integer; ACacheNo: Integer);
|
|
begin
|
|
if (ALine < CurFoundLine) or
|
|
((ALine = CurFoundLine) and (IndexOfEntry(ACloseEntry, ACacheNo) < AFirstIdx))
|
|
then begin
|
|
AOpenEntry := nil;
|
|
ACloseEntry := nil;
|
|
ALine := 0;
|
|
end;
|
|
end;
|
|
begin
|
|
if NeedDisableOpen or NeedEnableOpen or NeedTmpDisableOpen or NeedTmpEnableOpen or
|
|
ScanNodes
|
|
then
|
|
exit; // can not skip ahead
|
|
|
|
// assert: all nodes must end after ANodeInfo/AFirstIdx
|
|
CurFoundLine := -1;
|
|
|
|
if ScanDisNode and (DisabledCloseEntry <> nil) then
|
|
SetResultFor(DisabledCloseEntry, DisabledCloseLinePos);
|
|
|
|
if ScanEnaNode and (EnableCloseEntry <> nil) then begin
|
|
if (CurFoundLine < 0) or (EnableCloseLinePos < CurFoundLine) then begin
|
|
SetResultFor(EnableCloseEntry, EnableCloseLinePos);
|
|
end
|
|
else
|
|
if (EnableCloseLinePos = CurFoundLine) then begin
|
|
assert(ANodeInfo.HasNode and (ANodeInfo.Node = EnableCloseEntry.Line), 'ANodeInfo.HasNode and (ANodeInfo.Node = EnableCloseEntry.Line)');
|
|
if IndexOfEnableClose < AFirstIdx then AFirstIdx := IndexOfEnableClose;
|
|
end;
|
|
end;
|
|
|
|
if ScanTmpDisNode and (TmpDisabledCloseEntry <> nil) then begin
|
|
if (CurFoundLine < 0) or (TmpDisabledCloseLinePos < CurFoundLine) then begin
|
|
SetResultFor(TmpDisabledCloseEntry, TmpDisabledCloseLinePos);
|
|
end
|
|
else
|
|
if (TmpDisabledCloseLinePos = CurFoundLine) then begin
|
|
assert(ANodeInfo.HasNode and (ANodeInfo.Node = TmpDisabledCloseEntry.Line), 'ANodeInfo.HasNode and (ANodeInfo.Node = TmpDisabledCloseEntry.Line)');
|
|
if IndexOfTmpDisabledClose < AFirstIdx then AFirstIdx := IndexOfTmpDisabledClose;
|
|
end;
|
|
end;
|
|
|
|
if ScanTmpEnaNode and (TmpEnableCloseEntry <> nil) then begin
|
|
if (CurFoundLine < 0) or (TmpEnableCloseLinePos < CurFoundLine) then begin
|
|
SetResultFor(TmpEnableCloseEntry, TmpEnableCloseLinePos);
|
|
end
|
|
else
|
|
if (TmpEnableCloseLinePos = CurFoundLine) then begin
|
|
assert(ANodeInfo.HasNode and (ANodeInfo.Node = TmpEnableCloseEntry.Line), 'ANodeInfo.HasNode and (ANodeInfo.Node = TmpEnableCloseEntry.Line)');
|
|
if IndexOfTmpEnableClose < AFirstIdx then AFirstIdx := IndexOfTmpEnableClose;
|
|
end;
|
|
end;
|
|
|
|
if CurFoundLine < 0 then begin
|
|
ANodeInfo.ClearInfo; // FStartLine = 0
|
|
ANodeInfo.FStartLine := -1; // all nodes, end behind visible area
|
|
end
|
|
else begin
|
|
MaybeSetNil(DisableOpenEntry, DisabledCloseEntry, DisabledCloseLinePos, 0);
|
|
MaybeSetNil(EnableOpenEntry, EnableCloseEntry, EnableCloseLinePos, 1);
|
|
MaybeSetNil(TmpDisableOpenEntry, TmpDisabledCloseEntry, TmpDisabledCloseLinePos, 2);
|
|
MaybeSetNil(TmpEnableOpenEntry, TmpEnableCloseEntry, TmpEnableCloseLinePos, 3);
|
|
end;
|
|
end;
|
|
|
|
var
|
|
NestLvl, NestLvlLow, FoundLvl, CurLine, EndLvl, FirstEntryIdx: Integer;
|
|
j: Integer;
|
|
NodeInfo: TSynMarkupHighIfDefLinesNodeInfo;
|
|
Node: TSynMarkupHighIfDefLinesNode;
|
|
Entry: TSynMarkupHighIfDefEntry;
|
|
EntryFound: Boolean;
|
|
begin
|
|
Assert(FPaintLock = 0, 'DoTreeUnlocked in paintlock');
|
|
|
|
if (not SynEdit.IsVisible) then
|
|
exit;
|
|
if (Highlighter = nil) or (not HasEnabledMarkup) then begin
|
|
FIfDefTree.Clear;
|
|
if Matches.Count > 0 then
|
|
InvalidateSynLines(TopLine, ScreenRowToRow(LinesInWindow+1));
|
|
Matches.Count := 0;
|
|
exit;
|
|
end;
|
|
|
|
PrepareHighlighter;
|
|
LastLine := ScreenRowToRow(LinesInWindow+1);
|
|
if (FLastValidTopLine <= TopLine) and (FLastValidLastLine >= LastLine) and
|
|
(FLastValidTreeStep = FIfDefTree.ChangeStep)
|
|
then
|
|
exit;
|
|
|
|
FLastValidTopLine := TopLine;
|
|
FLastValidLastLine := LastLine;
|
|
FLastValidTreeStep := FIfDefTree.ChangeStep;
|
|
|
|
{$IFDEF WITH_SYN_DEBUG_MARKUP_IFDEF}
|
|
try
|
|
DebugCurTreeForAssert := FIfDefTree; try
|
|
{$ENDIF}
|
|
//debugln(['TSynEditMarkupIfDef.DoTreeUnlocked ', TopLine, ' - ', LastLine]);
|
|
|
|
ScanDisRange := MarkupInfoDisabled.IsEnabled;
|
|
ScanEnaRange := MarkupInfoEnabled.IsEnabled;
|
|
ScanTmpDisRange := MarkupInfoTempDisabled.IsEnabled;
|
|
ScanTmpEnaRange := MarkupInfoTempEnabled.IsEnabled;
|
|
|
|
ScanDisNode := MarkupInfoNodeDisabled.IsEnabled;
|
|
ScanEnaNode := MarkupInfoNodeEnabled.IsEnabled;
|
|
ScanTmpDisNode := MarkupInfoTempNodeDisabled.IsEnabled;
|
|
ScanTmpEnaNode := MarkupInfoTempNodeEnabled.IsEnabled;
|
|
ScanNodes := ScanDisNode or ScanEnaNode or ScanTmpDisNode or ScanTmpEnaNode;
|
|
|
|
StartMatchScan;
|
|
FMarkupEnabled.StartMatchScan;
|
|
FMarkupTemp.StartMatchScan;
|
|
FMarkupEnabledTemp.StartMatchScan;
|
|
FMarkupNodes.StartMatchScan;
|
|
ClearIdxCache;
|
|
|
|
// *** Check outerlines, for node that goes into visible area
|
|
DisableOpenEntry := nil;
|
|
EnableOpenEntry := nil;
|
|
TmpDisableOpenEntry := nil;
|
|
TmpEnableOpenEntry := nil;
|
|
DisabledCloseEntry := nil;
|
|
EnableCloseEntry := nil;
|
|
TmpDisabledCloseEntry := nil;
|
|
TmpEnableCloseEntry := nil;
|
|
|
|
NestLvl := -1;
|
|
while (NestLvl < FOuterLines.Count - 1) and
|
|
(NeedDisableOpen or NeedEnableOpen or NeedTmpDisableOpen or NeedTmpDisableOpen)
|
|
do begin
|
|
inc(NestLvl);
|
|
|
|
CurLine := ToPos(FOuterLines.NodeLine[NestLvl]);
|
|
NestLvlLow := NestLvl;
|
|
while (NestLvl < FOuterLines.Count - 1) and (ToPos(FOuterLines.NodeLine[NestLvl + 1]) = CurLine) do
|
|
inc(NestLvl);
|
|
|
|
NodeInfo := FIfDefTree.FindNodeAtPosition(CurLine, afmNil);
|
|
Node := NodeInfo.Node;
|
|
assert(NodeInfo.HasNode, 'ValidateMatches: No node for outerline');
|
|
|
|
//if NodeInfo.Node.DisabledEntryOpenCount <= NodeInfo.Node.DisabledEntryCloseCount then
|
|
// continue;
|
|
|
|
EndLvl := NodeInfo.NestDepthAtNodeEnd;
|
|
FoundLvl := NestLvl;
|
|
j := IndexOfLastNodeAtLevel(FoundLvl, Node, Node.EntryCount - 1, EndLvl);
|
|
while (j >= 0) do begin
|
|
Entry := Node.Entry[j];
|
|
dec(FoundLvl);
|
|
if Entry.IsTempOpening then begin
|
|
if Entry.IsDisabledOpening and NeedTmpDisableOpen then TmpDisableOpenEntry := Entry;
|
|
if Entry.IsEnabled and NeedTmpEnableOpen then TmpEnableOpenEntry := Entry;
|
|
end else begin
|
|
if Entry.IsDisabledOpening and NeedDisableOpen then DisableOpenEntry := Entry;
|
|
if Entry.IsEnabled and NeedEnableOpen then EnableOpenEntry := Entry;
|
|
end;
|
|
if FoundLvl < NestLvlLow then
|
|
break;
|
|
j := IndexOfLastNodeAtLevel(FoundLvl, Node, j, EndLvl);
|
|
end;
|
|
end; // while
|
|
|
|
AddRangeIfNotNil(DisableOpenEntry, CurLine, DisabledCloseEntry, DisabledCloseLinePos);
|
|
AddRangeIfNotNil(EnableOpenEntry, CurLine, EnableCloseEntry, EnableCloseLinePos);
|
|
AddRangeIfNotNil(TmpDisableOpenEntry, CurLine, TmpDisabledCloseEntry, TmpDisabledCloseLinePos);
|
|
AddRangeIfNotNil(TmpEnableOpenEntry, CurLine, TmpEnableCloseEntry, TmpEnableCloseLinePos);
|
|
|
|
// *** END Check outerlines, for node that goes into visible area
|
|
// *** if found, then it is in DisableOpenEntry and DisabledCloseEntry
|
|
|
|
FirstEntryIdx := 0;
|
|
if ScanNodes then begin
|
|
// FAdjustedTop
|
|
NodeInfo := FIfDefTree.FindNodeAtPosition(FAdjustedTop, afmNext);
|
|
//while NodeInfo.HasNode and (NodeInfo.EntryCount = 0) do
|
|
// NodeInfo := NodeInfo.Successor;
|
|
Node := NodeInfo.Node;
|
|
if (Node <> nil) and (NodeInfo.StartLine < TopLine) then
|
|
FirstEntryIdx := Node.EntryCount - 1; // May be visible
|
|
end
|
|
else begin
|
|
// Not scanning nodes
|
|
NodeInfo.ClearInfo;
|
|
FindNextScanPos(NodeInfo, FirstEntryIdx);
|
|
if (not NodeInfo.HasNode) and (NodeInfo.StartLine = 0) then begin
|
|
NodeInfo := FIfDefTree.FindNodeAtPosition(TopLine, afmNext);
|
|
Node := NodeInfo.Node;
|
|
FirstEntryIdx := 0;
|
|
end;
|
|
Node := NodeInfo.Node;
|
|
end;
|
|
CurLine := NodeInfo.StartLine;
|
|
|
|
while (Node <> nil) and (CurLine <= LastLine) do begin
|
|
while FirstEntryIdx < Node.EntryCount do begin
|
|
EntryFound := False;
|
|
Entry := Node.Entry[FirstEntryIdx];
|
|
inc(FirstEntryIdx);
|
|
|
|
if ScanNodes then AddNodeMatch(Entry, CurLine);
|
|
|
|
if Entry = DisabledCloseEntry then SetNil(DisableOpenEntry, DisabledCloseEntry, DisabledCloseLinePos);
|
|
if Entry = EnableCloseEntry then SetNil(EnableOpenEntry, EnableCloseEntry, EnableCloseLinePos);
|
|
if Entry = TmpDisabledCloseEntry then SetNil(TmpDisableOpenEntry, TmpDisabledCloseEntry, TmpDisabledCloseLinePos);
|
|
if Entry = TmpEnableCloseEntry then SetNil(TmpEnableOpenEntry, TmpEnableCloseEntry, TmpEnableCloseLinePos);
|
|
|
|
if NeedDisableOpen and (not Entry.IsTemp) and Entry.IsDisabledOpening then begin
|
|
DisableOpenEntry := Entry;
|
|
DisabledCloseEntry := GetValidClosingPeer(DisableOpenEntry);
|
|
if DisabledCloseEntry <> nil then begin
|
|
if DisabledCloseEntry.Line = Node
|
|
then DisabledCloseLinePos := CurLine
|
|
else DisabledCloseLinePos := DisabledCloseEntry.Line.GetPosition;
|
|
end;
|
|
assert((DisabledCloseEntry=nil) or (DisabledCloseLinePos >= CurLine), 'Node goes backward');
|
|
AddRangeMatch(DisableOpenEntry, CurLine, DisabledCloseEntry, DisabledCloseLinePos);
|
|
EntryFound := True;
|
|
end;
|
|
|
|
if NeedEnableOpen and (not Entry.IsTemp) and Entry.IsEnabled and Entry.IsOpening then begin
|
|
EnableOpenEntry := Entry;
|
|
EnableCloseEntry := GetValidClosingPeer(EnableOpenEntry);
|
|
if EnableCloseEntry <> nil then begin
|
|
if EnableCloseEntry.Line = Node
|
|
then EnableCloseLinePos := CurLine
|
|
else EnableCloseLinePos := EnableCloseEntry.Line.GetPosition;
|
|
end;
|
|
assert((EnableCloseEntry=nil) or (EnableCloseLinePos >= CurLine), 'Node goes backward');
|
|
AddRangeMatch(EnableOpenEntry, CurLine, EnableCloseEntry, EnableCloseLinePos);
|
|
EntryFound := True;
|
|
end;
|
|
|
|
if NeedTmpDisableOpen and (Entry.IsTemp) and Entry.IsDisabledOpening then begin
|
|
TmpDisableOpenEntry := Entry;
|
|
TmpDisabledCloseEntry := GetValidClosingPeer(TmpDisableOpenEntry);
|
|
if TmpDisabledCloseEntry <> nil then begin
|
|
if TmpDisabledCloseEntry.Line = Node
|
|
then TmpDisabledCloseLinePos := CurLine
|
|
else TmpDisabledCloseLinePos := TmpDisabledCloseEntry.Line.GetPosition;
|
|
end;
|
|
assert((TmpDisabledCloseEntry=nil) or (TmpDisabledCloseLinePos >= CurLine), 'Node goes backward');
|
|
AddRangeMatch(TmpDisableOpenEntry, CurLine, TmpDisabledCloseEntry, TmpDisabledCloseLinePos);
|
|
EntryFound := True;
|
|
end;
|
|
|
|
if NeedTmpEnableOpen and (Entry.IsTemp) and Entry.IsEnabled and Entry.IsOpening then begin
|
|
TmpEnableOpenEntry := Entry;
|
|
TmpEnableCloseEntry := GetValidClosingPeer(TmpEnableOpenEntry);
|
|
if TmpEnableCloseEntry <> nil then begin
|
|
if TmpEnableCloseEntry.Line = Node
|
|
then TmpEnableCloseLinePos := CurLine
|
|
else TmpEnableCloseLinePos := TmpEnableCloseEntry.Line.GetPosition;
|
|
end;
|
|
assert((TmpEnableCloseEntry=nil) or (TmpEnableCloseLinePos >= CurLine), 'Node goes backward');
|
|
AddRangeMatch(TmpEnableOpenEntry, CurLine, TmpEnableCloseEntry, TmpEnableCloseLinePos);
|
|
EntryFound := True;
|
|
end;
|
|
|
|
if EntryFound then begin
|
|
FindNextScanPos(NodeInfo, FirstEntryIdx); Node := NodeInfo.Node; CurLine := NodeInfo.StartLine;
|
|
end;
|
|
end; // while FirstEntryIdx < Node.EntryCount do
|
|
|
|
NodeInfo := NodeInfo.Successor;
|
|
if not NodeInfo.HasNode then
|
|
break;
|
|
CurLine := NodeInfo.StartLine;
|
|
Node := NodeInfo.Node;
|
|
FirstEntryIdx := 0;
|
|
end; // while
|
|
|
|
// delete remaining matchdata
|
|
FMarkupEnabled.EndMatchScan(LastLine);
|
|
FMarkupTemp.EndMatchScan(LastLine);
|
|
FMarkupEnabledTemp.EndMatchScan(LastLine);
|
|
FMarkupNodes.EndMatchScan(LastLine);
|
|
EndMatchScan(LastLine);
|
|
|
|
{$IFDEF WITH_SYN_DEBUG_MARKUP_IFDEF}
|
|
finally DebugCurTreeForAssert := nil; end;
|
|
except FIfDefTree.DebugPrint(true); end;
|
|
{$ENDIF}
|
|
end;
|
|
|
|
procedure TSynEditMarkupIfDef.DoTreeChanged(Sender: TObject);
|
|
begin
|
|
DebugLn('TSynEditMarkupIfDef.DoTreeChanged');
|
|
ValidateMatches;
|
|
end;
|
|
|
|
procedure TSynEditMarkupIfDef.PrepareHighlighter;
|
|
begin
|
|
Highlighter.CurrentLines := Lines;
|
|
if Highlighter.NeedScan then DebugLn('******** Highlighter.NeedScan ************');
|
|
Highlighter.ScanRanges;
|
|
end;
|
|
|
|
function TSynEditMarkupIfDef.HasEnabledMarkup: Boolean;
|
|
begin
|
|
Result := (inherited HasEnabledMarkup) or
|
|
FMarkupNodes.HasEnabledMarkup or
|
|
FMarkupEnabled.HasEnabledMarkup or
|
|
FMarkupTemp.HasEnabledMarkup or
|
|
FMarkupEnabledTemp.HasEnabledMarkup;
|
|
end;
|
|
|
|
procedure TSynEditMarkupIfDef.SetFoldView(AValue: TSynEditFoldedView);
|
|
begin
|
|
if FFoldView = AValue then Exit;
|
|
FFoldView := AValue;
|
|
end;
|
|
|
|
function TSynEditMarkupIfDef.GetMarkupInfoDisabled: TSynSelectedColor;
|
|
begin
|
|
Result := MarkupInfo;
|
|
end;
|
|
|
|
function TSynEditMarkupIfDef.GetMarkupInfoEnabled: TSynSelectedColor;
|
|
begin
|
|
Result := FMarkupEnabled.MarkupInfo;
|
|
end;
|
|
|
|
function TSynEditMarkupIfDef.GetMarkupInfoNodeDisabled: TSynSelectedColor;
|
|
begin
|
|
Result := FMarkupNodes.MarkupInfoDisabled;
|
|
end;
|
|
|
|
function TSynEditMarkupIfDef.GetMarkupInfoNodeEnabled: TSynSelectedColor;
|
|
begin
|
|
Result := FMarkupNodes.MarkupInfoEnabled;
|
|
end;
|
|
|
|
function TSynEditMarkupIfDef.GetMarkupInfoTempDisabled: TSynSelectedColor;
|
|
begin
|
|
Result := FMarkupTemp.MarkupInfo;
|
|
end;
|
|
|
|
function TSynEditMarkupIfDef.GetMarkupInfoTempEnabled: TSynSelectedColor;
|
|
begin
|
|
Result := FMarkupEnabledTemp.MarkupInfo;
|
|
end;
|
|
|
|
function TSynEditMarkupIfDef.GetMarkupInfoTempNodeDisabled: TSynSelectedColor;
|
|
begin
|
|
Result := FMarkupNodes.MarkupInfoTempDisabled;
|
|
end;
|
|
|
|
function TSynEditMarkupIfDef.GetMarkupInfoTempNodeEnabled: TSynSelectedColor;
|
|
begin
|
|
Result := FMarkupNodes.MarkupInfoTempEnabled;
|
|
end;
|
|
|
|
procedure TSynEditMarkupIfDef.SetHighlighter(AValue: TSynPasSyn);
|
|
begin
|
|
if FHighlighter = AValue then Exit;
|
|
FHighlighter := AValue;
|
|
FIfDefTree.Highlighter := AValue;
|
|
FOuterLines.HighLighter := AValue;
|
|
end;
|
|
|
|
procedure TSynEditMarkupIfDef.DoBufferChanging(Sender: TObject);
|
|
begin
|
|
FIfDefTree.Clear;
|
|
FIfDefTree.Lines := nil;
|
|
FOuterLines.Lines := nil;
|
|
end;
|
|
|
|
procedure TSynEditMarkupIfDef.ValidateMatches;
|
|
begin
|
|
if (FPaintLock > 0) or (not SynEdit.IsVisible) or (not HasEnabledMarkup) then
|
|
exit;
|
|
|
|
//debugln(['Validate without lock']);
|
|
DoTreeUnlocking(FIfDefTree);
|
|
DoTreeUnlocked(FIfDefTree);
|
|
end;
|
|
|
|
procedure TSynEditMarkupIfDef.DoFoldChanged(aLine: Integer);
|
|
begin
|
|
DoTopLineChanged(-1);
|
|
end;
|
|
|
|
procedure TSynEditMarkupIfDef.DoTopLineChanged(OldTopLine: Integer);
|
|
begin
|
|
ValidateMatches;
|
|
end;
|
|
|
|
procedure TSynEditMarkupIfDef.DoLinesInWindoChanged(OldLinesInWindow: Integer);
|
|
begin
|
|
DoTopLineChanged(-1);
|
|
end;
|
|
|
|
procedure TSynEditMarkupIfDef.DoMarkupChanged(AMarkup: TSynSelectedColor);
|
|
begin
|
|
if FIfDefTree = nil then
|
|
exit;
|
|
|
|
FIfDefTree.IncChangeStep; // force all validation to run
|
|
ValidateMatches;
|
|
SynEdit.Invalidate;
|
|
end;
|
|
|
|
procedure TSynEditMarkupIfDef.DoTextChanged(StartLine, EndLine, ACountDiff: Integer);
|
|
begin
|
|
ValidateMatches;
|
|
end;
|
|
|
|
procedure TSynEditMarkupIfDef.DoVisibleChanged(AVisible: Boolean);
|
|
begin
|
|
FLastValidTopLine := 0;
|
|
FLastValidLastLine := 0;
|
|
FLastValidTreeStep := 0;
|
|
ValidateMatches;
|
|
end;
|
|
|
|
procedure TSynEditMarkupIfDef.DoBufferChanged(Sender: TObject);
|
|
begin
|
|
//FIfDefTree.Lines pointing to view => so still valid
|
|
FIfDefTree.Clear;
|
|
FIfDefTree.Lines := Lines;
|
|
FOuterLines.Lines := Lines;
|
|
|
|
FLastValidTopLine := 0;
|
|
FLastValidLastLine := 0;
|
|
FLastValidTreeStep := 0;
|
|
end;
|
|
|
|
function TSynEditMarkupIfDef.DoNodeStateRequest(Sender: TObject; LinePos,
|
|
XStartPos: Integer; CurrentState: TSynMarkupIfdefNodeStateEx): TSynMarkupIfdefNodeState;
|
|
begin
|
|
if FOnNodeStateRequest <> nil then
|
|
Result := FOnNodeStateRequest(Self, LinePos, XStartPos, CurrentState)
|
|
else
|
|
if CurrentState in [low(TSynMarkupIfdefNodeState)..high(TSynMarkupIfdefNodeState)]
|
|
then
|
|
Result := CurrentState
|
|
else
|
|
Result := idnInvalid;
|
|
|
|
//DebugLn(['STATE REQUEST ', LinePos, ' ', XStartPos, ' : ', dbgs(Result)]);
|
|
end;
|
|
|
|
procedure TSynEditMarkupIfDef.SetMarkOnlyOpeningNodes(AValue: Boolean);
|
|
begin
|
|
if FMarkOnlyOpeningNodes = AValue then Exit;
|
|
FMarkOnlyOpeningNodes := AValue;
|
|
if FMarkupNodes.HasEnabledMarkup then
|
|
DoMarkupChanged(nil);
|
|
end;
|
|
|
|
procedure TSynEditMarkupIfDef.SetLines(const AValue: TSynEditStringsLinked);
|
|
begin
|
|
if Lines <> nil then begin
|
|
Lines.RemoveNotifyHandler(senrTextBufferChanged, @DoBufferChanged);
|
|
Lines.RemoveNotifyHandler(senrTextBufferChanging, @DoBufferChanging);
|
|
//FLines.RemoveEditHandler(@DoLinesEdited);
|
|
// FLines.RemoveChangeHandler(senrHighlightChanged, @DoHighlightChanged);
|
|
end;
|
|
|
|
inherited SetLines(AValue);
|
|
FIfDefTree.Lines := AValue;
|
|
FOuterLines.Lines := AValue;
|
|
|
|
if Lines <> nil then begin
|
|
Lines.AddNotifyHandler(senrTextBufferChanged, @DoBufferChanged);
|
|
Lines.AddNotifyHandler(senrTextBufferChanging, @DoBufferChanging);
|
|
//FLines.AddChangeHandler(senrHighlightChanged, @DoHighlightChanged);
|
|
// FLines.AddEditHandler(@DoLinesEdited);
|
|
end;
|
|
end;
|
|
|
|
procedure TSynEditMarkupIfDef.SetInvalidateLinesMethod(const AValue: TInvalidateLines);
|
|
begin
|
|
inherited SetInvalidateLinesMethod(AValue);
|
|
FMarkupNodes.InvalidateLinesMethod := AValue;
|
|
FMarkupEnabled.InvalidateLinesMethod := AValue;
|
|
FMarkupTemp.InvalidateLinesMethod := AValue;
|
|
FMarkupEnabledTemp.InvalidateLinesMethod := AValue;
|
|
|
|
end;
|
|
|
|
constructor TSynEditMarkupIfDef.Create(ASynEdit: TSynEditBase);
|
|
begin
|
|
inherited Create(ASynEdit);
|
|
FMarkupNodes := TSynEditMarkupIfDefNodes.Create(ASynEdit);
|
|
FMarkupEnabled := TSynEditMarkupIfDefBase.Create(ASynEdit);
|
|
FMarkupTemp := TSynEditMarkupIfDefBase.Create(ASynEdit);
|
|
FMarkupEnabledTemp := TSynEditMarkupIfDefBase.Create(ASynEdit);
|
|
|
|
FIfDefTree := TSynMarkupHighIfDefLinesTree.Create;
|
|
FIfDefTree.OnNodeStateRequest := @DoNodeStateRequest;
|
|
FIfDefTree.RegisterNotification(itnUnlocking, @DoTreeUnlocking);
|
|
FIfDefTree.RegisterNotification(itnUnlocked, @DoTreeUnlocked);
|
|
FIfDefTree.RegisterNotification(itnChanged, @DoTreeChanged);
|
|
|
|
FOuterLines := FIfDefTree.CreateOpeningList;
|
|
|
|
FLastValidTopLine := 0;
|
|
FLastValidLastLine := 0;
|
|
FLastValidTreeStep := 0;
|
|
|
|
IncPaintLock;
|
|
|
|
FMarkOnlyOpeningNodes := False;
|
|
|
|
MarkupInfo.Clear;
|
|
MarkupInfo.Foreground := clLtGray;
|
|
MarkupInfo.ForeAlpha := 180;
|
|
MarkupInfo.ForePriority := 99999;
|
|
|
|
MarkupInfoTempDisabled.Clear;
|
|
MarkupInfoTempDisabled.Foreground := clLtGray;
|
|
MarkupInfoTempDisabled.ForeAlpha := 140;
|
|
MarkupInfoTempDisabled.ForePriority := 99999;
|
|
|
|
FMarkupEnabled.MarkupInfo.OnChange := @MarkupChanged;
|
|
FMarkupTemp.MarkupInfo.OnChange := @MarkupChanged;
|
|
FMarkupEnabledTemp.MarkupInfo.OnChange := @MarkupChanged;
|
|
FMarkupNodes.MarkupInfoDisabled.OnChange := @MarkupChanged;
|
|
FMarkupNodes.MarkupInfoEnabled.OnChange := @MarkupChanged;
|
|
FMarkupNodes.MarkupInfoTempDisabled.OnChange := @MarkupChanged;
|
|
FMarkupNodes.MarkupInfoTempEnabled.OnChange := @MarkupChanged;
|
|
|
|
DecPaintLock;
|
|
end;
|
|
|
|
destructor TSynEditMarkupIfDef.Destroy;
|
|
begin
|
|
inherited Destroy;
|
|
FIfDefTree.DiscardOpeningList(FOuterLines);
|
|
|
|
FIfDefTree.UnRegisterNotification(itnUnlocking, @DoTreeUnlocking);
|
|
FIfDefTree.UnRegisterNotification(itnUnlocked, @DoTreeUnlocked);
|
|
FIfDefTree.UnRegisterNotification(itnChanged, @DoTreeChanged);
|
|
FreeAndNil(FMarkupNodes);
|
|
FreeAndNil(FIfDefTree);
|
|
FreeAndNil(FMarkupEnabled);
|
|
FreeAndNil(FMarkupTemp);
|
|
FreeAndNil(FMarkupEnabledTemp);
|
|
end;
|
|
|
|
procedure TSynEditMarkupIfDef.IncPaintLock;
|
|
begin
|
|
inherited IncPaintLock;
|
|
FIfDefTree.LockTree;
|
|
end;
|
|
|
|
procedure TSynEditMarkupIfDef.DecPaintLock;
|
|
begin
|
|
inherited DecPaintLock;
|
|
FIfDefTree.UnLockTree;
|
|
end;
|
|
|
|
procedure TSynEditMarkupIfDef.PrepareMarkupForRow(aRow: Integer);
|
|
begin
|
|
FMarkupEnabled.PrepareMarkupForRow(aRow);
|
|
FMarkupNodes.PrepareMarkupForRow(aRow);
|
|
FMarkupTemp.PrepareMarkupForRow(aRow);
|
|
FMarkupEnabledTemp.PrepareMarkupForRow(aRow);
|
|
inherited PrepareMarkupForRow(aRow);
|
|
end;
|
|
|
|
procedure TSynEditMarkupIfDef.FinishMarkupForRow(aRow: Integer);
|
|
begin
|
|
FMarkupEnabled.FinishMarkupForRow(aRow);
|
|
FMarkupNodes.FinishMarkupForRow(aRow);
|
|
FMarkupTemp.FinishMarkupForRow(aRow);
|
|
FMarkupEnabledTemp.FinishMarkupForRow(aRow);
|
|
inherited FinishMarkupForRow(aRow);
|
|
end;
|
|
|
|
procedure TSynEditMarkupIfDef.EndMarkup;
|
|
begin
|
|
FMarkupEnabled.EndMarkup;
|
|
FMarkupNodes.EndMarkup;
|
|
FMarkupTemp.EndMarkup;
|
|
FMarkupEnabledTemp.EndMarkup;
|
|
inherited EndMarkup;
|
|
end;
|
|
|
|
procedure TSynEditMarkupIfDef.GetNextMarkupColAfterRowCol(const aRow: Integer;
|
|
const aStartCol: TLazSynDisplayTokenBound; const AnRtlInfo: TLazSynDisplayRtlInfo; out
|
|
ANextPhys, ANextLog: Integer);
|
|
begin
|
|
inherited GetNextMarkupColAfterRowCol(aRow, aStartCol, AnRtlInfo, ANextPhys, ANextLog);
|
|
FMarkupEnabled.GetNextMarkupColAfterRowColEx(aRow, aStartCol, AnRtlInfo, ANextPhys, ANextLog);
|
|
FMarkupNodes.GetNextMarkupColAfterRowColEx(aRow, aStartCol, AnRtlInfo, ANextPhys, ANextLog);
|
|
FMarkupTemp.GetNextMarkupColAfterRowColEx(aRow, aStartCol, AnRtlInfo, ANextPhys, ANextLog);
|
|
FMarkupEnabledTemp.GetNextMarkupColAfterRowColEx(aRow, aStartCol, AnRtlInfo, ANextPhys, ANextLog);
|
|
end;
|
|
|
|
procedure TSynEditMarkupIfDef.MergeMarkupAttributeAtRowCol(const aRow: Integer;
|
|
const aStartCol, AEndCol: TLazSynDisplayTokenBound; const AnRtlInfo: TLazSynDisplayRtlInfo;
|
|
AMarkup: TSynSelectedColorMergeResult);
|
|
begin
|
|
FMarkupEnabled.MergeMarkupAttributeAtRowCol(aRow, aStartCol, AEndCol, AnRtlInfo, AMarkup);
|
|
FMarkupNodes.MergeMarkupAttributeAtRowCol(aRow, aStartCol, AEndCol, AnRtlInfo, AMarkup);
|
|
FMarkupTemp.MergeMarkupAttributeAtRowCol(aRow, aStartCol, AEndCol, AnRtlInfo, AMarkup);
|
|
FMarkupEnabledTemp.MergeMarkupAttributeAtRowCol(aRow, aStartCol, AEndCol, AnRtlInfo, AMarkup);
|
|
inherited MergeMarkupAttributeAtRowCol(aRow, aStartCol, AEndCol, AnRtlInfo, AMarkup);
|
|
end;
|
|
|
|
procedure TSynEditMarkupIfDef.InvalidateAll;
|
|
begin
|
|
FIfDefTree.Clear;
|
|
end;
|
|
|
|
procedure TSynEditMarkupIfDef.SetNodeState(ALinePos, AstartPos: Integer;
|
|
AState: TSynMarkupIfdefNodeState);
|
|
begin
|
|
if (Highlighter = nil) then
|
|
exit;
|
|
PrepareHighlighter;
|
|
FIfDefTree.SetNodeState(ALinePos, AstartPos, AState);
|
|
end;
|
|
|
|
|
|
{$IFDEF WITH_SYN_DEBUG_MARKUP_IFDEF}
|
|
var OldAssert: TAssertErrorProc = @SysAssert;
|
|
Procedure MyAssert(const Msg,FName:ShortString;LineNo:Longint;ErrorAddr:Pointer);
|
|
var
|
|
t: TSynMarkupHighIfDefLinesTree;
|
|
begin
|
|
debugln('################# '+Msg);
|
|
if DebugCurTreeForAssert <> nil then begin
|
|
t := DebugCurTreeForAssert;
|
|
DebugCurTreeForAssert:= nil;
|
|
t.DebugPrint(true);
|
|
end;
|
|
if OldAssert <> nil
|
|
then OldAssert(Msg, FName, LineNo, ErrorAddr)
|
|
else halt(0);
|
|
end;
|
|
{$ENDIF}
|
|
|
|
{$IFDEF WITH_SYN_DEBUG_MARKUP_IFDEF}
|
|
initialization
|
|
OldAssert := AssertErrorProc;
|
|
AssertErrorProc := @MyAssert;
|
|
{$ENDIF}
|
|
|
|
end.
|
|
|