unit SynEditMarks; {$I synedit.inc} {$IFOPT C+} {$DEFINE AssertSynMemIndex} {$ENDIF} {$IFDEF SynAssert} {$DEFINE AssertSynMemIndex} {$ENDIF} interface uses Classes, Controls, SysUtils, math, SynEditMiscClasses, LazSynEditText, LCLProc; type TSynEditMark = class; TSynEditMarkLine = class; TSynEditMarkLineList = class; TSynEditMarkList = class; TSynEditMarkChangeReason = ( smcrAdded, smcrRemoved, smcrLine, smcrColumn, smcrVisible, smcrChanged ); TSynEditMarkChangeReasons = set of TSynEditMarkChangeReason; TSynEditMarkSortOrder = (smsoUnsorted, smsoColumn, smsoPriority, smsoBookmarkFirst, smsoBookMarkLast); TSynEditMarkChangeEvent = procedure(Sender: TSynEditMark; Changes: TSynEditMarkChangeReasons) of object; TPlaceMarkEvent = procedure(Sender: TObject; var Mark: TSynEditMark) of object; { TSynEditMarkChangedHandlerList } TSynEditMarkChangedHandlerList = Class(TSynFilteredMethodList) public procedure Add(AHandler: TSynEditMarkChangeEvent; Changes: TSynEditMarkChangeReasons); procedure Remove(AHandler: TSynEditMarkChangeEvent); procedure CallMarkChangedHandlers(Sender: TSynEditMark; Changes: TSynEditMarkChangeReasons); end; { TSynEditMark } TSynEditMark = class private FImageList: TImageList; FMarkLine: TSynEditMarkLine; FMarkList: TSynEditMarkList; FLine: Integer; // Only valid, if not part of a TSynEditMarkLine FOldLine: integer; FOwnerEdit: TSynEditBase; function GetLine: integer; procedure SetMarkLine(const AValue: TSynEditMarkLine); procedure SetMarkList(const AValue: TSynEditMarkList); procedure SetOwnerEdit(const AValue: TSynEditBase); protected FColumn, FImage, FPriority: Integer; FVisible: boolean; FInternalImage: boolean; FBookmarkNum: integer; FChangeLock: Integer; FChanges: TSynEditMarkChangeReasons; procedure SetColumn(const Value: Integer); virtual; procedure SetImage(const Value: Integer); virtual; procedure SetLine(const Value: Integer); virtual; procedure SetPriority(const AValue: integer); virtual; procedure SetVisible(const Value: boolean); virtual; procedure SetInternalImage(const Value: boolean); function GetIsBookmark: boolean; procedure DoChange(AChanges: TSynEditMarkChangeReasons); virtual; procedure ForceChange(AChanges: TSynEditMarkChangeReasons); property MarkLine: TSynEditMarkLine read FMarkLine write SetMarkLine; property MarkList: TSynEditMarkList read FMarkList write SetMarkList; public constructor Create(ASynEdit: TSynEditBase); destructor Destroy; override; procedure IncChangeLock; procedure DecChangeLock; property OwnerEdit: TSynEditBase read FOwnerEdit write SetOwnerEdit; property OldLine: integer read FOldLine; // not used, if synedit insert/delete lines property Line: integer read GetLine write SetLine; property Column: integer read FColumn write SetColumn; // Logical position property Priority: integer read FPriority write SetPriority; property Visible: boolean read FVisible write SetVisible; property BookmarkNumber: integer read FBookmarkNum write fBookmarkNum; property IsBookmark: boolean read GetIsBookmark; // InternalImage: Use Internal bookmark image 0..9; // Ignore "BookMarkOpt.BookmarkImages" or "ImageList" property InternalImage: boolean read FInternalImage write SetInternalImage; // ImageIndex: Index in "BookMarkOpt.BookmarkImages" or "ImageList" property ImageIndex: integer read FImage write SetImage; // ImageList: If assigned, then use instead of "BookMarkOpt.BookmarkImages" // Must have same width as "BookMarkOpt.BookmarkImages" property ImageList: TImageList read FImageList write FImageList; end; { TSynEditMarkLine } TSynEditMarkLine = class(TSynSizedDifferentialAVLNode) private FMarks: TFPList; FLineList: TSynEditMarkLineList; function GetLineNum: Integer; function GetMark(Index: Integer): TSynEditMark; function GetLeft: TSynEditMarkLine; function GetRight: TSynEditMarkLine; procedure SetMark(Index: Integer; const AValue: TSynEditMark); protected FLockChangeSize: Integer; FCurrentSort1, FCurrentSort2: TSynEditMarkSortOrder; procedure ChangeSize; procedure IncLockChangeSize; // used in global destruction {$IFDEF SynDebug} function Debug: String; override; {$ENDIF} protected property Left: TSynEditMarkLine read GetLeft; property Right: TSynEditMarkLine read GetRight; property Size: Integer read FSize; property LeftSizeSum: Integer read FLeftSizeSum; property LineOffset: Integer read FPositionOffset write FPositionOffset; public constructor Create(ALineNum: Integer; AOwner: TSynEditMarkLineList); destructor Destroy; override; procedure Sort(PrimaryOrder: TSynEditMarkSortOrder; SecondaryOrder: TSynEditMarkSortOrder = smsoUnsorted); function Add(Item: TSynEditMark): Integer; procedure Delete(Index: Integer); function Remove(Item: TSynEditMark): Integer; procedure Clear(FreeMarks: Boolean = False); function Count: Integer; function IndexOf(AMark: TSynEditMark): Integer; property Items[Index: Integer]: TSynEditMark read GetMark write SetMark; default; property LineNum: Integer read GetLineNum; end; { TSynEditMarkIterator } TSynEditMarkIterator = class private FMarkList: TSynEditMarkList; FCurrentItem: TSynEditMark; FCurrentIndex: Integer; FBOL, FEOL: Boolean; public constructor Create(AOwner: TSynEditMarkList); procedure Invalidate; procedure GotoMark(AMark: TSynEditMark); procedure GotoBOL; procedure GotoEOL; function First: Boolean; function Last: Boolean; function Next: Boolean; function Previous: Boolean; function NextLine: Boolean; function PreviousLine: Boolean; function IsValid: Boolean; function IsValidAndNotModified: Boolean; property BOL: Boolean read FBOL; property EOL: Boolean read FEOL; property Mark: TSynEditMark read FCurrentItem; end; { TSynEditMarkLineList } TSynEditMarkLineList = class(TSynSizedDifferentialAVLTree) private FMarkList: TSynEditMarkList; FIndexIterator: TSynEditMarkIterator; // marks function GetMark(Index : Integer): TSynEditMark; function GetMarkCount: Integer; procedure SetMark(Index : Integer; const AValue: TSynEditMark); // lines function GetMarkLineByMarkIndex(var AMarkIndex: Integer): TSynEditMarkLine; function GetMarkLine(LineNum: Integer): TSynEditMarkLine; function GetOrAddMarkLine(LineNum: Integer; AddIfNotExist: Boolean; UseNext: Boolean = False): TSynEditMarkLine; Procedure AdjustForLinesInserted(AStartLine, ALineCount : Integer); Procedure AdjustForLinesDeleted(AStartLine, ALineCount : Integer); protected // will be reset by TSynEditMarkLine.ChangeSize; FLastIteratorIndex: Integer; public constructor Create(AOwner: TSynEditMarkList); destructor Destroy; override; procedure Remove(Item: TSynEditMarkLine; FreeMarks: Boolean = False); procedure Clear; override; // FreeMarks=False; procedure Clear(FreeMarks: Boolean); function GetOrAddLine(LineNum: Integer): TSynEditMarkLine; function GetLineOrNext(LineNum: Integer): TSynEditMarkLine; property Lines[LineNum: Integer]: TSynEditMarkLine read GetMarkLine; public function AddMark(Item: TSynEditMark): Integer; procedure DeleteMark(Index: Integer); function RemoveMark(Item: TSynEditMark): Integer; function IndexOfMark(AMark: TSynEditMark): Integer; property MarkCount: Integer read GetMarkCount; property Mark[Index : Integer]: TSynEditMark read GetMark write SetMark; end; { TSynEditMarkList A list of mark objects. Each object cause a litle picture to be drawn in the gutter. } TSynEditMarkList = class private function GetMarkLine(LineNum: Integer): TSynEditMarkLine; protected FLines: TSynEditStrings; FOwnerList: TFPList; FMarkLines: TSynEditMarkLineList; fOnChange: TNotifyEvent; FChangeHandlers: TSynEditMarkChangedHandlerList; FInternalIterator: TSynEditMarkIterator; procedure DoChange; procedure MarkChanged(Sender: TSynEditMark; AChanges: TSynEditMarkChangeReasons); virtual; function Get(Index: Integer): TSynEditMark; procedure Put(Index: Integer; Item: TSynEditMark); procedure DoLinesEdited(Sender: TSynEditStrings; aLinePos, aBytePos, aCount, aLineBrkCnt: Integer; aText: String); function HasOwnerEdit(AEdit: TSynEditBase): Boolean; public constructor Create(AOwner: TSynEditBase; ALines: TSynEditStrings); destructor Destroy; override; {$IFDEF SynDebug} procedure Debug; {$ENDIF} function Add(Item: TSynEditMark): Integer; procedure Delete(Index: Integer); function Remove(Item: TSynEditMark): Integer; function IndexOf(Item: TSynEditMark): Integer; function Count: Integer; procedure ClearLine(line: integer); procedure RegisterChangeHandler(Handler: TSynEditMarkChangeEvent; Filter: TSynEditMarkChangeReasons); procedure UnRegisterChangeHandler(Handler: TSynEditMarkChangeEvent); public property Items[Index: Integer]: TSynEditMark read Get write Put; default; property Line[LineNum: Integer]: TSynEditMarkLine read GetMarkLine; property OnChange: TNotifyEvent read FOnChange write FOnChange; end; function DoMarksCompareBookmarksFirst(Item1, Item2: Pointer): Integer; function DoMarksCompareBookmarksLast(Item1, Item2: Pointer): Integer; implementation function DoMarksCompareBookmarksFirst(Item1, Item2: Pointer): Integer; var Mark1: TSynEditMark absolute Item1; Mark2: TSynEditMark absolute Item2; begin Result := 0; if Mark1 = Mark2 then Exit; if Mark1.IsBookmark then Result := -1 else if Mark2.IsBookmark then Result := 1 else if Mark1.Priority <> Mark2.Priority then Result := Mark2.Priority - Mark1.Priority else Result := Mark2.Column - Mark1.Column; end; function DoMarksCompareBookmarksLast(Item1, Item2: Pointer): Integer; var Mark1: TSynEditMark absolute Item1; Mark2: TSynEditMark absolute Item2; begin Result := 0; if Mark1 = Mark2 then Exit; if Mark1.IsBookmark then Result := 1 else if Mark2.IsBookmark then Result := -1 else if Mark1.Priority <> Mark2.Priority then Result := Mark2.Priority - Mark1.Priority else Result := Mark2.Column - Mark1.Column; end; { TSynEditMark } procedure TSynEditMark.SetPriority(const AValue: integer); begin FPriority := AValue; end; procedure TSynEditMark.SetMarkList(const AValue: TSynEditMarkList); begin if AValue = FMarkList then exit; if FMarkList <> nil then begin DoChange([smcrRemoved]); ForceChange(FChanges); end; FMarkList := AValue; if FMarkList <> nil then DoChange([smcrAdded]); end; procedure TSynEditMark.SetOwnerEdit(const AValue: TSynEditBase); begin if FOwnerEdit = AValue then exit; if (AValue = nil) or (FMarkList = nil) or (not FMarkList.HasOwnerEdit(AValue)) then raise Exception.Create('Invalid Owner'); FOwnerEdit := AValue; end; function TSynEditMark.GetLine: integer; begin if FMarkLine <> nil then Result := FMarkLine.LineNum else Result := FLine; end; procedure TSynEditMark.SetMarkLine(const AValue: TSynEditMarkLine); begin if FMarkLine = AValue then exit; // keep the current line, if we loose the markline if (AValue = nil) and (FMarkLine <> nil) then FLine := FMarkLine.LineNum; FMarkLine := AValue; end; function TSynEditMark.GetIsBookmark: boolean; begin Result := (fBookmarkNum >= 0); end; procedure TSynEditMark.DoChange(AChanges: TSynEditMarkChangeReasons); begin if FChangeLock > 0 then begin FChanges := FChanges + AChanges; exit; end; ForceChange(AChanges); end; procedure TSynEditMark.ForceChange(AChanges: TSynEditMarkChangeReasons); begin if (FMarkList <> nil) and (AChanges <> []) then FMarkList.MarkChanged(Self, AChanges); FChanges := []; end; procedure TSynEditMark.SetColumn(const Value: Integer); begin if FColumn = Value then exit; FColumn := Value; DoChange([smcrColumn]); end; procedure TSynEditMark.SetImage(const Value: Integer); begin if FImage = Value then exit; FImage := Value; DoChange([smcrChanged]); end; procedure TSynEditMark.SetInternalImage(const Value: boolean); begin if FInternalImage = Value then exit; FInternalImage := Value; DoChange([smcrChanged]); end; procedure TSynEditMark.SetLine(const Value: Integer); var f: Boolean; begin if Line = Value then exit; f := FMarkLine <> nil; if f then FMarkLine.Remove(self); FMarkLine := nil;; FOldLine := FLine; FLine := Value; if f and (FMarkList <> nil) then FMarkList.FMarkLines.AddMark(self); DoChange([smcrLine]); end; procedure TSynEditMark.SetVisible(const Value: boolean); begin if FVisible = Value then exit; FVisible := Value; DoChange([smcrVisible]); end; constructor TSynEditMark.Create(ASynEdit: TSynEditBase); begin inherited Create; FOwnerEdit := ASynEdit; FBookmarkNum := -1; FPriority := 0; end; destructor TSynEditMark.Destroy; begin if FMarkList <> nil then begin DoChange([smcrRemoved]); FMarkList.Remove(self); // includes MarkLine end else if FMarkLine <> nil then FMarkLine.Remove(Self); inherited Destroy; end; procedure TSynEditMark.IncChangeLock; begin inc(FChangeLock); end; procedure TSynEditMark.DecChangeLock; begin dec(FChangeLock); if (FChangeLock = 0) and (FChanges <> []) then DoChange(FChanges); end; { TSynEditMarkLine } function TSynEditMarkLine.GetMark(Index: Integer): TSynEditMark; begin Result := TSynEditMark(FMarks[Index]); end; function TSynEditMarkLine.GetLeft: TSynEditMarkLine; begin Result := TSynEditMarkLine(FLeft); end; function TSynEditMarkLine.GetLineNum: Integer; begin Result := LineOffset; if FParent <> nil then Result := Result + TSynEditMarkLine(FParent).GetLineNum; end; function TSynEditMarkLine.GetRight: TSynEditMarkLine; begin Result := TSynEditMarkLine(FRight); end; procedure TSynEditMarkLine.SetMark(Index: Integer; const AValue: TSynEditMark); begin if Items[Index] = AValue then exit; Items[Index].MarkLine := nil; FMarks[Index] := AValue; AValue.MarkLine := Self; end; procedure TSynEditMarkLine.ChangeSize; var Cnt: SmallInt; begin Cnt := Count; if (FLockChangeSize > 0) or (FSize = Cnt) then exit; FLineList.FLastIteratorIndex := -1; if Cnt = 0 then begin inc(FLockChangeSize); FLineList.Remove(Self); end else begin AdjustParentLeftCount(Cnt - FSize); FSize := Cnt; end; end; procedure TSynEditMarkLine.IncLockChangeSize; begin inc(FLockChangeSize); end; {$IFDEF SynDebug} function TSynEditMarkLine.Debug: String; var i: Integer; begin Result := inherited; Result := Result + Format('Cnt=%d, LineNm=%d ', [Count, LineNum]); for i := 0 to Count-1 do Result := Result + Format(': L=%d, C=%d, Bk=%d %s, Img=%d %s :', [Items[i].Line, Items[i].Column, Items[i].BookmarkNumber, dbgs(Items[i].IsBookmark), Items[i].ImageIndex, dbgs(Items[i].InternalImage) ]); end; {$ENDIF} constructor TSynEditMarkLine.Create(ALineNum: Integer; AOwner: TSynEditMarkLineList); begin inherited Create;; FMarks := TFPList.Create; FLineList := AOwner; FPositionOffset := ALineNum; FLockChangeSize := 0; FCurrentSort1 := smsoUnsorted; end; destructor TSynEditMarkLine.Destroy; begin Clear(True); inherited Destroy; FreeAndNil(FMarks); end; function CompareSynEditMarks(Mark1, Mark2: Pointer): Integer; var m1: TSynEditMark absolute Mark1; m2: TSynEditMark absolute Mark2; begin case m1.MarkLine.FCurrentSort1 of smsoColumn: Result := m2.Column - m1.Column; smsoPriority: Result := m2.Priority - m1.Priority; smsoBookmarkFirst: if (m1.IsBookmark) and (not m2.IsBookmark) then Result := -1 else if (not m1.IsBookmark) and (m2.IsBookmark) then Result := 1 else Result := 0; smsoBookMarkLast: if (m1.IsBookmark) and (not m2.IsBookmark) then Result := 1 else if (not m1.IsBookmark) and (m2.IsBookmark) then Result := -1 else Result := 0; else Result := 0; end; if Result <> 0 then exit; case m1.MarkLine.FCurrentSort2 of smsoColumn: Result := m2.Column - m1.Column; smsoPriority: Result := m2.Priority - m1.Priority; smsoBookmarkFirst: if (m1.IsBookmark) and (not m2.IsBookmark) then Result := -1 else if (not m1.IsBookmark) and (m2.IsBookmark) then Result := 1 else Result := 0; smsoBookMarkLast: if (m1.IsBookmark) and (not m2.IsBookmark) then Result := 1 else if (not m1.IsBookmark) and (m2.IsBookmark) then Result := -1 else Result := 0; else Result := 0; end; if Result <> 0 then exit; Result := m2.Column - m1.Column; if Result <> 0 then exit; Result := m2.Priority - m1.Priority; if Result <> 0 then exit; Result := PtrInt(m2) - PtrInt(m1); end; procedure TSynEditMarkLine.Sort(PrimaryOrder: TSynEditMarkSortOrder; SecondaryOrder: TSynEditMarkSortOrder); begin if PrimaryOrder = smsoUnsorted then PrimaryOrder := SecondaryOrder; if PrimaryOrder = SecondaryOrder then SecondaryOrder := smsoUnsorted; if (PrimaryOrder = FCurrentSort1) and (SecondaryOrder = FCurrentSort2) then exit; FCurrentSort1 := PrimaryOrder; FCurrentSort2 := SecondaryOrder; if PrimaryOrder = smsoUnsorted then exit; FMarks.Sort(@CompareSynEditMarks); end; function TSynEditMarkLine.Add(Item: TSynEditMark): Integer; begin FCurrentSort1 := smsoUnsorted; Result := FMarks.Add(Item); Item.MarkLine := Self; ChangeSize; end; procedure TSynEditMarkLine.Delete(Index: Integer); begin Items[Index].MarkLine := nil; FMarks.Delete(Index); ChangeSize; end; function TSynEditMarkLine.Remove(Item: TSynEditMark): Integer; begin Item.MarkLine := nil; Result := FMarks.Remove(Item); ChangeSize; end; procedure TSynEditMarkLine.Clear(FreeMarks: Boolean = False); begin inc(FLockChangeSize); try while Count > 0 do begin if FreeMarks then begin Items[0].MarkList := nil; // stop destroy from removing item from list Items[0].FMarkLine := nil; // stop destroy from removing item from self Items[0].Free end else Items[0].MarkLine := nil; FMarks.Delete(0); end; finally dec(FLockChangeSize); end; ChangeSize; end; function TSynEditMarkLine.Count: Integer; begin Result := FMarks.Count; end; function TSynEditMarkLine.IndexOf(AMark: TSynEditMark): Integer; begin Result := FMarks.IndexOf(AMark); end; { TSynEditMarkLineList } function TSynEditMarkLineList.GetMark(Index : Integer): TSynEditMark; var MLine: TSynEditMarkLine; i: Integer; begin (* If any Mark was added/removed from it's line then FLastIteratorIndex will be -1 If Items in the current line changed order then IsValidAndNotModified is False If Items in another line changed order, it is valid to use the iterator *) if (FLastIteratorIndex >= 0) and ( (FLastIteratorIndex - 1 = Index) or // Return previous (FLastIteratorIndex = Index) or // Return current (FLastIteratorIndex + 1 = Index) ) and // Return next (FIndexIterator.IsValidAndNotModified) then begin // can use iterator for quick access if (FLastIteratorIndex + 1 = Index) then FIndexIterator.Next else if (FLastIteratorIndex - 1 = Index) then FIndexIterator.Previous; Result := FIndexIterator.Mark; FLastIteratorIndex := Index; exit; end; i := Index; MLine := GetMarkLineByMarkIndex(i); Result := MLine[i]; FIndexIterator.GotoMark(Result); FLastIteratorIndex := Index; end; function TSynEditMarkLineList.GetMarkCount: Integer; var n: TSynEditMarkLine; begin n := TSynEditMarkLine(FRoot); Result := 0; while n <> nil do begin Result := Result + n.LeftSizeSum + n.Size; n := n.Right; end; end; function TSynEditMarkLineList.GetMarkLineByMarkIndex(var AMarkIndex: Integer): TSynEditMarkLine; begin Result := TSynEditMarkLine(FRoot); while Result <> nil do begin if AMarkIndex < Result.LeftSizeSum then begin Result := Result.Left; continue; end; AMarkIndex := AMarkIndex - Result.LeftSizeSum; if AMarkIndex < Result.Size then begin break; end else begin AMarkIndex := AMarkIndex - Result.Size; Result := Result.Right; continue; end; end; end; function TSynEditMarkLineList.GetMarkLine(LineNum: Integer): TSynEditMarkLine; begin Result := GetOrAddMarkLine(LineNum, False); end; function TSynEditMarkLineList.GetOrAddMarkLine(LineNum: Integer; AddIfNotExist: Boolean; UseNext: Boolean = False): TSynEditMarkLine; var rStartPosition: Integer; p: TSynEditMarkLine; begin Result := TSynEditMarkLine(FRoot); if (Result = nil) and AddIfNotExist then begin Result := TSynEditMarkLine.Create(LineNum, Self); SetRoot(Result); exit; end; rStartPosition := fRootOffset; p := nil; while (Result <> nil) do begin rStartPosition := rStartPosition + Result.FPositionOffset; if rStartPosition > LineNum then begin if (Result.Left = nil) and AddIfNotExist then begin p := Result; Result := TSynEditMarkLine.Create(LineNum, Self); p.SetLeftChild(Result, -rStartPosition); BalanceAfterInsert(Result); break; end; // Store current in p, as posible UseNext p := Result; Result := Result.Left; end else if LineNum = rStartPosition then begin break; end else if rStartPosition < LineNum then begin if (Result.Right = nil) and AddIfNotExist then begin p := Result; Result := TSynEditMarkLine.Create(LineNum, Self); p.SetRightChild(Result, -rStartPosition); BalanceAfterInsert(Result); break; end; Result := Result.Right; end; end; // while if UseNext and (Result = nil) then Result := p; end; procedure TSynEditMarkLineList.AdjustForLinesInserted(AStartLine, ALineCount: Integer); var Current: TSynEditMarkLine; CurrentLine: Integer; begin Current := TSynEditMarkLine(fRoot); CurrentLine := FRootOffset; while (Current <> nil) do begin CurrentLine := CurrentLine + Current.LineOffset; if AStartLine <= CurrentLine then begin // move current node Current.LineOffset := Current.LineOffset + ALineCount; CurrentLine := CurrentLine + ALineCount; if Current.Left <> nil then Current.Left.LineOffset := Current.Left.LineOffset - ALineCount; Current := Current.Left; end else if AStartLine > CurrentLine then begin // The new lines are entirly behind the current node Current := Current.Right; end end; end; procedure TSynEditMarkLineList.AdjustForLinesDeleted(AStartLine, ALineCount: Integer); var Current : TSynEditMarkLine; CurrentLine, LastLineToDelete: Integer; begin Current := TSynEditMarkLine(fRoot); CurrentLine := FRootOffset;; LastLineToDelete := AStartLine + ALineCount - 1; // only valid for delete; ALineCount < 0 while (Current <> nil) do begin CurrentLine := CurrentLine + Current.LineOffset; if (AStartLine = CurrentLine) or ((AStartLine < CurrentLine) and (LastLineToDelete >= CurrentLine)) then begin { $IFDEF AssertSynMemIndex} raise Exception.Create('TSynEditMarkLineList.AdjustForLinesDeleted node to remove'); { $ENDIF} end else if AStartLine < CurrentLine then begin // move current node (includes right subtree / left subtree needs eval) Current.LineOffset := Current.LineOffset - ALineCount; CurrentLine := CurrentLine - ALineCount; Current := Current.Left; if Current <> nil then Current.LineOffset := Current.LineOffset + ALineCount; end else if AStartLine > CurrentLine then begin // The deleted lines are entirly behind the current node Current := Current.Right; end; end; end; constructor TSynEditMarkLineList.Create(AOwner: TSynEditMarkList); begin inherited Create; FMarkList := AOwner; FIndexIterator := TSynEditMarkIterator.Create(FMarkList); FLastIteratorIndex := -1; end; procedure TSynEditMarkLineList.SetMark(Index : Integer; const AValue: TSynEditMark); var l: TSynEditMarkLine; begin l := GetMarkLineByMarkIndex(Index); l[Index] := AValue; end; destructor TSynEditMarkLineList.Destroy; begin Clear(True); FreeAndNil(FIndexIterator); inherited Destroy; end; procedure TSynEditMarkLineList.Remove(Item: TSynEditMarkLine; FreeMarks: Boolean); begin Item.Clear(FreeMarks); RemoveNode(Item); Item.Free; end; procedure TSynEditMarkLineList.Clear; begin Clear(False); end; procedure TSynEditMarkLineList.Clear(FreeMarks: Boolean); procedure DeleteNode(ANode: TSynEditMarkLine); begin ANode.IncLockChangeSize; ANode.Clear(FreeMarks); if ANode.Left <> nil then DeleteNode(ANode.Left); if ANode.Right <> nil then DeleteNode(ANode.Right); DisposeNode(TSynSizedDifferentialAVLNode(ANode)); end; begin if FRoot <> nil then DeleteNode(TSynEditMarkLine(FRoot)); SetRoot(nil); end; function TSynEditMarkLineList.GetOrAddLine(LineNum: Integer): TSynEditMarkLine; begin Result := GetOrAddMarkLine(LineNum, True); end; function TSynEditMarkLineList.GetLineOrNext(LineNum: Integer): TSynEditMarkLine; begin Result := GetOrAddMarkLine(LineNum, False, True); end; function TSynEditMarkLineList.AddMark(Item: TSynEditMark): Integer; var l: TSynEditMarkLine; begin l := GetOrAddLine(Item.Line); Result := l.Add(Item); Result := Result + l.GetSizesBeforeSum; end; procedure TSynEditMarkLineList.DeleteMark(Index: Integer); var m: TSynEditMark; begin m := Mark[Index]; m.MarkLine.Remove(m) end; function TSynEditMarkLineList.RemoveMark(Item: TSynEditMark): Integer; begin if Item.MarkLine = nil then exit; Result := Item.MarkLine.GetSizesBeforeSum; Result := Result + Item.MarkLine.Remove(Item); end; function TSynEditMarkLineList.IndexOfMark(AMark: TSynEditMark): Integer; var l: TSynEditMarkLine; begin l := AMark.MarkLine; Result := l.GetSizesBeforeSum + l.IndexOf(AMark); end; { TSynEditMarkList } function TSynEditMarkList.Add(Item: TSynEditMark): Integer; begin Result := FMarkLines.AddMark(Item); Item.MarkList := Self; DoChange; end; procedure TSynEditMarkList.ClearLine(Line: integer); var l: TSynEditMarkLine; begin // TODO: move to marklinelist l := FMarkLines.Lines[Line]; if l <> nil then l.Clear(True); end; constructor TSynEditMarkList.Create(AOwner: TSynEditBase; ALines: TSynEditStrings); begin FOwnerList := TFPList.Create; FOwnerList.Add(AOwner); FMarkLines := TSynEditMarkLineList.Create(Self); FChangeHandlers := TSynEditMarkChangedHandlerList.Create; inherited Create; FLines := ALines; FLines.AddEditHandler(@DoLinesEdited); FInternalIterator := TSynEditMarkIterator.Create(Self); end; destructor TSynEditMarkList.Destroy; begin FLines.RemoveEditHandler(@DoLinesEdited); inherited Destroy; // Todo: clear changehandlers first? FreeAndNil(FMarkLines); // will free all Marks FreeAndNil(FChangeHandlers); FreeAndNil(FInternalIterator); FreeAndNil(FOwnerList); end; {$IFDEF SynDebug} procedure TSynEditMarkList.Debug; begin FMarkLines.Debug; end; {$ENDIF} function TSynEditMarkList.GetMarkLine(LineNum: Integer): TSynEditMarkLine; begin Result := FMarkLines.Lines[LineNum]; end; procedure TSynEditMarkList.DoChange; begin if Assigned(FOnChange) then FOnChange(Self); end; procedure TSynEditMarkList.MarkChanged(Sender: TSynEditMark; AChanges: TSynEditMarkChangeReasons); begin FChangeHandlers.CallMarkChangedHandlers(Sender, AChanges); end; function TSynEditMarkList.Get(Index: Integer): TSynEditMark; begin Result := FMarkLines.Mark[Index]; end; //Returns up to maxMarks book/gutter marks for a chosen line. procedure TSynEditMarkList.Delete(Index: Integer); var Mrk: TSynEditMark; begin Mrk := FMarkLines.Mark[Index]; Mrk.MarkList := Self; FMarkLines.RemoveMark(Mrk); DoChange; end; procedure TSynEditMarkList.Put(Index: Integer; Item: TSynEditMark); begin FMarkLines.Mark[Index] := Item; Item.MarkList := Self; DoChange; end; procedure TSynEditMarkList.DoLinesEdited(Sender: TSynEditStrings; aLinePos, aBytePos, aCount, aLineBrkCnt: Integer; aText: String); var i: Integer; CurLine, NextLine: TSynEditMarkLine; LinePos, LineBSize: Integer; f: Boolean; Mrk: TSynEditMark; begin CurLine := FMarkLines.GetLineOrNext(aLinePos); if (CurLine = nil) then exit; LinePos := CurLine.LineNum; LineBSize := 0; FInternalIterator.Invalidate; // TODO: better notification system if aLineBrkCnt > 0 then begin FMarkLines.AdjustForLinesInserted(aLinePos + 1, aLineBrkCnt); FInternalIterator.GotoMark(CurLine[0]); // TODO: better notification system if (LinePos = aLinePos) then begin i := CurLine.Count - 1; while i >= 0 do begin Mrk := CurLine.Items[i]; if (Mrk.Column > aBytePos) then begin Mrk.Line := Mrk.Line + aLineBrkCnt; Mrk.Column := Mrk.Column - aBytePos + 1; end; dec(i); end; end; end else if aLineBrkCnt < 0 then begin if (LinePos = aLinePos) then CurLine := TSynEditMarkLine(CurLine.Successor(LinePos, LineBSize)); while (CurLine <> nil) and (LinePos <= aLinePos - aLineBrkCnt) do begin f := LinePos = aLinePos - aLineBrkCnt; NextLine := TSynEditMarkLine(CurLine.Successor(LinePos, LineBSize)); i := CurLine.Count - 1; while i >= 0 do begin Mrk := CurLine.Items[i]; Mrk.Line := aLinePos; if f then Mrk.Column := Mrk.Column + aBytePos - 1 else Mrk.Column := aBytePos; // or delete ? dec(i); end; CurLine := NextLine; end; if CurLine <> nil then FInternalIterator.GotoMark(CurLine[0]); // TODO: better notification system FMarkLines.AdjustForLinesDeleted(aLinePos + 1, -aLineBrkCnt); end else if aLinePos = LinePos then begin if aCount > 0 then begin for i := 0 to CurLine.Count - 1 do if (CurLine.Items[i].Column > aBytePos) then CurLine.Items[i].Column := CurLine.Items[i].Column + aCount end else if aCount < 0 then begin for i := 0 to CurLine.Count - 1 do if (CurLine.Items[i].Column > aBytePos) then CurLine.Items[i].Column := Max(aBytePos, CurLine.Items[i].Column + aCount); end; end; if FInternalIterator.Mark <> nil then begin // TODO: better notification system repeat FInternalIterator.Mark.DoChange([smcrLine]); until not FInternalIterator.Next; end; end; function TSynEditMarkList.HasOwnerEdit(AEdit: TSynEditBase): Boolean; begin Result := FOwnerList.IndexOf(AEdit) >= 0; end; function TSynEditMarkList.Remove(Item: TSynEditMark): Integer; begin Item.MarkList := nil; Result := FMarkLines.RemoveMark(Item); DoChange; end; function TSynEditMarkList.IndexOf(Item: TSynEditMark): Integer; begin Result := FMarkLines.IndexOfMark(Item); end; function TSynEditMarkList.Count: Integer; begin Result := FMarkLines.MarkCount; end; procedure TSynEditMarkList.RegisterChangeHandler(Handler: TSynEditMarkChangeEvent; Filter: TSynEditMarkChangeReasons); begin FChangeHandlers.Add(Handler, Filter); end; procedure TSynEditMarkList.UnRegisterChangeHandler(Handler: TSynEditMarkChangeEvent); begin FChangeHandlers.Remove(Handler); end; { TSynEditMarkChangedHandlerList } procedure TSynEditMarkChangedHandlerList.Add(AHandler: TSynEditMarkChangeEvent; Changes: TSynEditMarkChangeReasons); begin AddBitFilter(TMethod(AHandler), LongInt(Changes)); end; procedure TSynEditMarkChangedHandlerList.Remove(AHandler: TSynEditMarkChangeEvent); begin inherited Remove(TMethod(AHandler)); end; procedure TSynEditMarkChangedHandlerList.CallMarkChangedHandlers(Sender: TSynEditMark; Changes: TSynEditMarkChangeReasons); var i: Integer; begin i:=Count; while NextDownIndexBitFilter(i, LongInt(Changes)) do TSynEditMarkChangeEvent(FItems[i].FHandler)(Sender, Changes); end; { TSynEditMarkIterator } procedure TSynEditMarkIterator.GotoBOL; begin FBOL := True; FEOL := False; FCurrentItem := nil; end; procedure TSynEditMarkIterator.GotoEOL; begin FBOL := False; FEOL := True; FCurrentItem := nil; end; constructor TSynEditMarkIterator.Create(AOwner: TSynEditMarkList); begin inherited Create; FMarkList := AOwner; FCurrentItem := nil; FBOL := False; FEOL := False; end; procedure TSynEditMarkIterator.Invalidate; begin FCurrentItem := nil; FBOL := False; FEOL := False; end; procedure TSynEditMarkIterator.GotoMark(AMark: TSynEditMark); begin if (AMark <> nil) and (AMark.FMarkList <> FMarkList) then raise Exception.Create('Invalid list'); FCurrentItem := AMark; FBOL := False; FEOL := False; end; function TSynEditMarkIterator.First: Boolean; var ML: TSynEditMarkLine; begin ML := TSynEditMarkLine(FMarkList.FMarkLines.First); if ML <> nil then FCurrentItem := ML[0] else FCurrentItem := nil; FCurrentIndex := 0; FBOL := FCurrentItem = nil; FEOL := FCurrentItem = nil; Result := FCurrentItem <> nil; end; function TSynEditMarkIterator.Last: Boolean; var ML: TSynEditMarkLine; begin ML := TSynEditMarkLine(FMarkList.FMarkLines.Last); if ML <> nil then FCurrentItem := ML[ML.Count - 1] else FCurrentItem := nil; FCurrentIndex := -1; FBOL := FCurrentItem = nil; FEOL := FCurrentItem = nil; Result := FCurrentItem <> nil; end; function TSynEditMarkIterator.Next: Boolean; var ML: TSynEditMarkLine; begin if FBOL then begin First; Result := FCurrentItem <> nil; exit; end else if FCurrentItem = nil then begin FBOL := False; FEOL := False; Result := False; exit; end; if (FCurrentIndex < 0) or (FCurrentIndex >= FCurrentItem.FMarkLine.Count) or (FCurrentItem.FMarkLine[FCurrentIndex] <> FCurrentItem) then FCurrentIndex := FCurrentItem.FMarkLine.IndexOf(FCurrentItem); inc(FCurrentIndex); if FCurrentIndex < FCurrentItem.FMarkLine.Count then begin FCurrentItem := FCurrentItem.FMarkLine[FCurrentIndex]; Result := True; end else begin ML := TSynEditMarkLine(FCurrentItem.FMarkLine.Successor); if ML <> nil then begin FCurrentIndex := 0; FCurrentItem := ML[FCurrentIndex]; Result := True; end else begin FCurrentItem := nil; FEOL := True; Result := False; end; end; end; function TSynEditMarkIterator.Previous: Boolean; var ML: TSynEditMarkLine; begin if FEOL then begin Last; Result := FCurrentItem <> nil; exit; end else if FCurrentItem = nil then begin FBOL := False; FEOL := False; Result := False; exit; end; if (FCurrentIndex < 0) or (FCurrentIndex >= FCurrentItem.FMarkLine.Count) or (FCurrentItem.FMarkLine[FCurrentIndex] <> FCurrentItem) then FCurrentIndex := FCurrentItem.FMarkLine.IndexOf(FCurrentItem); dec(FCurrentIndex); if FCurrentIndex >= 0 then begin FCurrentItem := FCurrentItem.FMarkLine[FCurrentIndex]; Result := True; end else begin ML := TSynEditMarkLine(FCurrentItem.FMarkLine.Precessor); if ML <> nil then begin FCurrentIndex := ML.Count - 1; FCurrentItem := ML[FCurrentIndex]; Result := True; end else begin FCurrentItem := nil; FBOL := True; Result := False; end; end; end; function TSynEditMarkIterator.NextLine: Boolean; var ML: TSynEditMarkLine; begin if FBOL then begin First; Result := FCurrentItem <> nil; exit; end else if FCurrentItem = nil then begin FBOL := False; FEOL := False; Result := False; exit; end; ML := TSynEditMarkLine(FCurrentItem.FMarkLine.Successor); if ML <> nil then begin FCurrentIndex := 0; FCurrentItem := ML[FCurrentIndex]; Result := True; end else begin FCurrentItem := nil; FEOL := True; Result := False; end; end; function TSynEditMarkIterator.PreviousLine: Boolean; var ML: TSynEditMarkLine; begin if FEOL then begin Last; Result := FCurrentItem <> nil; exit; end else if FCurrentItem = nil then begin FBOL := False; FEOL := False; Result := False; exit; end; ML := TSynEditMarkLine(FCurrentItem.FMarkLine.Precessor); if ML <> nil then begin FCurrentIndex := ML.Count - 1; FCurrentItem := ML[FCurrentIndex]; Result := True; end else begin FCurrentItem := nil; FBOL := True; Result := False; end; end; function TSynEditMarkIterator.IsValid: Boolean; begin Result := (FCurrentItem <> nil) or FBOL or FEOL; end; function TSynEditMarkIterator.IsValidAndNotModified: Boolean; begin Result := IsValid and (FCurrentIndex >= 0) and (FCurrentIndex < FCurrentItem.FMarkLine.Count) and (FCurrentItem.FMarkLine[FCurrentIndex] = FCurrentItem); end; end.