lazarus/components/synedit/syneditmarks.pp

1398 lines
36 KiB
ObjectPascal

unit SynEditMarks;
{$I synedit.inc}
{$IFOPT C+}
{$DEFINE AssertSynMemIndex}
{$ENDIF}
{$IFDEF SynAssert}
{$DEFINE AssertSynMemIndex}
{$ENDIF}
interface
uses
Classes, Controls, SysUtils, Math, SynEditMiscClasses, LazSynEditText,
LazLoggerBase, ImgList;
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: TCustomImageList;
FMarkLine: TSynEditMarkLine;
FMarkList: TSynEditMarkList;
FLine: Integer; // Only valid, if not part of a TSynEditMarkLine
FOldLine: integer;
FOwnerEdit: TSynEditBase;
function GetLine: integer;
procedure SetBookmarkNum(AValue: integer);
procedure SetMarkLine(const AValue: TSynEditMarkLine);
procedure SetMarkList(const AValue: TSynEditMarkList); virtual;
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; virtual;
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 SetBookmarkNum;
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: TCustomImageList read FImageList write FImageList;
end;
{ TSynEditBookMark }
TSynEditBookMark = class(TSynEditMark)
private type
{ TSynEditTopLeftMark }
TSynEditTopLeftMark = class(TSynEditMark)
private
FBookMark: TSynEditBookMark;
public
destructor Destroy; override;
end;
private
FTopLeftMark: TSynEditMark;
procedure SetMarkList(const AValue: TSynEditMarkList); override;
protected
function GetIsBookmark: boolean; override;
public
destructor Destroy; override;
procedure SetTopLeft(ATop, ALeft: integer);
property TopLeftMark: TSynEditMark read FTopLeftMark;
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 VisibleCount: 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;
FInClear: boolean;
// 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;
protected
// will be reset by TSynEditMarkLine.ChangeSize;
FLastIteratorIndex: Integer;
function CreateNode(APosition: Integer): TSynSizedDifferentialAVLNode; override;
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: TSynEditStringsLinked;
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: TSynEditStringsLinked);
destructor Destroy; override;
{$IFDEF SynDebug}
procedure Debug;
{$ENDIF}
procedure Add(Item: TSynEditMark);
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.SetBookmarkNum(AValue: integer);
begin
assert(Self is TSynEditBookMark, 'TSynEditMark.SetBookmarkNum: Self is TSynEditBookMark');
FBookmarkNum := AValue;
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 := False;
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;
{ TSynEditBookMark }
procedure TSynEditBookMark.SetMarkList(const AValue: TSynEditMarkList);
begin
inherited SetMarkList(AValue);
if (FMarkList <> nil) and (FTopLeftMark <> nil) then
FMarkList.Add(FTopLeftMark);
end;
function TSynEditBookMark.GetIsBookmark: boolean;
begin
Result := True;
end;
destructor TSynEditBookMark.Destroy;
begin
if FTopLeftMark <> nil then begin
TSynEditTopLeftMark(FTopLeftMark).FBookMark := nil;
if FMarkList <> nil then // Otherwise TSynEditMarkLineList.Clear will take care.
FTopLeftMark.Free;
end;
inherited Destroy;
end;
procedure TSynEditBookMark.SetTopLeft(ATop, ALeft: integer);
begin
if (ATop <= 0) or (ALeft <= 0) then
exit;
if FTopLeftMark = nil then begin
FTopLeftMark := TSynEditTopLeftMark.Create(OwnerEdit);
TSynEditTopLeftMark(FTopLeftMark).FBookMark := Self;
FTopLeftMark.Line := ATop;
FTopLeftMark.Column := ALeft;
if (FMarkList <> nil) then
FMarkList.Add(FTopLeftMark);
end
else
if (FTopLeftMark.Line <> ATop) or (FTopLeftMark.Column <> ALeft) then begin
FTopLeftMark.Line := ATop;
FTopLeftMark.Column := ALeft;
end;
end;
{ TSynEditBookMark.TSynEditTopLeftMark }
destructor TSynEditBookMark.TSynEditTopLeftMark.Destroy;
begin
if FBookMark <> nil then
FBookMark.FTopLeftMark := nil;
inherited Destroy;
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;
if Mark2 > Mark1 then
Result := 1
else if Mark1 > Mark2 then
Result := -1
else
Result := 0;
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);
var
m: TSynEditMark;
begin
inc(FLockChangeSize);
try
while Count > 0 do begin
m := Items[0];
FMarks.Delete(0);
if FreeMarks then begin
m.MarkList := nil; // stop destroy from removing item from list
m.FMarkLine := nil; // stop destroy from removing item from self
m.Free
end else
m.MarkLine := nil;
end;
finally
dec(FLockChangeSize);
end;
ChangeSize;
end;
function TSynEditMarkLine.Count: Integer;
begin
Result := FMarks.Count;
end;
function TSynEditMarkLine.VisibleCount: Integer;
var
i: Integer;
begin
Result := 0;
for i := 0 to FMarks.Count - 1 do
if Items[i].Visible then
inc(Result);
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;
var
d1, d2: Integer;
begin
Result := TSynEditMarkLine(FindNodeAtLeftSize(AMarkIndex, d1, d2));
AMarkIndex := AMarkIndex - d2;
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
d1, d2: Integer;
begin
if AddIfNotExist then
Result := TSynEditMarkLine(FindNodeAtPosition(LineNum, afmCreate, d1, d2))
else
if UseNext then
Result := TSynEditMarkLine(FindNodeAtPosition(LineNum, afmNext, d1, d2))
else
Result := TSynEditMarkLine(FindNodeAtPosition(LineNum, afmNil, d1, d2));
end;
function TSynEditMarkLineList.CreateNode(APosition: Integer): TSynSizedDifferentialAVLNode;
begin
Result := TSynEditMarkLine.Create(APosition, Self);
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
if FInClear then
exit;
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
FInClear := True;
if FRoot <> nil then DeleteNode(TSynEditMarkLine(FRoot));
SetRoot(nil);
FInClear := False;
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(-1);
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 }
procedure TSynEditMarkList.Add(Item: TSynEditMark);
begin
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: TSynEditStringsLinked);
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.