1119 lines
28 KiB
ObjectPascal

{
Copyright (C) Alexey Torgashin, uvviewsoft.com
License: MPL 2.0
}
{$mode objfpc}{$H+}
//{$define laz15}
unit ATStrings;
interface
uses
Classes, SysUtils, Graphics,
ATStringProc,
ATStringProc_Utf8Detect,
ATStrings_Undo;
const
//set to 2 to allow 2 editors to use one Strings obj, with different LinesHidden[n, nClient]
//set to 1 to allow only one editor for Strings obj (saves memory)
cMaxStringsClients = 2;
//if update count is less, do smarter wrapinfo update (find, replace items)
//smart update used only if lines chged (not deleted/inserted)
cMaxUpdatesCountEasy = 200;
{$Z1}
type
TATLineState = (
cLineStateNone,
cLineStateChanged,
cLineStateAdded,
cLineStateSaved
);
TATLineSeparator = (
cLineSepNone,
cLineSepTop,
cLineSepBottom
);
TATFileEncoding = (
cEncAnsi,
cEncUTF8,
cEncWideLE,
cEncWideBE
);
const
cEncodingSize: array[TATFileEncoding] of integer = (1, 1, 2, 2);
type
TATTrimSpaces = (
cTrimLeft,
cTrimRight,
cTrimAll
);
type
{ TATStringItem }
TATStringItem = packed class
public
ItemString: atString;
ItemEnd: TATLineEnds;
ItemState: TATLineState;
ItemSeparator: TATLineSeparator;
ItemBm: byte;
ItemHidden: array[0..cMaxStringsClients-1] of smallint;
//0: line visible,
//-1: line hidden,
//>0: line hidden from this char-pos
constructor Create(const AString: atString; AEnd: TATLineEnds); virtual;
function IsFake: boolean;
end;
type
TATStringsGetCarets = function: TATPointArray of object;
TATStringsSetCarets = procedure(const ACarets: TATPointArray) of object;
TATStringsLogEvent = procedure(Sender: TObject; ALine, ALen: integer) of object;
type
{ TATStrings }
TATStrings = class
private
FList: TList;
FListUpdates: TList;
FListUpdatesHard: boolean;
FUndoList,
FRedoList: TATUndoList;
FEndings: TATLineEnds;
FEncoding: TATFileEncoding;
FEncodingDetect: boolean;
FEncodingCodepage: string;
FEncodingDetectBufSizeKb: integer;
FModified: boolean;
FSaveSignUtf8: boolean;
FSaveSignWide: boolean;
FReadOnly: boolean;
FUndoAfterSave: boolean;
FUndoGroupCounter: integer;
FOneLine: boolean;
FProgress: integer;
FOnGetCaretsArray: TATStringsGetCarets;
FOnSetCaretsArray: TATStringsSetCarets;
FOnProgress: TNotifyEvent;
FOnLog: TATStringsLogEvent;
procedure DoAddUndo(AAction: TATEditAction; AIndex: integer;
const AText: atString; AEnd: TATLineEnds);
function DebugText: string;
function DoCheckFilled: boolean;
procedure DoEventLog(ALine, ALen: integer);
procedure DoFinalizeSaving;
procedure DoUndoRedo(AUndo: boolean; AGrouped: boolean);
function GetCaretsArray: TATPointArray;
function GetLine(N: integer): atString;
function GetLineBm(Index: integer): integer;
function GetLineEnd(N: integer): TATLineEnds;
function GetLineHidden(NLine, NClient: integer): integer;
function GetLineSep(Index: integer): TATLineSeparator;
function GetLineState(Index: integer): TATLineState;
function GetRedoCount: integer;
function GetUndoCount: integer;
function GetUndoLimit: integer;
function IsLastFakeLineUnneeded: boolean;
procedure LineAddEx(const AString: atString; AEnd: TATLineEnds);
procedure LineInsertRaw(N: integer; const AString: atString; AEnd: TATLineEnds);
procedure LineInsertEx(N: integer; const AString: atString; AEnd: TATLineEnds);
procedure SetCaretsArray(const L: TATPointArray);
procedure SetEndings(AValue: TATLineEnds);
procedure SetLine(Index: integer; const AValue: atString);
procedure SetLineBm(Index: integer; AValue: integer);
procedure SetLineEnd(Index: integer; AValue: TATLineEnds);
procedure SetLineHidden(IndexLine, IndexClient: integer; AValue: integer);
procedure SetLineSep(Index: integer; AValue: TATLineSeparator);
procedure SetLineState(Index: integer; AValue: TATLineState);
function GetTextString: atString;
procedure DoLoadFromStream(Stream: TStream);
procedure DoDetectEndings;
procedure DoFinalizeLoading;
procedure DoClearLineStates(ASaved: boolean);
procedure SetModified(AValue: boolean);
procedure SetUndoLimit(AValue: integer);
procedure DoUndoSingle(AUndoList: TATUndoList; out ASoftMarked, AHardMarked,
AHardMarkedNext, AUnmodifiedNext: boolean);
procedure DoAddUpdate(N: integer; AAction: TATEditAction);
public
constructor Create; virtual;
destructor Destroy; override;
procedure Clear;
function Count: integer;
function IsIndexValid(N: integer): boolean;
function IsLastLineFake: boolean;
function IsPosFolded(AX, AY, AIndexClient: integer): boolean;
procedure LineAddRaw(const AString: atString; AEnd: TATLineEnds);
procedure LineAdd(const AString: atString);
procedure LineInsert(N: integer; const AString: atString);
procedure LineInsertStrings(N: integer; AList: TATStrings; AWithFinalEol: boolean);
procedure LineDelete(N: integer; AForceLast: boolean = true);
property Lines[Index: integer]: atString read GetLine write SetLine;
property LinesEnds[Index: integer]: TATLineEnds read GetLineEnd write SetLineEnd;
property LinesHidden[IndexLine, IndexClient: integer]: integer read GetLineHidden write SetLineHidden;
property LinesState[Index: integer]: TATLineState read GetLineState write SetLineState;
property LinesBm[Index: integer]: integer read GetLineBm write SetLineBm;
property LinesSeparator[Index: integer]: TATLineSeparator read GetLineSep write SetLineSep;
property Encoding: TATFileEncoding read FEncoding write FEncoding;
property EncodingCodepage: string read FEncodingCodepage write FEncodingCodepage;
property EncodingDetect: boolean read FEncodingDetect write FEncodingDetect;
property EncodingDetectBufSizeKb: integer read FEncodingDetectBufSizeKb write FEncodingDetectBufSizeKb;
property Endings: TATLineEnds read FEndings write SetEndings;
property ListUpdates: TList read FListUpdates;
property ListUpdatesHard: boolean read FListUpdatesHard write FListUpdatesHard;
property Modified: boolean read FModified write SetModified;
property OneLine: boolean read FOneLine write FOneLine;
property Progress: integer read FProgress write FProgress;
procedure ActionDeleteFakeLine;
procedure ActionDeleteDupFakeLines;
procedure ActionAddFakeLineIfNeeded;
function ActionTrimSpaces(AMode: TATTrimSpaces): boolean;
function ActionEnsureFinalEol: boolean;
//file
procedure LoadFromStream(Stream: TStream);
procedure LoadFromFile(const Filename: string);
procedure LoadFromString(const AText: atString);
procedure SaveToStream(Stream: TStream; AEncoding: TATFileEncoding; AWithSignature: boolean);
procedure SaveToFile(const AFilename: string);
property SaveSignUtf8: boolean read FSaveSignUtf8 write FSaveSignUtf8;
property SaveSignWide: boolean read FSaveSignWide write FSaveSignWide;
//text
property ReadOnly: boolean read FReadOnly write FReadOnly;
property TextString: atString read GetTextString;
procedure TextInsert(AX, AY: integer; const AText: atString; AOverwrite: boolean;
out AShift, APosAfter: TPoint);
procedure TextInsertColumnBlock(AX, AY: integer; ABlock: TATStrings;
AOverwrite: boolean);
procedure TextDeleteLeft(AX, AY: integer; ALen: integer; out AShift, APosAfter: TPoint);
procedure TextDeleteRight(AX, AY: integer; ALen: integer; out AShift,
APosAfter: TPoint; ACanDelEol: boolean=true);
procedure TextDeleteRange(AFromX, AFromY, AToX, AToY: integer; out AShift, APosAfter: TPoint);
procedure TextInsertEol(AX, AY: integer; AKeepCaret: boolean;
const AStrIndent: atString; out AShift, APosAfter: TPoint);
procedure TextDeleteLine(AX, AY: integer; out AShift, APosAfter: TPoint);
procedure TextDuplicateLine(AX, AY: integer; out AShift, APosAfter: TPoint);
function TextSubstring(AX1, AY1, AX2, AY2: integer): atString;
//undo
property OnGetCaretsArray: TATStringsGetCarets read FOnGetCaretsArray write FOnGetCaretsArray;
property OnSetCaretsArray: TATStringsSetCarets read FOnSetCaretsArray write FOnSetCaretsArray;
procedure SetGroupMark;
procedure BeginUndoGroup;
procedure EndUndoGroup;
procedure Undo(AGrouped: boolean);
procedure Redo(AGrouped: boolean);
property UndoLimit: integer read GetUndoLimit write SetUndoLimit;
property UndoAfterSave: boolean read FUndoAfterSave write FUndoAfterSave;
property UndoCount: integer read GetUndoCount;
property RedoCount: integer read GetRedoCount;
procedure DoClearUndo(ALocked: boolean = false);
//misc
property OnProgress: TNotifyEvent read FOnProgress write FOnProgress;
property OnLog: TATStringsLogEvent read FOnLog write FOnLog;
end;
implementation
uses
Dialogs,
Math,
LazClasses,
LConvEncoding;
const
cSignUTF8: AnsiString = #$EF#$BB#$BF;
cSignWideLE: AnsiString = #$FF#$FE;
cSignWideBE: AnsiString = #$FE#$FF;
const
cMinSizeForProgress = 200*1024;
cMinIncForProgress = 5;
procedure DoEncError;
begin
raise Exception.Create('Unknown enc value');
end;
{ TATStringItem }
constructor TATStringItem.Create(const AString: atString; AEnd: TATLineEnds);
var
i: integer;
begin
ItemString:= AString;
ItemEnd:= AEnd;
ItemState:= cLineStateNone;
ItemSeparator:= cLineSepNone;
for i:= 0 to High(ItemHidden) do
ItemHidden[i]:= 0;
ItemBm:= 0;
end;
function TATStringItem.IsFake: boolean;
begin
Result:= (ItemString='') and (ItemEnd=cEndNone);
end;
{ TATStrings }
function TATStrings.GetLine(N: integer): atString;
begin
Assert(IsIndexValid(N));
Result:= TATStringItem(FList[N]).ItemString;
end;
function TATStrings.GetLineBm(Index: integer): integer;
begin
Assert(IsIndexValid(Index));
Result:= TATStringItem(FList[Index]).ItemBm;
end;
function TATStrings.GetLineEnd(N: integer): TATLineEnds;
begin
Assert(IsIndexValid(N));
Result:= TATStringItem(FList[N]).ItemEnd;
end;
function TATStrings.GetLineHidden(NLine, NClient: integer): integer;
begin
Assert(IsIndexValid(NLine));
Result:= TATStringItem(FList[NLine]).ItemHidden[NClient];
end;
function TATStrings.GetLineState(Index: integer): TATLineState;
begin
Assert(IsIndexValid(Index));
Result:= TATStringItem(FList[Index]).ItemState;
end;
function TATStrings.GetLineSep(Index: integer): TATLineSeparator;
begin
Assert(IsIndexValid(Index));
Result:= TATStringItem(FList[Index]).ItemSeparator;
end;
function TATStrings.GetUndoCount: integer;
begin
if Assigned(FUndoList) then
Result:= FUndoList.Count
else
Result:= 0;
end;
function TATStrings.GetRedoCount: integer;
begin
if Assigned(FRedoList) then
Result:= FRedoList.Count
else
Result:= 0;
end;
function TATStrings.GetUndoLimit: integer;
begin
if Assigned(FUndoList) then
Result:= FUndoList.MaxCount
else
Result:= 2000;
end;
procedure TATStrings.SetEndings(AValue: TATLineEnds);
var
i: integer;
begin
if FReadOnly then Exit;
FEndings:= AValue;
for i:= 0 to Count-1 do
if LinesEnds[i]<>AValue then
LinesEnds[i]:= AValue;
end;
procedure TATStrings.SetLine(Index: integer; const AValue: atString);
var
Item: TATStringItem;
i: integer;
begin
Assert(IsIndexValid(Index));
if FReadOnly then Exit;
Item:= TATStringItem(FList[Index]);
DoAddUndo(cEditActionChange, Index, Item.ItemString, Item.ItemEnd);
DoEventLog(Index, -Length(Item.ItemString));
DoEventLog(Index, Length(AValue));
Item.ItemString:= AValue;
//fully unfold this line
for i:= 0 to High(Item.ItemHidden) do
Item.ItemHidden[i]:= 0;
if Item.ItemState<>cLineStateAdded then
Item.ItemState:= cLineStateChanged;
end;
procedure TATStrings.SetLineBm(Index: integer; AValue: integer);
begin
if IsIndexValid(Index) then
TATStringItem(FList[Index]).ItemBm:= AValue;
end;
procedure TATStrings.SetLineSep(Index: integer; AValue: TATLineSeparator);
begin
if IsIndexValid(Index) then
TATStringItem(FList[Index]).ItemSeparator:= AValue;
end;
procedure TATStrings.SetLineEnd(Index: integer; AValue: TATLineEnds);
var
Item: TATStringItem;
begin
Assert(IsIndexValid(Index));
if FReadOnly then Exit;
Item:= TATStringItem(FList[Index]);
DoAddUndo(cEditActionChangeEol, Index, '', Item.ItemEnd);
Item.ItemEnd:= AValue;
if Item.ItemState<>cLineStateAdded then
Item.ItemState:= cLineStateChanged;
end;
procedure TATStrings.SetLineHidden(IndexLine, IndexClient: integer;
AValue: integer);
begin
Assert(IsIndexValid(IndexLine));
TATStringItem(FList[IndexLine]).ItemHidden[IndexClient]:= AValue;
end;
procedure TATStrings.SetLineState(Index: integer; AValue: TATLineState);
begin
Assert(IsIndexValid(Index));
TATStringItem(FList[Index]).ItemState:= AValue;
end;
function TATStrings.GetTextString: atString;
const
LenEol = 1;
CharEol: atChar = #10;
CharSize = SizeOf(atChar);
var
Len, i: integer;
Item: TATStringItem;
Ptr: pointer;
begin
Result:= '';
if Count=0 then Exit;
Len:= 0;
for i:= 0 to Count-1 do
begin
Item:= TATStringItem(FList[i]);
Inc(Len, Length(Item.ItemString)+LenEol);
end;
if Len=0 then Exit;
SetLength(Result, Len);
Ptr:= @Result[1];
for i:= 0 to Count-1 do
begin
Item:= TATStringItem(FList[i]);
Len:= Length(Item.ItemString);
if Len>0 then
begin
Move(Item.ItemString[1], Ptr^, Len*CharSize);
Inc(Ptr, Len*CharSize);
end;
PatChar(Ptr)^:= CharEol;
Inc(Ptr, LenEol*CharSize);
end;
end;
constructor TATStrings.Create;
begin
FList:= TList.Create;
FListUpdates:= TList.Create;
FListUpdatesHard:= false;
FUndoList:= TATUndoList.Create;
FRedoList:= TATUndoList.Create;
FEncoding:= cEncAnsi;
FEncodingDetect:= true;
FEncodingCodepage:= '';
FEncodingDetectBufSizeKb:= 16;
FEndings:= cEndWin;
FModified:= false;
FSaveSignUtf8:= true;
FSaveSignWide:= true;
FUndoAfterSave:= true;
FOneLine:= false;
FProgress:= 0;
ActionAddFakeLineIfNeeded;
DoClearUndo;
end;
destructor TATStrings.Destroy;
begin
//disable events: so Clear wont call
FOnGetCaretsArray:= nil;
FOnSetCaretsArray:= nil;
FOnProgress:= nil;
FOnLog:= nil;
//
DoClearUndo(true);
Clear;
FreeAndNil(FList);
FreeAndNil(FListUpdates);
FreeAndNil(FUndoList);
FreeAndNil(FRedoList);
inherited;
end;
function TATStrings.IsLastLineFake: boolean;
begin
Result:= (Count>0) and
(TATStringItem(FList.Last).IsFake);
end;
function TATStrings.IsLastFakeLineUnneeded: boolean;
begin
Result:= (Count>1) and
(TATStringItem(FList.Last).IsFake) and
(TATStringItem(FList[FList.Count-2]).ItemEnd=cEndNone);
end;
procedure TATStrings.ActionDeleteFakeLine;
begin
if IsLastLineFake then
LineDelete(Count-1, false{dont force});
end;
procedure TATStrings.ActionAddFakeLineIfNeeded;
begin
if Count=0 then
begin
LineAddRaw('', cEndNone);
Exit
end;
if IsLastLineFake then Exit;
if LinesEnds[Count-1]<>cEndNone then
begin
LineAddRaw('', cEndNone);
Exit
end;
end;
procedure TATStrings.LineAddRaw(const AString: atString; AEnd: TATLineEnds);
var
Item: TATStringItem;
begin
if FReadOnly then Exit;
if DoCheckFilled then Exit;
DoAddUndo(cEditActionInsert, Count, '', cEndNone);
DoEventLog(Count, Length(AString));
Item:= TATStringItem.Create(AString, AEnd);
Item.ItemState:= cLineStateAdded;
FList.Add(Item);
end;
procedure TATStrings.LineAddEx(const AString: atString; AEnd: TATLineEnds);
var
AEndInside: TATLineEnds;
begin
if FReadOnly then Exit;
AEndInside:= AEnd;
if AEndInside=cEndNone then
AEndInside:= FEndings;
if IsLastLineFake then
LineInsertRaw(Count-1, AString, AEndInside)
else
begin
LineAddRaw(AString, AEnd);
if AEnd<>cEndNone then
LineAddRaw('', cEndNone);
end;
end;
procedure TATStrings.LineAdd(const AString: atString);
begin
LineAddEx(AString, FEndings);
end;
function TATStrings.DoCheckFilled: boolean;
begin
Result:= false;
if FOneLine then
begin
Result:= Count>0;
if Result then
while Count>1 do
LineDelete(Count-1);
end;
end;
procedure TATStrings.LineInsertRaw(N: integer; const AString: atString; AEnd: TATLineEnds);
var
Item: TATStringItem;
begin
if FReadOnly then Exit;
if DoCheckFilled then Exit;
DoAddUndo(cEditActionInsert, N, '', cEndNone);
DoEventLog(N, Length(AString));
Item:= TATStringItem.Create(AString, AEnd);
Item.ItemState:= cLineStateAdded;
FList.Insert(N, Item);
end;
procedure TATStrings.LineInsertEx(N: integer; const AString: atString; AEnd: TATLineEnds);
begin
if FReadOnly then Exit;
if IsIndexValid(N) then
LineInsertRaw(N, AString, AEnd)
else
if N=Count then
LineAddEx(AString, AEnd)
else
raise Exception.Create('Incorrect Insert index: '+IntToStr(N));
end;
procedure TATStrings.LineInsert(N: integer; const AString: atString);
begin
LineInsertEx(N, AString, FEndings);
end;
procedure TATStrings.LineInsertStrings(N: integer; AList: TATStrings; AWithFinalEol: boolean);
var
Cnt, CntMove: integer;
Item: TATStringItem;
Str: atString;
i: integer;
begin
if AList.Count=0 then exit;
Cnt:= AList.Count;
if not AWithFinalEol then Dec(Cnt);
if Cnt>0 then
begin
CntMove:= FList.Count-N;
//fast! insert many
FList.Count:= FList.Count+Cnt;
System.Move(FList.List^[N], FList.List^[N+Cnt], CntMove*SizeOf(Pointer));
FillChar(FList.List^[N], Cnt*SizeOf(Pointer), 0);
for i:= 0 to Cnt-1 do
begin
DoAddUndo(cEditActionInsert, N+i, '', cEndNone);
DoEventLog(N+i, Length(AList.Lines[i]));
Item:= TATStringItem.Create(AList.Lines[i], Endings);
Item.ItemState:= cLineStateAdded;
FList[N+i]:= Item;
end;
end;
//insert last item specially, if no eol
if not AWithFinalEol then
begin
i:= N+AList.Count-1;
Str:= AList.Lines[AList.Count-1];
if IsIndexValid(i) then
Lines[i]:= Str+Lines[i]
else
LineAdd(Str);
end;
end;
function TATStrings.IsIndexValid(N: integer): boolean;
begin
Result:= (N>=0) and (N<Count);
end;
function TATStrings.Count: integer;
begin
Result:= FList.Count;
end;
procedure TATStrings.LineDelete(N: integer; AForceLast: boolean = true);
var
Item: TATStringItem;
begin
if FReadOnly then Exit;
if IsIndexValid(N) then
begin
Item:= TATStringItem(FList[N]);
DoAddUndo(cEditActionDelete, N, Item.ItemString, Item.ItemEnd);
DoEventLog(N, -Length(Item.ItemString));
Item.Free;
FList.Delete(N);
end;
//else
// raise Exception.Create('Invalid Delete index: '+IntToStr(N));
if AForceLast then
ActionAddFakeLineIfNeeded;
end;
procedure TATStrings.Clear;
var
i: integer;
begin
DoClearUndo(FUndoList.Locked);
DoEventLog(-1, 0);
for i:= Count-1 downto 0 do
TObject(FList[i]).Free;
FList.Clear;
end;
procedure TATStrings.DoClearLineStates(ASaved: boolean);
var
Item: TATStringItem;
i: integer;
begin
for i:= 0 to Count-1 do
begin
Item:= TATStringItem(FList[i]);
if ASaved then
begin
if Item.ItemState<>cLineStateNone then
Item.ItemState:= cLineStateSaved;
end
else
Item.ItemState:= cLineStateNone;
end;
end;
procedure TATStrings.SetModified(AValue: boolean);
begin
FModified:= AValue;
if not FModified then
FUndoList.AddUnmodifiedMark;
end;
procedure TATStrings.SetUndoLimit(AValue: integer);
begin
if Assigned(FUndoList) then
FUndoList.MaxCount:= AValue;
end;
procedure TATStrings.DoDetectEndings;
begin
if not IsIndexValid(0) then Exit;
FEndings:= LinesEnds[0]; //no range-chk
if FEndings=cEndNone then
FEndings:= cEndWin;
end;
function TATStrings.TextSubstring(AX1, AY1, AX2, AY2: integer): atString;
var
L: TStringList;
i: integer;
Str: atString;
begin
Result:= '';
if AY1>AY2 then Exit;
if AY1=AY2 then
begin
Result:= Copy(Lines[AY1], AX1+1, AX2-AX1);
Exit
end;
L:= TStringList.Create;
try
L.TextLineBreakStyle:= tlbsLF; //force eol=#10
//first line
Str:= Copy(Lines[AY1], AX1+1, MaxInt);
L.Add(UTF8Encode(Str));
//middle
for i:= AY1+1 to AY2-1 do
begin
Str:= Lines[i];
L.Add(UTF8Encode(Str));
end;
//last line
Str:= Copy(Lines[AY2], 1, AX2);
L.Add(UTF8Encode(Str));
TrimStringList(L);
Result:= UTF8Decode(L.Text);
//L.Text gives final eol
//let's keep it if AX2=0 (substr till column=0)
if Result<>'' then
if AX2<>0 then
if Result[Length(Result)]=#10 then
SetLength(Result, Length(Result)-1);
finally
FreeAndNil(L);
end;
end;
procedure TATStrings.SetGroupMark;
begin
if Assigned(FUndoList) then
FUndoList.SoftMark:= true;
end;
procedure TATStrings.BeginUndoGroup;
begin
Inc(FUndoGroupCounter);
if Assigned(FUndoList) then
begin
//softmark if not-nested call
if FUndoGroupCounter=1 then
FUndoList.SoftMark:= true;
//hardmark always
FUndoList.HardMark:= true;
end;
end;
procedure TATStrings.EndUndoGroup;
begin
FUndoGroupCounter:= Max(0, FUndoGroupCounter-1);
if FUndoGroupCounter=0 then
if Assigned(FUndoList) then
FUndoList.HardMark:= false;
end;
procedure TATStrings.DoUndoSingle(AUndoList: TATUndoList;
out ASoftMarked, AHardMarked, AHardMarkedNext, AUnmodifiedNext: boolean);
var
Item: TATUndoItem;
AAction: TATEditAction;
AText: atString;
AIndex: integer;
AEnd: TATLineEnds;
ACarets: TATPointArray;
NCount: integer;
begin
ASoftMarked:= true;
AHardMarked:= false;
AHardMarkedNext:= false;
AUnmodifiedNext:= false;
if FReadOnly then Exit;
if not Assigned(AUndoList) then Exit;
Item:= AUndoList.Last;
if Item=nil then Exit;
AAction:= Item.ItemAction;
AIndex:= Item.ItemIndex;
AText:= Item.ItemText;
AEnd:= Item.ItemEnd;
ACarets:= Item.ItemCarets;
ASoftMarked:= Item.ItemSoftMark;
AHardMarked:= Item.ItemHardMark;
NCount:= AUndoList.Count;
AHardMarkedNext:= (NCount>1) and (AUndoList[NCount-2].ItemHardMark);
AUnmodifiedNext:= (NCount>1) and (AUndoList[NCount-2].ItemAction=cEditActionClearModified);
//don't undo if one item left: unmodified-mark
if (NCount=1) and (AUndoList[0].ItemAction=cEditActionClearModified) then exit;
Item:= nil;
AUndoList.DeleteLast;
AUndoList.Locked:= true;
try
case AAction of
cEditActionChange:
begin
Lines[AIndex]:= AText;
end;
cEditActionChangeEol:
begin
LinesEnds[AIndex]:= AEnd;
end;
cEditActionInsert:
begin
if IsIndexValid(AIndex) then
LineDelete(AIndex);
end;
cEditActionDelete:
begin
if AIndex>=Count then
LineAddRaw(AText, AEnd)
else
LineInsertRaw(AIndex, AText, AEnd);
end;
cEditActionClearModified:
begin
//add unmodified mark to undo/redo
if AUndoList=FUndoList then
FRedoList.AddUnmodifiedMark
else
FUndoList.AddUnmodifiedMark;
exit;
end
else
raise Exception.Create('Unknown undo action');
end;
SetCaretsArray(ACarets);
ActionDeleteDupFakeLines;
finally
AUndoList.Locked:= false;
end;
end;
function TATStrings.DebugText: string;
var
Item: TATStringItem;
i: integer;
begin
Result:= '';
for i:= 0 to Min(20, Count-1) do
begin
Item:= TATStringItem(FList[i]);
Result:= Result+Format('[%d] "%s" <%s>', [i, UTF8Encode(Item.ItemString), cLineEndNiceNames[Item.ItemEnd] ])+#13;
end;
end;
function TATStrings.GetCaretsArray: TATPointArray;
begin
if Assigned(FOnGetCaretsArray) then
Result:= FOnGetCaretsArray();
end;
procedure TATStrings.SetCaretsArray(const L: TATPointArray);
begin
if Assigned(FOnSetCaretsArray) then
FOnSetCaretsArray(L);
end;
procedure TATStrings.DoAddUndo(AAction: TATEditAction; AIndex: integer; const AText: atString; AEnd: TATLineEnds);
begin
FModified:= true;
if not Assigned(FUndoList) then Exit;
if not Assigned(FRedoList) then Exit;
if not FUndoList.Locked and not FRedoList.Locked then
FRedoList.Clear;
if not FUndoList.Locked then
begin
DoAddUpdate(AIndex, AAction);
FUndoList.Add(AAction, AIndex, AText, AEnd, GetCaretsArray);
end
else
if not FRedoList.Locked then
begin
DoAddUpdate(AIndex, AAction);
FRedoList.Add(AAction, AIndex, AText, AEnd, GetCaretsArray);
end;
end;
procedure TATStrings.DoUndoRedo(AUndo: boolean; AGrouped: boolean);
var
List, ListOther: TATUndoList;
bSoftMarked,
bHardMarked,
bHardMarkedNext,
bMarkedUnmodified: boolean;
begin
if not Assigned(FUndoList) then Exit;
if not Assigned(FRedoList) then Exit;
if AUndo then
begin List:= FUndoList; ListOther:= FRedoList end
else
begin List:= FRedoList; ListOther:= FUndoList end;
repeat
//FUndolist.DebugShow; ////debug
if (List.Count=0) then Break;
if (List.Count=1) and (List[0].ItemAction=cEditActionClearModified) then Break;
DoUndoSingle(List, bSoftMarked, bHardMarked, bHardMarkedNext, bMarkedUnmodified);
//handle unmodified
if bMarkedUnmodified then
FModified:= false;
//apply Hardmark to ListOther
if bHardMarked then
if ListOther.Count>0 then
begin
ListOther.Last.ItemHardMark:= bHardMarked;
//ListOther.Last.ItemSoftMark:= ?? //for redo needed Softmark too but don't know how
end;
if bHardMarked and bHardMarkedNext and not bSoftMarked then Continue;
if (not AGrouped) or bSoftMarked then Break;
until false;
//apply Softmark to ListOther
if bSoftMarked and AGrouped then
ListOther.SoftMark:= true;
end;
procedure TATStrings.Undo(AGrouped: boolean);
begin
DoUndoRedo(true, AGrouped);
end;
procedure TATStrings.Redo(AGrouped: boolean);
begin
DoUndoRedo(false, AGrouped);
end;
procedure TATStrings.DoClearUndo(ALocked: boolean = false);
begin
if Assigned(FUndoList) then
begin
FUndoList.Clear;
FUndoList.Locked:= ALocked;
end;
if Assigned(FRedoList) then
begin
FRedoList.Clear;
FRedoList.Locked:= ALocked;
end;
if Assigned(FListUpdates) then
begin
FListUpdates.Clear;
FListUpdatesHard:= false;
end;
end;
procedure TATStrings.ActionDeleteDupFakeLines;
begin
while IsLastFakeLineUnneeded do
LineDelete(Count-1, false);
end;
procedure TATStrings.DoAddUpdate(N: integer; AAction: TATEditAction);
var
Ptr: pointer;
begin
if not Assigned(FListUpdates) then Exit;
if AAction in [cEditActionDelete, cEditActionInsert] then
begin
FListUpdatesHard:= true;
Exit
end;
if FListUpdates.Count>cMaxUpdatesCountEasy then
begin
FListUpdatesHard:= true;
Exit
end;
Ptr:= pointer{%H-}(N);
with FListUpdates do
if IndexOf(Ptr)<0 then Add(Ptr);
end;
function TATStrings.ActionEnsureFinalEol: boolean;
begin
Result:= false;
if IsLastLineFake then Exit;
if Count>0 then
begin
if LinesEnds[Count-1]=cEndNone then
begin
LinesEnds[Count-1]:= Endings;
Result:= true;
end;
end;
end;
function TATStrings.ActionTrimSpaces(AMode: TATTrimSpaces): boolean;
var
i: integer;
S1, S2: atString;
begin
Result:= false;
for i:= 0 to Count-1 do
begin
S1:= Lines[i];
case AMode of
cTrimLeft: S2:= TrimLeft(S1);
cTrimRight: S2:= TrimRight(S1);
cTrimAll: S2:= Trim(S1);
end;
if S2<>S1 then
begin
Lines[i]:= S2;
Result:= true;
end;
end;
end;
function TATStrings.IsPosFolded(AX, AY, AIndexClient: integer): boolean;
var
Flag: integer;
begin
Result:= true;
if not IsIndexValid(AY) then Exit;
//if -1: line hidden, if 0: not hidden, if >0: line hidden from this char-pos
Flag:= LinesHidden[AY, AIndexClient];
if (Flag=-1) then Exit;
if (Flag>0) and (AX>=Flag) then Exit;
Result:= false;
end;
procedure TATStrings.DoEventLog(ALine, ALen: integer);
begin
if Assigned(FOnLog) then
FOnLog(Self, ALine, ALen);
end;
{$I atstrings_editing.inc}
{$I atstrings_load.inc}
{$I atstrings_save.inc}
end.