(* 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, SynEditMiscClasses, SynHighlighterPas, SynEditMarkupHighAll, SynEditHighlighterFoldBase, SynEditFoldedView, LazSynEditText, SynEditMiscProcs, SynEditMarkup, SynEditPointClasses, SynEditHighlighter, LazClasses, LazLoggerBase, Graphics, LCLProc; 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: TSynEditStrings; 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: TSynEditStrings); 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 : TSynEditStrings 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: TSynEditStrings); 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 uses SynEdit; 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 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+1)^ 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=%d NType=%s State=%s OpenState=%s Flags=%s ' + ' StartCol=%d EndCol=%d', [FLine, 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.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.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 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: TSynEditStrings); 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(@GetHighLighterWithLines); 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 if ScanNodes then begin // FAdjustedTop NodeInfo := FIfDefTree.FindNodeAtPosition(FAdjustedTop, afmNext); //while NodeInfo.HasNode and (NodeInfo.EntryCount = 0) do // NodeInfo := NodeInfo.Successor; Node := NodeInfo.Node; FirstEntryIdx := 0; 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; end; procedure TSynEditMarkupIfDef.DoBufferChanging(Sender: TObject); begin FIfDefTree.Clear; FIfDefTree.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; 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: TSynEditStrings); begin if Lines <> nil then begin Lines.RemoveGenericHandler(senrTextBufferChanged, TMethod(@DoBufferChanged)); Lines.RemoveGenericHandler(senrTextBufferChanging, TMethod(@DoBufferChanging)); //FLines.RemoveEditHandler(@DoLinesEdited); // FLines.RemoveChangeHandler(senrHighlightChanged, @DoHighlightChanged); end; inherited SetLines(AValue); FIfDefTree.Lines := AValue; if Lines <> nil then begin Lines.AddGenericHandler(senrTextBufferChanged, TMethod(@DoBufferChanged)); Lines.AddGenericHandler(senrTextBufferChanging, TMethod(@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.