mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-04-07 00:18:26 +02:00
1046 lines
29 KiB
ObjectPascal
1046 lines
29 KiB
ObjectPascal
{-------------------------------------------------------------------------------
|
|
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.
|
|
|
|
-------------------------------------------------------------------------------}
|
|
unit SynEditTextBase;
|
|
|
|
{$I synedit.inc}
|
|
{$IFOPT C+}
|
|
{$DEFINE AssertSynMemIndex}
|
|
{$ENDIF}
|
|
{$IFDEF SynAssert}
|
|
{$DEFINE AssertSynMemIndex}
|
|
{$ENDIF}
|
|
{$IFDEF SynUndoDebug}
|
|
{$Define SynUndoDebugItems}
|
|
{$Define SynUndoDebugCalls}
|
|
{$ENDIF}
|
|
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils, LCLProc, SynEditMiscProcs, SynEditKeyCmds;
|
|
|
|
type
|
|
|
|
TSynEditUndoList = class;
|
|
TSynEditUndoItem = class;
|
|
|
|
type
|
|
|
|
{ TSynEditStorageMem }
|
|
|
|
TSynEditStorageMem = class
|
|
private
|
|
FItemSize: Integer;
|
|
FMem: PByte;
|
|
FCount, FCapacity: Integer;
|
|
function GetItemPointer(Index: Integer): Pointer; inline;
|
|
procedure SetItemSize(const AValue: Integer);
|
|
protected
|
|
function GetInintialForItemSize: Integer; virtual;
|
|
procedure SetCapacity(const AValue: Integer); virtual;
|
|
procedure SetCount(const AValue: Integer); virtual;
|
|
procedure Move(AFrom, ATo, ALen: Integer); virtual;
|
|
|
|
property Mem: PByte read FMem;
|
|
property ItemPointer[Index: Integer]: Pointer read GetItemPointer;
|
|
// ItemSize must be set in the constructor, and never be changed
|
|
property ItemSize: Integer read FItemSize write SetItemSize;
|
|
public
|
|
constructor Create;
|
|
destructor Destroy; override;
|
|
procedure InsertRows(AIndex, ACount: Integer); virtual;
|
|
procedure DeleteRows(AIndex, ACount: Integer); virtual;
|
|
property Capacity: Integer read FCapacity write SetCapacity;
|
|
// Capacity must be maintained by owner (Shrink)
|
|
property Count: Integer read FCount write SetCount;
|
|
end;
|
|
|
|
{ TSynManagedStorageMem }
|
|
|
|
TSynManagedStorageMem = class(TSynEditStorageMem)
|
|
protected
|
|
// Todo: Add Flags,which updates are required
|
|
procedure LineTextChanged(AIndex: Integer; ACount: Integer = 1); virtual;
|
|
procedure InsertedLines(AIndex, ACount: Integer); virtual;
|
|
procedure DeletedLines(AIndex, ACount: Integer); virtual;
|
|
end;
|
|
|
|
|
|
{ TSynManagedStorageMemList }
|
|
|
|
TSynManagedStorageMemList = class
|
|
private
|
|
FStorageMemList: Array of TSynManagedStorageMem;
|
|
FClassList: Array of Pointer;
|
|
function GetStorageMems(Index: Pointer): TSynManagedStorageMem;
|
|
procedure SetStorageMems(Index: Pointer; const AValue: TSynManagedStorageMem);
|
|
procedure SetChildCapacities(const AValue: Integer);
|
|
procedure SetChildCounts(const AValue: Integer);
|
|
public
|
|
procedure ChildInsertRows(AIndex, ACount: Integer);
|
|
procedure ChildDeleteRows(AIndex, ACount: Integer);
|
|
procedure CallMove(AFrom, ATo, ALen: Integer);
|
|
procedure CallLineTextChanged(AIndex: Integer; ACount: Integer = 1);
|
|
procedure CallInsertedLines(AIndex, ACount: Integer);
|
|
procedure CallDeletedLines(AIndex, ACount: Integer);
|
|
property ChildCapacities: Integer write SetChildCapacities;
|
|
property ChildCounts: Integer write SetChildCounts;
|
|
property StorageMems[Index: Pointer]: TSynManagedStorageMem
|
|
read GetStorageMems write SetStorageMems; default;
|
|
end;
|
|
|
|
{ TSynEditStringsBase }
|
|
|
|
TSynEditStringsBase = class(TStrings)
|
|
protected
|
|
function GetRange(Index: Pointer): TSynManagedStorageMem; virtual; abstract;
|
|
procedure PutRange(Index: Pointer; const ARange: TSynManagedStorageMem); virtual; abstract;
|
|
public
|
|
procedure SendHighlightChanged(aIndex, aCount: Integer); virtual; abstract;
|
|
function GetPChar(ALineIndex: Integer): PChar; // experimental
|
|
function GetPChar(ALineIndex: Integer; out ALen: Integer): PChar; virtual; abstract; // experimental
|
|
property Ranges[Index: Pointer]: TSynManagedStorageMem read GetRange write PutRange;
|
|
end;
|
|
|
|
{ TSynEditUndoItem }
|
|
|
|
TSynEditUndoItem = class(TObject)
|
|
protected
|
|
// IsEqual is only needed/implemented for Carets
|
|
function IsEqualContent(AnItem: TSynEditUndoItem): Boolean; virtual;
|
|
function DebugString: String; virtual;
|
|
public
|
|
function IsEqual(AnItem: TSynEditUndoItem): Boolean;
|
|
function IsCaretInfo: Boolean; virtual;
|
|
function PerformUndo(Caller: TObject): Boolean; virtual; abstract;
|
|
end;
|
|
|
|
{ TSynEditUndoGroup }
|
|
|
|
TSynEditUndoGroup = class(TObject)
|
|
private
|
|
FItems: Array of TSynEditUndoItem;
|
|
FCount, FCapacity: Integer;
|
|
FReason: TSynEditorCommand;
|
|
function GetItem(Index: Integer): TSynEditUndoItem;
|
|
procedure Grow(ANeededCapacity: Integer = 0);
|
|
protected
|
|
Function HasUndoInfo: Boolean;
|
|
procedure Append(AnUndoGroup: TSynEditUndoItem);
|
|
function GetLast: TSynEditUndoItem; // Excludes caret // does not POP
|
|
function PopLast: TSynEditUndoItem; // Excludes caret
|
|
procedure TranferTo(AnUndoGroup: TSynEditUndoGroup);
|
|
function CanMergeWith(AnUndoGroup: TSynEditUndoGroup): Boolean;
|
|
procedure MergeWith(AnUndoGroup: TSynEditUndoGroup);
|
|
public
|
|
constructor Create;
|
|
Destructor Destroy; override;
|
|
|
|
procedure Assign(AnUndoGroup: TSynEditUndoGroup);
|
|
procedure Add(AnItem: TSynEditUndoItem);
|
|
procedure Clear(OnlyFreeItems: Boolean = False);
|
|
procedure Insert(AIndex: Integer; AnItem: TSynEditUndoItem);
|
|
procedure Delete(AIndex: Integer);
|
|
function Pop: TSynEditUndoItem;
|
|
property Count: Integer read FCount;
|
|
property Items [Index: Integer]: TSynEditUndoItem read GetItem;
|
|
property Reason: TSynEditorCommand read FReason write FReason;
|
|
end;
|
|
|
|
|
|
TSynGetCaretUndoProc = function: TSynEditUndoItem of object;
|
|
TSynUpdateCaretUndoProc = procedure(var AnUndoItem: TSynEditUndoItem; AnIsBeginUndo: Boolean) of object;
|
|
|
|
{ TSynEditUpdateCaretUndoProcList }
|
|
|
|
TSynEditUpdateCaretUndoProcList = Class(TMethodList)
|
|
public
|
|
procedure CallSearchUpdateCaretUndoProcs(var AnUndoItem: TSynEditUndoItem; AnIsBeginUndo: Boolean);
|
|
end;
|
|
|
|
{ TSynEditUndoList }
|
|
|
|
TSynEditUndoList = class(TObject)
|
|
private
|
|
FGroupUndo: Boolean;
|
|
FIsInsideRedo: Boolean;
|
|
FUndoGroup: TSynEditUndoGroup;
|
|
FInGroupCount: integer;
|
|
fFullUndoImposible: boolean;
|
|
fItems: TList;
|
|
fLockCount: integer;
|
|
fMaxUndoActions: integer;
|
|
fOnAdded: TNotifyEvent;
|
|
FOnNeedCaretUndo: TSynGetCaretUndoProc;
|
|
FOnNeedCaretUndoList: TSynEditUpdateCaretUndoProcList;
|
|
fUnModifiedItem: integer;
|
|
FForceGroupEnd: Boolean;
|
|
procedure EnsureMaxEntries;
|
|
function GetCanUndo: boolean;
|
|
function GetCurrentReason: TSynEditorCommand;
|
|
function GetItemCount: integer;
|
|
procedure SetCurrentReason(const AValue: TSynEditorCommand);
|
|
procedure SetMaxUndoActions(Value: integer);
|
|
public
|
|
{$IFDEF SynUndoDebugCalls}
|
|
DebugName: string;
|
|
{$ENDIF}
|
|
constructor Create;
|
|
destructor Destroy; override;
|
|
procedure AddChange(AChange: TSynEditUndoItem);
|
|
// "LastChange: Either in current Group, or in last Group, if no current
|
|
procedure AppendToLastChange(AChange: TSynEditUndoItem);
|
|
function GetLastChange: TSynEditUndoItem; // Excludes caret
|
|
function PopLastChange: TSynEditUndoItem; // Excludes caret
|
|
procedure BeginBlock;
|
|
procedure EndBlock;
|
|
procedure Clear;
|
|
procedure Lock;
|
|
function PopItem: TSynEditUndoGroup;
|
|
function PeekItem: TSynEditUndoGroup;
|
|
procedure Unlock;
|
|
function IsLocked: Boolean;
|
|
procedure MarkTopAsUnmodified;
|
|
procedure ForceGroupEnd;
|
|
function RealCount: Integer;
|
|
function IsTopMarkedAsUnmodified: boolean;
|
|
function UnModifiedMarkerExists: boolean;
|
|
{$IFDEF SynUndoDebugBeginEnd}
|
|
property InGroupCount: integer read FInGroupCount;
|
|
{$ENDIF}
|
|
public
|
|
procedure RegisterUpdateCaretUndo(AnUpdateProc: TSynUpdateCaretUndoProc);
|
|
procedure UnregisterUpdateCaretUndo(AnUpdateProc: TSynUpdateCaretUndoProc);
|
|
|
|
property CanUndo: boolean read GetCanUndo;
|
|
property FullUndoImpossible: boolean read fFullUndoImposible;
|
|
property ItemCount: integer read GetItemCount;
|
|
property MaxUndoActions: integer read fMaxUndoActions
|
|
write SetMaxUndoActions;
|
|
property IsInsideRedo: Boolean read FIsInsideRedo write FIsInsideRedo;
|
|
property OnAddedUndo: TNotifyEvent read fOnAdded write fOnAdded;
|
|
property OnNeedCaretUndo : TSynGetCaretUndoProc
|
|
read FOnNeedCaretUndo write FOnNeedCaretUndo;
|
|
property GroupUndo: Boolean read FGroupUndo write FGroupUndo;
|
|
property CurrentGroup: TSynEditUndoGroup read FUndoGroup;
|
|
property CurrentReason: TSynEditorCommand read GetCurrentReason
|
|
write SetCurrentReason;
|
|
end;
|
|
|
|
ESynEditStorageMem = class(Exception);
|
|
|
|
implementation
|
|
|
|
const
|
|
SListIndexOutOfBounds = 'Invalid stringlist index %d';
|
|
|
|
procedure ListIndexOutOfBounds(Index: integer);
|
|
begin
|
|
raise ESynEditStorageMem.CreateFmt(SListIndexOutOfBounds, [Index]);
|
|
end;
|
|
|
|
{ TSynEditUpdateCaretUndoProcList }
|
|
|
|
procedure TSynEditUpdateCaretUndoProcList.CallSearchUpdateCaretUndoProcs(var AnUndoItem: TSynEditUndoItem;
|
|
AnIsBeginUndo: Boolean);
|
|
var
|
|
i: LongInt;
|
|
begin
|
|
i:=Count;
|
|
while NextDownIndex(i) do
|
|
TSynUpdateCaretUndoProc(Items[i])(AnUndoItem, AnIsBeginUndo);
|
|
end;
|
|
|
|
{ TSynEditStringsBase }
|
|
|
|
function TSynEditStringsBase.GetPChar(ALineIndex: Integer): PChar;
|
|
var
|
|
l: Integer;
|
|
begin
|
|
Result := GetPChar(ALineIndex, l);
|
|
end;
|
|
|
|
{ TSynEditUndoList }
|
|
|
|
constructor TSynEditUndoList.Create;
|
|
begin
|
|
inherited Create;
|
|
// Create and keep one undo group => avoids resizing the FItems list
|
|
FOnNeedCaretUndoList := TSynEditUpdateCaretUndoProcList.Create;
|
|
FUndoGroup := TSynEditUndoGroup.Create;
|
|
FIsInsideRedo := False;
|
|
fItems := TList.Create;
|
|
fMaxUndoActions := 1024;
|
|
fUnModifiedItem:=-1;
|
|
FForceGroupEnd := False;
|
|
end;
|
|
|
|
destructor TSynEditUndoList.Destroy;
|
|
begin
|
|
Clear;
|
|
fItems.Free;
|
|
FreeAndNil(FUndoGroup);
|
|
FreeAndNil(FOnNeedCaretUndoList);
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TSynEditUndoList.AddChange(AChange: TSynEditUndoItem);
|
|
var
|
|
ugroup: TSynEditUndoGroup;
|
|
begin
|
|
if fLockCount > 0 then begin
|
|
AChange.Free;
|
|
exit;
|
|
end;
|
|
|
|
if FInGroupCount > 0 then
|
|
FUndoGroup.Add(AChange)
|
|
else begin
|
|
ugroup := TSynEditUndoGroup.Create;
|
|
ugroup.Add(AChange);
|
|
fItems.Add(ugroup);
|
|
if Assigned(fOnAdded) then
|
|
fOnAdded(Self);
|
|
end;
|
|
|
|
EnsureMaxEntries;
|
|
end;
|
|
|
|
procedure TSynEditUndoList.AppendToLastChange(AChange: TSynEditUndoItem);
|
|
var
|
|
cur: Boolean;
|
|
begin
|
|
cur := FUndoGroup.HasUndoInfo;
|
|
if (fLockCount <> 0) or ((fItems.Count = 0) and not cur) then begin
|
|
AChange.Free;
|
|
exit;
|
|
end;
|
|
|
|
if cur then
|
|
FUndoGroup.Append(AChange)
|
|
else
|
|
TSynEditUndoGroup(fItems[fItems.Count-1]).Append(AChange);
|
|
|
|
// Do not callback to synedit, or Redo Info is lost
|
|
EnsureMaxEntries;
|
|
end;
|
|
|
|
function TSynEditUndoList.GetLastChange: TSynEditUndoItem;
|
|
var
|
|
cur: Boolean;
|
|
begin
|
|
Result := nil;
|
|
cur := FUndoGroup.HasUndoInfo;
|
|
if (fItems.Count = 0) and (not cur) then
|
|
exit;
|
|
|
|
if cur then
|
|
Result := FUndoGroup.GetLast
|
|
else
|
|
Result := TSynEditUndoGroup(fItems[fItems.Count-1]).GetLast;
|
|
end;
|
|
|
|
function TSynEditUndoList.PopLastChange: TSynEditUndoItem;
|
|
var
|
|
cur: Boolean;
|
|
begin
|
|
Result := nil;
|
|
cur := FUndoGroup.HasUndoInfo;
|
|
if (fItems.Count = 0) and (not cur) then
|
|
exit;
|
|
|
|
if cur then
|
|
Result := FUndoGroup.PopLast
|
|
else
|
|
Result := TSynEditUndoGroup(fItems[fItems.Count-1]).PopLast;
|
|
end;
|
|
|
|
procedure TSynEditUndoList.BeginBlock;
|
|
var
|
|
c: TSynEditUndoItem;
|
|
begin
|
|
Inc(FInGroupCount);
|
|
if (FInGroupCount = 1) then begin
|
|
FUndoGroup.Clear;
|
|
c := nil;
|
|
if assigned(FOnNeedCaretUndo) then
|
|
c := FOnNeedCaretUndo();
|
|
FOnNeedCaretUndoList.CallSearchUpdateCaretUndoProcs(c, True);
|
|
if c <> nil then
|
|
FUndoGroup.add(c);
|
|
end;
|
|
{$IFDEF SynUndoDebugCalls}
|
|
DebugLnEnter(['>> TSynEditUndoList.BeginBlock ', DebugName, ' ', DbgSName(self), ' ', dbgs(Self), ' fLockCount=', fLockCount, ' Cnt=', fItems.Count, ' FInGroupCount=', FInGroupCount, ' fUnModifiedItem=', fUnModifiedItem]);
|
|
{$ENDIF}
|
|
end;
|
|
|
|
procedure TSynEditUndoList.Clear;
|
|
var
|
|
i: integer;
|
|
begin
|
|
{$IFDEF SynUndoDebugCalls}
|
|
DebugLn(['>> TSynEditUndoList.Clear ', DebugName, ' ', DbgSName(self), ' ', dbgs(Self), ' fLockCount=', fLockCount, ' Cnt=', fItems.Count, ' FInGroupCount=', FInGroupCount]);
|
|
{$ENDIF}
|
|
for i := 0 to fItems.Count - 1 do
|
|
TSynEditUndoGroup(fItems[i]).Free;
|
|
fItems.Clear;
|
|
fFullUndoImposible := FALSE;
|
|
fUnModifiedItem:=-1;
|
|
end;
|
|
|
|
procedure TSynEditUndoList.EndBlock;
|
|
var
|
|
ugroup: TSynEditUndoGroup;
|
|
c: TSynEditUndoItem;
|
|
begin
|
|
if FInGroupCount > 0 then begin
|
|
Dec(FInGroupCount);
|
|
if (FInGroupCount = 0) and FUndoGroup.HasUndoInfo then
|
|
begin
|
|
// Keep position for REDO; Do not replace if present
|
|
if (not FUndoGroup.Items[FUndoGroup.Count - 1].IsCaretInfo) then begin
|
|
c := nil;
|
|
if assigned(FOnNeedCaretUndo) then
|
|
c := FOnNeedCaretUndo();
|
|
FOnNeedCaretUndoList.CallSearchUpdateCaretUndoProcs(c, False);
|
|
if c <> nil then
|
|
FUndoGroup.add(c);
|
|
end;
|
|
if (fItems.Count > 0) and FGroupUndo and (not IsTopMarkedAsUnmodified) and
|
|
(not FForceGroupEnd) and
|
|
FUndoGroup.CanMergeWith(TSynEditUndoGroup(fItems[fItems.Count - 1])) then
|
|
begin
|
|
FUndoGroup.MergeWith(TSynEditUndoGroup(fItems[fItems.Count - 1]));
|
|
FUndoGroup.TranferTo(TSynEditUndoGroup(fItems[fItems.Count - 1]));
|
|
end else begin
|
|
ugroup := TSynEditUndoGroup.Create;
|
|
FUndoGroup.TranferTo(ugroup);
|
|
fItems.Add(ugroup);
|
|
end;
|
|
if Assigned(fOnAdded) then
|
|
fOnAdded(Self);
|
|
FIsInsideRedo := False;
|
|
FForceGroupEnd := False;
|
|
end;
|
|
{$IFDEF SynUndoDebugCalls}
|
|
DebugLnExit(['<< TSynEditUndoList.EndBlock ', DebugName, ' ', DbgSName(self), ' ', dbgs(Self), ' fLockCount=', fLockCount, ' Cnt=', fItems.Count, ' FInGroupCount=', FInGroupCount, ' fUnModifiedItem=', fUnModifiedItem]);
|
|
end else begin
|
|
DebugLn(['** EXTRA TSynEditUndoList.EndBlock ', DebugName, ' ', DbgSName(self), ' ', dbgs(Self), ' fLockCount=', fLockCount, ' Cnt=', fItems.Count, ' FInGroupCount=', FInGroupCount, ' fUnModifiedItem=', fUnModifiedItem]);
|
|
{$ENDIF}
|
|
end;
|
|
end;
|
|
|
|
procedure TSynEditUndoList.EnsureMaxEntries;
|
|
var
|
|
Item: TSynEditUndoGroup;
|
|
begin
|
|
if fItems.Count > fMaxUndoActions then begin
|
|
fFullUndoImposible := TRUE;
|
|
while fItems.Count > fMaxUndoActions do begin
|
|
Item := TSynEditUndoGroup(fItems[0]);
|
|
Item.Free;
|
|
fItems.Delete(0);
|
|
if fUnModifiedItem>=0 then dec(fUnModifiedItem);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TSynEditUndoList.GetCanUndo: boolean;
|
|
begin
|
|
Result := fItems.Count > 0;
|
|
end;
|
|
|
|
function TSynEditUndoList.GetCurrentReason: TSynEditorCommand;
|
|
begin
|
|
Result := FUndoGroup.Reason;
|
|
end;
|
|
|
|
function TSynEditUndoList.GetItemCount: integer;
|
|
begin
|
|
Result := fItems.Count;
|
|
end;
|
|
|
|
procedure TSynEditUndoList.SetCurrentReason(const AValue: TSynEditorCommand);
|
|
begin
|
|
if FUndoGroup.Reason = ecNone then
|
|
FUndoGroup.Reason := AValue;
|
|
end;
|
|
|
|
procedure TSynEditUndoList.Lock;
|
|
begin
|
|
Inc(fLockCount);
|
|
{$IFDEF SynUndoDebugCalls}
|
|
DebugLnEnter(['>> TSynEditUndoList.Lock ', DebugName, ' ', DbgSName(self), ' ', dbgs(Self), ' fLockCount=', fLockCount, ' Cnt=', fItems.Count, ' FInGroupCount=', FInGroupCount]);
|
|
{$ENDIF}
|
|
end;
|
|
|
|
function TSynEditUndoList.PopItem: TSynEditUndoGroup;
|
|
var
|
|
iLast: integer;
|
|
begin
|
|
Result := nil;
|
|
iLast := fItems.Count - 1;
|
|
if iLast >= 0 then begin
|
|
Result := TSynEditUndoGroup(fItems[iLast]);
|
|
fItems.Delete(iLast);
|
|
if fUnModifiedItem>fItems.Count then
|
|
fUnModifiedItem:=-1;
|
|
end;
|
|
end;
|
|
|
|
function TSynEditUndoList.PeekItem: TSynEditUndoGroup;
|
|
begin
|
|
Result := nil;
|
|
if fItems.Count > 0 then
|
|
Result := TSynEditUndoGroup(fItems[fItems.Count - 1]);
|
|
end;
|
|
|
|
procedure TSynEditUndoList.SetMaxUndoActions(Value: integer);
|
|
begin
|
|
if Value < 0 then
|
|
Value := 0;
|
|
if Value <> fMaxUndoActions then begin
|
|
fMaxUndoActions := Value;
|
|
EnsureMaxEntries;
|
|
end;
|
|
end;
|
|
|
|
function TSynEditUndoList.RealCount: Integer;
|
|
begin
|
|
Result := fItems.Count;
|
|
if (FInGroupCount > 0) and FUndoGroup.HasUndoInfo then
|
|
Result := Result + 1;
|
|
end;
|
|
|
|
procedure TSynEditUndoList.Unlock;
|
|
begin
|
|
if fLockCount > 0 then
|
|
Dec(fLockCount);
|
|
{$IFDEF SynUndoDebugCalls}
|
|
DebugLnExit(['<< TSynEditUndoList.UnLock ', DebugName, ' ', DbgSName(self), ' ', dbgs(Self), ' fLockCount=', fLockCount, ' Cnt=', fItems.Count, ' FInGroupCount=', FInGroupCount]);
|
|
{$ENDIF}
|
|
end;
|
|
|
|
function TSynEditUndoList.IsLocked: Boolean;
|
|
begin
|
|
Result := fLockCount > 0;
|
|
end;
|
|
|
|
procedure TSynEditUndoList.MarkTopAsUnmodified;
|
|
begin
|
|
fUnModifiedItem := RealCount;
|
|
end;
|
|
|
|
procedure TSynEditUndoList.ForceGroupEnd;
|
|
begin
|
|
FForceGroupEnd := True;
|
|
end;
|
|
|
|
function TSynEditUndoList.IsTopMarkedAsUnmodified: boolean;
|
|
begin
|
|
Result := (RealCount = fUnModifiedItem);
|
|
end;
|
|
|
|
function TSynEditUndoList.UnModifiedMarkerExists: boolean;
|
|
begin
|
|
Result := fUnModifiedItem >= 0;
|
|
end;
|
|
|
|
procedure TSynEditUndoList.RegisterUpdateCaretUndo(AnUpdateProc: TSynUpdateCaretUndoProc);
|
|
begin
|
|
FOnNeedCaretUndoList.Add(TMethod(AnUpdateProc));
|
|
end;
|
|
|
|
procedure TSynEditUndoList.UnregisterUpdateCaretUndo(AnUpdateProc: TSynUpdateCaretUndoProc);
|
|
begin
|
|
FOnNeedCaretUndoList.Remove(TMethod(AnUpdateProc));
|
|
end;
|
|
|
|
{ TSynEditUndoItem }
|
|
|
|
function TSynEditUndoItem.IsEqualContent(AnItem: TSynEditUndoItem): Boolean;
|
|
begin
|
|
Result := False;
|
|
end;
|
|
|
|
function TSynEditUndoItem.IsEqual(AnItem: TSynEditUndoItem): Boolean;
|
|
begin
|
|
Result := (ClassType = AnItem.ClassType);
|
|
if Result then Result := Result and IsEqualContent(AnItem);
|
|
end;
|
|
|
|
function TSynEditUndoItem.DebugString: String;
|
|
begin
|
|
Result := '';
|
|
end;
|
|
|
|
function TSynEditUndoItem.IsCaretInfo: Boolean;
|
|
begin
|
|
Result := False;
|
|
end;
|
|
|
|
(*
|
|
function TSynEditUndoItem.ChangeStartPos: TPoint;
|
|
begin
|
|
if (fChangeStartPos.Y < fChangeEndPos.Y)
|
|
or ((fChangeStartPos.Y = fChangeEndPos.Y) and (fChangeStartPos.X < fChangeEndPos.X))
|
|
then result := fChangeStartPos
|
|
else result := fChangeEndPos;
|
|
end;
|
|
|
|
function TSynEditUndoItem.ChangeEndPos: TPoint;
|
|
begin
|
|
if (fChangeStartPos.Y < fChangeEndPos.Y)
|
|
or ((fChangeStartPos.Y = fChangeEndPos.Y) and (fChangeStartPos.X < fChangeEndPos.X))
|
|
then result := fChangeEndPos
|
|
else result := fChangeStartPos;
|
|
end;
|
|
*)
|
|
|
|
{ TSynEditUndoGroup }
|
|
|
|
procedure TSynEditUndoGroup.Grow(ANeededCapacity: Integer);
|
|
begin
|
|
if ANeededCapacity > 0 then
|
|
FCapacity := Max(ANeededCapacity, FCapacity)
|
|
else
|
|
FCapacity := FCapacity + Max(10, FCapacity Div 8);
|
|
SetLength(FItems, FCapacity);
|
|
end;
|
|
|
|
function TSynEditUndoGroup.HasUndoInfo: Boolean;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
i := 0;
|
|
while i < FCount do
|
|
if FItems[i].IsCaretInfo then
|
|
inc(i)
|
|
else
|
|
exit(true);
|
|
Result := False;
|
|
end;
|
|
|
|
procedure TSynEditUndoGroup.Append(AnUndoGroup: TSynEditUndoItem);
|
|
var
|
|
i: Integer;
|
|
begin
|
|
i := FCount - 1;
|
|
while (i >= 0) and FItems[i].IsCaretInfo do
|
|
dec(i);
|
|
inc(i);
|
|
Insert(i, AnUndoGroup);
|
|
end;
|
|
|
|
function TSynEditUndoGroup.GetLast: TSynEditUndoItem;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
Result := nil;
|
|
i := FCount - 1;
|
|
while (i >= 0) and FItems[i].IsCaretInfo do
|
|
dec(i);
|
|
if i >= 0 then
|
|
Result := FItems[i];
|
|
end;
|
|
|
|
function TSynEditUndoGroup.PopLast: TSynEditUndoItem;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
Result := nil;
|
|
i := FCount - 1;
|
|
while (i >= 0) and FItems[i].IsCaretInfo do
|
|
dec(i);
|
|
if i >= 0 then begin
|
|
Result := FItems[i];
|
|
Delete(i);
|
|
end;
|
|
end;
|
|
|
|
procedure TSynEditUndoGroup.TranferTo(AnUndoGroup: TSynEditUndoGroup);
|
|
begin
|
|
//AnUndoGroup.Assign(self);
|
|
AnUndoGroup.Clear(True);
|
|
AnUndoGroup.FCapacity := Count;
|
|
AnUndoGroup.FCount := Count;
|
|
AnUndoGroup.FItems := FItems;
|
|
FItems := nil;
|
|
FCapacity := 0;
|
|
FCount := 0; // Do not clear; that would free the items
|
|
|
|
AnUndoGroup.FReason := Reason;
|
|
end;
|
|
|
|
function TSynEditUndoGroup.CanMergeWith(AnUndoGroup: TSynEditUndoGroup): Boolean;
|
|
begin
|
|
// Check if the other group can be merged to the START of this node
|
|
if AnUndoGroup.Count = 0 then exit(True);
|
|
Result := (FReason <> ecNone) and (AnUndoGroup.Reason = FReason)
|
|
and Items[0].IsCaretInfo
|
|
and AnUndoGroup.Items[AnUndoGroup.Count - 1].IsEqual(Items[0]);
|
|
end;
|
|
|
|
procedure TSynEditUndoGroup.MergeWith(AnUndoGroup: TSynEditUndoGroup);
|
|
begin
|
|
// Merge other group to start
|
|
AnUndoGroup.Pop.Free;
|
|
if AnUndoGroup.Count = 0 then
|
|
exit;
|
|
|
|
Grow(Count + AnUndoGroup.Count); // since we replace item[0], this is one extra
|
|
|
|
If AnUndoGroup.Count > 1 then
|
|
System.Move(FItems[1], FItems[AnUndoGroup.Count],
|
|
(FCount - 1) * SizeOf(TSynEditUndoItem));
|
|
assert(Count > 0, 'TSynEditUndoGroup.MergeWith: Count > 0');
|
|
FItems[0].Free;
|
|
System.Move(AnUndoGroup.FItems[0], FItems[0],
|
|
(AnUndoGroup.Count) * SizeOf(TSynEditUndoItem));
|
|
FCount := FCount + AnUndoGroup.FCount - 1;
|
|
AnUndoGroup.FCount := 0;
|
|
end;
|
|
|
|
function TSynEditUndoGroup.GetItem(Index: Integer): TSynEditUndoItem;
|
|
begin
|
|
Result := FItems[Index];
|
|
end;
|
|
|
|
constructor TSynEditUndoGroup.Create;
|
|
begin
|
|
FCount := 0;
|
|
FCapacity := 0;
|
|
end;
|
|
|
|
destructor TSynEditUndoGroup.Destroy;
|
|
begin
|
|
Clear(True);
|
|
FItems := nil;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TSynEditUndoGroup.Assign(AnUndoGroup: TSynEditUndoGroup);
|
|
begin
|
|
Clear;
|
|
FCapacity := AnUndoGroup.Count;
|
|
FCount := FCapacity;
|
|
SetLength(FItems, FCapacity);
|
|
if FCapacity = 0 then
|
|
exit;
|
|
System.Move(AnUndoGroup.FItems[0], FItems[0], FCapacity * SizeOf(TSynEditUndoItem));
|
|
FReason := AnUndoGroup.Reason;
|
|
end;
|
|
|
|
procedure TSynEditUndoGroup.Add(AnItem: TSynEditUndoItem);
|
|
begin
|
|
if (FCount > 0) and AnItem.IsCaretInfo
|
|
and FItems[FCount - 1].IsCaretInfo then
|
|
begin
|
|
FItems[FCount - 1].Free;
|
|
FItems[FCount - 1] := AnItem;
|
|
exit;
|
|
end;
|
|
if FCount >= FCapacity then
|
|
Grow;
|
|
FItems[FCount] := AnItem;
|
|
inc (FCount);
|
|
end;
|
|
|
|
procedure TSynEditUndoGroup.Clear(OnlyFreeItems: Boolean);
|
|
begin
|
|
while FCount > 0 do begin
|
|
dec(FCount);
|
|
FItems[FCount].Free;
|
|
end;
|
|
if (not OnlyFreeItems) and (FCapacity > 100) then begin
|
|
FCapacity := 100;
|
|
SetLength(FItems, FCapacity);
|
|
end;
|
|
FReason := ecNone;
|
|
end;
|
|
|
|
procedure TSynEditUndoGroup.Insert(AIndex: Integer; AnItem: TSynEditUndoItem);
|
|
begin
|
|
if FCount >= FCapacity then
|
|
Grow;
|
|
If AIndex < FCount then
|
|
System.Move(FItems[AIndex], FItems[AIndex+1],
|
|
(FCount - AIndex) * SizeOf(TSynEditUndoItem));
|
|
FItems[AIndex] := AnItem;
|
|
inc (FCount);
|
|
end;
|
|
|
|
procedure TSynEditUndoGroup.Delete(AIndex: Integer);
|
|
begin
|
|
{$IFDEF AssertSynMemIndex}
|
|
if (AIndex < 0) or (AIndex >= FCount) then
|
|
raise Exception.Create(Format('Bad AIndex cnt= %d cap= %d idx= %d',[FCount, FCapacity, AIndex]));
|
|
{$ENDIF}
|
|
If AIndex < FCount - 1 then
|
|
System.Move(FItems[AIndex+1], FItems[AIndex],
|
|
(FCount - AIndex - 1) * SizeOf(TSynEditUndoItem));
|
|
dec (FCount);
|
|
end;
|
|
|
|
function TSynEditUndoGroup.Pop: TSynEditUndoItem;
|
|
begin
|
|
if FCount <= 0 then
|
|
exit(nil);
|
|
dec(FCount);
|
|
Result := FItems[FCount];
|
|
end;
|
|
|
|
{ TSynEditStorageMem }
|
|
|
|
function TSynEditStorageMem.GetItemPointer(Index: Integer): Pointer;
|
|
begin
|
|
{$IFDEF AssertSynMemIndex}
|
|
if (Index < 0) or (Index >= FCapacity) or (FCount > FCapacity) then
|
|
raise Exception.Create(Format('Bad Index cnt= %d cap= %d idx= %d',[FCount, FCapacity, Index]));
|
|
{$ENDIF}
|
|
Result := Pointer(FMem + Index * ItemSize);
|
|
end;
|
|
|
|
procedure TSynEditStorageMem.SetItemSize(const AValue: Integer);
|
|
begin
|
|
if (FCapacity <> 0) then raise Exception.Create('Not allowe dto change ItemSize');
|
|
if FItemSize = AValue then exit;
|
|
FItemSize := AValue;
|
|
end;
|
|
|
|
function TSynEditStorageMem.GetInintialForItemSize: Integer;
|
|
begin
|
|
Result := 0;
|
|
end;
|
|
|
|
procedure TSynEditStorageMem.SetCapacity(const AValue: Integer);
|
|
begin
|
|
{$IFDEF AssertSynMemIndex}
|
|
if (AValue < 0) or (AValue < FCount) then raise Exception.Create('Bad Capacity');
|
|
{$ENDIF}
|
|
if FCapacity = AValue then exit;
|
|
FMem := ReallocMem(FMem, AValue * ItemSize);
|
|
if AValue > FCapacity then
|
|
FillChar((FMem+FCapacity*ItemSize)^, (AValue-FCapacity)*ItemSize, 0);
|
|
FCapacity := AValue;
|
|
end;
|
|
|
|
procedure TSynEditStorageMem.SetCount(const AValue: Integer);
|
|
begin
|
|
{$IFDEF AssertSynMemIndex}
|
|
if (AValue < 0) or (AValue > FCapacity) then raise Exception.Create('Bad Count');
|
|
{$ENDIF}
|
|
FCount := AValue;
|
|
end;
|
|
|
|
constructor TSynEditStorageMem.Create;
|
|
begin
|
|
FItemSize := GetInintialForItemSize;
|
|
FCapacity := 0;
|
|
FCount := 0;
|
|
end;
|
|
|
|
destructor TSynEditStorageMem.Destroy;
|
|
begin
|
|
SetCount(0);
|
|
SetCapacity(0);
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TSynEditStorageMem.InsertRows(AIndex, ACount: Integer);
|
|
begin
|
|
if (AIndex < 0) or (AIndex > Count) then
|
|
ListIndexOutOfBounds(AIndex);
|
|
if Capacity < Count + ACount then
|
|
SetCapacity(Count + ACount + 8);
|
|
if AIndex < Count then
|
|
Move(AIndex, AIndex + ACount, Count - AIndex);
|
|
Count := Count + ACount;
|
|
end;
|
|
|
|
procedure TSynEditStorageMem.DeleteRows(AIndex, ACount: Integer);
|
|
var
|
|
LinesAfter: Integer;
|
|
begin
|
|
if (AIndex < 0) or (AIndex + ACount > Count) then
|
|
ListIndexOutOfBounds(AIndex);
|
|
LinesAfter := Count - (AIndex + ACount);
|
|
if LinesAfter > 0 then
|
|
Move(AIndex + ACount, AIndex, LinesAfter);
|
|
Count := Count - ACount;
|
|
if (Capacity > 16) and (Capacity > Count * 2) then
|
|
Capacity := Capacity - (Count div 2);
|
|
end;
|
|
|
|
procedure TSynEditStorageMem.Move(AFrom, ATo, ALen: Integer);
|
|
var
|
|
len: Integer;
|
|
begin
|
|
{$IFDEF AssertSynMemIndex}
|
|
if (FCount > FCapacity) or (aTo=AFrom) or
|
|
(AFrom < 0) or (AFrom >= FCapacity) or
|
|
(ATo < 0) or (ATo >= FCapacity) then
|
|
raise Exception.Create('Bad Move');
|
|
{$ENDIF}
|
|
if ATo < AFrom then begin
|
|
Len := Min(ALen, AFrom-ATo);
|
|
System.Move((FMem+AFrom*ItemSize)^, (FMem+ATo*ItemSize)^, Alen*ItemSize);
|
|
FillChar((FMem+(AFrom+ALen-Len)*ItemSize)^, Len*ItemSize, 0);
|
|
end else begin
|
|
Len := Min(ALen, ATo-AFrom);
|
|
System.Move((FMem+AFrom*ItemSize)^, (FMem+ATo*ItemSize)^, Alen*ItemSize);
|
|
FillChar((FMem+AFrom*ItemSize)^, Len*ItemSize, 0);
|
|
end;
|
|
end;
|
|
|
|
{ TSynManagedStorageMem }
|
|
|
|
procedure TSynManagedStorageMem.LineTextChanged(AIndex: Integer; ACount: Integer = 1);
|
|
begin // empty base class
|
|
end;
|
|
|
|
procedure TSynManagedStorageMem.InsertedLines(AIndex, ACount: Integer);
|
|
begin // empty base class
|
|
end;
|
|
|
|
procedure TSynManagedStorageMem.DeletedLines(AIndex, ACount: Integer);
|
|
begin // empty base class
|
|
end;
|
|
|
|
{ TSynManagedStorageMemList }
|
|
|
|
function TSynManagedStorageMemList.GetStorageMems(Index: Pointer): TSynManagedStorageMem;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
Result := nil;
|
|
i := length(FClassList);
|
|
while (i > 0) and (Result = nil) do begin
|
|
dec(i);
|
|
if FClassList[i] = Index then
|
|
Result := FStorageMemList[i];
|
|
end;
|
|
end;
|
|
|
|
procedure TSynManagedStorageMemList.SetStorageMems(Index: Pointer;
|
|
const AValue: TSynManagedStorageMem);
|
|
var
|
|
i, j: Integer;
|
|
begin
|
|
i := length(FClassList) - 1;
|
|
while (i >= 0) and (FClassList[i] <> Index) do
|
|
dec(i);
|
|
|
|
if i < 0 then begin
|
|
if AValue = nil then begin
|
|
debugln('Removing none existent range');
|
|
exit;
|
|
end;
|
|
j := length(FClassList);
|
|
SetLength(FClassList, j + 1);
|
|
SetLength(FStorageMemList, j + 1);
|
|
FClassList[j] := Index;
|
|
FStorageMemList[j] := AValue;
|
|
end
|
|
else begin
|
|
if AValue <> nil then
|
|
DebugLn(['TSynEditStringMemory.SetRange - Overwriting old range at index=', i, ' index=', dbgs(Index)]);
|
|
FStorageMemList[i] := AValue;
|
|
if AValue = nil then begin
|
|
for j := i to length(FClassList) - 2 do begin
|
|
FClassList[j] := FClassList[j+1];
|
|
FStorageMemList[j] := FStorageMemList[j+1];
|
|
end;
|
|
SetLength(FClassList, length(FClassList) - 1);
|
|
SetLength(FStorageMemList, length(FStorageMemList) - 1);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TSynManagedStorageMemList.SetChildCapacities(const AValue: Integer);
|
|
var
|
|
i: Integer;
|
|
begin
|
|
for i := 0 to high(FStorageMemList) do
|
|
FStorageMemList[i].Capacity := AValue;
|
|
end;
|
|
|
|
procedure TSynManagedStorageMemList.SetChildCounts(const AValue: Integer);
|
|
var
|
|
i: Integer;
|
|
begin
|
|
for i := 0 to high(FStorageMemList) do
|
|
FStorageMemList[i].Count := AValue;
|
|
end;
|
|
|
|
procedure TSynManagedStorageMemList.ChildInsertRows(AIndex, ACount: Integer);
|
|
var
|
|
i: Integer;
|
|
begin
|
|
for i := 0 to high(FStorageMemList) do
|
|
FStorageMemList[i].InsertRows(AIndex, ACount);
|
|
end;
|
|
|
|
procedure TSynManagedStorageMemList.ChildDeleteRows(AIndex, ACount: Integer);
|
|
var
|
|
i: Integer;
|
|
begin
|
|
for i := 0 to high(FStorageMemList) do
|
|
FStorageMemList[i].DeleteRows(AIndex, ACount);
|
|
end;
|
|
|
|
procedure TSynManagedStorageMemList.CallMove(AFrom, ATo, ALen: Integer);
|
|
var
|
|
i: Integer;
|
|
begin
|
|
for i := 0 to high(FStorageMemList) do
|
|
FStorageMemList[i].Move(AFrom, ATo, ALen);
|
|
end;
|
|
|
|
procedure TSynManagedStorageMemList.CallLineTextChanged(AIndex: Integer; ACount: Integer = 1);
|
|
var
|
|
i: Integer;
|
|
begin
|
|
for i := 0 to high(FStorageMemList) do
|
|
FStorageMemList[i].LineTextChanged(AIndex, ACount);
|
|
end;
|
|
|
|
procedure TSynManagedStorageMemList.CallInsertedLines(AIndex, ACount: Integer);
|
|
var
|
|
i: Integer;
|
|
begin
|
|
for i := 0 to high(FStorageMemList) do
|
|
FStorageMemList[i].InsertedLines(AIndex, ACount);
|
|
end;
|
|
|
|
procedure TSynManagedStorageMemList.CallDeletedLines(AIndex, ACount: Integer);
|
|
var
|
|
i: Integer;
|
|
begin
|
|
for i := 0 to high(FStorageMemList) do
|
|
FStorageMemList[i].DeletedLines(AIndex, ACount);
|
|
end;
|
|
|
|
end.
|
|
|