{------------------------------------------------------------------------------- The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.mozilla.org/MPL/ Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License. Alternatively, the contents of this file may be used under the terms of the GNU General Public License Version 2 or later (the "GPL"), in which case the provisions of the GPL are applicable instead of those above. If you wish to allow use of your version of this file only under the terms of the GPL and not to allow others to use your version of this file under the MPL, indicate your decision by deleting the provisions above and replace them with the notice and other provisions required by the GPL. If you do not delete the provisions above, a recipient may use your version of this file under either the MPL or the GPL. This file was added to the Lazarus branch of SynEdit. The original Author is M Friebe } (* Provides folding for Xml and Html *) unit SynEditHighlighterXMLBase; interface {$I SynEdit.inc} uses SysUtils, Classes, math, LCLType, SynEditTextBase, SynEditHighlighter, SynEditHighlighterFoldBase; type TSynXmlRangeInfo = record ElementOpenList: Array of String; // List of words opened in this line (and still open at the end of line) ElementCloseList: Array of Smallint; // include close, for open on same line end; { TSynHighlighterXmlRangeList } TSynHighlighterXmlRangeList = class(TSynHighlighterRangeList) private FItemOffset: Integer; function GetXmlRangeInfo(Index: Integer): TSynXmlRangeInfo; procedure SetXmlRangeInfo(Index: Integer; const AValue: TSynXmlRangeInfo); protected procedure SetCapacity(const AValue: Integer); override; public constructor Create; procedure Move(AFrom, ATo, ALen: Integer); override; property XmlRangeInfo[Index: Integer]: TSynXmlRangeInfo read GetXmlRangeInfo write SetXmlRangeInfo; end; TSynCustomXmlHighlighter = class(TSynCustomFoldHighlighter) private FXmlRangeInfo: TSynXmlRangeInfo; FXmlRangeInfoChanged: Boolean; FXmlRangeInfoOpenPos: integer; FXmlRangeInfoClosePos: integer; protected function CreateRangeList(ALines: TSynEditStringsBase): TSynHighlighterRangeList; override; function UpdateRangeInfoAtLine(Index: Integer): Boolean; override; // Returns true if range changed function StartXmlCodeFoldBlock(ABlockType: Integer): TSynCustomCodeFoldBlock; function StartXmlNodeCodeFoldBlock(ABlockType: Integer; OpenPos: Integer; AName: String): TSynCustomCodeFoldBlock; procedure EndXmlCodeFoldBlock; procedure EndXmlNodeCodeFoldBlock(ClosePos: Integer = -1; AName: String = ''); public procedure SetLine(const NewValue: string; LineNumber:Integer); override; end; implementation function TSynCustomXmlHighlighter.CreateRangeList(ALines: TSynEditStringsBase): TSynHighlighterRangeList; begin Result := TSynHighlighterXmlRangeList.Create; end; function TSynCustomXmlHighlighter.UpdateRangeInfoAtLine(Index: Integer): Boolean; var InfoOpenLenChanged, InfoCloseLenChanged: Boolean; begin Result := inherited UpdateRangeInfoAtLine(Index); InfoOpenLenChanged := Length(FXmlRangeInfo.ElementOpenList) <> FXmlRangeInfoOpenPos; InfoCloseLenChanged := Length(FXmlRangeInfo.ElementCloseList) <> FXmlRangeInfoClosePos; if FXmlRangeInfoChanged or InfoOpenLenChanged or InfoCloseLenChanged then begin Result := True; if InfoOpenLenChanged then SetLength(FXmlRangeInfo.ElementOpenList, FXmlRangeInfoOpenPos); if InfoCloseLenChanged then SetLength(FXmlRangeInfo.ElementCloseList, FXmlRangeInfoClosePos); TSynHighlighterXmlRangeList(CurrentRanges).XmlRangeInfo[LineIndex] := FXmlRangeInfo; // Store on this line FXmlRangeInfoChanged := False; end; end; procedure TSynCustomXmlHighlighter.SetLine(const NewValue: string; LineNumber:Integer); begin inherited; FXmlRangeInfo := TSynHighlighterXmlRangeList(CurrentRanges).XmlRangeInfo[LineIndex]; // From this line, not from the previous line FXmlRangeInfoChanged := False; FXmlRangeInfoOpenPos := 0; FXmlRangeInfoClosePos := 0; end; function TSynCustomXmlHighlighter.StartXmlCodeFoldBlock(ABlockType: Integer): TSynCustomCodeFoldBlock; var FoldBlock: Boolean; p: PtrInt; begin FoldBlock := FFoldConfig[ABlockType].Enabled; p := 0; if not FoldBlock then p := PtrInt(GetFoldConfigInternalCount); Result := StartCodeFoldBlock(p + Pointer(PtrInt(ABlockType)), FoldBlock); end; function TSynCustomXmlHighlighter.StartXmlNodeCodeFoldBlock(ABlockType: Integer; OpenPos: Integer; AName: String): TSynCustomCodeFoldBlock; var i: Integer; begin If IsScanning then begin AName := LowerCase(AName); i := Length(FXmlRangeInfo.ElementOpenList); if (FXmlRangeInfoOpenPos < i) then begin if (FXmlRangeInfo.ElementOpenList[FXmlRangeInfoOpenPos] <> AName) then begin FXmlRangeInfo.ElementOpenList[FXmlRangeInfoOpenPos] := AName; FXmlRangeInfoChanged := true; // TODO:if this node closes on the same line, it may not be amodified .... end; end else begin // append - modified will be deteced by the new length SetLength(FXmlRangeInfo.ElementOpenList, FXmlRangeInfoOpenPos + 10); FXmlRangeInfo.ElementOpenList[FXmlRangeInfoOpenPos] := AName; end; end; inc(FXmlRangeInfoOpenPos); result := StartXmlCodeFoldBlock(ABlockType); end; procedure TSynCustomXmlHighlighter.EndXmlCodeFoldBlock; var DecreaseLevel: Boolean; begin DecreaseLevel := TopCodeFoldBlockType < Pointer(PtrInt(GetFoldConfigInternalCount)); EndCodeFoldBlock(DecreaseLevel); end; procedure TSynCustomXmlHighlighter.EndXmlNodeCodeFoldBlock(ClosePos: Integer = -1; AName: String = ''); var cnt, i, k, lvl: Integer; LInfo: Array of String; begin AName := LowerCase(AName); cnt := 0; If IsScanning then begin if (AName = '') and (CodeFoldRange.CodeFoldStackSize > 0) then begin cnt := 1; end else begin cnt := 1; i := FXmlRangeInfoOpenPos; while i > 0 do begin if (FXmlRangeInfo.ElementOpenList[i-1] = AName) then break; dec(i); inc(cnt); end; if i = 0 then begin i := LineIndex - 1; lvl := FoldBlockEndLevel(i); while i >= 0 do begin if FoldBlockMinLevel(i) < lvl then begin LInfo := TSynHighlighterXmlRangeList(CurrentRanges).XmlRangeInfo[i].ElementOpenList; k := length(LInfo) - Max(FoldBlockEndLevel(i) - lvl, 0) - 1; while (k >= 0) do begin if (LInfo[k] = AName) then break; inc(cnt); dec(k); dec(lvl); end; if k >= 0 then break; end; dec(i); end; if (i < 0) or (cnt > CodeFoldRange.CodeFoldStackSize ) then cnt := 0; // never opened, do not close end; end; i := Length(FXmlRangeInfo.ElementCloseList); if (FXmlRangeInfoClosePos < i) then begin if (FXmlRangeInfo.ElementCloseList[FXmlRangeInfoClosePos] <> cnt) then begin FXmlRangeInfo.ElementCloseList[FXmlRangeInfoClosePos] := cnt; FXmlRangeInfoChanged := true; end; end else begin // append - modified will be deteced by the new length SetLength(FXmlRangeInfo.ElementCloseList, FXmlRangeInfoClosePos + 10); FXmlRangeInfo.ElementCloseList[FXmlRangeInfoClosePos] := cnt; end; end else begin if FXmlRangeInfoClosePos < length(FXmlRangeInfo.ElementCloseList) then cnt := FXmlRangeInfo.ElementCloseList[FXmlRangeInfoClosePos] else cnt := 0; end; inc(FXmlRangeInfoClosePos); for i := 1 to cnt do begin if FXmlRangeInfoOpenPos > 0 then dec(FXmlRangeInfoOpenPos); EndXmlCodeFoldBlock; end; end; { TSynHighlighterXmlRangeList } function TSynHighlighterXmlRangeList.GetXmlRangeInfo(Index: Integer): TSynXmlRangeInfo; begin if (Index < 0) or (Index >= Count) then begin Result.ElementOpenList := nil; exit; end; Result := TSynXmlRangeInfo((ItemPointer[Index] + FItemOffset)^); end; procedure TSynHighlighterXmlRangeList.SetXmlRangeInfo(Index: Integer; const AValue: TSynXmlRangeInfo); begin TSynXmlRangeInfo((ItemPointer[Index] + FItemOffset)^) := AValue; end; procedure TSynHighlighterXmlRangeList.SetCapacity(const AValue: Integer); var i: LongInt; begin for i := AValue to Capacity-1 do with TSynXmlRangeInfo((ItemPointer[i] + FItemOffset)^) do begin ElementOpenList := nil; ElementCloseList := nil; end; inherited SetCapacity(AValue); end; constructor TSynHighlighterXmlRangeList.Create; begin inherited; FItemOffset := ItemSize; ItemSize := FItemOffset + SizeOf(TSynXmlRangeInfo); end; procedure TSynHighlighterXmlRangeList.Move(AFrom, ATo, ALen: Integer); var i: LongInt; begin if ATo > AFrom then for i:= Max(AFrom + ALen, ATo) to ATo + ALen - 1 do // move forward with TSynXmlRangeInfo((ItemPointer[i] + FItemOffset)^) do begin ElementOpenList := nil; ElementCloseList := nil; end else for i:= ATo to Min(ATo + ALen , AFrom) - 1 do // move backward with TSynXmlRangeInfo((ItemPointer[i] + FItemOffset)^) do begin ElementOpenList := nil; ElementCloseList := nil; end; inherited Move(AFrom, ATo, ALen); end; end.