1 {
2 Copyright (C) Alexey Torgashin, uvviewsoft.com
3 License: MPL 2.0 or LGPL
4 }
5 unit ATStrings;
6 
7 {$mode objfpc}{$H+}
8 {$ModeSwitch advancedrecords}
9 {$MinEnumSize 1}
10 
11 interface
12 
13 uses
14   {$ifdef windows} Windows, {$endif}
15   SysUtils, Classes, Graphics, Forms,
16   ATStringProc,
17   ATStringProc_UTF8Detect,
18   ATStringProc_UTF8Decode,
19   ATStrings_Undo,
20   ATSynEdit_fgl,
21   ATSynEdit_Gaps,
22   ATSynEdit_Bookmarks,
23   ATSynEdit_Gutter_Decor,
24   ATSynEdit_Commands,
25   EncConv;
26 
27 const
28   //set it to number of editors, which share same Strings obj
29   //(needed when UI tab is splitted to N parts, for the same file)
30   //set to 1 to allow only one editor for Strings obj (saves memory)
31   cMaxStringsClients = 2;
32 
33   //if update count is less, do smarter wrapinfo update (find, replace items)
34   //smart update used only if lines changed, not deleted/inserted
35   cMaxUpdatesCountEasy = 200;
36 
37   cStringsProgressLoadChars = 1000*1000;
38   cStringsProgressSaveLines = 100*1000;
39 
40   //force utf8 for huge files on loading
41   cMaxFileSizeMbToDetectEncoding: integer = 50;
42 
43 type
44   TATIntegerList = specialize TFPGList<integer>;
45 
46 type
47   TATLineIndentKind = (
48     cLineIndentOther,
49     cLineIndentSpaces,
50     cLineIndentTabs
51     );
52 
53   TATLineSeparator = (
54     cLineSepNone,
55     cLineSepTop,
56     cLineSepBottom
57     );
58 
59   TATFileEncoding = (
60     cEncAnsi,
61     cEncUTF8,
62     cEncWideLE,
63     cEncWideBE,
64     cEnc32LE,
65     cEnc32BE
66     );
67 
68   TATBlockChangeKind = (
69     cBlockDeleteLines,
70     cBlockInsertLines,
71     cBlockDeleteColumn,
72     cBlockInsertColumn
73   );
74 
75   TATStringGitMarker = (
76     cGitMarkNone,
77     cGitMarkBegin,
78     cGitMarkMiddle,
79     cGitMarkEnd
80     );
81 
82 const
83   cEncodingSize: array[TATFileEncoding] of integer = (1, 1, 2, 2, 4, 4);
84 
85 type
86   TATTrimSpaces = (
87     cTrimLeft,
88     cTrimRight,
89     cTrimAll
90     );
91 
92 type
93   TATLineFlag = (
94     cFlagUnknown,
95     cFlagNo,
96     cFlagYes
97     );
98 
99   TATStringsSortAction = (
100     cSortActionAsc,
101     cSortActionDesc,
102     cSortActionAscNoCase,
103     cSortActionDescNoCase
104     );
105 
106 type
107   { TATStringItem }
108 
109   TATBits2 = 0..3;
110   TATStringItem_FoldFrom = 0..255; //8 bits should be enougth
111 
112   TATStringItemEx = bitpacked record
113     Ends: TATBits2;
114     State: TATBits2;
115     HasTab: TATBits2;
116     HasAsciiNoTabs: TATBits2;
117     FoldFrom_0,
118     FoldFrom_1: TATStringItem_FoldFrom;
119       //0: line not folded
120       //>0: line folded from this char-pos
121     Wide: boolean;
122     Updated: boolean;
123     Sep: TATBits2;
124     Hidden_0, Hidden_1: boolean;
125   end;
126 
127   TATStringItem = packed record
128   private
129     Buf: string;
GetLinenull130     function GetLine: UnicodeString;
GetLineEndsnull131     function GetLineEnds: TATLineEnds;
GetLineStatenull132     function GetLineState: TATLineState;
133     procedure SetLineW(const S: UnicodeString);
134     procedure SetLineA(const S: string);
135   public
136     Ex: TATStringItemEx;
CharLennull137     function CharLen: integer;
138     property Line: UnicodeString read GetLine write SetLineW;
139     property LineState: TATLineState read GetLineState;
140     property LineEnds: TATLineEnds read GetLineEnds;
LineSubnull141     function LineSub(AFrom, ALen: integer): UnicodeString;
142     procedure LineToBuffer(OtherBuf: PWideChar);
CharAtnull143     function CharAt(AIndex: integer): WideChar;
HasTabnull144     function HasTab: boolean;
HasAsciiNoTabsnull145     function HasAsciiNoTabs: boolean;
146     procedure Init(const S: string; AEnd: TATLineEnds);
147     procedure Init(const S: UnicodeString; AEnd: TATLineEnds);
148     procedure LineStateToChanged;
149     procedure LineStateToSaved; inline;
150     procedure LineStateToNone; inline;
IsFakenull151     function IsFake: boolean; inline;
152     procedure GetIndentProp(out ACharCount: integer; out AKind: TATLineIndentKind);
CharLenWithoutSpacenull153     function CharLenWithoutSpace: integer;
IsBlanknull154     function IsBlank: boolean;
IsGitMarkernull155     function IsGitMarker: TATStringGitMarker;
156   end;
157   PATStringItem = ^TATStringItem;
158 
159   { TATStringItemList }
160 
161   TATStringItemList = class(TFPSList)
162   public
163     constructor Create;
GetItemnull164     function GetItem(AIndex: integer): PATStringItem;
165     procedure Deref(Item: Pointer); override; overload;
166     procedure SortRange(L, R: integer; Compare: TFPSListCompareFunc);
167   end;
168 
169 type
170   TATStringsProgressKind = (
171     cStringsProgressNone,
172     cStringsProgressLoading,
173     cStringsProgressSaving
174     );
175 
176 type
TATPointArraynull177   TATStringsGetCarets = function: TATPointArray of object;
TATInt64Arraynull178   TATStringsGetMarkers = function: TATInt64Array of object;
179   TATStringsSetCarets = procedure(const ACarets: TATPointArray) of object;
180   TATStringsSetMarkers = procedure(const AMarkers: TATInt64Array) of object;
181   TATStringsChangeLogEvent = procedure(Sender: TObject; ALine: integer) of object;
182   TATStringsChangeExEvent = procedure(Sender: TObject; AChange: TATLineChangeKind; ALine, AItemCount: integer) of object;
183   TATStringsChangeBlockEvent = procedure(Sender: TObject; const AStartPos, AEndPos: TPoint;
184                                  AChange: TATBlockChangeKind; ABlock: TStringList) of object;
185   TATStringsUndoEvent = procedure(Sender: TObject; AX, AY: integer) of object;
186 
187 type
188   { TATStrings }
189 
190   TATStrings = class
191   private
192     FList: TATStringItemList;
193     FListUpdates: TATIntegerList;
194     FListUpdatesHard: boolean;
195     FGaps: TATGaps;
196     FBookmarks: TATBookmarks;
197     FBookmarks2: TATBookmarks;
198     FGutterDecor1: TATGutterDecor;
199     FGutterDecor2: TATGutterDecor;
200     FUndoList,
201     FRedoList: TATUndoList;
202     FCommandCode: integer;
203     FUndoLimit: integer;
204     FEndings: TATLineEnds;
205     FEncoding: TATFileEncoding;
206     FEncodingDetect: boolean;
207     FEncodingDetectDefaultUtf8: boolean;
208     FEncodingCodepage: TEncConvId;
209     FModified: boolean;
210     FModifiedRecent: boolean;
211     FModifiedVersion: Int64;
212     FSaveSignUtf8: boolean;
213     FSaveSignWide: boolean;
214     FReadOnly: boolean;
215     FUndoAfterSave: boolean;
216     FUndoGroupCounter: integer;
217     FOneLine: boolean;
218     FProgressValue: integer;
219     FProgressKind: TATStringsProgressKind;
220     FOnGetCaretsArray: TATStringsGetCarets;
221     FOnGetMarkersArray: TATStringsGetMarkers;
222     FOnSetCaretsArray: TATStringsSetCarets;
223     FOnSetMarkersArray: TATStringsSetMarkers;
224     FOnProgress: TNotifyEvent;
225     FOnChangeLog: TATStringsChangeLogEvent;
226     FOnChangeEx: TATStringsChangeExEvent;
227     FOnUndoBefore: TATStringsUndoEvent;
228     FOnUndoAfter: TATStringsUndoEvent;
229     FOnChangeBlock: TATStringsChangeBlockEvent;
230     FChangeBlockActive: boolean;
231       //to use with OnChangeBlock:
232       //indicates that program can ignore separate line changes in OnChange,
233       //because OnChangeBlock is called for all lines at once
234     FLastCommandChangedLines: integer;
235     FEnabledBookmarksUpdate: boolean;
236     FEnabledChangeEvents: boolean;
237     FLoadingForcedANSI: boolean;
238     FLastUndoY: integer;
239 
Compare_Ascnull240     function Compare_Asc(Key1, Key2: Pointer): Integer;
Compare_AscNoCasenull241     function Compare_AscNoCase(Key1, Key2: Pointer): Integer;
Compare_Descnull242     function Compare_Desc(Key1, Key2: Pointer): Integer;
Compare_DescNoCasenull243     function Compare_DescNoCase(Key1, Key2: Pointer): Integer;
244     procedure AddUndoItem(AAction: TATEditAction; AIndex: integer;
245       const AText: atString; AEnd: TATLineEnds; ALineState: TATLineState;
246       ACommandCode: integer);
DebugTextnull247     function DebugText: string;
DoCheckFillednull248     function DoCheckFilled: boolean;
249     procedure DoFinalizeSaving;
GetCaretsArraynull250     function GetCaretsArray: TATPointArray;
GetMarkersArraynull251     function GetMarkersArray: TATInt64Array;
GetLinenull252     function GetLine(AIndex: integer): atString;
GetLineAsciinull253     function GetLineAscii(AIndex: integer): boolean;
GetLineBlanknull254     function GetLineBlank(AIndex: integer): boolean;
GetLineGitMarkernull255     function GetLineGitMarker(AIndex: integer): TATStringGitMarker;
GetLineEndnull256     function GetLineEnd(AIndex: integer): TATLineEnds;
GetLineFoldFromnull257     function GetLineFoldFrom(ALine, AClient: integer): integer;
GetLineHasTabnull258     function GetLineHasTab(AIndex: integer): boolean;
GetLineHasAsciiNoTabsnull259     function GetLineHasAsciiNoTabs(AIndex: integer): boolean;
GetLineHiddennull260     function GetLineHidden(ALine, AClient: integer): boolean;
GetLineSepnull261     function GetLineSep(AIndex: integer): TATLineSeparator;
GetLineStatenull262     function GetLineState(AIndex: integer): TATLineState;
GetLineUpdatednull263     function GetLineUpdated(AIndex: integer): boolean;
GetLineLennull264     function GetLineLen(AIndex: integer): integer;
GetLineLenPhysicalnull265     function GetLineLenPhysical(AIndex: integer): integer;
GetRedoAsStringnull266     function GetRedoAsString: string;
GetRedoCountnull267     function GetRedoCount: integer;
GetRedoEmptynull268     function GetRedoEmpty: boolean;
GetUndoAsStringnull269     function GetUndoAsString: string;
GetUndoCountnull270     function GetUndoCount: integer;
GetUndoEmptynull271     function GetUndoEmpty: boolean;
GetUndoLimitnull272     function GetUndoLimit: integer;
IsLastFakeLineUnneedednull273     function IsLastFakeLineUnneeded: boolean;
274     procedure LineAddEx(const AString: atString; AEnd: TATLineEnds);
275     procedure LineInsertRaw(ALineIndex: integer; const AString: atString; AEnd: TATLineEnds;
276       AWithEvent: boolean=true);
277     procedure LineInsertEx(ALineIndex: integer; const AString: atString; AEnd: TATLineEnds;
278       AWithEvent: boolean=true);
IsSavingWithSignaturenull279     function IsSavingWithSignature: boolean;
280     procedure SetCaretsArray(const L: TATPointArray);
281     procedure SetMarkersArray(const L: TATInt64Array);
282     procedure SetEndings(AValue: TATLineEnds);
283     procedure SetLine(AIndex: integer; const AValue: atString);
284     procedure SetLineEnd(AIndex: integer; AValue: TATLineEnds);
285     procedure SetLineFoldFrom(AIndexLine, AIndexClient: integer; AValue: integer);
286     procedure SetLineHidden(AIndexLine, AIndexClient: integer; AValue: boolean);
287     procedure SetLineSep(AIndex: integer; AValue: TATLineSeparator);
288     procedure SetLineState(AIndex: integer; AValue: TATLineState);
289     procedure SetLineUpdated(AIndex: integer; AValue: boolean);
290     procedure DoLoadFromStream(Stream: TStream; AFromUTF8: boolean; out AForcedToANSI: boolean);
291     procedure DoDetectEndings;
292     procedure DoFinalizeLoading;
293     procedure ClearLineStates(ASaved: boolean);
294     procedure SetModified(AValue: boolean);
295     procedure SetRedoAsString(const AValue: string);
296     procedure SetUndoAsString(const AValue: string);
297     procedure SetUndoLimit(AValue: integer);
298     procedure UndoSingle(ACurList: TATUndoList; out ASoftMarked, AHardMarked,
299       AHardMarkedNext, AUnmodifiedNext: boolean;
300       out ACommandCode: integer;
301       out ATickCount: QWord);
302     procedure AddUpdatesAction(N: integer; AAction: TATEditAction);
303     procedure UpdateModified;
304   public
305     CaretsAfterLastEdition: TATPointArray;
306     EditingActive: boolean;
307     EditingTopLine: integer;
308     constructor Create(AUndoLimit: integer); virtual;
309     destructor Destroy; override;
310     procedure Clear(AWithEvent: boolean=true);
311     procedure ClearSeparators;
Countnull312     function Count: integer;
IsIndexValidnull313     function IsIndexValid(N: integer): boolean; inline;
IsLastLineFakenull314     function IsLastLineFake: boolean;
IsPosFoldednull315     function IsPosFolded(AX, AY, AIndexClient: integer): boolean;
316     procedure LineAddRaw_NoUndo(const S: string; AEnd: TATLineEnds);
317     procedure LineAddRaw_NoUndo(const S: UnicodeString; AEnd: TATLineEnds);
318     procedure LineAddRaw(const AString: atString; AEnd: TATLineEnds; AWithEvent: boolean=true);
319     procedure LineAdd(const AString: atString);
320     procedure LineInsert(ALineIndex: integer; const AString: atString; AWithEvent: boolean=true);
321     procedure LineInsertStrings(ALineIndex: integer; ABlock: TATStrings; AWithFinalEol: boolean);
322     procedure LineDelete(ALineIndex: integer; AForceLast: boolean= true;
323       AWithEvent: boolean=true; AWithUndo: boolean=true);
324     procedure LineMove(AIndexFrom, AIndexTo: integer; AWithUndo: boolean=true);
325     property Lines[Index: integer]: atString read GetLine write SetLine;
326     property LinesAscii[Index: integer]: boolean read GetLineAscii;
327     property LinesLen[Index: integer]: integer read GetLineLen;
328     property LinesLenPhysical[Index: integer]: integer read GetLineLenPhysical;
329     property LinesEnds[Index: integer]: TATLineEnds read GetLineEnd write SetLineEnd;
330     property LinesHidden[IndexLine, IndexClient: integer]: boolean read GetLineHidden write SetLineHidden;
331     property LinesHasTab[Index: integer]: boolean read GetLineHasTab;
332     property LinesHasAsciiNoTabs[Index: integer]: boolean read GetLineHasAsciiNoTabs;
333     property LinesBlank[Index: integer]: boolean read GetLineBlank;
334     property LinesGitMarker[Index: integer]: TATStringGitMarker read GetLineGitMarker;
335     property LinesFoldFrom[IndexLine, IndexClient: integer]: integer read GetLineFoldFrom write SetLineFoldFrom;
336     property LinesState[Index: integer]: TATLineState read GetLineState write SetLineState;
337     property LinesUpdated[Index: integer]: boolean read GetLineUpdated write SetLineUpdated;
338     property LinesSeparator[Index: integer]: TATLineSeparator read GetLineSep write SetLineSep;
LineSubnull339     function LineSub(ALineIndex, APosFrom, ALen: integer): atString;
LineCharAtnull340     function LineCharAt(ALineIndex, ACharIndex: integer): WideChar;
341     procedure GetIndentProp(ALineIndex: integer; out ACharCount: integer; out AKind: TATLineIndentKind);
LineLenWithoutSpacenull342     function LineLenWithoutSpace(ALineIndex: integer): integer;
343     procedure LineBlockDelete(ALine1, ALine2: integer);
344     procedure LineBlockInsert(ALineFrom: integer; ANewLines: TStringList);
ColumnPosToCharPosnull345     function ColumnPosToCharPos(AIndex: integer; AX: integer; ATabHelper: TATStringTabHelper): integer;
CharPosToColumnPosnull346     function CharPosToColumnPos(AIndex: integer; AX: integer; ATabHelper: TATStringTabHelper): integer;
GetItemPtrnull347     function GetItemPtr(AIndex: integer): PATStringItem;
348 
349     property Encoding: TATFileEncoding read FEncoding write FEncoding;
350     property EncodingCodepage: TEncConvId read FEncodingCodepage write FEncodingCodepage;
351     property EncodingDetect: boolean read FEncodingDetect write FEncodingDetect;
352     property EncodingDetectDefaultUtf8: boolean read FEncodingDetectDefaultUtf8 write FEncodingDetectDefaultUtf8;
353     property Endings: TATLineEnds read FEndings write SetEndings;
354     property LoadingForcedANSI: boolean read FLoadingForcedANSI;
355     property ListUpdates: TATIntegerList read FListUpdates;
356     property ListUpdatesHard: boolean read FListUpdatesHard write FListUpdatesHard;
357     property Modified: boolean read FModified write SetModified;
358     property ModifiedRecent: boolean read FModifiedRecent write FModifiedRecent;
359     property ModifiedVersion: Int64 read FModifiedVersion;
360     property OneLine: boolean read FOneLine write FOneLine;
361     property ProgressValue: integer read FProgressValue write FProgressValue;
362     property ProgressKind: TATStringsProgressKind read FProgressKind write FProgressKind;
363     property ChangeBlockActive: boolean read FChangeBlockActive write FChangeBlockActive;
364     property EnabledBookmarksUpdate: boolean read FEnabledBookmarksUpdate write FEnabledBookmarksUpdate;
365     property EnabledChangeEvents: boolean read FEnabledChangeEvents write FEnabledChangeEvents;
366     property Gaps: TATGaps read FGaps;
367     property Bookmarks: TATBookmarks read FBookmarks;
368     property Bookmarks2: TATBookmarks read FBookmarks2;
369     property GutterDecor1: TATGutterDecor read FGutterDecor1 write FGutterDecor1;
370     property GutterDecor2: TATGutterDecor read FGutterDecor2 write FGutterDecor2;
371     property CommandCode: integer read FCommandCode write FCommandCode;
372     //actions
373     procedure ActionDeleteFakeLine;
374     procedure ActionDeleteFakeLineAndFinalEol;
375     procedure ActionDeleteDupFakeLines;
376     procedure ActionDeleteAllBlanks;
377     procedure ActionDeleteAdjacentBlanks;
378     procedure ActionDeleteAdjacentDups;
379     procedure ActionDeleteAllDups(AKeepBlanks: boolean);
380     procedure ActionAddFakeLineIfNeeded;
ActionTrimSpacesnull381     function ActionTrimSpaces(AMode: TATTrimSpaces): boolean;
ActionEnsureFinalEolnull382     function ActionEnsureFinalEol: boolean;
ActionTrimFinalEmptyLinesnull383     function ActionTrimFinalEmptyLines: boolean;
384     procedure ActionSort(AAction: TATStringsSortAction; AFrom, ATo: integer);
385     procedure ActionReverseLines;
386     procedure ActionShuffleLines;
387     procedure ActionAddJumpToUndo(constref ACaretsArray: TATPointArray);
388     //file
389     procedure LoadFromStream(Stream: TStream; AFromUTF8: boolean=false);
390     procedure LoadFromFile(const AFilename: string);
391     procedure LoadFromString(const AText: string);
392     procedure LoadFromStrings(AList: TStrings; AEnds: TATLineEnds);
393     procedure SaveToStream(AStream: TStream; AEncoding: TATFileEncoding; AWithSignature: boolean);
394     procedure SaveToFile(const AFilename: string);
395     property SaveSignUtf8: boolean read FSaveSignUtf8 write FSaveSignUtf8;
396     property SaveSignWide: boolean read FSaveSignWide write FSaveSignWide;
397     //text
398     property ReadOnly: boolean read FReadOnly write FReadOnly;
TextString_Unicodenull399     function TextString_Unicode(AMaxLen: integer=0): UnicodeString;
400     procedure TextInsert(AX, AY: integer; const AText: atString; AOverwrite: boolean;
401       out AShift, APosAfter: TPoint);
402     procedure TextAppend(const AText: atString; out AShift, APosAfter: TPoint);
403     procedure TextInsertColumnBlock(AX, AY: integer; ABlock: TATStrings;
404       AOverwrite: boolean);
405     procedure TextDeleteLeft(AX, AY: integer; ALen: integer; out AShift,
406       APosAfter: TPoint; AllowGoToPrevLine: boolean);
407     procedure TextDeleteRight(AX, AY: integer; ALen: integer; out AShift,
408       APosAfter: TPoint; ACanDelEol: boolean=true);
TextDeleteRangenull409     function TextDeleteRange(AFromX, AFromY, AToX, AToY: integer; out AShift, APosAfter: TPoint): boolean;
410     procedure TextInsertEol(AX, AY: integer; AKeepCaret: boolean;
411       const AStrIndent: atString; out AShift, APosAfter: TPoint);
412     procedure TextDeleteLine(AX, AY: integer; out AShift, APosAfter: TPoint);
413     procedure TextReplace_OneLine(AY, AX1, AX2: integer; const AText: atString);
414     procedure TextReplace_OneLine_ReplaceOneEol(AY, AX1, AX2: integer; const ATextPart1, ATextPart2: atString);
415     procedure TextReplaceRange(AFromX, AFromY, AToX, AToY: integer; const AText: atString; out AShift,
416       APosAfter: TPoint; AWithUndoGroup: boolean);
TextReplaceLines_UTF8null417     function TextReplaceLines_UTF8(ALineFrom, ALineTo: integer; ANewLines: TStringList): boolean;
TextSubstringnull418     function TextSubstring(AX1, AY1, AX2, AY2: integer; const AEolString: UnicodeString = #10): atString;
TextSubstringLengthnull419     function TextSubstringLength(AX1, AY1, AX2, AY2: integer; const AEolString: UnicodeString=#10): integer;
420     //undo
421     property OnGetCaretsArray: TATStringsGetCarets read FOnGetCaretsArray write FOnGetCaretsArray;
422     property OnGetMarkersArray: TATStringsGetMarkers read FOnGetMarkersArray write FOnGetMarkersArray;
423     property OnSetCaretsArray: TATStringsSetCarets read FOnSetCaretsArray write FOnSetCaretsArray;
424     property OnSetMarkersArray: TATStringsSetMarkers read FOnSetMarkersArray write FOnSetMarkersArray;
425     procedure SetGroupMark;
426     procedure SetNewCommandMark;
427     procedure BeginUndoGroup;
428     procedure EndUndoGroup;
429     procedure UndoOrRedo(AUndo: boolean; AGrouped: boolean);
430     property UndoLimit: integer read GetUndoLimit write SetUndoLimit;
431     property UndoAfterSave: boolean read FUndoAfterSave write FUndoAfterSave;
432     property UndoCount: integer read GetUndoCount;
433     property RedoCount: integer read GetRedoCount;
434     property UndoEmpty: boolean read GetUndoEmpty;
435     property RedoEmpty: boolean read GetRedoEmpty;
436     property UndoAsString: string read GetUndoAsString write SetUndoAsString;
437     property RedoAsString: string read GetRedoAsString write SetRedoAsString;
438     procedure ClearUndo(ALocked: boolean = false);
439     procedure ClearLineStatesUpdated;
440     procedure DoEventLog(ALine: integer);
441     procedure DoEventChange(AChange: TATLineChangeKind; ALineIndex, AItemCount: integer);
442     //misc
443     procedure ActionSaveLastEditionPos(AX: integer=-1; AY: integer=-1);
444     procedure ActionGotoLastEditionPos;
445     procedure DoOnChangeBlock(AX1, AY1, AX2, AY2: integer;
446       AChange: TATBlockChangeKind; ABlock: TStringList);
447     property LastCommandChangedLines: integer read FLastCommandChangedLines write FLastCommandChangedLines;
448     //events
449     property OnProgress: TNotifyEvent read FOnProgress write FOnProgress;
450     property OnChangeLog: TATStringsChangeLogEvent read FOnChangeLog write FOnChangeLog;
451     property OnChangeEx: TATStringsChangeExEvent read FOnChangeEx write FOnChangeEx;
452     property OnChangeBlock: TATStringsChangeBlockEvent read FOnChangeBlock write FOnChangeBlock;
453     property OnUndoBefore: TATStringsUndoEvent read FOnUndoBefore write FOnUndoBefore;
454     property OnUndoAfter: TATStringsUndoEvent read FOnUndoAfter write FOnUndoAfter;
455   end;
456 
457 type
458   TBufferUTF8State = ATStringProc_Utf8Detect.TBufferUTF8State;
459 
ATStrings_To_StringListnull460 function ATStrings_To_StringList(AStr: TATStrings): TStringList;
DetectStreamUtf8NoBomnull461 function DetectStreamUtf8NoBom(Stream: TStream; BufSizeKb: word): TBufferUTF8State;
DetectStreamUtf16NoBomnull462 function DetectStreamUtf16NoBom(Stream: TStream; BufSizeWords: integer; out IsLE: boolean): boolean;
463 
464 var
465   GlobalDetectUtf8BufferKb: integer = 8;
466   GlobalDetectUf16BufferWords: integer = 5;
467 
468 implementation
469 
470 uses
471   FileUtil,
472   LCLVersion,
473   Math,
474   ATStringProc_Separator;
475 
476 const
477   cSignUTF8: string = #$EF#$BB#$BF;
478   cSignWideLE: string = #$FF#$FE;
479   cSignWideBE: string = #$FE#$FF;
480   cSign32LE: string = #$FF#$FE#0#0;
481   cSign32BE: string = #0#0#$FE#$FF;
482 
483 procedure DoEncError;
484 begin
485   raise Exception.Create('Unknown enc value');
486 end;
487 
488 procedure _ReadFileToStream(AStream: TStream;
489   const AFileName: string;
490   AMaxSize: integer=20*1024*1024);
491 const
492   BufSize = 4096;
493 var
494   Buf: array[0..BufSize-1] of char;
495   fs: TFileStream;
496   NSize, NTotalSize: integer;
497 begin
498   fs:= TFileStream.Create(AFileName, fmOpenRead or fmShareDenyNone);
499   try
500     AStream.Position:= 0;
501     NTotalSize:= 0;
502     repeat
503       NSize:= fs.Read(Buf, BufSize);
504       if NSize>0 then
505       begin
506         AStream.Write(Buf, NSize);
507         Inc(NTotalSize, NSize);
508         if NTotalSize>=AMaxSize then
509           Break;
510       end;
511     until NSize<BufSize;
512   finally
513     FreeAndNil(fs);
514   end;
515 end;
516 
517 { TATStringItem }
518 
IsFakenull519 function TATStringItem.IsFake: boolean; inline;
520 begin
521   Result:=
522     (Buf='') and
523     (LineEnds=cEndNone);
524 end;
525 
526 procedure TATStringItem.GetIndentProp(out ACharCount: integer; out
527   AKind: TATLineIndentKind);
528 var
529   NSpaces, NTabs: integer;
530   i: integer;
531 begin
532   ACharCount:= 0;
533   AKind:= cLineIndentOther;
534   NSpaces:= 0;
535   NTabs:= 0;
536 
537   for i:= 1 to CharLen do
538   begin
539     case CharAt(i) of
540       ' ':
541         begin
542           Inc(ACharCount);
543           Inc(NSpaces);
544         end;
545       #9:
546         begin
547           Inc(ACharCount);
548           Inc(NTabs);
549         end
550       else
551         Break;
552     end;
553   end;
554 
555   if ACharCount=0 then exit;
556   if (NSpaces>0) and (NTabs>0) then exit;
557   if NSpaces>0 then
558     AKind:= cLineIndentSpaces
559   else
560     AKind:= cLineIndentTabs;
561 end;
562 
TATStringItem.CharLenWithoutSpacenull563 function TATStringItem.CharLenWithoutSpace: integer;
564 var
565   ch: WideChar;
566 begin
567   Result:= CharLen;
568   repeat
569     if Result<=0 then Break;
570     ch:= CharAt(Result);
571     if not IsCharSpace(ch) then Break;
572     Dec(Result);
573   until false;
574 end;
575 
IsBlanknull576 function TATStringItem.IsBlank: boolean;
577 var
578   PtrChar: PChar;
579   Len, i: integer;
580   code: byte;
581 begin
582   Len:= Length(Buf);
583   if Len=0 then
584     exit(true);
585   if Ex.Wide then
586     exit(false);
587   PtrChar:= PChar(Buf);
588   for i:= 1 to Len do
589   begin
590     code:= byte(PtrChar^);
591     Inc(PtrChar);
592     if code=9 then Continue;
593     if code=32 then Continue;
594     exit(false);
595   end;
596   Result:= true;
597 end;
598 
TATStringItem.IsGitMarkernull599 function TATStringItem.IsGitMarker: TATStringGitMarker;
600 const
601   MarkBegin: PChar  = '<<<<<<< ';
602   MarkMiddle: PChar = '=======';
603   MarkEnd: PChar    = '>>>>>>> ';
604 var
605   NLen: integer;
606 begin
607   Result:= cGitMarkNone;
608   if Ex.Wide then exit;
609   NLen:= Length(Buf);
610   if NLen>=7 then
611   begin
612     if NLen>=8 then
613     begin
614       if strlcomp(PChar(Buf), MarkBegin, 8)=0 then
615         exit(cGitMarkBegin);
616       if strlcomp(PChar(Buf), MarkEnd, 8)=0 then
617         exit(cGitMarkEnd);
618     end;
619     if NLen=7 then
620       if strlcomp(PChar(Buf), MarkMiddle, 7)=0 then
621         exit(cGitMarkMiddle);
622   end;
623 end;
624 
625 
TATStringItem.CharLennull626 function TATStringItem.CharLen: integer;
627 begin
628   if Ex.Wide then
629     Result:= Length(Buf) div 2
630   else
631     Result:= Length(Buf);
632 end;
633 
GetLinenull634 function TATStringItem.GetLine: UnicodeString;
635 var
636   NLen, i: integer;
637 begin
638   NLen:= Length(Buf);
639   if NLen=0 then exit('');
640   if Ex.Wide then
641   begin
642     SetLength(Result, NLen div 2);
643     Move(Buf[1], Result[1], NLen);
644   end
645   else
646   begin
647     SetLength(Result, NLen);
648     for i:= 1 to NLen do
649       Result[i]:= WideChar(Ord(Buf[i]));
650   end;
651 end;
652 
653 procedure TATStringItem.LineToBuffer(OtherBuf: PWideChar);
654 //OtherBuf must point to WideChar array of enough size
655 var
656   NLen, i: integer;
657   SrcBuf: PChar;
658 begin
659   NLen:= Length(Buf);
660   if NLen=0 then exit;
661   if Ex.Wide then
662   begin
663     Move(Buf[1], OtherBuf^, NLen);
664   end
665   else
666   begin
667     SrcBuf:= PChar(Buf);
668     for i:= 1 to NLen do
669     begin
670       OtherBuf^:= WideChar(Ord(SrcBuf^));
671       Inc(SrcBuf);
672       Inc(OtherBuf);
673     end;
674   end;
675 end;
676 
GetLineEndsnull677 function TATStringItem.GetLineEnds: TATLineEnds;
678 begin
679   Result:= TATLineEnds(Ex.Ends);
680 end;
681 
TATStringItem.GetLineStatenull682 function TATStringItem.GetLineState: TATLineState;
683 begin
684   Result:= TATLineState(Ex.State);
685 end;
686 
687 procedure TATStringItem.SetLineW(const S: UnicodeString);
688 var
689   NLen, i: integer;
690 begin
691   NLen:= Length(S);
692   if NLen=0 then
693   begin
694     Ex.Wide:= false;
695     Buf:= '';
696   end
697   else
698   if not IsStringWithUnicode(S) then
699   begin
700     Ex.Wide:= false;
701     SetLength(Buf, NLen);
702     for i:= 1 to NLen do
703       Buf[i]:= Chr(Ord(S[i]));
704   end
705   else
706   begin
707     Ex.Wide:= true;
708     SetLength(Buf, NLen*2);
709     Move(S[1], Buf[1], NLen*2);
710   end;
711 
712   LineStateToChanged;
713   Ex.HasTab:= 0; //cFlagUnknown
714   Ex.HasAsciiNoTabs:= 0; //cFlagUnknown
715   Ex.Updated:= true;
716 end;
717 
718 procedure TATStringItem.SetLineA(const S: string);
719 var
720   NLen, N: integer;
721 begin
722   LineStateToChanged;
723   Ex.HasTab:= 0; //cFlagUnknown
724   Ex.HasAsciiNoTabs:= 0; //cFlagUnknown
725   Ex.Updated:= true;
726 
727   NLen:= Length(S);
728   if NLen=0 then
729   begin
730     Ex.Wide:= false;
731     Buf:= '';
732   end
733   else
734   if not IsStringWithUnicode(S) then
735   begin
736     Ex.Wide:= false;
737     Buf:= S;
738     //UniqueString(Buf); //makes slower loading file by 5-7%
739   end
740   else
741   begin
742     Ex.Wide:= true;
743     SetLength(Buf, NLen*2);
744     try
745       //this func is the same as Utf8ToUnicode but raises exception
746       N:= CustomUtf8ToUnicode(PUnicodeChar(PChar(Buf)), NLen, PChar(S), NLen);
747       if N>0 then
748         SetLength(Buf, 2*(N-1))
749       else
750         Buf:= '';
751     except
752       //failed to load as UTF8
753       //load it again with FPC which replaces bad characters with '?'
754       //and raise exception, to allow outer procedure to load file again
755       N:= Utf8ToUnicode(PUnicodeChar(PChar(Buf)), NLen, PChar(S), NLen);
756       if N>0 then
757         SetLength(Buf, 2*(N-1))
758       else
759         Buf:= '';
760       RaiseUTF8TextError;
761     end;
762   end;
763 end;
764 
765 procedure TATStringItem.Init(const S: string; AEnd: TATLineEnds);
766 begin
767   FillChar(Ex, SizeOf(Ex), 0);
768   SetLineA(S);
769 
770   Ex.Ends:= TATBits2(AEnd);
771   Ex.State:= TATBits2(cLineStateAdded);
772   Ex.Updated:= true;
773 end;
774 
775 procedure TATStringItem.Init(const S: UnicodeString; AEnd: TATLineEnds);
776 begin
777   FillChar(Ex, SizeOf(Ex), 0);
778   SetLineW(S);
779 
780   Ex.Ends:= TATBits2(AEnd);
781   Ex.State:= TATBits2(cLineStateAdded);
782   Ex.Updated:= true;
783 end;
784 
785 procedure TATStringItem.LineStateToChanged;
786 //switch LineState to "changed" only for "none"+"saved" lines,
787 //but skip "added" lines
788 // https://github.com/Alexey-T/CudaText/issues/2617
789 begin
790   case TATLineState(Ex.State) of
791     cLineStateNone,
792     cLineStateSaved:
793       Ex.State:= TATBits2(cLineStateChanged);
794   end;
795 end;
796 
797 procedure TATStringItem.LineStateToSaved;
798 begin
799   if TATLineState(Ex.State)<>cLineStateNone then
800     Ex.State:= TATBits2(cLineStateSaved);
801 end;
802 
803 procedure TATStringItem.LineStateToNone;
804 begin
805   Ex.State:= TATBits2(cLineStateNone);
806 end;
807 
TATStringItem.LineSubnull808 function TATStringItem.LineSub(AFrom, ALen: integer): UnicodeString;
809 var
810   NLen, ResLen, i: integer;
811 begin
812   Result:= '';
813   NLen:= Length(Buf);
814   if NLen=0 then exit;
815   if Ex.Wide then
816   begin
817     ResLen:= Max(0, Min(ALen, NLen div 2 - AFrom + 1));
818     SetLength(Result, ResLen);
819     if ResLen>0 then
820       Move(Buf[AFrom*2-1], Result[1], ResLen*2);
821   end
822   else
823   begin
824     ResLen:= Max(0, Min(ALen, NLen-AFrom+1));
825     SetLength(Result, ResLen);
826     for i:= 1 to ResLen do
827       Result[i]:= WideChar(Ord(Buf[i+AFrom-1]));
828   end;
829 end;
830 
CharAtnull831 function TATStringItem.CharAt(AIndex: integer): WideChar;
832 var
833   NLen: integer;
834 begin
835   if AIndex<=0 then exit(#0);
836   NLen:= CharLen;
837   if NLen=0 then exit(#0);
838   if AIndex>NLen then exit(#0);
839   if Ex.Wide then
840     Move(Buf[AIndex*2-1], Result, 2)
841   else
842     Result:= WideChar(Ord(Buf[AIndex]));
843 end;
844 
HasTabnull845 function TATStringItem.HasTab: boolean;
846 var
847   Value: TATLineFlag;
848   NLen, i: integer;
849   Ptr: PWideChar;
850 begin
851   case TATLineFlag(Ex.HasTab) of
852     cFlagNo:
853       exit(false);
854     cFlagYes:
855       exit(true);
856   end;
857 
858   Result:= false;
859   NLen:= Length(Buf);
860   if NLen>0 then
861     if Ex.Wide then
862     begin
863       Ptr:= @Buf[1];
864       for i:= 1 to NLen div 2 do
865       begin
866         if Ptr^=#9 then
867         begin
868           Result:= true;
869           Break
870         end;
871         Inc(Ptr);
872       end;
873     end
874     else
875     begin
876       for i:= 1 to NLen do
877         if Buf[i]=#9 then
878         begin
879           Result:= true;
880           Break
881         end;
882     end;
883 
884   if Result then
885     Value:= cFlagYes
886   else
887     Value:= cFlagNo;
888   Ex.HasTab:= TATBits2(Value);
889 end;
890 
TATStringItem.HasAsciiNoTabsnull891 function TATStringItem.HasAsciiNoTabs: boolean;
892 var
893   Value: TATLineFlag;
894   NLen, NCode, i: integer;
895   Ptr: PWideChar;
896 begin
897   case TATLineFlag(Ex.HasAsciiNoTabs) of
898     cFlagNo:
899       exit(false);
900     cFlagYes:
901       exit(true);
902   end;
903 
904   Result:= true;
905   NLen:= Length(Buf);
906   if NLen>0 then
907     if Ex.Wide then
908     begin
909       Ptr:= @Buf[1];
910       for i:= 1 to NLen div 2 do
911       begin
912         NCode:= Ord(Ptr^);
913         if (NCode<32) or (NCode>=127) then
914         begin
915           Result:= false;
916           Break
917         end;
918         Inc(Ptr);
919       end;
920     end
921     else
922     begin
923       for i:= 1 to NLen do
924       begin
925         NCode:= Ord(Buf[i]);
926         if (NCode<32) or (NCode>=127) then
927         begin
928           Result:= false;
929           Break
930         end;
931       end;
932     end;
933 
934   if Result then
935     Value:= cFlagYes
936   else
937     Value:= cFlagNo;
938   Ex.HasAsciiNoTabs:= TATBits2(Value);
939 end;
940 
941 
ATStrings_To_StringListnull942 function ATStrings_To_StringList(AStr: TATStrings): TStringList;
943 var
944   i: integer;
945 begin
946   Result:= TStringList.Create;
947   for i:= 0 to AStr.Count-1 do
948     Result.Add(AStr.Lines[i]);
949 end;
950 
951 { TATStringItemList }
952 
953 constructor TATStringItemList.Create;
954 begin
955   inherited Create(SizeOf(TATStringItem));
956 end;
957 
GetItemnull958 function TATStringItemList.GetItem(AIndex: integer): PATStringItem; inline;
959 begin
960   Result:= PATStringItem(Get(AIndex));
961 end;
962 
963 procedure TATStringItemList.Deref(Item: Pointer);
964 begin
965   PATStringItem(Item)^.Buf:= '';
966 end;
967 
968 procedure TATStringItemList.SortRange(L, R: integer; Compare: TFPSListCompareFunc);
969 begin
970   QuickSort(L, R, Compare);
971 end;
972 
973 { TATStrings }
974 
GetLinenull975 function TATStrings.GetLine(AIndex: integer): atString;
976 begin
977   Result:= FList.GetItem(AIndex)^.Line;
978 end;
979 
GetLineAsciinull980 function TATStrings.GetLineAscii(AIndex: integer): boolean;
981 begin
982   Result:= not FList.GetItem(AIndex)^.Ex.Wide;
983 end;
984 
GetLineBlanknull985 function TATStrings.GetLineBlank(AIndex: integer): boolean;
986 begin
987   Result:= FList.GetItem(AIndex)^.IsBlank;
988 end;
989 
GetLineGitMarkernull990 function TATStrings.GetLineGitMarker(AIndex: integer): TATStringGitMarker;
991 begin
992   Result:= FList.GetItem(AIndex)^.IsGitMarker;
993 end;
994 
GetLineLennull995 function TATStrings.GetLineLen(AIndex: integer): integer;
996 begin
997   Result:= FList.GetItem(AIndex)^.CharLen;
998 end;
999 
TATStrings.GetLineEndnull1000 function TATStrings.GetLineEnd(AIndex: integer): TATLineEnds;
1001 begin
1002   Result:= FList.GetItem(AIndex)^.LineEnds;
1003 end;
1004 
GetLineFoldFromnull1005 function TATStrings.GetLineFoldFrom(ALine, AClient: integer): integer;
1006 begin
1007   case AClient of
1008     0: Result:= FList.GetItem(ALine)^.Ex.FoldFrom_0;
1009     1: Result:= FList.GetItem(ALine)^.Ex.FoldFrom_1;
1010     else Result:= 0;
1011   end;
1012 end;
1013 
GetLineHiddennull1014 function TATStrings.GetLineHidden(ALine, AClient: integer): boolean;
1015 begin
1016   case AClient of
1017     0: Result:= FList.GetItem(ALine)^.Ex.Hidden_0;
1018     1: Result:= FList.GetItem(ALine)^.Ex.Hidden_1;
1019     else Result:= false;
1020   end;
1021 end;
1022 
TATStrings.GetLineStatenull1023 function TATStrings.GetLineState(AIndex: integer): TATLineState;
1024 begin
1025   Result:= FList.GetItem(AIndex)^.LineState;
1026 end;
1027 
TATStrings.GetLineUpdatednull1028 function TATStrings.GetLineUpdated(AIndex: integer): boolean;
1029 begin
1030   Result:= FList.GetItem(AIndex)^.Ex.Updated;
1031 end;
1032 
TATStrings.GetLineLenPhysicalnull1033 function TATStrings.GetLineLenPhysical(AIndex: integer): integer;
1034 var
1035   ItemPtr: PATStringItem;
1036 begin
1037   ItemPtr:= FList.GetItem(AIndex);
1038   Result:= ItemPtr^.CharLen + cLineEndLength[ItemPtr^.LineEnds];
1039 end;
1040 
GetRedoAsStringnull1041 function TATStrings.GetRedoAsString: string;
1042 begin
1043   Result:= FRedoList.AsString;
1044 end;
1045 
TATStrings.GetLineSepnull1046 function TATStrings.GetLineSep(AIndex: integer): TATLineSeparator;
1047 begin
1048   Result:= TATLineSeparator(FList.GetItem(AIndex)^.Ex.Sep);
1049 end;
1050 
TATStrings.GetLineHasTabnull1051 function TATStrings.GetLineHasTab(AIndex: integer): boolean;
1052 begin
1053   Result:= FList.GetItem(AIndex)^.HasTab;
1054 end;
1055 
GetLineHasAsciiNoTabsnull1056 function TATStrings.GetLineHasAsciiNoTabs(AIndex: integer): boolean;
1057 begin
1058   Result:= FList.GetItem(AIndex)^.HasAsciiNoTabs;
1059 end;
1060 
1061 
TATStrings.GetUndoCountnull1062 function TATStrings.GetUndoCount: integer;
1063 begin
1064   if Assigned(FUndoList) then
1065     Result:= FUndoList.Count
1066   else
1067     Result:= 0;
1068 end;
1069 
TATStrings.GetUndoEmptynull1070 function TATStrings.GetUndoEmpty: boolean;
1071 begin
1072   Result:= FUndoList.IsEmpty;
1073 end;
1074 
TATStrings.GetRedoCountnull1075 function TATStrings.GetRedoCount: integer;
1076 begin
1077   if Assigned(FRedoList) then
1078     Result:= FRedoList.Count
1079   else
1080     Result:= 0;
1081 end;
1082 
TATStrings.GetRedoEmptynull1083 function TATStrings.GetRedoEmpty: boolean;
1084 begin
1085   Result:= FRedoList.IsEmpty;
1086 end;
1087 
TATStrings.GetUndoAsStringnull1088 function TATStrings.GetUndoAsString: string;
1089 begin
1090   Result:= FUndoList.AsString;
1091 end;
1092 
1093 
TATStrings.GetUndoLimitnull1094 function TATStrings.GetUndoLimit: integer;
1095 begin
1096   if Assigned(FUndoList) then
1097     Result:= FUndoList.MaxCount
1098   else
1099     Result:= 2000;
1100 end;
1101 
1102 procedure TATStrings.SetNewCommandMark;
1103 begin
1104   if Assigned(FUndoList) then
1105     FUndoList.NewCommandMark:= true;
1106 end;
1107 
1108 procedure TATStrings.SetEndings(AValue: TATLineEnds);
1109 var
1110   typ: TATLineEnds;
1111   i: integer;
1112 begin
1113   if FReadOnly then Exit;
1114 
1115   FEndings:= AValue;
1116   for i:= 0 to Count-1 do
1117   begin
1118     typ:= LinesEnds[i];
1119     if (typ<>AValue) and (typ<>cEndNone) then
1120       LinesEnds[i]:= AValue;
1121   end;
1122 end;
1123 
1124 procedure TATStrings.SetLine(AIndex: integer; const AValue: atString);
1125 var
1126   Item: PATStringItem;
1127 begin
1128   //Assert(IsIndexValid(AIndex));
1129   if FReadOnly then Exit;
1130   Item:= FList.GetItem(AIndex);
1131 
1132   UpdateModified;
1133   AddUndoItem(aeaChange, AIndex, Item^.Line, Item^.LineEnds, Item^.LineState, FCommandCode);
1134   DoEventLog(AIndex);
1135   DoEventChange(cLineChangeEdited, AIndex, 1);
1136 
1137   Item^.Line:= AValue;
1138 
1139   //fully unfold this line
1140   Item^.Ex.FoldFrom_0:= 0;
1141   Item^.Ex.FoldFrom_1:= 0;
1142 
1143   Item^.LineStateToChanged;
1144 
1145   Item^.Ex.Updated:= true;
1146   Item^.Ex.HasTab:= 0; //unknown
1147 end;
1148 
1149 procedure TATStrings.SetLineSep(AIndex: integer; AValue: TATLineSeparator);
1150 var
1151   Item: PATStringItem;
1152 begin
1153   if IsIndexValid(AIndex) then
1154   begin
1155     Item:= FList.GetItem(AIndex);
1156     Item^.Ex.Sep:= TATBits2(AValue);
1157   end;
1158 end;
1159 
1160 
1161 procedure TATStrings.SetLineEnd(AIndex: integer; AValue: TATLineEnds);
1162 var
1163   Item: PATStringItem;
1164 begin
1165   //Assert(IsIndexValid(AIndex));
1166   if FReadOnly then Exit;
1167 
1168   Item:= FList.GetItem(AIndex);
1169 
1170   UpdateModified;
1171   AddUndoItem(aeaChangeEol, AIndex, '', Item^.LineEnds, Item^.LineState, FCommandCode);
1172 
1173   Item^.Ex.Ends:= TATBits2(AValue);
1174   Item^.LineStateToChanged;
1175   Item^.Ex.Updated:= true;
1176 end;
1177 
1178 procedure TATStrings.SetLineFoldFrom(AIndexLine, AIndexClient: integer; AValue: integer);
1179 const
1180   cMax = High(TATStringItem_FoldFrom);
1181 var
1182   Item: PATStringItem;
1183 begin
1184   //Assert(IsIndexValid(AIndexLine));
1185   if AValue<0 then AValue:= 0;
1186   if AValue>cMax then AValue:= cMax;
1187 
1188   Item:= FList.GetItem(AIndexLine);
1189   case AIndexClient of
1190     0: Item^.Ex.FoldFrom_0:= AValue;
1191     1: Item^.Ex.FoldFrom_1:= AValue;
1192   end;
1193 end;
1194 
1195 procedure TATStrings.SetLineHidden(AIndexLine, AIndexClient: integer; AValue: boolean);
1196 var
1197   Item: PATStringItem;
1198 begin
1199   //Assert(IsIndexValid(AIndexLine));
1200   Item:= FList.GetItem(AIndexLine);
1201   case AIndexClient of
1202     0: Item^.Ex.Hidden_0:= AValue;
1203     1: Item^.Ex.Hidden_1:= AValue;
1204   end;
1205 end;
1206 
1207 procedure TATStrings.SetLineState(AIndex: integer; AValue: TATLineState);
1208 var
1209   Item: PATStringItem;
1210 begin
1211   //Assert(IsIndexValid(AIndex));
1212   Item:= FList.GetItem(AIndex);
1213   Item^.Ex.State:= TATBits2(AValue);
1214 end;
1215 
1216 procedure TATStrings.SetLineUpdated(AIndex: integer; AValue: boolean);
1217 var
1218   Item: PATStringItem;
1219 begin
1220   //Assert(IsIndexValid(AIndex));
1221   Item:= FList.GetItem(AIndex);
1222   Item^.Ex.Updated:= AValue;
1223 end;
1224 
1225 
TATStrings.TextString_Unicodenull1226 function TATStrings.TextString_Unicode(AMaxLen: integer=0): UnicodeString;
1227 const
1228   LenEol = 1;
1229   CharEol = #10;
1230 var
1231   Len, LastIndex, i: integer;
1232   Item: PATStringItem;
1233   Ptr: pointer;
1234   bFinalEol: boolean;
1235 begin
1236   Result:= '';
1237   if Count=0 then Exit;
1238   LastIndex:= Count-1;
1239 
1240   Len:= 0;
1241   for i:= 0 to LastIndex-1 do
1242   begin
1243     Item:= FList.GetItem(i);
1244     Inc(Len, Item^.CharLen+LenEol);
1245   end;
1246 
1247   Item:= FList.GetItem(LastIndex);
1248   Inc(Len, Item^.CharLen);
1249 
1250   bFinalEol:= LinesEnds[LastIndex]<>cEndNone;
1251   if bFinalEol then
1252     Inc(Len, LenEol);
1253 
1254   if Len=0 then Exit;
1255 
1256   SetLength(Result, Len);
1257   Ptr:= @Result[1];
1258 
1259   for i:= 0 to LastIndex do
1260   begin
1261     Item:= FList.GetItem(i);
1262     Len:= Item^.CharLen;
1263     //copy string
1264     if Len>0 then
1265     begin
1266       if (AMaxLen>0) and (Len>AMaxLen) then
1267         FillChar(Ptr^, Len*2, $20) //fill item with spaces
1268       else
1269         Item^.LineToBuffer(Ptr);
1270       Inc(Ptr, Len*2);
1271     end;
1272     //copy eol
1273     if bFinalEol or (i<LastIndex) then
1274     begin
1275       PWideChar(Ptr)^:= CharEol;
1276       Inc(Ptr, LenEol*2);
1277     end;
1278   end;
1279 end;
1280 
1281 
1282 constructor TATStrings.Create(AUndoLimit: integer);
1283 begin
1284   FList:= TATStringItemList.Create;
1285   FListUpdates:= TATIntegerList.Create;
1286   FListUpdatesHard:= false;
1287   FUndoLimit:= AUndoLimit;
1288   FUndoList:= TATUndoList.Create(FUndoLimit);
1289   FRedoList:= TATUndoList.Create(FUndoLimit);
1290   FGaps:= TATGaps.Create;
1291   FBookmarks:= TATBookmarks.Create;
1292   FBookmarks2:= TATBookmarks.Create;
1293   FEnabledBookmarksUpdate:= true;
1294   FEnabledChangeEvents:= true;
1295 
1296   FEncoding:= cEncUTF8;
1297   FEncodingDetect:= true;
1298   FEncodingDetectDefaultUtf8:= true;
1299   FEncodingCodepage:= EncConvGetANSI;
1300   FEndings:= cEndWin;
1301 
1302   FModified:= false;
1303   FModifiedRecent:= false;
1304   FModifiedVersion:= 0;
1305   FChangeBlockActive:= false;
1306 
1307   FSaveSignUtf8:= true;
1308   FSaveSignWide:= true;
1309   FUndoAfterSave:= true;
1310   FOneLine:= false;
1311   FProgressValue:= 0;
1312   FProgressKind:= cStringsProgressNone;
1313   SetLength(CaretsAfterLastEdition, 0);
1314 
1315   ActionAddFakeLineIfNeeded;
1316   ClearUndo;
1317 end;
1318 
1319 destructor TATStrings.Destroy;
1320 begin
1321   //disable events: so Clear won't call them
1322   FOnChangeEx:= nil;
1323   FOnChangeLog:= nil;
1324   FOnChangeBlock:= nil;
1325   FOnGetCaretsArray:= nil;
1326   FOnSetCaretsArray:= nil;
1327   FOnGetMarkersArray:= nil;
1328   FOnSetMarkersArray:= nil;
1329   FOnProgress:= nil;
1330 
1331   GutterDecor1:= nil;
1332   GutterDecor2:= nil;
1333 
1334   ClearUndo(true);
1335   FList.Clear; //Clear calls event, no need
1336 
1337   FreeAndNil(FList);
1338   FreeAndNil(FBookmarks2);
1339   FreeAndNil(FBookmarks);
1340   FreeAndNil(FGaps);
1341   FreeAndNil(FListUpdates);
1342   FreeAndNil(FUndoList);
1343   FreeAndNil(FRedoList);
1344 
1345   inherited;
1346 end;
1347 
IsLastLineFakenull1348 function TATStrings.IsLastLineFake: boolean;
1349 begin
1350   Result:= (Count>0) and
1351     FList.GetItem(FList.Count-1)^.IsFake;
1352 end;
1353 
IsLastFakeLineUnneedednull1354 function TATStrings.IsLastFakeLineUnneeded: boolean;
1355 begin
1356   Result:= (Count>1) and
1357     IsLastLineFake and
1358     (FList.GetItem(FList.Count-2)^.LineEnds=cEndNone);
1359 end;
1360 
1361 procedure TATStrings.ActionDeleteFakeLine;
1362 begin
1363   if IsLastLineFake then
1364     LineDelete(Count-1, false{AForceLast}, false, false);
1365 end;
1366 
1367 procedure TATStrings.ActionDeleteFakeLineAndFinalEol;
1368 begin
1369   ActionDeleteFakeLine;
1370   if Count>0 then
1371     if LinesEnds[Count-1]<>cEndNone then
1372       LinesEnds[Count-1]:= cEndNone;
1373 end;
1374 
1375 procedure TATStrings.ActionAddFakeLineIfNeeded;
1376 begin
1377   if Count=0 then
1378   begin
1379     LineAddRaw('', cEndNone, false{AWithEvent});
1380     Exit
1381   end;
1382 
1383   if IsLastLineFake then Exit;
1384 
1385   if LinesEnds[Count-1]<>cEndNone then
1386   begin
1387     LineAddRaw('', cEndNone, false{AWithEvent});
1388     Exit
1389   end;
1390 end;
1391 
1392 procedure TATStrings.LineAddRaw(const AString: atString; AEnd: TATLineEnds; AWithEvent: boolean);
1393 var
1394   Item: TATStringItem;
1395 begin
1396   if FReadOnly then Exit;
1397   if DoCheckFilled then Exit;
1398 
1399   UpdateModified;
1400   AddUndoItem(aeaInsert, Count, '', cEndNone, cLineStateNone, FCommandCode);
1401   if AWithEvent then
1402   begin
1403     DoEventLog(Count);
1404     DoEventChange(cLineChangeAdded, Count, 1);
1405   end;
1406 
1407   Item.Init(AString, AEnd);
1408   FList.Add(@Item);
1409   FillChar(Item, SizeOf(Item), 0);
1410 end;
1411 
1412 procedure TATStrings.LineAddEx(const AString: atString; AEnd: TATLineEnds);
1413 var
1414   AEndInside: TATLineEnds;
1415 begin
1416   if FReadOnly then Exit;
1417 
1418   AEndInside:= AEnd;
1419   if AEndInside=cEndNone then
1420     AEndInside:= FEndings;
1421 
1422   if IsLastLineFake then
1423     LineInsertRaw(Count-1, AString, AEndInside)
1424   else
1425   begin
1426     LineAddRaw(AString, AEnd);
1427     if AEnd<>cEndNone then
1428       LineAddRaw('', cEndNone);
1429   end;
1430 end;
1431 
1432 procedure TATStrings.LineAdd(const AString: atString);
1433 begin
1434   LineAddEx(AString, FEndings);
1435 end;
1436 
1437 
DoCheckFillednull1438 function TATStrings.DoCheckFilled: boolean;
1439 begin
1440   Result:= false;
1441   if FOneLine then
1442   begin
1443     Result:= Count>0;
1444     if Result then
1445       while Count>1 do
1446         LineDelete(Count-1);
1447   end;
1448 end;
1449 
1450 procedure TATStrings.LineInsertRaw(ALineIndex: integer; const AString: atString;
1451   AEnd: TATLineEnds; AWithEvent: boolean=true);
1452 var
1453   Item: TATStringItem;
1454 begin
1455   if FReadOnly then Exit;
1456   if DoCheckFilled then Exit;
1457 
1458   UpdateModified;
1459   AddUndoItem(aeaInsert, ALineIndex, '', cEndNone, cLineStateNone, FCommandCode);
1460 
1461   if AWithEvent then
1462   begin
1463     DoEventLog(ALineIndex);
1464     DoEventChange(cLineChangeAdded, ALineIndex, 1);
1465   end;
1466 
1467   Item.Init(AString, AEnd);
1468   FList.Insert(ALineIndex, @Item);
1469   FillChar(Item, SizeOf(Item), 0);
1470 end;
1471 
1472 procedure TATStrings.LineInsertEx(ALineIndex: integer; const AString: atString; AEnd: TATLineEnds;
1473   AWithEvent: boolean=true);
1474 begin
1475   if FReadOnly then Exit;
1476 
1477   if IsIndexValid(ALineIndex) then
1478     LineInsertRaw(ALineIndex, AString, AEnd, AWithEvent)
1479   else
1480   if ALineIndex=Count then
1481     LineAddEx(AString, AEnd);
1482   //else
1483   //  raise Exception.Create('Incorrect Insert index: '+IntToStr(ALineIndex));
1484 end;
1485 
1486 procedure TATStrings.LineInsert(ALineIndex: integer; const AString: atString;
1487   AWithEvent: boolean=true);
1488 begin
1489   LineInsertEx(ALineIndex, AString, FEndings, AWithEvent);
1490 end;
1491 
1492 procedure TATStrings.LineInsertStrings(ALineIndex: integer; ABlock: TATStrings; AWithFinalEol: boolean);
1493 //AWithFinalEol:
1494 //  True to insert whole lines;
1495 //  False to insert whole lines except last + concat last item to existing line
1496 var
1497   Item: TATStringItem;
1498   Str: atString;
1499   NCount, i: integer;
1500 begin
1501   NCount:= ABlock.Count;
1502   if NCount=0 then exit;
1503   if not AWithFinalEol then Dec(NCount);
1504 
1505   UpdateModified;
1506 
1507   if NCount>0 then
1508   begin
1509     for i:= 0 to NCount-1 do
1510     begin
1511       AddUndoItem(aeaInsert, ALineIndex+i, '', cEndNone, cLineStateNone, FCommandCode);
1512 
1513       Item.Init(
1514         ABlock.GetLine(i),
1515         Endings
1516         );
1517       FList.Insert(ALineIndex+i, @Item);
1518       FillChar(Item, SizeOf(Item), 0);
1519     end;
1520 
1521     DoEventLog(ALineIndex);
1522     DoEventChange(cLineChangeAdded, ALineIndex, NCount);
1523   end;
1524 
1525   //insert last item specially, if no eol
1526   if not AWithFinalEol then
1527   begin
1528     i:= ALineIndex+ABlock.Count-1;
1529     Str:= ABlock.Lines[ABlock.Count-1];
1530     if IsIndexValid(i) then
1531       Lines[i]:= Str+Lines[i]
1532     else
1533       LineAdd(Str);
1534   end;
1535 end;
1536 
1537 
IsIndexValidnull1538 function TATStrings.IsIndexValid(N: integer): boolean; inline;
1539 begin
1540   Result:= (N>=0) and (N<FList.Count);
1541 end;
1542 
Countnull1543 function TATStrings.Count: integer; inline;
1544 begin
1545   Result:= FList.Count;
1546 end;
1547 
1548 procedure TATStrings.LineDelete(ALineIndex: integer; AForceLast: boolean = true;
1549   AWithEvent: boolean=true; AWithUndo: boolean=true);
1550 var
1551   Item: PATStringItem;
1552 begin
1553   if FReadOnly then Exit;
1554 
1555   if IsIndexValid(ALineIndex) then
1556   begin
1557     Item:= FList.GetItem(ALineIndex);
1558 
1559     UpdateModified;
1560     if AWithUndo then
1561       AddUndoItem(aeaDelete, ALineIndex, Item^.Line, Item^.LineEnds, Item^.LineState, FCommandCode);
1562 
1563     if AWithEvent then
1564     begin
1565       DoEventLog(ALineIndex);
1566       DoEventChange(cLineChangeDeleted, ALineIndex, 1);
1567     end;
1568 
1569     FList.Delete(ALineIndex);
1570   end;
1571   //else
1572   //  raise Exception.Create('Invalid Delete index: '+IntToStr(ALineIndex));
1573 
1574   if AForceLast then
1575     ActionAddFakeLineIfNeeded;
1576 end;
1577 
1578 procedure TATStrings.LineMove(AIndexFrom, AIndexTo: integer; AWithUndo: boolean=true);
1579 var
1580   ItemFrom, ItemTo: PATStringItem;
1581   NLineMin: integer;
1582 begin
1583   UpdateModified;
1584 
1585   if AWithUndo then
1586   begin
1587     ItemFrom:= GetItemPtr(AIndexFrom);
1588     ItemTo:= GetItemPtr(AIndexTo);
1589 
1590     AddUndoItem(aeaDelete, AIndexFrom, ItemFrom^.Line, ItemFrom^.LineEnds, ItemFrom^.LineState, FCommandCode);
1591     AddUndoItem(aeaInsert, AIndexTo, ItemTo^.Line, ItemTo^.LineEnds, ItemTo^.LineState, FCommandCode);
1592   end;
1593 
1594   FList.Move(AIndexFrom, AIndexTo);
1595 
1596   LinesState[AIndexFrom]:= cLineStateChanged;
1597   if LinesEnds[AIndexFrom]=cEndNone then
1598     LinesEnds[AIndexFrom]:= Endings;
1599   if LinesEnds[AIndexTo]=cEndNone then
1600     LinesEnds[AIndexTo]:= Endings;
1601 
1602   ActionAddFakeLineIfNeeded;
1603   Modified:= true;
1604 
1605   NLineMin:= Min(AIndexFrom, AIndexTo);
1606   DoEventLog(NLineMin);
1607 end;
1608 
LineSubnull1609 function TATStrings.LineSub(ALineIndex, APosFrom, ALen: integer): atString;
1610 var
1611   Item: PATStringItem;
1612 begin
1613   if ALen=0 then exit('');
1614   Item:= GetItemPtr(ALineIndex);
1615   Result:= Item^.LineSub(APosFrom, ALen);
1616 end;
1617 
LineCharAtnull1618 function TATStrings.LineCharAt(ALineIndex, ACharIndex: integer): WideChar;
1619 begin
1620   Result:= GetItemPtr(ALineIndex)^.CharAt(ACharIndex);
1621 end;
1622 
1623 procedure TATStrings.GetIndentProp(ALineIndex: integer; out
1624   ACharCount: integer; out AKind: TATLineIndentKind);
1625 begin
1626   GetItemPtr(ALineIndex)^.GetIndentProp(ACharCount, AKind);
1627 end;
1628 
LineLenWithoutSpacenull1629 function TATStrings.LineLenWithoutSpace(ALineIndex: integer): integer;
1630 begin
1631   Result:= GetItemPtr(ALineIndex)^.CharLenWithoutSpace;
1632 end;
1633 
ColumnPosToCharPosnull1634 function TATStrings.ColumnPosToCharPos(AIndex: integer; AX: integer; ATabHelper: TATStringTabHelper): integer;
1635 var
1636   SLine: atString;
1637 begin
1638   if not LinesHasTab[AIndex] then exit(AX);
1639 
1640   //optimized for huge lines
1641   SLine:= LineSub(AIndex, 1, AX+ATabHelper.TabSize);
1642   Result:= ATabHelper.ColumnPosToCharPos(AIndex, SLine, AX);
1643 end;
1644 
CharPosToColumnPosnull1645 function TATStrings.CharPosToColumnPos(AIndex: integer; AX: integer; ATabHelper: TATStringTabHelper): integer;
1646 var
1647   SLine: atString;
1648 begin
1649   if not LinesHasTab[AIndex] then exit(AX);
1650 
1651   //optimized for huge lines
1652   SLine:= LineSub(AIndex, 1, AX+ATabHelper.TabSize);
1653   Result:= ATabHelper.CharPosToColumnPos(AIndex, SLine, AX);
1654 end;
1655 
GetItemPtrnull1656 function TATStrings.GetItemPtr(AIndex: integer): PATStringItem;
1657 begin
1658   Result:= FList.GetItem(AIndex);
1659 end;
1660 
1661 procedure TATStrings.Clear(AWithEvent: boolean);
1662 begin
1663   ClearUndo(FUndoList.Locked);
1664 
1665   if AWithEvent then
1666   begin
1667     DoEventLog(0);
1668     DoEventChange(cLineChangeDeletedAll, -1, 1);
1669   end;
1670 
1671   FList.Clear;
1672 end;
1673 
1674 procedure TATStrings.ClearLineStates(ASaved: boolean);
1675 var
1676   Item: PATStringItem;
1677   i: integer;
1678 begin
1679   for i:= 0 to Count-1 do
1680   begin
1681     Item:= FList.GetItem(i);
1682     if ASaved then
1683       Item^.LineStateToSaved
1684     else
1685       Item^.LineStateToNone;
1686   end;
1687 end;
1688 
1689 procedure TATStrings.SetModified(AValue: boolean);
1690 begin
1691   FModified:= AValue;
1692   if FModified then
1693   begin
1694   end
1695   else
1696     FUndoList.AddUnmodifiedMark;
1697 end;
1698 
1699 procedure TATStrings.SetRedoAsString(const AValue: string);
1700 begin
1701   FRedoList.AsString:= AValue;
1702 end;
1703 
1704 procedure TATStrings.SetUndoAsString(const AValue: string);
1705 begin
1706   FUndoList.AsString:= AValue;
1707 end;
1708 
1709 procedure TATStrings.SetUndoLimit(AValue: integer);
1710 begin
1711   if Assigned(FUndoList) then
1712     FUndoList.MaxCount:= AValue;
1713 end;
1714 
1715 procedure TATStrings.DoDetectEndings;
1716 begin
1717   if not IsIndexValid(0) then Exit;
1718   FEndings:= LinesEnds[0]; //no range-chk
1719   if FEndings=cEndNone then
1720     FEndings:= cEndWin;
1721 end;
1722 
TextSubstringnull1723 function TATStrings.TextSubstring(AX1, AY1, AX2, AY2: integer;
1724   const AEolString: UnicodeString = #10): atString;
1725 var
1726   i: integer;
1727 begin
1728   Result:= '';
1729   if AY1>AY2 then Exit;
1730   if not IsIndexValid(AY1) then Exit;
1731   if not IsIndexValid(AY2) then Exit;
1732 
1733   if AY1=AY2 then
1734     Exit(LineSub(AY1, AX1+1, AX2-AX1));
1735 
1736   //first line
1737   Result:= LineSub(AY1, AX1+1, MaxInt);
1738 
1739   //middle
1740   for i:= AY1+1 to AY2-1 do
1741     Result+= AEolString+Lines[i];
1742 
1743   //last line
1744   Result+= AEolString+LineSub(AY2, 1, AX2);
1745 end;
1746 
TextSubstringLengthnull1747 function TATStrings.TextSubstringLength(AX1, AY1, AX2, AY2: integer;
1748   const AEolString: UnicodeString = #10): integer;
1749 var
1750   NLen, NLenEol, i: integer;
1751 begin
1752   Result:= 0;
1753   if AY1>AY2 then Exit;
1754   if not IsIndexValid(AY1) then Exit;
1755   if not IsIndexValid(AY2) then Exit;
1756 
1757   NLenEol:= Length(AEolString);
1758 
1759   if AY1=AY2 then
1760   begin
1761     NLen:= LinesLen[AY1];
1762     Exit(Max(0, Min(NLen, AX2)-AX1));
1763   end;
1764 
1765   //first line
1766   NLen:= LinesLen[AY1];
1767   Result:= Max(0, NLen-AX1);
1768 
1769   //middle
1770   for i:= AY1+1 to AY2-1 do
1771   begin
1772     NLen:= LinesLen[i];
1773     Result+= NLen+NLenEol;
1774   end;
1775 
1776   //last line
1777   NLen:= LinesLen[AY2];
1778   Result+= Min(NLen, AX2)+NLenEol;
1779 end;
1780 
1781 procedure TATStrings.SetGroupMark;
1782 begin
1783   if Assigned(FUndoList) then
1784     FUndoList.SoftMark:= true;
1785 end;
1786 
1787 procedure TATStrings.BeginUndoGroup;
1788 begin
1789   Inc(FUndoGroupCounter);
1790   if Assigned(FUndoList) then
1791   begin
1792     if FUndoList.Locked then exit;
1793     //softmark if not-nested call
1794     if FUndoGroupCounter=1 then
1795       FUndoList.SoftMark:= true;
1796     //hardmark always
1797     FUndoList.HardMark:= true;
1798   end;
1799 end;
1800 
1801 procedure TATStrings.EndUndoGroup;
1802 begin
1803   if FUndoGroupCounter>0 then
1804     Dec(FUndoGroupCounter)
1805   else
1806     FUndoGroupCounter:= 0;
1807 
1808   if FUndoGroupCounter=0 then
1809     if Assigned(FUndoList) then
1810     begin
1811       if FUndoList.Locked then exit;
1812       FUndoList.HardMark:= false;
1813     end;
1814 end;
1815 
1816 procedure TATStrings.UndoSingle(ACurList: TATUndoList;
1817   out ASoftMarked, AHardMarked, AHardMarkedNext, AUnmodifiedNext: boolean;
1818   out ACommandCode: integer; out ATickCount: QWord);
1819 var
1820   CurItem, PrevItem: TATUndoItem;
1821   CurAction: TATEditAction;
1822   CurText: atString;
1823   CurIndex: integer;
1824   CurLineEnd: TATLineEnds;
1825   CurLineState: TATLineState;
1826   CurCaretsArray: TATPointArray;
1827   CurMarkersArray: TATInt64Array;
1828   OtherList: TATUndoList;
1829   NCount: integer;
1830   NEventX, NEventY: integer;
1831   bWithoutPause: boolean;
1832   bEnableEventBefore,
1833   bEnableEventAfter: boolean;
1834 begin
1835   ASoftMarked:= true;
1836   AHardMarked:= false;
1837   AHardMarkedNext:= false;
1838   AUnmodifiedNext:= false;
1839   if FReadOnly then Exit;
1840   if ACurList=nil then Exit;
1841 
1842   CurItem:= ACurList.Last;
1843   if CurItem=nil then Exit;
1844   CurAction:= CurItem.ItemAction;
1845   CurIndex:= CurItem.ItemIndex;
1846 
1847   //CurIndex=Count is allowed, CudaText issue #3258
1848   if (CurIndex<0) or (CurIndex>Count) then exit;
1849 
1850   CurText:= CurItem.ItemText;
1851   CurLineEnd:= CurItem.ItemEnd;
1852   CurLineState:= CurItem.ItemLineState;
1853   CurCaretsArray:= CurItem.ItemCarets;
1854   CurMarkersArray:= CurItem.ItemMarkers;
1855   ACommandCode:= CurItem.ItemCommandCode;
1856   ASoftMarked:= CurItem.ItemSoftMark;
1857   AHardMarked:= CurItem.ItemHardMark;
1858   ATickCount:= CurItem.ItemTickCount;
1859   NCount:= ACurList.Count;
1860   bWithoutPause:= IsCommandToUndoInOneStep(ACommandCode);
1861 
1862   //note: do not break this issue https://github.com/Alexey-T/CudaText/issues/2677
1863   if NCount>=2 then
1864   begin
1865     PrevItem:= ACurList[NCount-2];
1866     AHardMarkedNext:= PrevItem.ItemHardMark;
1867     AUnmodifiedNext:= PrevItem.ItemAction=aeaClearModified;
1868   end;
1869 
1870   //don't undo if one item left: unmodified-mark
1871   if ACurList.IsEmpty then exit;
1872 
1873   CurItem:= nil;
1874   ACurList.DeleteLast;
1875   ACurList.Locked:= true;
1876 
1877   if ACurList=FUndoList then
1878     OtherList:= FRedoList
1879   else
1880     OtherList:= FUndoList;
1881 
1882   case CurAction of
1883     aeaChange,
1884     aeaDelete,
1885     aeaInsert:
1886       begin
1887         bEnableEventAfter:= ASoftMarked or AHardMarked;
1888       end;
1889     aeaCaretJump:
1890       begin
1891         bEnableEventAfter:= true;
1892       end;
1893     else
1894       begin
1895         bEnableEventAfter:= false;
1896       end;
1897   end;
1898 
1899   if Length(CurCaretsArray)>0 then
1900   begin
1901     NEventX:= CurCaretsArray[0].X;
1902     NEventY:= CurCaretsArray[0].Y; //CurIndex is 0 for CaretJump
1903   end
1904   else
1905   begin
1906     NEventX:= -1;
1907     NEventY:= -1;
1908   end;
1909 
1910   bEnableEventBefore:= (NEventY>=0) and (NEventY<>FLastUndoY);
1911   FLastUndoY:= NEventY;
1912 
1913   //fixing issue #3427, flag nnnAfter must be false if nnnBefore=false
1914   if not bEnableEventBefore then
1915     bEnableEventAfter:= false;
1916 
1917   if bWithoutPause then
1918   begin
1919     bEnableEventBefore:= false;
1920     bEnableEventAfter:= false;
1921   end;
1922 
1923   if bEnableEventBefore then
1924     if Assigned(FOnUndoBefore) then
1925       FOnUndoBefore(Self, NEventX, NEventY);
1926 
1927   try
1928     case CurAction of
1929       aeaChange:
1930         begin
1931           if IsIndexValid(CurIndex) then
1932           begin
1933             Lines[CurIndex]:= CurText;
1934             LinesState[CurIndex]:= CurLineState;
1935           end;
1936         end;
1937 
1938       aeaChangeEol:
1939         begin
1940           if IsIndexValid(CurIndex) then
1941           begin
1942             LinesEnds[CurIndex]:= CurLineEnd;
1943             LinesState[CurIndex]:= CurLineState;
1944           end;
1945         end;
1946 
1947       aeaInsert:
1948         begin
1949           if IsIndexValid(CurIndex) then
1950             LineDelete(CurIndex);
1951         end;
1952 
1953       aeaDelete:
1954         begin
1955           if CurIndex>=Count then
1956             LineAddRaw(CurText, CurLineEnd)
1957           else
1958             LineInsertRaw(CurIndex, CurText, CurLineEnd);
1959           if IsIndexValid(CurIndex) then
1960             LinesState[CurIndex]:= CurLineState;
1961         end;
1962 
1963       aeaClearModified:
1964         begin
1965           OtherList.AddUnmodifiedMark;
1966           exit;
1967         end;
1968 
1969       aeaCaretJump:
1970         begin
1971           OtherList.Add(CurAction, 0, '', cEndNone, cLineStateNone, CurCaretsArray, CurMarkersArray, ACommandCode);
1972         end;
1973     end;
1974 
1975     if Length(CurCaretsArray)>0 then
1976       SetCaretsArray(CurCaretsArray);
1977     SetMarkersArray(CurMarkersArray);
1978 
1979     if bEnableEventAfter then
1980       if Assigned(FOnUndoAfter) then
1981         FOnUndoAfter(Self, NEventX, NEventY);
1982 
1983     ActionDeleteDupFakeLines;
1984   finally
1985     ACurList.Locked:= false;
1986   end;
1987 end;
1988 
DebugTextnull1989 function TATStrings.DebugText: string;
1990 var
1991   Item: PATStringItem;
1992   i: integer;
1993 begin
1994   Result:= '';
1995   for i:= 0 to Min(20, Count-1) do
1996   begin
1997     Item:= FList.GetItem(i);
1998     Result:= Result+Format('[%d] "%s" <%s>', [
1999       i,
2000       Item^.Line,
2001       cLineEndNiceNames[Item^.LineEnds]
2002       ])+#10;
2003   end;
2004 end;
2005 
GetCaretsArraynull2006 function TATStrings.GetCaretsArray: TATPointArray;
2007 begin
2008   if Assigned(FOnGetCaretsArray) then
2009     Result:= FOnGetCaretsArray()
2010   else
2011     SetLength(Result, 0);
2012 end;
2013 
TATStrings.GetMarkersArraynull2014 function TATStrings.GetMarkersArray: TATInt64Array;
2015 begin
2016   if Assigned(FOnGetMarkersArray) then
2017     Result:= FOnGetMarkersArray()
2018   else
2019     SetLength(Result, 0);
2020 end;
2021 
2022 procedure TATStrings.SetCaretsArray(const L: TATPointArray);
2023 begin
2024   if Assigned(FOnSetCaretsArray) then
2025     FOnSetCaretsArray(L);
2026 end;
2027 
2028 procedure TATStrings.SetMarkersArray(const L: TATInt64Array);
2029 begin
2030   if Assigned(FOnSetMarkersArray) then
2031     FOnSetMarkersArray(L);
2032 end;
2033 
2034 procedure TATStrings.UpdateModified;
2035 begin
2036   FModified:= true;
2037   FModifiedRecent:= true;
2038   Inc(FModifiedVersion);
2039 end;
2040 
2041 procedure TATStrings.AddUndoItem(AAction: TATEditAction; AIndex: integer;
2042   const AText: atString; AEnd: TATLineEnds; ALineState: TATLineState;
2043   ACommandCode: integer);
2044 var
2045   CurList: TATUndoList;
2046 begin
2047   if FUndoList=nil then exit;
2048   if FRedoList=nil then exit;
2049 
2050   if not FUndoList.Locked then
2051     CurList:= FUndoList
2052   else
2053   if not FRedoList.Locked then
2054     CurList:= FRedoList
2055   else
2056     exit;
2057 
2058   //handle CaretJump:
2059   //if last item was also CaretJump, delete the last item  (don't make huge list on many clicks)
2060   if AAction=aeaCaretJump then
2061   begin
2062     if (CurList.Count>0) and (CurList.Last.ItemAction=AAction) then
2063       CurList.DeleteLast;
2064   end
2065   else
2066   begin
2067     if not FUndoList.Locked and not FRedoList.Locked then
2068       FRedoList.Clear;
2069     AddUpdatesAction(AIndex, AAction);
2070   end;
2071 
2072   CurList.Add(AAction, AIndex, AText, AEnd, ALineState, GetCaretsArray, GetMarkersArray, ACommandCode);
2073 end;
2074 
2075 procedure TATStrings.UndoOrRedo(AUndo: boolean; AGrouped: boolean);
2076 var
2077   List, ListOther: TATUndoList;
2078   LastItem: TATUndoItem;
2079   bSoftMarked,
2080   bHardMarked,
2081   bHardMarkedNext,
2082   bMarkedUnmodified: boolean;
2083   NCommandCode: integer;
2084   NTickCount: QWord;
2085 begin
2086   if not Assigned(FUndoList) then Exit;
2087   if not Assigned(FRedoList) then Exit;
2088 
2089   if AUndo then
2090   begin
2091     List:= FUndoList;
2092     ListOther:= FRedoList;
2093   end
2094   else
2095   begin
2096     List:= FRedoList;
2097     ListOther:= FUndoList;
2098   end;
2099 
2100   //ShowMessage('Undo list:'#10+FUndolist.DebugText);
2101 
2102   {
2103   solve CudaText #3261:
2104    - Type something on line e.g. 100
2105    - Press Ctrl+F and find any text on line e.g. 25
2106    - Go to main window and try Undo/Redo
2107   it undoes/redoes editing on the line 100, but moves the caret to line 25.
2108   Usually first time it doesn't jump (as expected) but after repeating steps 2 and 3 it's starting to jump again.
2109   }
2110   if Length(CaretsAfterLastEdition)>0 then
2111     SetCaretsArray(CaretsAfterLastEdition);
2112 
2113   {
2114   solve CudaText #3268
2115   - Type something in line 100
2116   - Scroll screen to line 1 (to hide line 100 from the screen)
2117   - Perform undo
2118   In my case I see blink but don't see the change itself on the line 100.
2119   }
2120   FLastUndoY:= -1;
2121 
2122   repeat
2123     //better to have this, e.g. for Undo after Ctrl+A, Del
2124     //we can have not fixed 'chain reaction of pauses'
2125     if Application.Terminated then Break;
2126 
2127     if List.Count=0 then Break;
2128     if List.IsEmpty then Break;
2129 
2130     UndoSingle(List, bSoftMarked, bHardMarked, bHardMarkedNext, bMarkedUnmodified, NCommandCode, NTickCount);
2131 
2132     //handle unmodified
2133     //don't clear FModified if List.IsEmpty! http://synwrite.sourceforge.net/forums/viewtopic.php?f=5&t=2504
2134     if bMarkedUnmodified then
2135       FModified:= false;
2136 
2137     //apply Hardmark to ListOther
2138     if bHardMarked then
2139       if ListOther.Count>0 then
2140       begin
2141         ListOther.Last.ItemHardMark:= bHardMarked;
2142         //ListOther.Last.ItemSoftMark:= ?? //for redo needed Softmark too but don't know how
2143       end;
2144 
2145     if bHardMarked and bHardMarkedNext and not bSoftMarked then
2146       Continue;
2147     if not AGrouped then
2148       Break;
2149 
2150     //make commands with non-zero ItemCommandCode grouped (ie 'move lines up/down')
2151     if NCommandCode<>0 then
2152       if List.Count>0 then
2153       begin
2154         LastItem:= List.Last;
2155         if LastItem.ItemCommandCode=NCommandCode then
2156           if Abs(Int64(LastItem.ItemTickCount)-Int64(NTickCount))<List.PauseForMakingGroup then
2157             Continue;
2158       end;
2159 
2160     if bSoftMarked then
2161       Break;
2162   until false;
2163 
2164   //apply SoftMark to ListOther
2165   if bSoftMarked and AGrouped then
2166     ListOther.SoftMark:= true;
2167 
2168   //to fix this:
2169   // - new tab, make 5 lines "dd'
2170   // - caret at end of 1st line
2171   // - Shift+Alt+Down to make 5 carets column
2172   // - do fast: 'd', Undo, 'dd', Undo, 'd', Undo...
2173   // -> it gave return to single caret, but must return to multi-carets
2174   // https://github.com/Alexey-T/CudaText/issues/3274#issuecomment-810522418
2175   if bSoftMarked then
2176     List.SoftMark:= true;
2177 end;
2178 
2179 procedure TATStrings.ClearUndo(ALocked: boolean = false);
2180 begin
2181   if Assigned(FUndoList) then
2182   begin
2183     FUndoList.Clear;
2184     FUndoList.Locked:= ALocked;
2185   end;
2186 
2187   if Assigned(FRedoList) then
2188   begin
2189     FRedoList.Clear;
2190     FRedoList.Locked:= ALocked;
2191   end;
2192 
2193   if Assigned(FListUpdates) then
2194   begin
2195     FListUpdates.Clear;
2196     FListUpdatesHard:= false;
2197   end;
2198 end;
2199 
2200 procedure TATStrings.ClearLineStatesUpdated;
2201 var
2202   i: integer;
2203 begin
2204   for i:= 0 to FList.Count-1 do
2205     FList.GetItem(i)^.Ex.Updated:= false;
2206 end;
2207 
2208 procedure TATStrings.ActionSaveLastEditionPos(AX: integer; AY: integer);
2209 var
2210   Ar: TATPointArray;
2211 begin
2212   ModifiedRecent:= false;
2213 
2214   if (AX>=0) and (AY>=0) then
2215   begin
2216     //2 items per caret
2217     SetLength(Ar, 2);
2218     Ar[0].X:= AX;
2219     Ar[0].Y:= AY;
2220     Ar[1].X:= -1;
2221     Ar[1].Y:= -1;
2222   end
2223   else
2224     Ar:= GetCaretsArray;
2225 
2226   if Length(Ar)>0 then
2227     CaretsAfterLastEdition:= Ar;
2228 end;
2229 
2230 procedure TATStrings.ActionGotoLastEditionPos;
2231 begin
2232   if Length(CaretsAfterLastEdition)>0 then
2233     SetCaretsArray(CaretsAfterLastEdition);
2234 end;
2235 
2236 procedure TATStrings.ActionDeleteDupFakeLines;
2237 begin
2238   while IsLastFakeLineUnneeded do
2239     LineDelete(Count-1, false, false, false);
2240 end;
2241 
2242 procedure TATStrings.ActionDeleteAllBlanks;
2243 var
2244   i: integer;
2245 begin
2246   ClearUndo;
2247   ClearLineStates(false);
2248 
2249   for i:= Count-1 downto 0 do
2250     if LinesBlank[i] then
2251       FList.Delete(i);
2252 
2253   ActionAddFakeLineIfNeeded;
2254   ClearLineStates(false);
2255 
2256   DoEventChange(cLineChangeDeletedAll, -1, 1);
2257   DoEventLog(0);
2258 end;
2259 
2260 procedure TATStrings.ActionDeleteAdjacentBlanks;
2261 var
2262   i: integer;
2263 begin
2264   ClearUndo;
2265   ClearLineStates(false);
2266 
2267   for i:= Count-1 downto 1{!} do
2268     if LinesBlank[i] and LinesBlank[i-1] then
2269       FList.Delete(i);
2270 
2271   ActionAddFakeLineIfNeeded;
2272   ClearLineStates(false);
2273 
2274   DoEventChange(cLineChangeDeletedAll, -1, 1);
2275   DoEventLog(0);
2276 end;
2277 
2278 procedure TATStrings.ActionDeleteAdjacentDups;
2279 var
2280   i: integer;
2281 begin
2282   ClearUndo;
2283   ClearLineStates(false);
2284 
2285   for i:= Count-1 downto 1{!} do
2286     if (LinesLen[i]=LinesLen[i-1]) and (Lines[i]=Lines[i-1]) then
2287       FList.Delete(i);
2288 
2289   ActionAddFakeLineIfNeeded;
2290   ClearLineStates(false);
2291 
2292   DoEventChange(cLineChangeDeletedAll, -1, 1);
2293   DoEventLog(0);
2294 end;
2295 
2296 procedure TATStrings.ActionDeleteAllDups(AKeepBlanks: boolean);
2297 var
2298   i, j, NLen: integer;
2299   S: UnicodeString;
2300 begin
2301   ClearUndo;
2302   ClearLineStates(false);
2303 
2304   for i:= Count-1 downto 1{!} do
2305   begin
2306     if AKeepBlanks then
2307       if LinesBlank[i] then Continue;
2308     S:= Lines[i];
2309     NLen:= Length(S);
2310     for j:= 0 to i-1 do
2311       if (NLen=LinesLen[j]) and (S=Lines[j]) then
2312       begin
2313         FList.Delete(i);
2314         Break
2315       end;
2316   end;
2317 
2318   ActionAddFakeLineIfNeeded;
2319   ClearLineStates(false);
2320 
2321   DoEventChange(cLineChangeDeletedAll, -1, 1);
2322   DoEventLog(0);
2323 end;
2324 
2325 
2326 procedure TATStrings.ActionReverseLines;
2327 var
2328   Cnt, i, mid: integer;
2329 begin
2330   ActionEnsureFinalEol;
2331   ActionDeleteFakeLine;
2332 
2333   Cnt:= Count;
2334   if Cnt<2 then
2335   begin
2336     ActionAddFakeLineIfNeeded;
2337     exit;
2338   end;
2339 
2340   ClearUndo;
2341   ClearLineStates(false);
2342 
2343   mid:= Cnt div 2;
2344   if Odd(Cnt) then
2345     Inc(mid);
2346 
2347   for i:= Cnt-1 downto mid do
2348     FList.Exchange(i, Cnt-1-i);
2349 
2350   ActionAddFakeLineIfNeeded;
2351   ClearLineStates(false);
2352 
2353   DoEventChange(cLineChangeDeletedAll, -1, 1);
2354   DoEventLog(0);
2355 end;
2356 
2357 procedure TATStrings.ActionShuffleLines;
2358 var
2359   Cnt, i: integer;
2360 begin
2361   UpdateModified;
2362   ActionEnsureFinalEol;
2363   ActionDeleteFakeLine;
2364 
2365   Cnt:= Count;
2366   if Cnt<2 then
2367   begin
2368     ActionAddFakeLineIfNeeded;
2369     exit;
2370   end;
2371 
2372   ClearUndo;
2373   ClearLineStates(false);
2374 
2375   // https://stackoverflow.com/a/14006825/6792690
2376   for i:= Cnt-1 downto 1 do
2377     FList.Exchange(i, Random(i+1));
2378 
2379   ActionAddFakeLineIfNeeded;
2380   ClearLineStates(false);
2381 
2382   DoEventChange(cLineChangeDeletedAll, -1, 1);
2383   DoEventLog(0);
2384 end;
2385 
2386 procedure TATStrings.ActionAddJumpToUndo(constref ACaretsArray: TATPointArray);
2387 var
2388   Item: TATUndoItem;
2389 begin
2390   if FUndoList.Locked then exit;
2391   AddUndoItem(aeaCaretJump, 0, '', cEndNone, cLineStateNone, FCommandCode);
2392   Item:= FUndoList.Last;
2393   if Assigned(Item) then
2394     if Length(ACaretsArray)>0 then
2395       Item.ItemCarets:= ACaretsArray;
2396 end;
2397 
2398 
2399 procedure TATStrings.AddUpdatesAction(N: integer; AAction: TATEditAction);
2400 begin
2401   if not Assigned(FListUpdates) then Exit;
2402 
2403   if AAction in [aeaDelete, aeaInsert] then
2404   begin
2405     FListUpdatesHard:= true;
2406     Exit
2407   end;
2408 
2409   if FListUpdates.Count>cMaxUpdatesCountEasy then
2410   begin
2411     FListUpdatesHard:= true;
2412     Exit
2413   end;
2414 
2415   with FListUpdates do
2416     if IndexOf(N)<0 then
2417       Add(N);
2418 end;
2419 
2420 procedure TATStrings.DoOnChangeBlock(AX1, AY1, AX2, AY2: integer;
2421   AChange: TATBlockChangeKind; ABlock: TStringList);
2422 begin
2423   if Assigned(FOnChangeBlock) then
2424     FOnChangeBlock(Self,
2425       Point(AX1, AY1),
2426       Point(AX2, AY2),
2427       AChange,
2428       ABlock);
2429 end;
2430 
ActionEnsureFinalEolnull2431 function TATStrings.ActionEnsureFinalEol: boolean;
2432 begin
2433   Result:= false;
2434   if IsLastLineFake then Exit;
2435   if Count>0 then
2436   begin
2437     if LinesEnds[Count-1]=cEndNone then
2438     begin
2439       LinesEnds[Count-1]:= Endings;
2440       ActionAddFakeLineIfNeeded;
2441       Result:= true;
2442     end;
2443   end;
2444 end;
2445 
ActionTrimFinalEmptyLinesnull2446 function TATStrings.ActionTrimFinalEmptyLines: boolean;
2447 begin
2448   Result:= false;
2449   while (Count>1) and (Lines[Count-1]='') and (Lines[Count-2]='') do
2450   begin
2451     LineDelete(Count-2);
2452     Result:= true;
2453   end;
2454 end;
2455 
ActionTrimSpacesnull2456 function TATStrings.ActionTrimSpaces(AMode: TATTrimSpaces): boolean;
2457 var
2458   i: integer;
2459   S1, S2: atString;
2460 begin
2461   Result:= false;
2462   FLastCommandChangedLines:= 0;
2463 
2464   for i:= 0 to Count-1 do
2465   begin
2466     S1:= Lines[i];
2467     if S1='' then Continue;
2468 
2469     case AMode of
2470       cTrimLeft:
2471         begin
2472           if not IsCharSpace(S1[1]) then
2473             Continue;
2474           S2:= STrimLeft(S1);
2475         end;
2476       cTrimRight:
2477         begin
2478           if not IsCharSpace(S1[Length(S1)]) then
2479             Continue;
2480           S2:= STrimRight(S1);
2481         end;
2482       cTrimAll:
2483         begin
2484           if not IsCharSpace(S1[1]) and not IsCharSpace(S1[Length(S1)]) then
2485             Continue;
2486           S2:= STrimAll(S1);
2487         end;
2488     end;
2489 
2490     if S2<>S1 then
2491     begin
2492       Inc(FLastCommandChangedLines);
2493       Lines[i]:= S2;
2494       Result:= true;
2495     end;
2496   end;
2497 end;
2498 
IsPosFoldednull2499 function TATStrings.IsPosFolded(AX, AY, AIndexClient: integer): boolean;
2500 var
2501   ValueFoldFrom: integer;
2502 begin
2503   Result:= false;
2504   if not IsIndexValid(AY) then Exit;
2505 
2506   if LinesHidden[AY, AIndexClient] then
2507     Exit(true);
2508 
2509   ValueFoldFrom:= LinesFoldFrom[AY, AIndexClient];
2510   if (ValueFoldFrom>0) and (AX>=ValueFoldFrom) then
2511     Exit(true);
2512 end;
2513 
2514 procedure TATStrings.LineAddRaw_NoUndo(const S: string; AEnd: TATLineEnds);
2515 var
2516   Item: TATStringItem;
2517 begin
2518   Item.Init(S, AEnd);
2519   Item.Ex.State:= TATBits2(cLineStateAdded);
2520   FList.Add(@Item);
2521   FillChar(Item, SizeOf(Item), 0);
2522 end;
2523 
2524 procedure TATStrings.LineAddRaw_NoUndo(const S: UnicodeString; AEnd: TATLineEnds);
2525 var
2526   Item: TATStringItem;
2527 begin
2528   Item.Init(S, AEnd);
2529   Item.Ex.State:= TATBits2(cLineStateAdded);
2530   FList.Add(@Item);
2531   FillChar(Item, SizeOf(Item), 0);
2532 end;
2533 
2534 procedure TATStrings.DoEventLog(ALine: integer);
2535 begin
2536   if not FEnabledChangeEvents then exit;
2537 
2538   if (EditingTopLine<0) or (ALine<EditingTopLine) then
2539     EditingTopLine:= ALine;
2540 
2541   if Assigned(FOnChangeLog) then
2542     FOnChangeLog(Self, ALine);
2543 end;
2544 
2545 procedure TATStrings.DoEventChange(AChange: TATLineChangeKind; ALineIndex, AItemCount: integer);
2546 begin
2547   if not FEnabledChangeEvents then exit;
2548 
2549   FGaps.Update(AChange, ALineIndex, AItemCount);
2550 
2551   if FEnabledBookmarksUpdate then
2552   begin
2553     FBookmarks.Update(AChange, ALineIndex, AItemCount, Count);
2554     FBookmarks2.Update(AChange, ALineIndex, AItemCount, Count);
2555   end;
2556 
2557   if Assigned(FGutterDecor1) then
2558     FGutterDecor1.Update(AChange, ALineIndex, AItemCount, Count);
2559   if Assigned(FGutterDecor2) then
2560     FGutterDecor2.Update(AChange, ALineIndex, AItemCount, Count);
2561 
2562   if Assigned(FOnChangeEx) then
2563     FOnChangeEx(Self, AChange, ALineIndex, AItemCount);
2564 end;
2565 
2566 procedure TATStrings.ClearSeparators;
2567 var
2568   Item: PATStringItem;
2569   i: integer;
2570 begin
2571   for i:= 0 to Count-1 do
2572   begin
2573     Item:= FList.GetItem(i);
2574     Item^.Ex.Sep:= TATBits2(cLineSepNone);
2575   end;
2576 end;
2577 
Compare_Ascnull2578 function TATStrings.Compare_Asc(Key1, Key2: Pointer): Integer;
2579 var
2580   P1, P2: PATStringItem;
2581 begin
2582   P1:= PATStringItem(Key1);
2583   P2:= PATStringItem(Key2);
2584   if P1^.Ex.Wide or P2^.Ex.Wide then
2585     Result:= UnicodeCompareStr(P1^.Line, P2^.Line)
2586   else
2587     Result:= CompareStr(P1^.Buf, P2^.Buf);
2588 end;
2589 
Compare_AscNoCasenull2590 function TATStrings.Compare_AscNoCase(Key1, Key2: Pointer): Integer;
2591 var
2592   P1, P2: PATStringItem;
2593 begin
2594   P1:= PATStringItem(Key1);
2595   P2:= PATStringItem(Key2);
2596   if P1^.Ex.Wide or P2^.Ex.Wide then
2597     Result:= UnicodeCompareText(P1^.Line, P2^.Line)
2598   else
2599     Result:= CompareText(P1^.Buf, P2^.Buf);
2600 end;
2601 
Compare_Descnull2602 function TATStrings.Compare_Desc(Key1, Key2: Pointer): Integer;
2603 begin
2604   Result:= -Compare_Asc(Key1, Key2);
2605 end;
2606 
Compare_DescNoCasenull2607 function TATStrings.Compare_DescNoCase(Key1, Key2: Pointer): Integer;
2608 begin
2609   Result:= -Compare_AscNoCase(Key1, Key2);
2610 end;
2611 
2612 
2613 procedure TATStrings.ActionSort(AAction: TATStringsSortAction; AFrom, ATo: integer);
2614 var
2615   Func: TFPSListCompareFunc;
2616   i: integer;
2617 begin
2618   ActionEnsureFinalEol;
2619   ActionDeleteFakeLine;
2620 
2621   if Count<2 then
2622   begin
2623     ActionAddFakeLineIfNeeded;
2624     exit;
2625   end;
2626 
2627   case AAction of
2628     cSortActionAsc:
2629       Func:= @Compare_Asc;
2630     cSortActionAscNoCase:
2631       Func:= @Compare_AscNoCase;
2632     cSortActionDesc:
2633       Func:= @Compare_Desc;
2634     cSortActionDescNoCase:
2635       Func:= @Compare_DescNoCase;
2636   end;
2637 
2638   ClearUndo;
2639   ClearLineStates(false);
2640 
2641   for i:= Count-1 downto 0 do
2642     if LinesLen[i]=0 then
2643       FList.Delete(i);
2644 
2645   if AFrom<0 then
2646     FList.Sort(Func)
2647   else
2648     FList.SortRange(AFrom, ATo, Func);
2649 
2650   ActionAddFakeLineIfNeeded;
2651   ClearLineStates(false);
2652 
2653   //this clears all bookmarks, ranges, decors - it's ok
2654   DoEventChange(cLineChangeDeletedAll, -1, 1);
2655   DoEventLog(0);
2656 end;
2657 
2658 
2659 {$I atstrings_editing.inc}
2660 {$I atstrings_load.inc}
2661 {$I atstrings_save.inc}
2662 
2663 end.
2664 
2665