mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-04-05 09:58:06 +02:00
1928 lines
60 KiB
ObjectPascal
1928 lines
60 KiB
ObjectPascal
{-------------------------------------------------------------------------------
|
|
The contents of this file are subject to the Mozilla Public License
|
|
Version 1.1 (the "License"); you may not use this file except in compliance
|
|
with the License. You may obtain a copy of the License at
|
|
http://www.mozilla.org/MPL/
|
|
|
|
Software distributed under the License is distributed on an "AS IS" basis,
|
|
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
|
|
the specific language governing rights and limitations under the License.
|
|
|
|
The Original Code is: SynEditTextBuffer.pas, released 2000-04-07.
|
|
The Original Code is based on parts of mwCustomEdit.pas by Martin Waldenburg,
|
|
part of the mwEdit component suite.
|
|
Portions created by Martin Waldenburg are Copyright (C) 1998 Martin Waldenburg.
|
|
All Rights Reserved.
|
|
|
|
Contributors to the SynEdit and mwEdit projects are listed in the
|
|
Contributors.txt file.
|
|
|
|
Alternatively, the contents of this file may be used under the terms of the
|
|
GNU General Public License Version 2 or later (the "GPL"), in which case
|
|
the provisions of the GPL are applicable instead of those above.
|
|
If you wish to allow use of your version of this file only under the terms
|
|
of the GPL and not to allow others to use your version of this file
|
|
under the MPL, indicate your decision by deleting the provisions above and
|
|
replace them with the notice and other provisions required by the GPL.
|
|
If you do not delete the provisions above, a recipient may use your version
|
|
of this file under either the MPL or the GPL.
|
|
|
|
$Id$
|
|
|
|
You may retrieve the latest version of this file at the SynEdit home page,
|
|
located at http://SynEdit.SourceForge.net
|
|
|
|
Known Issues:
|
|
-------------------------------------------------------------------------------}
|
|
|
|
unit SynEditTextBuffer;
|
|
|
|
{$I synedit.inc}
|
|
|
|
{$IFOPT C+}
|
|
{$DEFINE SynAssert}
|
|
{$ENDIF}
|
|
{$IFDEF SynUndoDebug} {$Define SynUndoDebugItems} {$ENDIF}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils, Graphics, LCLProc, LazLoggerBase,
|
|
SynEditTypes, LazSynEditText, SynEditTextBase, SynEditMiscProcs, SynEditMiscClasses,
|
|
SynEditHighlighter, LazEditMiscProcs;
|
|
|
|
type
|
|
TSynEditFlagsClass = class end; // For Register
|
|
|
|
TSynEditStringFlag = (
|
|
sfModified, // a line is modified and not saved after
|
|
sfSaved // a line is modified and saved after
|
|
);
|
|
TSynEditStringFlags = set of TSynEditStringFlag;
|
|
PSynEditStringFlags = ^TSynEditStringFlags;
|
|
|
|
TStringListIndexEvent = procedure(Index: Integer) of object;
|
|
|
|
{ TLinesModifiedNotificationList }
|
|
|
|
TLinesModifiedNotificationList = Class(TSynMethodList)
|
|
public
|
|
Procedure CallRangeNotifyEvents(Sender: TSynEditStrings; aIndex, aNewCount, aOldCount: Integer);
|
|
end;
|
|
|
|
{ TLineRangeNotificationList }
|
|
|
|
TLineRangeNotificationList = Class(TSynMethodList)
|
|
public
|
|
Procedure CallRangeNotifyEvents(Sender: TSynEditStrings; aIndex, aCount: Integer);
|
|
end;
|
|
|
|
{ TLineEditNotificationList }
|
|
|
|
TLineEditNotificationList = Class(TSynMethodList)
|
|
public
|
|
Procedure CallRangeNotifyEvents(Sender: TSynEditStrings;
|
|
aLinePos, aBytePos, aCount, aLineBrkCnt: Integer; aText: String);
|
|
end;
|
|
|
|
{ TSynEditStringMemory }
|
|
|
|
TSynEditStringMemory = class(TSynEditStorageMem)
|
|
private
|
|
FRangeList: TSynManagedStorageMemList;
|
|
FRangeListLock: Integer;
|
|
function GetFlags(Index: Integer): TSynEditStringFlags;
|
|
function GetObject(Index: Integer): TObject;
|
|
function GetRange(Index: Pointer): TSynManagedStorageMem;
|
|
function GetString(Index: Integer): String;
|
|
procedure SetFlags(Index: Integer; const AValue: TSynEditStringFlags);
|
|
procedure SetObject(Index: Integer; const AValue: TObject);
|
|
procedure SetRange(Index: Pointer; const AValue: TSynManagedStorageMem);
|
|
procedure SetString(Index: Integer; const AValue: String);
|
|
protected
|
|
procedure Move(AFrom, ATo, ALen: Integer); override;
|
|
procedure SetCount(const AValue: Integer); override;
|
|
procedure SetCapacity(const AValue: Integer); override;
|
|
public
|
|
constructor Create;
|
|
destructor Destroy; override;
|
|
|
|
procedure InsertRows(AIndex, ACount: Integer); override;
|
|
procedure DeleteRows(AIndex, ACount: Integer); override;
|
|
function GetPChar(ALineIndex: Integer; out ALen: Integer): PChar; // experimental
|
|
property Strings[Index: Integer]: String read GetString write SetString; default;
|
|
property Objects[Index: Integer]: TObject read GetObject write SetObject;
|
|
property RangeList[Index: Pointer]: TSynManagedStorageMem read GetRange write SetRange;
|
|
property Flags[Index: Integer]: TSynEditStringFlags read GetFlags write SetFlags;
|
|
end;
|
|
|
|
TSynEditStringList = class;
|
|
|
|
{ TLazSynDisplayBuffer }
|
|
|
|
TLazSynDisplayBuffer = class(TLazSynDisplayViewEx)
|
|
private
|
|
FBuffer: TSynEditStringList;
|
|
FAtLineStart: Boolean;
|
|
public
|
|
constructor Create(ABuffer: TSynEditStringList);
|
|
procedure SetHighlighterTokensLine(ALine: TLineIdx; out ARealLine: TLineIdx; out ASubLineIdx, AStartBytePos, AStartPhysPos, ALineByteLen: Integer); override;
|
|
function GetNextHighlighterToken(out ATokenInfo: TLazSynDisplayTokenInfo): Boolean; override;
|
|
function GetDrawDividerInfo: TSynDividerDrawConfigSetting; override;
|
|
function GetLinesCount: Integer; override;
|
|
|
|
function TextToViewIndex(ATextIndex: TLineIdx): TLineRange; override;
|
|
function ViewToTextIndex(AViewIndex: TLineIdx): TLineIdx; override;
|
|
function ViewToTextIndexEx(AViewIndex: TLineIdx; out AViewRange: TLineRange): TLineIdx; override;
|
|
end;
|
|
|
|
{ TSynEditStringList }
|
|
|
|
TSynEditStringList = class(TSynEditStringListBase)
|
|
private
|
|
FList: TSynEditStringMemory;
|
|
FDisplayView: TLazSynDisplayBuffer;
|
|
|
|
FAttachedSynEditList: TFPList;
|
|
FNotifyLists: Array [TSynEditNotifyReason] of TSynMethodList;
|
|
FCachedNotify: Boolean;
|
|
FCachedNotifyStart, FCachedNotifyCount: Integer;
|
|
FCachedNotifySender: TSynEditStrings;
|
|
FModifiedNotifyStart, FModifiedNotifyNewCount, FModifiedNotifyOldCount: Integer;
|
|
|
|
FIsInEditAction: Integer;
|
|
FIgnoreSendNotification: array [TSynEditNotifyReason] of Integer;
|
|
fDosFileFormat: boolean;
|
|
fIndexOfLongestLine: integer;
|
|
FRedoList: TSynEditUndoList;
|
|
FUndoList: TSynEditUndoList;
|
|
FIsUndoing, FIsRedoing: Boolean;
|
|
FIsInDecPaintLock: Boolean;
|
|
|
|
FIsUtf8: Boolean;
|
|
FModified: Boolean;
|
|
FTextChangeStamp: int64;
|
|
|
|
function GetAttachedSynEdits(Index: Integer): TSynEditBase;
|
|
function GetFlags(Index: Integer): TSynEditStringFlags;
|
|
procedure Grow;
|
|
procedure InsertItem(Index: integer; const S: string);
|
|
procedure SetFlags(Index: Integer; const AValue: TSynEditStringFlags);
|
|
procedure SetModified(const AValue: Boolean);
|
|
protected
|
|
function GetIsUtf8 : Boolean; override;
|
|
procedure SetIsUtf8(const AValue : Boolean); override;
|
|
function GetExpandedString(Index: integer): string; override;
|
|
function GetLengthOfLongestLine: integer; override;
|
|
function GetTextChangeStamp: int64; override;
|
|
|
|
function GetIsInEditAction: Boolean; override;
|
|
procedure IncIsInEditAction; override;
|
|
procedure DecIsInEditAction; override;
|
|
function GetRedoList: TSynEditUndoList; override;
|
|
function GetUndoList: TSynEditUndoList; override;
|
|
function GetCurUndoList: TSynEditUndoList; override;
|
|
procedure SetIsUndoing(const AValue: Boolean); override;
|
|
function GetIsUndoing: Boolean; override;
|
|
procedure SetIsRedoing(const AValue: Boolean); override;
|
|
function GetIsRedoing: Boolean; override;
|
|
procedure UndoRedoAdded(Sender: TObject);
|
|
procedure IgnoreSendNotification(AReason: TSynEditNotifyReason;
|
|
IncIgnore: Boolean); override;
|
|
|
|
function GetRange(Index: Pointer): TSynManagedStorageMem; override;
|
|
procedure PutRange(Index: Pointer; const ARange: TSynManagedStorageMem); override;
|
|
function Get(Index: integer): string; override;
|
|
function GetCapacity: integer; override;
|
|
function GetCount: integer; override;
|
|
procedure SetCount(const AValue: Integer);
|
|
function GetObject(Index: integer): TObject; override;
|
|
procedure Put(Index: integer; const S: string); override;
|
|
procedure PutObject(Index: integer; AObject: TObject); override;
|
|
procedure SetCapacity(NewCapacity: integer); override;
|
|
procedure MaybeSendSenrLinesModified; inline;
|
|
procedure SetUpdateState(Updating: Boolean; Sender: TObject); override;
|
|
|
|
procedure UndoEditLinesDelete(LogY, ACount: Integer);
|
|
procedure IncreaseTextChangeStamp;
|
|
procedure DoGetPhysicalCharWidths(Line: PChar; LineLen, Index: Integer; PWidths: PPhysicalCharWidth); override;
|
|
|
|
function GetDisplayView: TLazSynDisplayView; override;
|
|
|
|
procedure AddManagedHandler(AReason: TSynEditNotifyReason;
|
|
AHandler: TMethod); override;
|
|
procedure RemoveManagedHandler(AReason: TSynEditNotifyReason;
|
|
AHandler: TMethod); override;
|
|
procedure RemoveManagedHandlers(AOwner: TObject); override;
|
|
public
|
|
constructor Create;
|
|
destructor Destroy; override;
|
|
function Add(const S: string): integer; override;
|
|
procedure AddStrings(AStrings: TStrings); override;
|
|
procedure Clear; override;
|
|
procedure Delete(Index: integer); override;
|
|
procedure DeleteLines(Index, NumLines: integer); override;
|
|
procedure Insert(Index: integer; const S: string); override;
|
|
procedure InsertLines(Index, NumLines: integer); override;
|
|
procedure InsertStrings(Index: integer; NewStrings: TStrings); override;
|
|
function GetPChar(ALineIndex: Integer; out ALen: Integer): PChar; override; // experimental
|
|
procedure MarkModified(AFirst, ALast: Integer);
|
|
procedure MarkSaved;
|
|
procedure SendNotification(AReason: TSynEditNotifyReason;
|
|
ASender: TSynEditStrings; aIndex, aCount: Integer); override;
|
|
procedure SendNotification(AReason: TSynEditNotifyReason;
|
|
ASender: TSynEditStrings; aIndex, aCount: Integer;
|
|
aBytePos: Integer; aLen: Integer; aTxt: String); override;
|
|
procedure SendNotification(AReason: TSynEditNotifyReason;
|
|
ASender: TObject); override;
|
|
procedure FlushNotificationCache; override;
|
|
procedure AttachSynEdit(AEdit: TSynEditBase);
|
|
procedure DetachSynEdit(AEdit: TSynEditBase);
|
|
function AttachedSynEditCount: Integer;
|
|
property AttachedSynEdits[Index: Integer]: TSynEditBase read GetAttachedSynEdits;
|
|
procedure CopyHanlders(OtherLines: TSynEditStringList; AOwner: TObject = nil); deprecated 'Use "CopyHandlers" / Will be removed in 4.99';
|
|
procedure CopyHandlers(OtherLines: TSynEditStringList; AOwner: TObject = nil);
|
|
procedure SendCachedNotify; // ToDO: review caching versus changes to topline and other values
|
|
public
|
|
property DosFileFormat: boolean read fDosFileFormat write fDosFileFormat;
|
|
property LengthOfLongestLine: integer read GetLengthOfLongestLine;
|
|
property Flags[Index: Integer]: TSynEditStringFlags read GetFlags
|
|
write SetFlags;
|
|
property Modified: Boolean read FModified write SetModified;
|
|
public
|
|
// Char bounds // 1 based (1 is the 1st char in the line)
|
|
function LogicPosAddChars(const ALine: String; ALogicalPos, ACount: integer;
|
|
AFlags: LPosFlags = []): Integer; override;
|
|
function LogicPosIsAtChar(const ALine: String; ALogicalPos: integer;
|
|
AFlags: LPosFlags = []): Boolean; override;
|
|
function LogicPosAdjustToChar(const ALine: String; ALogicalPos: integer;
|
|
AFlags: LPosFlags = []): Integer; override;
|
|
property UndoList: TSynEditUndoList read GetUndoList write fUndoList;
|
|
property RedoList: TSynEditUndoList read GetRedoList write fRedoList;
|
|
procedure EditInsert(LogX, LogY: Integer; AText: String); override;
|
|
function EditDelete(LogX, LogY, ByteLen: Integer): String; override;
|
|
function EditReplace(LogX, LogY, ByteLen: Integer; AText: String): String; override;
|
|
procedure EditLineBreak(LogX, LogY: Integer); override;
|
|
procedure EditLineJoin(LogY: Integer; FillText: String = ''); override;
|
|
procedure EditLinesInsert(LogY, ACount: Integer; AText: String = ''); override;
|
|
procedure EditLinesDelete(LogY, ACount: Integer); override;
|
|
procedure EditUndo(Item: TSynEditUndoItem); override;
|
|
procedure EditRedo(Item: TSynEditUndoItem); override;
|
|
public
|
|
PaintLockOwner: TSynEditBase;
|
|
end;
|
|
|
|
ESynEditStringList = class(Exception);
|
|
{end} //mh 2000-10-10
|
|
|
|
implementation
|
|
|
|
const
|
|
SListIndexOutOfBounds = 'Invalid stringlist index %d';
|
|
|
|
type
|
|
|
|
{ TSynEditUndoTxtInsert }
|
|
|
|
TSynEditUndoTxtInsert = class(TSynEditUndoItem)
|
|
private
|
|
FPosX, FPosY, FLen: Integer;
|
|
protected
|
|
function DebugString: String; override;
|
|
public
|
|
constructor Create(APosX, APosY, ALen: Integer);
|
|
function PerformUndo(Caller: TObject): Boolean; override;
|
|
end;
|
|
|
|
{ TSynEditUndoTxtDelete }
|
|
|
|
TSynEditUndoTxtDelete = class(TSynEditUndoItem)
|
|
private
|
|
FPosX, FPosY: Integer;
|
|
FText: String;
|
|
protected
|
|
function DebugString: String; override;
|
|
public
|
|
constructor Create(APosX, APosY: Integer; AText: String);
|
|
function PerformUndo(Caller: TObject): Boolean; override;
|
|
end;
|
|
|
|
{ TSynEditUndoTxtLineBreak }
|
|
|
|
TSynEditUndoTxtLineBreak = class(TSynEditUndoItem)
|
|
private
|
|
FPosY: Integer;
|
|
protected
|
|
function DebugString: String; override;
|
|
public
|
|
constructor Create(APosY: Integer);
|
|
function PerformUndo(Caller: TObject): Boolean; override;
|
|
end;
|
|
|
|
{ TSynEditUndoTxtLineJoin }
|
|
|
|
TSynEditUndoTxtLineJoin = class(TSynEditUndoItem)
|
|
private
|
|
FPosX, FPosY: Integer;
|
|
protected
|
|
function DebugString: String; override;
|
|
public
|
|
constructor Create(APosX, APosY: Integer);
|
|
function PerformUndo(Caller: TObject): Boolean; override;
|
|
end;
|
|
|
|
{ TSynEditUndoTxtLinesIns }
|
|
|
|
TSynEditUndoTxtLinesIns = class(TSynEditUndoItem)
|
|
private
|
|
FPosY, FCount: Integer;
|
|
protected
|
|
function DebugString: String; override;
|
|
public
|
|
constructor Create(ALine, ACount: Integer);
|
|
function PerformUndo(Caller: TObject): Boolean; override;
|
|
end;
|
|
|
|
{ TSynEditUndoTxtLinesDel }
|
|
|
|
TSynEditUndoTxtLinesDel = class(TSynEditUndoItem)
|
|
private
|
|
FPosY, FCount: Integer;
|
|
protected
|
|
function DebugString: String; override;
|
|
public
|
|
constructor Create(ALine, ACount: Integer);
|
|
function PerformUndo(Caller: TObject): Boolean; override;
|
|
end;
|
|
|
|
TSynEditStringFlagsArray = packed array of TSynEditStringFlags;
|
|
|
|
{ TSynEditUndoMarkModified }
|
|
|
|
TSynEditUndoMarkModified = class(TSynEditUndoItem)
|
|
private
|
|
FPosY: TLineIdx;
|
|
FWasSaved: TSynEditStringFlagsArray;
|
|
protected
|
|
function DebugString: String; override;
|
|
public
|
|
constructor Create(ALine: TLineIdx; AWasSaved: TSynEditStringFlagsArray);
|
|
function PerformUndo(Caller: TObject): Boolean; override;
|
|
end;
|
|
|
|
var
|
|
(* Re-usable arrays for the most common cases *)
|
|
SynEditUndoMarkModifiedOneEmpty: TSynEditStringFlagsArray = nil; // = [];
|
|
SynEditUndoMarkModifiedOneSaved: TSynEditStringFlagsArray = nil; // = [sfSaved];
|
|
SynEditUndoMarkModifiedOneModified: TSynEditStringFlagsArray = nil; // = [sfModified];
|
|
|
|
|
|
{ TLazSynDisplayBuffer }
|
|
|
|
constructor TLazSynDisplayBuffer.Create(ABuffer: TSynEditStringList);
|
|
begin
|
|
inherited Create;
|
|
FBuffer := ABuffer;
|
|
end;
|
|
|
|
procedure TLazSynDisplayBuffer.SetHighlighterTokensLine(ALine: TLineIdx; out
|
|
ARealLine: TLineIdx; out ASubLineIdx, AStartBytePos, AStartPhysPos, ALineByteLen: Integer);
|
|
begin
|
|
CurrentTokenLine := ALine;
|
|
ARealLine := ALine;
|
|
ASubLineIdx := 0;
|
|
AStartBytePos := 1;
|
|
AStartPhysPos := 1;
|
|
ALineByteLen := Length(FBuffer[ARealLine]);
|
|
FAtLineStart := True;
|
|
end;
|
|
|
|
function TLazSynDisplayBuffer.GetNextHighlighterToken(out ATokenInfo: TLazSynDisplayTokenInfo): Boolean;
|
|
begin
|
|
Result := False;
|
|
ATokenInfo := Default(TLazSynDisplayTokenInfo);
|
|
if not Initialized then exit;
|
|
|
|
if CurrentTokenHighlighter = nil then begin
|
|
ATokenInfo.TokenOrigin := dtoAfterText;
|
|
Result := FAtLineStart;
|
|
if not Result then exit;
|
|
ATokenInfo.TokenStart := FBuffer.GetPChar(CurrentTokenLine, ATokenInfo.TokenLength);
|
|
Result := ATokenInfo.TokenLength > 0;
|
|
if not Result then exit;
|
|
ATokenInfo.TokenAttr := nil;
|
|
ATokenInfo.TokenOrigin := dtoVirtualText;
|
|
FAtLineStart := False;
|
|
end
|
|
else begin
|
|
if FAtLineStart then
|
|
CurrentTokenHighlighter.StartAtLineIndex(CurrentTokenLine);
|
|
FAtLineStart := False;
|
|
|
|
Result := not CurrentTokenHighlighter.GetEol;
|
|
if not Result then begin
|
|
ATokenInfo.TokenStart := nil;
|
|
ATokenInfo.TokenLength := 0;
|
|
ATokenInfo.TokenAttr := CurrentTokenHighlighter.GetEndOfLineAttribute;
|
|
ATokenInfo.TokenOrigin := dtoAfterText;
|
|
Result := ATokenInfo.TokenAttr <> nil;
|
|
exit;
|
|
end;
|
|
|
|
CurrentTokenHighlighter.GetTokenEx(ATokenInfo.TokenStart, ATokenInfo.TokenLength);
|
|
ATokenInfo.TokenAttr := CurrentTokenHighlighter.GetTokenAttribute;
|
|
ATokenInfo.TokenOrigin := dtoVirtualText;
|
|
CurrentTokenHighlighter.Next;
|
|
end;
|
|
end;
|
|
|
|
function TLazSynDisplayBuffer.GetDrawDividerInfo: TSynDividerDrawConfigSetting;
|
|
begin
|
|
if CurrentTokenHighlighter <> nil then
|
|
Result := CurrentTokenHighlighter.DrawDivider[CurrentTokenLine]
|
|
else
|
|
Result.Color := clNone;
|
|
end;
|
|
|
|
function TLazSynDisplayBuffer.GetLinesCount: Integer;
|
|
begin
|
|
Result := FBuffer.Count;
|
|
end;
|
|
|
|
function TLazSynDisplayBuffer.TextToViewIndex(ATextIndex: TLineIdx): TLineRange;
|
|
begin
|
|
Result.Top := ATextIndex;
|
|
Result.Bottom := ATextIndex;
|
|
end;
|
|
|
|
function TLazSynDisplayBuffer.ViewToTextIndex(AViewIndex: TLineIdx): TLineIdx;
|
|
begin
|
|
Result := AViewIndex;
|
|
end;
|
|
|
|
function TLazSynDisplayBuffer.ViewToTextIndexEx(AViewIndex: TLineIdx; out
|
|
AViewRange: TLineRange): TLineIdx;
|
|
begin
|
|
Result := AViewIndex;
|
|
AViewRange.Top := AViewIndex;
|
|
AViewRange.Bottom := AViewIndex;
|
|
end;
|
|
|
|
{ TSynEditUndoTxtInsert }
|
|
|
|
function TSynEditUndoTxtInsert.DebugString: String;
|
|
begin
|
|
Result := 'X='+dbgs(FPosX) + ' Y='+ dbgs(FPosY) + ' len=' + dbgs(FLen);
|
|
end;
|
|
|
|
constructor TSynEditUndoTxtInsert.Create(APosX, APosY, ALen: Integer);
|
|
begin
|
|
FPosX := APosX;
|
|
FPosY := APosY;
|
|
FLen := ALen;
|
|
{$IFDEF SynUndoDebugItems}debugln(['--- Undo Insert ',DbgSName(self), ' ', dbgs(Self), ' - ', DebugString]);{$ENDIF}
|
|
end;
|
|
|
|
function TSynEditUndoTxtInsert.PerformUndo(Caller: TObject): Boolean;
|
|
begin
|
|
Result := Caller is TSynEditStringList;
|
|
{$IFDEF SynUndoDebugItems}if Result then debugln(['--- Undo Perform ',DbgSName(self), ' ', dbgs(Self), ' - ', DebugString]);{$ENDIF}
|
|
if Result then
|
|
TSynEditStringList(Caller).EditDelete(FPosX, FPosY, FLen);
|
|
end;
|
|
|
|
{ TSynEditUndoTxtDelete }
|
|
function TSynEditUndoTxtDelete.DebugString: String;
|
|
begin
|
|
Result := 'X='+dbgs(FPosX) + ' Y='+ dbgs(FPosY) + ' text=' + FText;
|
|
end;
|
|
|
|
constructor TSynEditUndoTxtDelete.Create(APosX, APosY: Integer; AText: String);
|
|
begin
|
|
FPosX := APosX;
|
|
FPosY := APosY;
|
|
FText := AText;
|
|
{$IFDEF SynUndoDebugItems}debugln(['--- Undo Insert ',DbgSName(self), ' ', dbgs(Self), ' - ', DebugString]);{$ENDIF}
|
|
end;
|
|
|
|
function TSynEditUndoTxtDelete.PerformUndo(Caller: TObject): Boolean;
|
|
begin
|
|
Result := Caller is TSynEditStringList;
|
|
{$IFDEF SynUndoDebugItems}if Result then debugln(['--- Undo Perform ',DbgSName(self), ' ', dbgs(Self), ' - ', DebugString]);{$ENDIF}
|
|
if Result then
|
|
TSynEditStringList(Caller).EditInsert(FPosX, FPosY, FText);
|
|
end;
|
|
|
|
{ TSynEditUndoTxtLineBreak }
|
|
function TSynEditUndoTxtLineBreak.DebugString: String;
|
|
begin
|
|
Result := ' Y='+ dbgs(FPosY);
|
|
end;
|
|
|
|
constructor TSynEditUndoTxtLineBreak.Create(APosY: Integer);
|
|
begin
|
|
FPosY := APosY;
|
|
{$IFDEF SynUndoDebugItems}debugln(['--- Undo Insert ',DbgSName(self), ' ', dbgs(Self), ' - ', DebugString]);{$ENDIF}
|
|
end;
|
|
|
|
function TSynEditUndoTxtLineBreak.PerformUndo(Caller: TObject): Boolean;
|
|
begin
|
|
Result := Caller is TSynEditStringList;
|
|
{$IFDEF SynUndoDebugItems}if Result then debugln(['--- Undo Perform ',DbgSName(self), ' ', dbgs(Self), ' - ', DebugString]);{$ENDIF}
|
|
if Result then
|
|
TSynEditStringList(Caller).EditLineJoin(FPosY)
|
|
end;
|
|
|
|
{ TSynEditUndoTxtLineJoin }
|
|
function TSynEditUndoTxtLineJoin.DebugString: String;
|
|
begin
|
|
Result := 'X='+dbgs(FPosX) + ' Y='+ dbgs(FPosY);
|
|
end;
|
|
|
|
constructor TSynEditUndoTxtLineJoin.Create(APosX, APosY: Integer);
|
|
begin
|
|
FPosX := APosX;
|
|
FPosY := APosY;
|
|
{$IFDEF SynUndoDebugItems}debugln(['--- Undo Insert ',DbgSName(self), ' ', dbgs(Self), ' - ', DebugString]);{$ENDIF}
|
|
end;
|
|
|
|
function TSynEditUndoTxtLineJoin.PerformUndo(Caller: TObject): Boolean;
|
|
begin
|
|
Result := Caller is TSynEditStringList;
|
|
{$IFDEF SynUndoDebugItems}if Result then debugln(['--- Undo Perform ',DbgSName(self), ' ', dbgs(Self), ' - ', DebugString]);{$ENDIF}
|
|
if Result then
|
|
TSynEditStringList(Caller).EditLineBreak(FPosX, FPosY)
|
|
end;
|
|
|
|
{ TSynEditUndoTxtLinesIns }
|
|
function TSynEditUndoTxtLinesIns.DebugString: String;
|
|
begin
|
|
Result := 'Y='+dbgs(FPosY) + ' Cnt='+ dbgs(FCount);
|
|
end;
|
|
|
|
constructor TSynEditUndoTxtLinesIns.Create(ALine, ACount: Integer);
|
|
begin
|
|
FPosY := ALine;
|
|
FCount := ACount;
|
|
{$IFDEF SynUndoDebugItems}debugln(['--- Undo Insert ',DbgSName(self), ' ', dbgs(Self), ' - ', DebugString]);{$ENDIF}
|
|
end;
|
|
|
|
function TSynEditUndoTxtLinesIns.PerformUndo(Caller: TObject): Boolean;
|
|
begin
|
|
Result := Caller is TSynEditStringList;
|
|
{$IFDEF SynUndoDebugItems}if Result then debugln(['--- Undo Perform ',DbgSName(self), ' ', dbgs(Self), ' - ', DebugString]);{$ENDIF}
|
|
if Result then
|
|
TSynEditStringList(Caller).UndoEditLinesDelete(FPosY, FCount)
|
|
end;
|
|
|
|
{ TSynEditUndoTxtLinesDel }
|
|
function TSynEditUndoTxtLinesDel.DebugString: String;
|
|
begin
|
|
Result := 'Y='+dbgs(FPosY) + ' Cnt='+ dbgs(FCount);
|
|
end;
|
|
|
|
constructor TSynEditUndoTxtLinesDel.Create(ALine, ACount: Integer);
|
|
begin
|
|
FPosY := ALine;
|
|
FCount := ACount;
|
|
{$IFDEF SynUndoDebugItems}debugln(['--- Undo Insert ',DbgSName(self), ' ', dbgs(Self), ' - ', DebugString]);{$ENDIF}
|
|
end;
|
|
|
|
function TSynEditUndoTxtLinesDel.PerformUndo(Caller: TObject): Boolean;
|
|
begin
|
|
Result := Caller is TSynEditStringList;
|
|
{$IFDEF SynUndoDebugItems}if Result then debugln(['--- Undo Perform ',DbgSName(self), ' ', dbgs(Self), ' - ', DebugString]);{$ENDIF}
|
|
if Result then
|
|
TSynEditStringList(Caller).EditLinesInsert(FPosY, FCount)
|
|
end;
|
|
|
|
{ TSynEditUndoMarkModified }
|
|
|
|
function TSynEditUndoMarkModified.DebugString: String;
|
|
begin
|
|
Result := 'Y='+dbgs(FPosY) + ' Cnt='+ dbgs(Length(FWasSaved));
|
|
end;
|
|
|
|
constructor TSynEditUndoMarkModified.Create(ALine: TLineIdx;
|
|
AWasSaved: TSynEditStringFlagsArray);
|
|
begin
|
|
FPosY := ALine;
|
|
FWasSaved := AWasSaved;
|
|
{$IFDEF SynUndoDebugItems}debugln(['--- Undo Insert ',DbgSName(self), ' ', dbgs(Self), ' - ', DebugString]);{$ENDIF}
|
|
end;
|
|
|
|
function TSynEditUndoMarkModified.PerformUndo(Caller: TObject): Boolean;
|
|
var
|
|
i: Integer;
|
|
WasSaved: TSynEditStringFlagsArray;
|
|
Buffer: TSynEditStringList absolute Caller;
|
|
UnSaved: Boolean;
|
|
begin
|
|
Result := Caller is TSynEditStringList;
|
|
{$IFDEF SynUndoDebugItems}if Result then debugln(['--- Undo Perform ',DbgSName(self), ' ', dbgs(Self), ' - ', DebugString]);{$ENDIF}
|
|
WasSaved := nil;
|
|
if Result then begin
|
|
UnSaved := Buffer.CurUndoList.SavedMarkerExists and (not Buffer.CurUndoList.IsTopMarkedAsSaved);
|
|
if Length(FWasSaved) = 1 then begin
|
|
if FPosY < Buffer.Count then begin
|
|
if sfSaved in Buffer.Flags[FPosY] then
|
|
WasSaved := SynEditUndoMarkModifiedOneSaved
|
|
else
|
|
if sfModified in Buffer.Flags[FPosY] then
|
|
WasSaved := SynEditUndoMarkModifiedOneModified
|
|
else
|
|
WasSaved := SynEditUndoMarkModifiedOneEmpty;
|
|
|
|
if (sfSaved in FWasSaved[0]) and UnSaved then
|
|
Buffer.Flags[FPosY] := [sfModified]
|
|
else
|
|
Buffer.Flags[FPosY] := FWasSaved[0];
|
|
end;
|
|
end
|
|
else begin
|
|
SetLength(WasSaved, Length(FWasSaved));
|
|
for i := 0 to Min(Length(FWasSaved), Buffer.Count - FPosY) - 1 do begin
|
|
WasSaved[i] := Buffer.Flags[FPosY + i];
|
|
|
|
if (sfSaved in FWasSaved[i]) and UnSaved then
|
|
Buffer.Flags[FPosY + i] := [sfModified]
|
|
else
|
|
Buffer.Flags[FPosY + i] := FWasSaved[i];
|
|
end;
|
|
end;
|
|
if WasSaved <> nil then
|
|
Buffer.CurUndoList.AddChange(TSynEditUndoMarkModified.Create(FPosY, WasSaved), True);
|
|
end;
|
|
end;
|
|
|
|
{ TSynEditStringList }
|
|
|
|
procedure ListIndexOutOfBounds(Index: integer);
|
|
begin
|
|
raise ESynEditStringList.CreateFmt(SListIndexOutOfBounds, [Index]);
|
|
end;
|
|
|
|
constructor TSynEditStringList.Create;
|
|
var
|
|
r: TSynEditNotifyReason;
|
|
begin
|
|
fList := TSynEditStringMemory.Create;
|
|
FDisplayView := TLazSynDisplayBuffer.Create(Self);
|
|
|
|
FAttachedSynEditList := TFPList.Create;
|
|
FUndoList := TSynEditUndoList.Create;
|
|
fUndoList.OnAddedUndo := @UndoRedoAdded;
|
|
FRedoList := TSynEditUndoList.Create;
|
|
fRedoList.OnAddedUndo := @UndoRedoAdded;
|
|
FIsUndoing := False;
|
|
FIsRedoing := False;
|
|
FModified := False;
|
|
FIsInEditAction := 0;
|
|
|
|
for r := low(TSynEditNotifyReason) to high(TSynEditNotifyReason)
|
|
do case r of
|
|
senrLineCount, senrLineChange, senrHighlightChanged, senrLineMappingChanged:
|
|
FNotifyLists[r] := TLineRangeNotificationList.Create;
|
|
senrLinesModified:
|
|
FNotifyLists[r] := TLinesModifiedNotificationList.Create;
|
|
senrEditAction:
|
|
FNotifyLists[r] := TLineEditNotificationList.Create;
|
|
else
|
|
FNotifyLists[r] := TSynMethodList.Create;
|
|
end;
|
|
|
|
for r := low(TSynEditNotifyReason) to high(TSynEditNotifyReason) do
|
|
FIgnoreSendNotification[r] := 0;
|
|
inherited Create;
|
|
fDosFileFormat := TRUE;
|
|
{begin} //mh 2000-10-19
|
|
fIndexOfLongestLine := -1;
|
|
{end} //mh 2000-10-19
|
|
end;
|
|
|
|
destructor TSynEditStringList.Destroy;
|
|
var
|
|
i: TSynEditNotifyReason;
|
|
begin
|
|
inherited Destroy;
|
|
SetCount(0);
|
|
SetCapacity(0);
|
|
for i := low(TSynEditNotifyReason) to high(TSynEditNotifyReason) do
|
|
FreeAndNil(FNotifyLists[i]);
|
|
FreeAndNil(FUndoList);
|
|
FreeAndNil(FRedoList);
|
|
FreeAndNil(FAttachedSynEditList);
|
|
|
|
FreeAndNil(FDisplayView);
|
|
FreeAndNil(fList);
|
|
end;
|
|
|
|
function TSynEditStringList.Add(const S: string): integer;
|
|
begin
|
|
BeginUpdate;
|
|
Result := Count;
|
|
InsertItem(Result, S);
|
|
SendNotification(senrLineCount, self, Result, Count - Result);
|
|
EndUpdate;
|
|
end;
|
|
|
|
procedure TSynEditStringList.AddStrings(AStrings: TStrings);
|
|
var
|
|
i, FirstAdded: integer;
|
|
begin
|
|
{begin} //mh 2000-10-19
|
|
if AStrings.Count > 0 then begin
|
|
fIndexOfLongestLine := -1;
|
|
BeginUpdate;
|
|
try
|
|
i := Count + AStrings.Count;
|
|
if i > Capacity then
|
|
SetCapacity((i + 15) and (not 15));
|
|
FirstAdded := Count;
|
|
for i := 0 to AStrings.Count - 1 do begin
|
|
SetCount(Count + 1);
|
|
with fList do begin
|
|
Strings[Count-1] := AStrings[i];
|
|
Objects[Count-1] := AStrings.Objects[i];
|
|
end;
|
|
Flags[Count-1] := [];
|
|
end;
|
|
SendNotification(senrLineCount, self, FirstAdded, Count - FirstAdded);
|
|
finally
|
|
EndUpdate;
|
|
end;
|
|
end;
|
|
{end} //mh 2000-10-19
|
|
end;
|
|
|
|
procedure TSynEditStringList.Clear;
|
|
var
|
|
c: Integer;
|
|
begin
|
|
c := Count;
|
|
if c <> 0 then begin
|
|
BeginUpdate;
|
|
SetCount(0);
|
|
SetCapacity(0);
|
|
SendNotification(senrLineCount, self, 0, -c);
|
|
SendNotification(senrCleared, Self);
|
|
EndUpdate;
|
|
end;
|
|
fIndexOfLongestLine := -1;
|
|
end;
|
|
|
|
procedure TSynEditStringList.Delete(Index: integer);
|
|
begin
|
|
// Ensure correct index, so DeleteLines will not throw exception
|
|
if (Index < 0) or (Index >= Count) then
|
|
ListIndexOutOfBounds(Index);
|
|
BeginUpdate;
|
|
FList.DeleteRows(Index, 1);
|
|
IncreaseTextChangeStamp;
|
|
fIndexOfLongestLine := -1;
|
|
SendNotification(senrLineCount, self, Index, -1);
|
|
EndUpdate;
|
|
end;
|
|
|
|
procedure TSynEditStringList.DeleteLines(Index, NumLines: integer);
|
|
begin
|
|
if NumLines > 0 then begin
|
|
// Ensure correct index, so DeleteLines will not throw exception
|
|
if (Index < 0) or (Index + NumLines > Count) then
|
|
ListIndexOutOfBounds(Index);
|
|
BeginUpdate;
|
|
FList.DeleteRows(Index, NumLines);
|
|
IncreaseTextChangeStamp;
|
|
SendNotification(senrLineCount, self, Index, -NumLines);
|
|
EndUpdate;
|
|
end;
|
|
end;
|
|
|
|
function TSynEditStringList.GetFlags(Index: Integer): TSynEditStringFlags;
|
|
begin
|
|
if (Index >= 0) and (Index < Count) then
|
|
Result := FList.Flags[Index]
|
|
else
|
|
Result := [];
|
|
end;
|
|
|
|
function TSynEditStringList.GetAttachedSynEdits(Index: Integer): TSynEditBase;
|
|
begin
|
|
Result := TSynEditBase(FAttachedSynEditList[Index]);
|
|
end;
|
|
|
|
function TSynEditStringList.Get(Index: integer): string;
|
|
begin
|
|
if (Index >= 0) and (Index < Count) then
|
|
Result := fList[Index]
|
|
else
|
|
Result := '';
|
|
end;
|
|
|
|
function TSynEditStringList.GetCapacity: integer;
|
|
begin
|
|
Result := fList.Capacity;
|
|
end;
|
|
|
|
function TSynEditStringList.GetCount: integer;
|
|
begin
|
|
Result := FList.Count;
|
|
end;
|
|
|
|
procedure TSynEditStringList.SetCount(const AValue: Integer);
|
|
begin
|
|
IncreaseTextChangeStamp;
|
|
fList.Count := AValue;
|
|
end;
|
|
|
|
{begin} //mh 2000-10-19
|
|
function TSynEditStringList.GetExpandedString(Index: integer): string;
|
|
begin
|
|
if (Index >= 0) and (Index < Count) then begin
|
|
Result := FList[Index];
|
|
end else
|
|
Result := '';
|
|
end;
|
|
|
|
function TSynEditStringList.GetLengthOfLongestLine: integer; //mh 2000-10-19
|
|
var
|
|
i, j, MaxLen: integer;
|
|
begin
|
|
if fIndexOfLongestLine < 0 then begin
|
|
MaxLen := 0;
|
|
if Count > 0 then begin
|
|
for i := 0 to Count - 1 do begin
|
|
j := length(FList[i]);
|
|
if j > MaxLen then begin
|
|
MaxLen := j;
|
|
fIndexOfLongestLine := i;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
if (fIndexOfLongestLine >= 0) and (fIndexOfLongestLine < Count) then
|
|
Result := length(FList[fIndexOfLongestLine])
|
|
else
|
|
Result := 0;
|
|
end;
|
|
|
|
function TSynEditStringList.GetTextChangeStamp: int64;
|
|
begin
|
|
Result := FTextChangeStamp;
|
|
end;
|
|
|
|
function TSynEditStringList.GetIsInEditAction: Boolean;
|
|
begin
|
|
Result := FIsInEditAction > 0;
|
|
end;
|
|
|
|
procedure TSynEditStringList.IncIsInEditAction;
|
|
begin
|
|
inc(FIsInEditAction);
|
|
end;
|
|
|
|
procedure TSynEditStringList.DecIsInEditAction;
|
|
begin
|
|
dec(FIsInEditAction);
|
|
end;
|
|
|
|
function TSynEditStringList.GetRedoList: TSynEditUndoList;
|
|
begin
|
|
Result := fRedoList;
|
|
end;
|
|
|
|
function TSynEditStringList.GetUndoList: TSynEditUndoList;
|
|
begin
|
|
Result := fUndoList;
|
|
end;
|
|
|
|
function TSynEditStringList.GetCurUndoList: TSynEditUndoList;
|
|
begin
|
|
if FIsUndoing then
|
|
Result := fRedoList
|
|
else
|
|
Result := fUndoList;
|
|
end;
|
|
|
|
procedure TSynEditStringList.SetIsUndoing(const AValue: Boolean);
|
|
begin
|
|
if FIsUndoing = AValue then
|
|
exit;
|
|
|
|
if not AValue then
|
|
SendNotification(senrEndUndoRedo, Self); // before UNDO ends
|
|
|
|
if (not AValue) and fUndoList.IsTopMarkedAsSaved then begin
|
|
fRedoList.MarkTopAsSaved;
|
|
MarkSaved;
|
|
end;
|
|
|
|
FIsUndoing := AValue;
|
|
|
|
if AValue then
|
|
SendNotification(senrBeginUndoRedo, Self); // after UNDO started
|
|
end;
|
|
|
|
function TSynEditStringList.GetIsUndoing: Boolean;
|
|
begin
|
|
Result := FIsUndoing;
|
|
end;
|
|
|
|
procedure TSynEditStringList.SetIsRedoing(const AValue: Boolean);
|
|
begin
|
|
if FIsRedoing = AValue then
|
|
exit;
|
|
|
|
if not AValue then
|
|
SendNotification(senrEndUndoRedo, Self); // before UNDO ends
|
|
|
|
if (not AValue) and fRedoList.IsTopMarkedAsSaved then begin
|
|
fUndoList.MarkTopAsSaved;
|
|
MarkSaved;
|
|
end;
|
|
|
|
FIsRedoing := AValue;
|
|
|
|
if AValue then
|
|
SendNotification(senrBeginUndoRedo, Self); // after UNDO started
|
|
end;
|
|
|
|
function TSynEditStringList.GetIsRedoing: Boolean;
|
|
begin
|
|
Result := FIsRedoing;
|
|
end;
|
|
|
|
procedure TSynEditStringList.UndoRedoAdded(Sender: TObject);
|
|
begin
|
|
// we have to clear the redo information, since adding undo info removes
|
|
// the necessary context to undo earlier edit actions
|
|
if (Sender = fUndoList) and not (fUndoList.IsInsideRedo) then
|
|
fRedoList.Clear;
|
|
if fUndoList.UnModifiedMarkerExists then
|
|
Modified := not fUndoList.IsTopMarkedAsUnmodified
|
|
else if fRedoList.UnModifiedMarkerExists then
|
|
Modified := not fRedoList.IsTopMarkedAsUnmodified
|
|
else
|
|
Modified := fUndoList.CanUndo or fUndoList.FullUndoImpossible;
|
|
|
|
SendNotification(senrUndoRedoAdded, Sender);
|
|
end;
|
|
|
|
// Maps the Physical Width (ScreenCells) to each character
|
|
// Multibyte Chars have thw width on the first byte, and a 0 Width for all other bytes
|
|
procedure TSynEditStringList.DoGetPhysicalCharWidths(Line: PChar;
|
|
LineLen, Index: Integer; PWidths: PPhysicalCharWidth);
|
|
var
|
|
i: Integer;
|
|
begin
|
|
if not IsUtf8 then begin
|
|
for i := 0 to LineLen-1 do
|
|
PWidths[i] := 1;
|
|
exit;
|
|
end;
|
|
|
|
for i := 0 to LineLen-1 do begin
|
|
case Line^ of
|
|
#$00..#$7F:
|
|
PWidths^ := 1;
|
|
#$80..#$BF:
|
|
PWidths^ := 0;
|
|
else
|
|
if IsCombiningCodePoint(Line) then
|
|
PWidths^ := 0
|
|
else
|
|
PWidths^ := 1;
|
|
//#$CC:
|
|
// if ((Line+1)^ in [#$80..#$FF]) and (i>0)
|
|
// then PWidths^ := 0 // Combining Diacritical Marks (belongs to previos char) 0300-036F
|
|
// else PWidths^ := 1;
|
|
//#$CD:
|
|
// if ((Line+1)^ in [#$00..#$AF]) and (i>0)
|
|
// then PWidths^ := 0 // Combining Diacritical Marks
|
|
// else PWidths^ := 1;
|
|
//#$E1:
|
|
// if (((Line+1)^ = #$B7) and ((Line+2)^ in [#$80..#$BF])) and (i>0)
|
|
// then PWidths^ := 0 // Combining Diacritical Marks Supplement 1DC0-1DFF
|
|
// else PWidths^ := 1;
|
|
//#$E2:
|
|
// if (((Line+1)^ = #$83) and ((Line+2)^ in [#$90..#$FF])) and (i>0)
|
|
// then PWidths^ := 0 // Combining Diacritical Marks for Symbols 20D0-20FF
|
|
// else PWidths^ := 1;
|
|
//#$EF:
|
|
// if (((Line+1)^ = #$B8) and ((Line+2)^ in [#$A0..#$AF])) and (i>0)
|
|
// then PWidths^ := 0 // Combining half Marks FE20-FE2F
|
|
// else PWidths^ := 1;
|
|
//else
|
|
// PWidths^ := 1;
|
|
end;
|
|
inc(PWidths);
|
|
inc(Line);
|
|
end;
|
|
|
|
end;
|
|
|
|
function TSynEditStringList.GetDisplayView: TLazSynDisplayView;
|
|
begin
|
|
Result := FDisplayView;
|
|
end;
|
|
|
|
procedure TSynEditStringList.AttachSynEdit(AEdit: TSynEditBase);
|
|
begin
|
|
if FAttachedSynEditList.IndexOf(AEdit) < 0 then
|
|
FAttachedSynEditList.Add(AEdit);
|
|
end;
|
|
|
|
procedure TSynEditStringList.DetachSynEdit(AEdit: TSynEditBase);
|
|
begin
|
|
FAttachedSynEditList.Remove(AEdit);
|
|
end;
|
|
|
|
function TSynEditStringList.AttachedSynEditCount: Integer;
|
|
begin
|
|
Result := FAttachedSynEditList.Count;
|
|
end;
|
|
|
|
procedure TSynEditStringList.CopyHanlders(OtherLines: TSynEditStringList; AOwner: TObject);
|
|
begin
|
|
CopyHandlers(OtherLines, AOwner);
|
|
end;
|
|
|
|
function TSynEditStringList.GetObject(Index: integer): TObject;
|
|
begin
|
|
if (Index >= 0) and (Index < Count) then
|
|
Result := fList.Objects[Index]
|
|
else
|
|
Result := nil;
|
|
end;
|
|
|
|
function TSynEditStringList.GetRange(Index: Pointer): TSynManagedStorageMem;
|
|
begin
|
|
Result := FList.RangeList[Index];
|
|
end;
|
|
|
|
procedure TSynEditStringList.Grow;
|
|
var
|
|
Delta: Integer;
|
|
begin
|
|
if Capacity > 64 then
|
|
Delta := Capacity div 4
|
|
else
|
|
Delta := 16;
|
|
SetCapacity(Capacity + Delta);
|
|
end;
|
|
|
|
procedure TSynEditStringList.Insert(Index: integer; const S: string);
|
|
var
|
|
OldCnt : integer;
|
|
begin
|
|
if (Index < 0) or (Index > Count) then
|
|
ListIndexOutOfBounds(Index);
|
|
BeginUpdate;
|
|
OldCnt:=Count;
|
|
InsertItem(Index, S);
|
|
SendNotification(senrLineCount, self, Index, Count - OldCnt);
|
|
EndUpdate;
|
|
end;
|
|
|
|
procedure TSynEditStringList.InsertItem(Index: integer; const S: string);
|
|
begin
|
|
// Ensure correct index, so DeleteLines will not throw exception
|
|
if (Index < 0) or (Index > Count) then
|
|
ListIndexOutOfBounds(Index);
|
|
BeginUpdate;
|
|
if Count = Capacity then
|
|
Grow;
|
|
FList.InsertRows(Index, 1);
|
|
IncreaseTextChangeStamp;
|
|
fIndexOfLongestLine := -1; //mh 2000-10-19
|
|
fList[Index] := S;
|
|
FList.Objects[Index] := nil;
|
|
Flags[Index] := [];
|
|
EndUpdate;
|
|
end;
|
|
|
|
{begin} // DJLP 2000-11-01
|
|
procedure TSynEditStringList.InsertLines(Index, NumLines: integer);
|
|
begin
|
|
if NumLines > 0 then begin
|
|
// Ensure correct index, so DeleteLines will not throw exception
|
|
if (Index < 0) or (Index > Count) then
|
|
ListIndexOutOfBounds(Index);
|
|
BeginUpdate;
|
|
try
|
|
if Capacity<Count + NumLines then
|
|
SetCapacity(Count + NumLines);
|
|
FList.InsertRows(Index, NumLines);
|
|
IncreaseTextChangeStamp;
|
|
SendNotification(senrLineCount, self, Index, NumLines);
|
|
finally
|
|
EndUpdate;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TSynEditStringList.InsertStrings(Index: integer;
|
|
NewStrings: TStrings);
|
|
var
|
|
i, Cnt: integer;
|
|
begin
|
|
Cnt := NewStrings.Count;
|
|
if Cnt > 0 then begin
|
|
BeginUpdate;
|
|
try
|
|
InsertLines(Index, Cnt);
|
|
for i := 0 to Cnt - 1 do
|
|
Strings[Index + i] := NewStrings[i];
|
|
finally
|
|
EndUpdate;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TSynEditStringList.GetPChar(ALineIndex: Integer; out ALen: Integer): PChar;
|
|
begin
|
|
ALen := 0;
|
|
if (ALineIndex = 0) and (Count = 0) then // simulate empty line
|
|
Result := nil
|
|
else
|
|
Result := FList.GetPChar(ALineIndex, ALen);
|
|
end;
|
|
|
|
{end} // DJLP 2000-11-01
|
|
|
|
procedure TSynEditStringList.Put(Index: integer; const S: string);
|
|
begin
|
|
if (Index = 0) and (Count = 0) then
|
|
Add(S)
|
|
else begin
|
|
if (Index < 0) or (Index >= Count) then
|
|
ListIndexOutOfBounds(Index);
|
|
BeginUpdate;
|
|
fIndexOfLongestLine := -1;
|
|
FList[Index] := S;
|
|
IncreaseTextChangeStamp;
|
|
SendNotification(senrLineChange, self, Index, 1);
|
|
EndUpdate;
|
|
end;
|
|
end;
|
|
|
|
procedure TSynEditStringList.PutObject(Index: integer; AObject: TObject);
|
|
begin
|
|
if (Index < 0) or (Index >= Count) then
|
|
ListIndexOutOfBounds(Index);
|
|
if fList.Objects[Index] = AObject then exit;
|
|
BeginUpdate;
|
|
fList.Objects[Index]:= AObject;
|
|
EndUpdate;
|
|
end;
|
|
|
|
procedure TSynEditStringList.PutRange(Index: Pointer; const ARange: TSynManagedStorageMem);
|
|
begin
|
|
FList.RangeList[Index] := ARange;
|
|
end;
|
|
|
|
procedure TSynEditStringList.SetFlags(Index: Integer; const AValue: TSynEditStringFlags);
|
|
begin
|
|
FList.Flags[Index] := AValue;
|
|
end;
|
|
|
|
procedure TSynEditStringList.SetModified(const AValue: Boolean);
|
|
begin
|
|
if AValue then
|
|
IncreaseTextChangeStamp;
|
|
if (FModified = AValue) and
|
|
(CurUndoList.IsTopMarkedAsUnmodified <> AValue)
|
|
then exit;
|
|
FModified := AValue;
|
|
if not FModified then
|
|
begin
|
|
// the current state should be the unmodified state.
|
|
FUndoList.MarkTopAsUnmodified;
|
|
FRedoList.MarkTopAsUnmodified;
|
|
end;
|
|
SendNotification(senrModifiedChanged, Self);
|
|
end;
|
|
|
|
function TSynEditStringList.GetIsUtf8: Boolean;
|
|
begin
|
|
Result := FIsUtf8;
|
|
end;
|
|
|
|
procedure TSynEditStringList.SetIsUtf8(const AValue: Boolean);
|
|
begin
|
|
FIsUtf8 := AValue;
|
|
end;
|
|
|
|
procedure TSynEditStringList.SendCachedNotify;
|
|
begin
|
|
//debugln(['--- send cached notify ', FCachedNotifyStart,' / ',FCachedNotifyCount]);
|
|
if (FCachedNotifyCount <> 0) and FCachedNotify then begin
|
|
FCachedNotify := False;
|
|
TLineRangeNotificationList(FNotifyLists[senrLineCount])
|
|
.CallRangeNotifyEvents(FCachedNotifySender, FCachedNotifyStart, FCachedNotifyCount);
|
|
end;
|
|
end;
|
|
|
|
function TSynEditStringList.LogicPosAddChars(const ALine: String; ALogicalPos,
|
|
ACount: integer; AFlags: LPosFlags): Integer;
|
|
var
|
|
l: Integer;
|
|
begin
|
|
// UTF8 handing of chars
|
|
Result := ALogicalPos;
|
|
l := length(ALine);
|
|
if ACount > 0 then begin
|
|
while (Result < l) and (ACount > 0) do begin
|
|
inc(Result);
|
|
if (ALine[Result] in [#0..#127, #192..#255]) and
|
|
( (lpStopAtCodePoint in AFlags) or (not IsCombiningCodePoint(@ALine[Result])) )
|
|
then
|
|
dec(ACount);
|
|
end;
|
|
if lpAllowPastEOL in AFlags then
|
|
Result := Result + ACount;
|
|
|
|
if (Result <= l) then
|
|
while (Result > 1) and
|
|
( (not(ALine[Result] in [#0..#127, #192..#255])) or
|
|
( (not(lpStopAtCodePoint in AFlags)) and IsCombiningCodePoint(@ALine[Result]) )
|
|
)
|
|
do
|
|
dec(Result);
|
|
end else begin
|
|
while (Result > 1) and (ACount < 0) do begin
|
|
dec(Result);
|
|
if (Result > l) or (Result = 1) or
|
|
( (ALine[Result] in [#0..#127, #192..#255]) and
|
|
( (lpStopAtCodePoint in AFlags) or (not IsCombiningCodePoint(@ALine[Result])) )
|
|
)
|
|
then
|
|
inc(ACount);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TSynEditStringList.LogicPosIsAtChar(const ALine: String; ALogicalPos: integer;
|
|
AFlags: LPosFlags): Boolean;
|
|
begin
|
|
// UTF8 handing of chars
|
|
Result := (lpAllowPastEol in AFlags) and (ALogicalPos >= 1);
|
|
if (ALogicalPos < 1) or (ALogicalPos > length(ALine)) then exit;
|
|
Result := ALine[ALogicalPos] in [#0..#127, #192..#255];
|
|
|
|
if Result then
|
|
Result := (ALogicalPos = 1) or
|
|
(lpStopAtCodePoint in AFlags) or
|
|
(not IsCombiningCodePoint(@ALine[ALogicalPos]));
|
|
end;
|
|
|
|
function TSynEditStringList.LogicPosAdjustToChar(const ALine: String; ALogicalPos: integer;
|
|
AFlags: LPosFlags): Integer;
|
|
begin
|
|
// UTF8 handing of chars
|
|
Result := ALogicalPos;
|
|
if (ALogicalPos < 1) or (ALogicalPos > length(ALine)) then exit;
|
|
|
|
if lpAdjustToNext in AFlags then begin
|
|
while (Result <= length(ALine)) and
|
|
( (not(ALine[Result] in [#0..#127, #192..#255])) or
|
|
((Result <> 1) and
|
|
(not(lpStopAtCodePoint in AFlags)) and IsCombiningCodePoint(@ALine[Result])
|
|
)
|
|
)
|
|
do
|
|
inc(Result);
|
|
end;
|
|
|
|
if (not (lpAllowPastEol in AFlags)) and (Result > length(ALine)) then
|
|
Result := length(ALine); // + 1
|
|
if (Result > length(ALine)) then exit;
|
|
|
|
while (Result > 1) and
|
|
( (not(ALine[Result] in [#0..#127, #192..#255])) or
|
|
( (not(lpStopAtCodePoint in AFlags)) and IsCombiningCodePoint(@ALine[Result]) )
|
|
)
|
|
do
|
|
dec(Result);
|
|
end;
|
|
|
|
procedure TSynEditStringList.MarkModified(AFirst, ALast: Integer);
|
|
var
|
|
Index, i: Integer;
|
|
WasSaved: TSynEditStringFlagsArray;
|
|
NeedUndo: Boolean;
|
|
begin
|
|
if IsUndoing or IsRedoing then
|
|
exit;
|
|
AFirst := ToIdx(AFirst);
|
|
ALast := ToIdx(ALast);
|
|
if ALast = AFirst then begin
|
|
if sfSaved in Flags[AFirst] then
|
|
CurUndoList.AddChange(TSynEditUndoMarkModified.Create(AFirst, SynEditUndoMarkModifiedOneSaved), True)
|
|
else
|
|
if not (sfModified in Flags[AFirst]) then
|
|
CurUndoList.AddChange(TSynEditUndoMarkModified.Create(AFirst, SynEditUndoMarkModifiedOneEmpty), True);
|
|
Flags[AFirst] := Flags[AFirst] + [sfModified] - [sfSaved];
|
|
end
|
|
else begin
|
|
SetLength(WasSaved, ALast - AFirst + 1);
|
|
i := 0;
|
|
NeedUndo := False;
|
|
for Index := Max(0, AFirst) to Min(ALast, Count - 1) do begin
|
|
NeedUndo := NeedUndo or (Flags[Index] <> [sfModified]);
|
|
WasSaved[i] := Flags[Index];
|
|
Flags[Index] := Flags[Index] + [sfModified] - [sfSaved];
|
|
inc(i);
|
|
end;
|
|
if NeedUndo then
|
|
CurUndoList.AddChange(TSynEditUndoMarkModified.Create(AFirst, WasSaved), True);
|
|
end;
|
|
end;
|
|
|
|
procedure TSynEditStringList.MarkSaved;
|
|
var
|
|
Index: Integer;
|
|
begin
|
|
for Index := 0 to Count - 1 do
|
|
if sfModified in Flags[Index] then
|
|
Flags[Index] := Flags[Index] + [sfSaved];
|
|
|
|
if not (IsUndoing or IsRedoing) then begin
|
|
fUndoList.MarkTopAsSaved;
|
|
FRedoList.MarkTopAsSaved;
|
|
end;
|
|
|
|
SendNotification(senrHighlightChanged, Self, -1, -1);
|
|
end;
|
|
|
|
procedure TSynEditStringList.AddManagedHandler(AReason: TSynEditNotifyReason;
|
|
AHandler: TMethod);
|
|
begin
|
|
FNotifyLists[AReason].Add(AHandler);
|
|
end;
|
|
|
|
procedure TSynEditStringList.RemoveManagedHandler(AReason: TSynEditNotifyReason;
|
|
AHandler: TMethod);
|
|
begin
|
|
FNotifyLists[AReason].Remove(AHandler);
|
|
end;
|
|
|
|
procedure TSynEditStringList.CopyHandlers(OtherLines: TSynEditStringList; AOwner: TObject = nil);
|
|
var
|
|
i: TSynEditNotifyReason;
|
|
begin
|
|
for i := low(TSynEditNotifyReason) to high(TSynEditNotifyReason) do
|
|
FNotifyLists[i].AddCopyFrom(OtherLines.FNotifyLists[i], AOwner);
|
|
end;
|
|
|
|
procedure TSynEditStringList.RemoveManagedHandlers(AOwner: TObject);
|
|
var
|
|
i: TSynEditNotifyReason;
|
|
begin
|
|
for i := low(TSynEditNotifyReason) to high(TSynEditNotifyReason) do
|
|
FNotifyLists[i].RemoveAllMethodsOfObject(AOwner);
|
|
end;
|
|
|
|
procedure TSynEditStringList.SetCapacity(NewCapacity: integer);
|
|
begin
|
|
if NewCapacity < Count then
|
|
fList.Count := NewCapacity;
|
|
fList.SetCapacity(NewCapacity);
|
|
IncreaseTextChangeStamp;
|
|
end;
|
|
|
|
procedure TSynEditStringList.MaybeSendSenrLinesModified;
|
|
begin
|
|
assert( (FModifiedNotifyOldCount >= 0) and (FModifiedNotifyNewCount >= 0), 'FModifiedNotify___Count >= 0');
|
|
if (FModifiedNotifyOldCount > 0) or (FModifiedNotifyNewCount > 0) then
|
|
TLinesModifiedNotificationList(FNotifyLists[senrLinesModified])
|
|
.CallRangeNotifyEvents(Self, FModifiedNotifyStart, FModifiedNotifyNewCount, FModifiedNotifyOldCount);
|
|
end;
|
|
|
|
procedure TSynEditStringList.SetUpdateState(Updating: Boolean; Sender: TObject);
|
|
begin
|
|
if FIsInDecPaintLock then exit;
|
|
if Updating then begin
|
|
SendNotification(senrBeforeIncPaintLock, Sender);
|
|
SendNotification(senrIncPaintLock, Sender); // DoIncPaintLock
|
|
SendNotification(senrAfterIncPaintLock, Sender);
|
|
FCachedNotify := False;
|
|
FModifiedNotifyStart := -1;
|
|
FModifiedNotifyOldCount := 0;
|
|
FModifiedNotifyNewCount := 0;
|
|
end else begin
|
|
if FCachedNotify then
|
|
SendCachedNotify;
|
|
MaybeSendSenrLinesModified; // must be before senrDecPaintLock is sent
|
|
FIsInDecPaintLock := True;
|
|
try
|
|
SendNotification(senrBeforeDecPaintLock, Sender);
|
|
SendNotification(senrDecPaintLock, Sender); // DoDecPaintLock
|
|
SendNotification(senrAfterDecPaintLock, Sender);
|
|
finally
|
|
FIsInDecPaintLock := False;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TSynEditStringList.EditInsert(LogX, LogY: Integer; AText: String);
|
|
var
|
|
s: string;
|
|
begin
|
|
IncIsInEditAction;
|
|
s := Strings[LogY - 1];
|
|
if LogX - 1 > Length(s) then begin
|
|
AText := StringOfChar(' ', LogX - 1 - Length(s)) + AText;
|
|
LogX := Length(s) + 1;
|
|
end;
|
|
Strings[LogY - 1] := copy(s,1, LogX - 1) + AText + copy(s, LogX, length(s));
|
|
if AText <> '' then
|
|
CurUndoList.AddChange(TSynEditUndoTxtInsert.Create(LogX, LogY, Length(AText)));
|
|
MarkModified(LogY, LogY);
|
|
SendNotification(senrEditAction, self, LogY, 0, LogX, length(AText), AText);
|
|
DecIsInEditAction;
|
|
end;
|
|
|
|
function TSynEditStringList.EditDelete(LogX, LogY, ByteLen: Integer): String;
|
|
var
|
|
s: string;
|
|
begin
|
|
Result := '';
|
|
if ByteLen <= 0 then
|
|
exit;
|
|
IncIsInEditAction;
|
|
s := Strings[LogY - 1];
|
|
if LogX - 1 <= Length(s) then begin
|
|
Result := copy(s, LogX, ByteLen);
|
|
Strings[LogY - 1] := copy(s,1, LogX - 1) + copy(s, LogX + ByteLen, length(s));
|
|
if Result <> '' then
|
|
CurUndoList.AddChange(TSynEditUndoTxtDelete.Create(LogX, LogY, Result));
|
|
MarkModified(LogY, LogY);
|
|
end;
|
|
SendNotification(senrEditAction, self, LogY, 0, LogX, -ByteLen, '');
|
|
DecIsInEditAction;
|
|
end;
|
|
|
|
function TSynEditStringList.EditReplace(LogX, LogY, ByteLen: Integer; AText: String): String;
|
|
var
|
|
s, s2: string;
|
|
begin
|
|
IncIsInEditAction;
|
|
|
|
if ByteLen <= 0 then
|
|
ByteLen := 0;
|
|
s := Strings[LogY - 1];
|
|
if LogX - 1 > Length(s) then begin
|
|
AText := StringOfChar(' ', LogX - 1 - Length(s)) + AText;
|
|
LogX := Length(s) + 1;
|
|
end;
|
|
|
|
if LogX - 1 + ByteLen > Length(s) then
|
|
ByteLen := Length(s) - (LogX-1);
|
|
Result := copy(s, LogX, ByteLen);
|
|
|
|
SetLength(s2, Length(s) - ByteLen + Length(AText));
|
|
if LogX > 1 then
|
|
system.Move(s[1], s2[1], LogX-1);
|
|
if AText <> '' then
|
|
system.Move(AText[1], s2[LogX], Length(AText));
|
|
if Length(s)-(LogX-1)-ByteLen > 0 then
|
|
system.Move(s[LogX+ByteLen], s2[LogX+Length(AText)], Length(s)-(LogX-1)-ByteLen);
|
|
Strings[LogY - 1] := s2;
|
|
//Strings[LogY - 1] := copy(s,1, LogX - 1) + AText + copy(s, LogX + ByteLen, length(s));
|
|
|
|
if Result <> '' then
|
|
CurUndoList.AddChange(TSynEditUndoTxtDelete.Create(LogX, LogY, Result));
|
|
if AText <> '' then
|
|
CurUndoList.AddChange(TSynEditUndoTxtInsert.Create(LogX, LogY, Length(AText)));
|
|
|
|
MarkModified(LogY, LogY);
|
|
SendNotification(senrEditAction, self, LogY, 0, LogX, -ByteLen, '');
|
|
SendNotification(senrEditAction, self, LogY, 0, LogX, length(AText), AText);
|
|
DecIsInEditAction;
|
|
end;
|
|
|
|
procedure TSynEditStringList.EditLineBreak(LogX, LogY: Integer);
|
|
var
|
|
s: string;
|
|
ModEnd, ModStart: Integer;
|
|
begin
|
|
IncIsInEditAction;
|
|
if Count = 0 then Add('');
|
|
s := Strings[LogY - 1];
|
|
ModStart := LogY;
|
|
ModEnd := LogY + 1;
|
|
if LogX = 1 then
|
|
dec(ModEnd);
|
|
if LogX - 1 < length(s) then
|
|
Strings[LogY - 1] := copy(s, 1, LogX - 1)
|
|
else
|
|
inc(ModStart);
|
|
Insert(LogY, copy(s, LogX, length(s)));
|
|
CurUndoList.AddChange(TSynEditUndoTxtLineBreak.Create(LogY));
|
|
MarkModified(ModStart, ModEnd);
|
|
SendNotification(senrEditAction, self, LogY, 1, LogX, 0, '');
|
|
DecIsInEditAction;
|
|
end;
|
|
|
|
procedure TSynEditStringList.EditLineJoin(LogY: Integer; FillText: String = '');
|
|
var
|
|
t: string;
|
|
begin
|
|
IncIsInEditAction;
|
|
t := Strings[LogY - 1];
|
|
if FillText <> '' then
|
|
EditInsert(1 + Length(t), LogY, FillText);
|
|
CurUndoList.AddChange(TSynEditUndoTxtLineJoin.Create(1 + Length(Strings[LogY-1]),
|
|
LogY));
|
|
t := t + FillText;
|
|
Strings[LogY - 1] := t + Strings[LogY] ;
|
|
Delete(LogY);
|
|
MarkModified(LogY, LogY);
|
|
SendNotification(senrEditAction, self, LogY, -1, 1+length(t), 0, '');
|
|
DecIsInEditAction;
|
|
end;
|
|
|
|
procedure TSynEditStringList.EditLinesInsert(LogY, ACount: Integer;
|
|
AText: String = '');
|
|
begin
|
|
IncIsInEditAction;
|
|
InsertLines(LogY - 1, ACount);
|
|
CurUndoList.AddChange(TSynEditUndoTxtLinesIns.Create(LogY, ACount));
|
|
SendNotification(senrEditAction, self, LogY, ACount, 1, 0, '');
|
|
if AText <> '' then
|
|
EditInsert(1, LogY, AText);
|
|
MarkModified(LogY, LogY + ACount - 1);
|
|
DecIsInEditAction;
|
|
end;
|
|
|
|
procedure TSynEditStringList.EditLinesDelete(LogY, ACount: Integer);
|
|
var
|
|
i: Integer;
|
|
begin
|
|
IncIsInEditAction;
|
|
for i := LogY to LogY + ACount - 1 do
|
|
EditDelete(1, i, length(Strings[i-1]));
|
|
DeleteLines(LogY - 1, ACount);
|
|
CurUndoList.AddChange(TSynEditUndoTxtLinesDel.Create(LogY, ACount));
|
|
i := LogY;
|
|
if i >= Count then dec(i);
|
|
if i > 0 then
|
|
MarkModified(LogY, LogY);
|
|
SendNotification(senrEditAction, self, LogY, -ACount, 1, 0, '');
|
|
DecIsInEditAction;
|
|
end;
|
|
|
|
procedure TSynEditStringList.EditUndo(Item: TSynEditUndoItem);
|
|
begin
|
|
IncIsInEditAction; // all undo calls edit actions
|
|
EditRedo(Item);
|
|
DecIsInEditAction;
|
|
end;
|
|
|
|
procedure TSynEditStringList.UndoEditLinesDelete(LogY, ACount: Integer);
|
|
begin
|
|
CurUndoList.AddChange(TSynEditUndoTxtLinesDel.Create(LogY, ACount));
|
|
DeleteLines(LogY - 1, ACount);
|
|
SendNotification(senrEditAction, self, LogY, -ACount, 1, 0, '');
|
|
end;
|
|
|
|
procedure TSynEditStringList.IncreaseTextChangeStamp;
|
|
begin
|
|
if FTextChangeStamp=High(FTextChangeStamp) then
|
|
FTextChangeStamp:=Low(FTextChangeStamp)
|
|
else
|
|
inc(FTextChangeStamp);
|
|
end;
|
|
|
|
procedure TSynEditStringList.EditRedo(Item: TSynEditUndoItem);
|
|
begin
|
|
IncIsInEditAction; // all undo calls edit actions
|
|
Item.PerformUndo(self);
|
|
DecIsInEditAction;
|
|
end;
|
|
|
|
procedure TSynEditStringList.SendNotification(AReason: TSynEditNotifyReason;
|
|
ASender: TSynEditStrings; aIndex, aCount: Integer);
|
|
var
|
|
i, oldcount, overlap: Integer;
|
|
begin
|
|
assert(AReason in [senrLineChange, senrLineCount, senrLinesModified, senrHighlightChanged, senrLineMappingChanged], 'Correct SendNotification');
|
|
if FIgnoreSendNotification[AReason] > 0 then exit;
|
|
|
|
if IsUpdating and (AReason in [senrLineChange, senrLineCount]) then begin
|
|
// senrLinesModified
|
|
assert( (FModifiedNotifyOldCount >= 0) and (FModifiedNotifyNewCount >= 0), 'FModifiedNotify___Count >= 0');
|
|
assert(aIndex >= 0, 'SendNotification index');
|
|
if (FModifiedNotifyOldCount = 0) and (FModifiedNotifyNewCount = 0) then
|
|
FModifiedNotifyStart := aIndex;
|
|
|
|
if aIndex < FModifiedNotifyStart then begin
|
|
i := FModifiedNotifyStart - aIndex;
|
|
FModifiedNotifyStart := aIndex;
|
|
FModifiedNotifyNewCount := FModifiedNotifyNewCount + i;
|
|
FModifiedNotifyOldCount := FModifiedNotifyOldCount + i;
|
|
end;
|
|
|
|
oldcount := 0;
|
|
if AReason = senrLineCount then begin
|
|
if aCount < 0 then begin
|
|
oldcount := -aCount;
|
|
if (aIndex < FModifiedNotifyStart + FModifiedNotifyNewCount) then begin
|
|
overlap := (FModifiedNotifyStart + FModifiedNotifyNewCount) - aIndex;
|
|
if overlap > oldcount then overlap := oldcount;
|
|
FModifiedNotifyNewCount := FModifiedNotifyNewCount - overlap;
|
|
oldcount := oldcount - overlap;
|
|
end;
|
|
FModifiedNotifyOldCount := FModifiedNotifyOldCount + oldcount;
|
|
oldcount := 0;
|
|
end
|
|
else begin
|
|
FModifiedNotifyNewCount := FModifiedNotifyNewCount + aCount;
|
|
oldcount := aCount; // because already added to newcount
|
|
end;
|
|
end
|
|
else
|
|
if AReason = senrLineChange then begin
|
|
oldcount := aCount;
|
|
end;
|
|
|
|
if aIndex + oldcount > FModifiedNotifyStart + FModifiedNotifyNewCount then begin
|
|
i := (aIndex + oldcount) - (FModifiedNotifyStart + FModifiedNotifyNewCount);
|
|
FModifiedNotifyNewCount := FModifiedNotifyNewCount + i;
|
|
FModifiedNotifyOldCount := FModifiedNotifyOldCount + i;
|
|
end;
|
|
|
|
// CacheNotify
|
|
if AReason = senrLineCount then begin
|
|
// maybe cache and combine
|
|
if not FCachedNotify then begin
|
|
FCachedNotify := True;
|
|
FCachedNotifySender := ASender;
|
|
FCachedNotifyStart := aIndex;
|
|
FCachedNotifyCount := aCount;
|
|
exit;
|
|
end
|
|
else
|
|
if (FCachedNotifySender = ASender) and (aIndex >= FCachedNotifyStart) and
|
|
(FCachedNotifyCount > 0) and
|
|
(aIndex <= FCachedNotifyStart + FCachedNotifyCount) and
|
|
((aCount > 0) or (aIndex - aCount <= FCachedNotifyStart + FCachedNotifyCount))
|
|
then begin
|
|
FCachedNotifyCount := FCachedNotifyCount + aCount;
|
|
if FCachedNotifyCount = 0 then
|
|
FCachedNotify := False;
|
|
exit;
|
|
end;
|
|
end
|
|
else
|
|
if FCachedNotify and (AReason = senrLineChange) and
|
|
(ASender = FCachedNotifySender) and (FCachedNotifyCount > 0) and
|
|
(aIndex >= FCachedNotifyStart) and
|
|
(aIndex + aCount {- 1} <= FCachedNotifyStart + FCachedNotifyCount {- 1})
|
|
then
|
|
exit; // Will send senrLineCount instead
|
|
|
|
if FCachedNotify then
|
|
SendCachedNotify;
|
|
end
|
|
else begin
|
|
case AReason of
|
|
senrLineChange:
|
|
TLinesModifiedNotificationList(FNotifyLists[senrLinesModified])
|
|
.CallRangeNotifyEvents(ASender, aIndex, aCount, aCount);
|
|
senrLineCount:
|
|
TLinesModifiedNotificationList(FNotifyLists[senrLinesModified])
|
|
.CallRangeNotifyEvents(ASender, aIndex, aCount, 0);
|
|
end;
|
|
end;
|
|
|
|
TLineRangeNotificationList(FNotifyLists[AReason])
|
|
.CallRangeNotifyEvents(ASender, aIndex, aCount);
|
|
end;
|
|
|
|
procedure TSynEditStringList.SendNotification(AReason: TSynEditNotifyReason;
|
|
ASender: TSynEditStrings; aIndex, aCount: Integer;
|
|
aBytePos: Integer; aLen: Integer; aTxt: String);
|
|
begin
|
|
assert(AReason in [senrEditAction], 'Correct SendNotification');
|
|
if FIgnoreSendNotification[AReason] > 0 then exit;
|
|
|
|
if FCachedNotify then
|
|
SendCachedNotify;
|
|
|
|
// aindex is mis-named (linepos) for edit action
|
|
TLineEditNotificationList(FNotifyLists[AReason])
|
|
.CallRangeNotifyEvents(ASender, aIndex, aBytePos, aLen, aCount, aTxt);
|
|
end;
|
|
|
|
procedure TSynEditStringList.SendNotification(AReason: TSynEditNotifyReason;
|
|
ASender: TObject);
|
|
begin
|
|
if AReason in [senrLineChange, senrLineCount, senrLinesModified, senrHighlightChanged, senrEditAction] then
|
|
raise Exception.Create('Invalid');
|
|
if FCachedNotify then
|
|
SendCachedNotify;
|
|
if (AReason in [senrCleared, senrTextBufferChanging]) then
|
|
MaybeSendSenrLinesModified;
|
|
FNotifyLists[AReason].CallNotifyEvents(ASender);
|
|
end;
|
|
|
|
procedure TSynEditStringList.FlushNotificationCache;
|
|
begin
|
|
if FCachedNotify then
|
|
SendCachedNotify;
|
|
end;
|
|
|
|
procedure TSynEditStringList.IgnoreSendNotification(AReason: TSynEditNotifyReason;
|
|
IncIgnore: Boolean);
|
|
begin
|
|
if IncIgnore then
|
|
inc(FIgnoreSendNotification[AReason])
|
|
else
|
|
if FIgnoreSendNotification[AReason] > 0 then
|
|
dec(FIgnoreSendNotification[AReason])
|
|
end;
|
|
|
|
{ TSynEditStringMemory }
|
|
type
|
|
PObject = ^TObject;
|
|
|
|
constructor TSynEditStringMemory.Create;
|
|
const
|
|
FlagSize = ((SizeOf(TSynEditStringFlags) + 1 ) Div 2) * 2; // ensure boundary
|
|
begin
|
|
inherited Create;
|
|
ItemSize := SizeOf(String) + SizeOf(TObject) + FlagSize;
|
|
FRangeList := TSynManagedStorageMemList.Create;
|
|
FRangeListLock := 0;
|
|
end;
|
|
|
|
destructor TSynEditStringMemory.Destroy;
|
|
begin
|
|
inherited Destroy;
|
|
FreeAndNil(FRangeList);
|
|
end;
|
|
|
|
procedure TSynEditStringMemory.InsertRows(AIndex, ACount: Integer);
|
|
begin
|
|
// Managed lists to get Mave, Count, instead of InsertRows
|
|
inc(FRangeListLock);
|
|
inherited InsertRows(AIndex, ACount);
|
|
dec(FRangeListLock);
|
|
FRangeList.CallInsertedLines(AIndex, ACount);
|
|
end;
|
|
|
|
procedure TSynEditStringMemory.DeleteRows(AIndex, ACount: Integer);
|
|
begin
|
|
// Managed lists to get Mave, Count, instead of InsertRows
|
|
inc(FRangeListLock);
|
|
inherited DeleteRows(AIndex, ACount);
|
|
dec(FRangeListLock);
|
|
FRangeList.CallDeletedLines(AIndex, ACount);
|
|
end;
|
|
|
|
function TSynEditStringMemory.GetPChar(ALineIndex: Integer; out ALen: Integer): PChar;
|
|
var
|
|
ip: Pointer;
|
|
begin
|
|
ip := ItemPointer[ALineIndex];
|
|
ALen := length(PString(ip)^);
|
|
Result := PPChar(ip)^;
|
|
end;
|
|
|
|
procedure TSynEditStringMemory.Move(AFrom, ATo, ALen: Integer);
|
|
var
|
|
Len, i: Integer;
|
|
begin
|
|
if ATo < AFrom then begin
|
|
Len := Min(ALen, AFrom-ATo);
|
|
for i:=ATo to ATo + Len -1 do Strings[i]:='';
|
|
end else begin
|
|
Len := Min(ALen, ATo-AFrom);
|
|
for i:=ATo+Alen-Len to ATo+ALen -1 do Strings[i]:='';
|
|
end;
|
|
inherited Move(AFrom, ATo, ALen);
|
|
FRangeList.CallMove(AFrom, ATo, ALen);
|
|
end;
|
|
|
|
procedure TSynEditStringMemory.SetCount(const AValue: Integer);
|
|
var
|
|
OldCount, i : Integer;
|
|
begin
|
|
If Count = AValue then exit;
|
|
for i:= AValue to Count-1 do
|
|
Strings[i]:='';
|
|
OldCount := Count;
|
|
inherited SetCount(AValue);
|
|
FRangeList.ChildCounts := AValue;
|
|
if FRangeListLock = 0 then begin
|
|
if OldCount > Count then
|
|
FRangeList.CallDeletedLines(Count, OldCount - Count)
|
|
else
|
|
FRangeList.CallInsertedLines(OldCount, Count - OldCount);
|
|
end;
|
|
end;
|
|
|
|
function TSynEditStringMemory.GetString(Index: Integer): String;
|
|
begin
|
|
Result := (PString(ItemPointer[Index]))^;
|
|
end;
|
|
|
|
procedure TSynEditStringMemory.SetFlags(Index: Integer; const AValue: TSynEditStringFlags);
|
|
begin
|
|
(PSynEditStringFlags(ItemPointer[Index] + SizeOf(String) + SizeOf(TObject) ))^ := AValue;
|
|
end;
|
|
|
|
procedure TSynEditStringMemory.SetString(Index: Integer; const AValue: String);
|
|
begin
|
|
(PString(ItemPointer[Index]))^ := AValue;
|
|
if FRangeListLock = 0 then
|
|
FRangeList.CallLineTextChanged(Index);
|
|
end;
|
|
|
|
procedure TSynEditStringMemory.SetCapacity(const AValue: Integer);
|
|
begin
|
|
inherited SetCapacity(AValue);
|
|
FRangeList.ChildCapacities := AValue;
|
|
end;
|
|
|
|
function TSynEditStringMemory.GetObject(Index: Integer): TObject;
|
|
begin
|
|
Result := (PObject(ItemPointer[Index] + SizeOf(String)))^;
|
|
end;
|
|
|
|
function TSynEditStringMemory.GetFlags(Index: Integer): TSynEditStringFlags;
|
|
begin
|
|
Result := (PSynEditStringFlags(ItemPointer[Index] + SizeOf(String) + SizeOf(TObject) ))^;
|
|
end;
|
|
|
|
function TSynEditStringMemory.GetRange(Index: Pointer): TSynManagedStorageMem;
|
|
begin
|
|
Result := FRangeList[Index];
|
|
end;
|
|
|
|
procedure TSynEditStringMemory.SetObject(Index: Integer; const AValue: TObject);
|
|
begin
|
|
(PObject(ItemPointer[Index] + SizeOf(String)))^ := AValue;
|
|
end;
|
|
|
|
procedure TSynEditStringMemory.SetRange(Index: Pointer; const AValue: TSynManagedStorageMem);
|
|
begin
|
|
FRangeList[Index] := AValue;
|
|
|
|
if AValue <> nil then begin
|
|
AValue.Capacity := Capacity;
|
|
AValue.Count := Count;
|
|
end;
|
|
end;
|
|
|
|
{ TLinesModifiedNotificationList }
|
|
|
|
procedure TLinesModifiedNotificationList.CallRangeNotifyEvents(Sender: TSynEditStrings; aIndex,
|
|
aNewCount, aOldCount: Integer);
|
|
var
|
|
i: LongInt;
|
|
begin
|
|
i:=Count;
|
|
while NextDownIndex(i) do
|
|
TStringListLinesModifiedEvent(Items[i])(Sender, aIndex, aNewCount, aOldCount);
|
|
end;
|
|
|
|
{ TLineRangeNotificationList }
|
|
|
|
procedure TLineRangeNotificationList.CallRangeNotifyEvents(Sender: TSynEditStrings;
|
|
aIndex, aCount: Integer);
|
|
var
|
|
i: LongInt;
|
|
begin
|
|
i:=Count;
|
|
while NextDownIndex(i) do
|
|
TStringListLineCountEvent(Items[i])(Sender, aIndex, aCount);
|
|
end;
|
|
|
|
{ TLineEditNotificationList }
|
|
|
|
procedure TLineEditNotificationList.CallRangeNotifyEvents(Sender: TSynEditStrings;
|
|
aLinePos, aBytePos, aCount, aLineBrkCnt: Integer; aText: String);
|
|
var
|
|
i: LongInt;
|
|
begin
|
|
i:=Count;
|
|
while NextDownIndex(i) do
|
|
TStringListLineEditEvent(Items[i])(Sender, aLinePos, aBytePos, aCount,
|
|
aLineBrkCnt, aText);
|
|
end;
|
|
|
|
initialization
|
|
SetLength(SynEditUndoMarkModifiedOneEmpty, 1);
|
|
SetLength(SynEditUndoMarkModifiedOneSaved, 1);
|
|
SetLength(SynEditUndoMarkModifiedOneModified, 1);
|
|
SynEditUndoMarkModifiedOneEmpty[0] := [];
|
|
SynEditUndoMarkModifiedOneSaved[0] := [sfSaved, sfModified];
|
|
SynEditUndoMarkModifiedOneModified[0] := [sfModified];
|
|
|
|
|
|
end.
|
|
|