SynEdit: Refactor the Undo/Redo system. Should fix some issues with column selection too; and enables group-undo for all kind of edit-actions; also fixes bug #13298

git-svn-id: trunk@19001 -
This commit is contained in:
martin 2009-03-15 16:56:58 +00:00
parent b8e7143c97
commit a26d1805de
7 changed files with 1829 additions and 1264 deletions

View File

@ -84,7 +84,6 @@ begin
end;
function TSynBeautifier.UnIndentLine(const Editor: TSynEditBase;
// XXXXX viewed
const Line: string; const Lines: TSynEditStrings; const PhysCaret: TPoint;
out DelChars, InsChars: String; out CaretNewX: Integer): String;
var
@ -109,13 +108,13 @@ begin
LogSpacePos := TSynEdit(Editor).PhysicalToLogicalCol(Line, PhysCaret.y-1, SpaceCount2 + 1);
LogCaret := TSynEdit(Editor).PhysicalToLogicalPos(PhysCaret);
CaretNewX := SpaceCount2 + 1;
Lines.EditDelete(LogSpacePos, PhysCaret.Y, LogCaret.X - LogSpacePos);
DelChars := copy(Line, LogSpacePos, LogCaret.X - LogSpacePos);
InsChars := ''; // TODO: if tabs were removed, maybe fill-up with spaces
Result :=copy(Line, 1, LogSpacePos-1) + copy(Line, LogCaret.X, MaxInt);
end;
function TSynBeautifier.IndentLine(const Editor: TSynEditBase; Line: string;
// XXXXX viewed
const Lines: TSynEditStrings; const PhysCaret: TPoint; out DelChars,
InsChars: String; out CaretNewX: Integer; RemoveCurrentIndent: Boolean): String;
var
@ -151,8 +150,8 @@ begin
else
InsChars := '';
end;
Result := InsChars + Line;
CaretNewX := TSynEdit(Editor).LogicalToPhysicalCol(Result, PhysCaret.y - 1, SpaceCount2+1);
Result := InsChars;
CaretNewX := TSynEdit(Editor).LogicalToPhysicalCol(Result + Line, PhysCaret.y - 1, SpaceCount2+1);
end;
function TSynBeautifier.GetIndentForLine(Editor: TSynEditBase;

File diff suppressed because it is too large Load Diff

View File

@ -38,7 +38,7 @@ uses
{$IFDEF SYN_MBCSSUPPORT}
Imm,
{$ENDIF}
SynEditTextBase, SynEditTypes, SynEditMiscProcs, SynEditTextBuffer;
SynEditTextBase, SynEditTypes, SynEditMiscProcs;//, SynEditTextBuffer;
type
@ -78,7 +78,6 @@ type
FLinesDeletedMethod: TLinesCountChanged;
FLinesInsertedMethod: TLinesCountChanged;
FEnabled: Boolean;
FSpacesToTabs: Boolean;
FTabWidth: Integer;
FActiveSelectionMode: TSynSelectionMode;
FSelectionMode: TSynSelectionMode;
@ -103,13 +102,11 @@ type
constructor Create(ALines: TSynEditStrings);
//destructor Destroy; override;
procedure AdjustAfterTrimming; // TODO: Move into TrimView
procedure SetSelTextPrimitive(PasteMode: TSynSelectionMode; Value: PChar;
AddToUndoList: Boolean = false; ChangeReason: TSynChangeReason = crInsert);
procedure SetSelTextPrimitive(PasteMode: TSynSelectionMode; Value: PChar);
function SelAvail: Boolean;
function SelCanContinue(ACaret: TSynEditCaret): Boolean;
function IsBackwardSel: Boolean; // SelStart < SelEnd ?
property Enabled: Boolean read FEnabled write SetEnabled;
property SpacesToTabs: Boolean read FSpacesToTabs write FSpacesToTabs;
property ActiveSelectionMode: TSynSelectionMode
read FActiveSelectionMode write SetActiveSelectionMode;
property SelectionMode: TSynSelectionMode
@ -142,7 +139,7 @@ type
TSynEditCaret = class(TSynEditPointBase)
private
FAllowPastEOL: Boolean;
FForcePastEOL: Boolean;
FForcePastEOL: Integer;
FLinePos: Integer; // 1 based
FCharPos: Integer; // 1 based
FOldLinePos: Integer; // 1 based
@ -166,6 +163,8 @@ type
Procedure DoUnlock; override;
public
constructor Create;
procedure IncForcePastEOL;
procedure DecForcePastEOL;
property OldLinePos: Integer read FOldLinePos;
property OldCharPos: Integer read FOldCharPos;
property LinePos: Integer read fLinePos write setLinePos;
@ -176,7 +175,6 @@ type
property LineText: string read GetLineText write SetLineText;
property AdjustToNextChar: Boolean read FAdjustToNextChar write FAdjustToNextChar;
property AllowPastEOL: Boolean read FAllowPastEOL write SetAllowPastEOL;
property ForcePastEOL: Boolean read FForcePastEOL write FForcePastEOL;
end;
implementation
@ -240,7 +238,17 @@ begin
fLinePos:= 1;
fCharPos:= 1;
FAllowPastEOL := True;
FForcePastEOL := False;
FForcePastEOL := 0;
end;
procedure TSynEditCaret.IncForcePastEOL;
begin
inc(FForcePastEOL);
end;
procedure TSynEditCaret.DecForcePastEOL;
begin
dec(FForcePastEOL);
end;
procedure TSynEditCaret.setLinePos(const AValue : Integer);
@ -316,10 +324,10 @@ begin
if NewLine < 1 then begin
// this is just to make sure if Lines stringlist should be empty
NewLine := 1;
if not (FAllowPastEOL or FForcePastEOL) then
if (not FAllowPastEOL) and (FForcePastEOL = 0) then
nMaxX := 1;
end else begin
if not (FAllowPastEOL or FForcePastEOL) then begin
if (not FAllowPastEOL) and (FForcePastEOL = 0) then begin
Line := Lines[NewLine - 1];
nMaxX := Lines.LogicalToPhysicalCol(Line, NewLine - 1, length(Line)+1);
end;
@ -581,23 +589,18 @@ end;
procedure TSynEditSelection.SetSelText(const Value : string);
begin
SetSelTextPrimitive(smNormal, PChar(Value), true);
SetSelTextPrimitive(smNormal, PChar(Value));
end;
procedure TSynEditSelection.SetSelTextPrimitive(PasteMode : TSynSelectionMode;
Value : PChar; AddToUndoList: Boolean = false;
ChangeReason: TSynChangeReason = crInsert);
Value : PChar);
var
BB, BE: TPoint;
TempString: string;
procedure DeleteSelection;
var
x, MarkOffset: Integer;
y, l, r, xb, xe, MarkOffset: Integer;
UpdateMarks: boolean;
{$IFDEF SYN_MBCSSUPPORT}
l, r: Integer;
{$ENDIF}
begin
UpdateMarks := FALSE;
MarkOffset := 0;
@ -605,62 +608,59 @@ var
smNormal:
begin
if FLines.Count > 0 then begin
// Create a string that contains everything on the first line up
// to the selection mark, and everything on the last line after
// the selection mark.
TempString := Copy(FLines[BB.Y - 1], 1, BB.X - 1) +
Copy(FLines[BE.Y - 1], BE.X, MaxInt);
// Delete all FLines in the selection range.
FLines.DeleteLines(BB.Y-1, BE.Y - BB.Y);
FLines[BB.Y - 1] := TempString;
if BE.Y > BB.Y + 1 then begin
FLines.EditLinesDelete(BB.Y + 1, BE.Y - BB.Y - 1);
BE.Y := BB.Y + 1;
end;
if BE.Y > BB.Y then begin
BE.X := BE.X + length(FLines[BB.Y - 1]);
FLines.EditLineJoin(BB.Y);
BE.Y := BB.Y;
end;
FLines.EditDelete(BB.X, BB.Y, BE.X - BB.X);
end;
UpdateMarks := TRUE;
FCaret.LineBytePos := BB;
end;
smColumn:
begin
// swap X if needed
if BB.X > BE.X then
FCaret.LineBytePos := BB;
l := FCaret.CharPos;
FCaret.LineBytePos := BE;
r := FCaret.CharPos;
// swap l, r if needed
if l > r then
{$IFDEF SYN_COMPILER_3_UP}
SwapInt(BB.X, BE.X);
SwapInt(l, r);
{$ELSE}
begin
x := BB.X;
BB.X := BE.X;
BE.X := x;
y := l;
l := r;
r := y;
end;
{$ENDIF}
for x := BB.Y - 1 to BE.Y - 1 do begin
TempString := FLines[x];
{$IFNDEF SYN_MBCSSUPPORT}
Delete(TempString, BB.X, BE.X - BB.X);
{$ELSE}
l := BB.X;
r := BE.X;
MBCSGetSelRangeInLineWhenColumnActiveSelectionMode(TempString, l, r);
{$IFDEF USE_UTF8BIDI_LCL}
VDelete(TempString, l, r - 1);
{$ELSE USE_UTF8BIDI_LCL}
Delete(TempString, l, r - l);
{$ENDIF USE_UTF8BIDI_LCL}
{$ENDIF}
FLines[x] := TempString;
for y := BB.Y to BE.Y do begin
FCaret.LineCharPos := Point(l, y);
xb := FCaret.BytePos;
FCaret.LineCharPos := Point(r, y);
xe := Min(FCaret.BytePos, 1 + length(FCaret.LineText));
if xe > xb then
FLines.EditDelete(xb, y, xe - xb);
end;
// FLines never get deleted completely, so keep caret at end.
FCaret.LineBytePos := Point(BB.X, FEndLinePos);
FCaret.LineCharPos := Point(l, FEndLinePos);
// Column deletion never removes a line entirely, so no mark
// updating is needed here.
end;
smLine:
begin
if BE.Y = FLines.Count then begin
FLines[BE.Y - 1] := '';
for x := BE.Y - 2 downto BB.Y - 1 do
FLines.Delete(x);
end else
for x := BE.Y - 1 downto BB.Y - 1 do
FLines.Delete(x);
// smLine deletion always resets to first column.
// Keep the (CrLf of) last line, since no Line exists to replace it
FLines.EditDelete(1, BE.Y, length(FLines[BE.Y - 1]));
dec(BE.Y)
end;
if BE.Y >= BB.Y then
FLines.EditLinesDelete(BB.Y, BE.Y - BB.Y + 1);
FCaret.LineCharPos := Point(1, BB.Y);
UpdateMarks := TRUE;
MarkOffset := 1;
@ -688,62 +688,43 @@ var
function InsertNormal: Integer;
var
sLeftSide: string;
sRightSide: string;
Str: string;
Start: PChar;
P: PChar;
LogCaretXY: TPoint;
PhysicalLineEndPos: LongInt;
begin
Result := 0;
LogCaretXY := FCaret.LineBytePos;
sLeftSide := Copy(FCaret.LineText, 1, LogCaretXY.X - 1);
if LogCaretXY.X - 1 > Length(sLeftSide) then begin
PhysicalLineEndPos:= FLines.LogicalToPhysicalPos
(Point(Length(sLeftSide)+1, FCaret.LinePos)).X-1;
sLeftSide := sLeftSide
+ CreateTabsAndSpaces(FCaret.CharPos,
FCaret.CharPos-1-PhysicalLineEndPos,
FTabWidth, FSpacesToTabs);
end;
sRightSide := Copy(FCaret.LineText, LogCaretXY.X,
Length(FCaret.LineText) - (LogCaretXY.X - 1));
// step1: insert the first line of Value into current line
Start := PChar(Value);
P := GetEOL(Start);
if P^ <> #0 then begin
SetString(Str, Value, P - Start);
FLines.InsertLines(FCaret.LinePos - 1, CountLines(P));
FLines[FCaret.LinePos - 1] := sLeftSide + Str;
if P^ = #0 then begin
FLines.EditInsert(LogCaretXY.X, LogCaretXY.Y, Value);
FCaret.BytePos := FCaret.BytePos + Length(Value);
end else begin
FLines[FCaret.LinePos - 1] := sLeftSide + Value + sRightSide;
FCaret.BytePos := 1 + Length(sLeftSide + Value);
end;
// step2: insert left lines of Value
while P^ <> #0 do begin
if P^ = #13 then
Inc(P);
if P^ = #10 then
Inc(P);
FCaret.LinePos := FCaret.LinePos + 1;
Start := P;
P := GetEOL(Start);
if P = Start then begin
if p^ <> #0 then
FLines[FCaret.LinePos - 1] := ''
SetString(Str, Value, P - Start);
FLines.EditInsert(LogCaretXY.X, LogCaretXY.Y, Str);
FLines.EditLineBreak(LogCaretXY.X + Length(Str), LogCaretXY.Y);
Result := CountLines(P);
if Result > 1 then
FLines.EditLinesInsert(LogCaretXY.Y + 1, Result - 1);
while P^ <> #0 do begin
if P^ = #13 then
Inc(P);
if P^ = #10 then
Inc(P);
LogCaretXY.Y := LogCaretXY.Y + 1;
Start := P;
P := GetEOL(Start);
if P <> Start then begin
SetString(Str, Start, P - Start);
FLines.EditInsert(1, LogCaretXY.Y, Str);
end
else
FLines[FCaret.LinePos - 1] := sRightSide;
end else begin
SetString(Str, Start, P - Start);
if p^ <> #0 then
FLines[FCaret.LinePos - 1] := Str
else
FLines[FCaret.LinePos - 1] := Str + sRightSide
Str := '';
end;
if p^=#0 then
FCaret.BytePos := 1 + Length(FLines[FCaret.LinePos - 1]) - Length(sRightSide);
Inc(Result);
FCaret.LinePos := LogCaretXY.Y;
FCaret.BytePos := 1 + Length(Str);
end;
end;
@ -752,40 +733,17 @@ var
Str: string;
Start: PChar;
P: PChar;
Len: Integer;
InsertPos: Integer;
LogicalInsertPos: Integer;
begin
// Insert string at current position
InsertPos := FCaret.CharPos;
Result := 0;
FCaret.IncForcePastEOL;
Start := PChar(Value);
repeat
P := GetEOL(Start);
if P <> Start then begin
SetLength(Str, P - Start);
Move(Start^, Str[1], P - Start);
if FCaret.LinePos > FLines.Count then {useless check. FCaret.LinePos cannot exceed FLines.Count}
FLines.Add(StringOfChar(' ', InsertPos - 1) + Str)
else begin
TempString := FLines[FCaret.LinePos - 1];
Len := Length(TempString);
LogicalInsertPos := FLines.PhysicalToLogicalCol(TempString,
FCaret.LinePos - 1, InsertPos);
if Len < LogicalInsertPos
then begin
TempString :=
TempString + StringOfChar(' ', LogicalInsertPos - Len - 1)
+ Str
end else begin
{$IFDEF SYN_MBCSSUPPORT}
if mbTrailByte = ByteType(TempString, InsertPos) then
Insert(Str, TempString, InsertPos + 1)
else
{$ENDIF}
System.Insert(Str, TempString, LogicalInsertPos);
end;
FLines[FCaret.LinePos - 1] := TempString;
end;
FLines.EditInsert(FCaret.BytePos, FCaret.LinePos, Str);
end;
if p^ in [#10,#13] then begin
if (p[1] in [#10,#13]) and (p[1]<>p^) then
@ -793,13 +751,14 @@ var
else
Inc(P);
if FCaret.LinePos = FLines.Count then
FLines.Add(StringOfChar(' ', InsertPos - 1));
FLines.EditLinesInsert(FCaret.LinePos + 1, 1);
// No need to inc result => adding at EOF
FCaret.LinePos := FCaret.LinePos + 1;
end;
Start := P;
until P^ = #0;
FCaret.BytePos:= FCaret.BytePos + Length(Str);
Result := 0;
FCaret.DecForcePastEOL;
end;
function InsertLine: Integer;
@ -807,7 +766,6 @@ var
Start: PChar;
P: PChar;
Str: string;
n: Integer;
begin
Result := 0;
FCaret.CharPos := 1;
@ -821,14 +779,10 @@ var
end else
Str := '';
if (P^ = #0) then begin // Not a full line?
n := FLines.Count;
if (n >= FCaret.LinePos) then
FLines[FCaret.LinePos - 1] := Str + FLines[FCaret.LinePos - 1]
else
FLines.Add(Str);
FCaret.CharPos := 1 + Length(Str);
FLines.EditInsert(1, FCaret.LinePos, Str);
FCaret.BytePos := 1 + Length(Str);
end else begin
FLines.Insert(FCaret.LinePos - 1, Str);
FLines.EditLinesInsert(FCaret.LinePos, 1, Str);
FCaret.LinePos := FCaret.LinePos + 1;
Inc(Result);
if P^ = #13 then
@ -876,8 +830,6 @@ var
FLinesInsertedMethod(StartLine, InsertedLines);
end;
var
StartInsert, EndInsert: TPoint;
begin
FLines.BeginUpdate;
try
@ -885,45 +837,11 @@ begin
BB := FirstLineBytePos;
BE := LastLineBytePos;
if SelAvail then begin
// todo: better move add-undo past actual delete
FLines[BB.Y - 1] := FLines[BB.Y - 1]; // TrimRealSpaces (in case of smNormal or smLine
if AddToUndoList then begin
if ChangeReason in [crSilentDelete, crSilentDeleteAfterCursor] then begin
if IsBackwardSel then
fUndoList.AddChange(crSilentDeleteAfterCursor, StartLineBytePos, EndLineBytePos,
GetSelText, ActiveSelectionMode)
else
fUndoList.AddChange(crSilentDelete, StartLineBytePos, EndLineBytePos,
GetSelText, ActiveSelectionMode);
end else begin
if IsBackwardSel then
fUndoList.AddChange(crDeleteAfterCursor, StartLineBytePos, EndLineBytePos,
GetSelText, ActiveSelectionMode)
else
fUndoList.AddChange(crDelete, StartLineBytePos, EndLineBytePos,
GetSelText, ActiveSelectionMode);
end;
end;
DeleteSelection;
EndLineBytePos := BB; // deletes selection // calls selection changed
end;
if (Value <> nil) and (Value[0] <> #0) then begin
StartInsert := FCaret.LineBytePos;
InsertText;
if AddToUndoList then begin
EndInsert := FCaret.LineBytePos;
if ActiveSelectionMode = smLine then begin // The ActiveSelectionMode of the deleted block
StartInsert.x := 1;
if EndInsert.x = 1 then begin
dec(EndInsert.y);
EndInsert.x := Length(FLines[EndInsert.y - 1]);
end;
end;
if ChangeReason in [crSilentDelete, crSilentDeleteAfterCursor, crDelete,
crDeleteAfterCursor] then
ChangeReason := crInsert;
fUndoList.AddChange(ChangeReason, StartInsert, EndInsert, '', PasteMode);
end;
StartLineBytePos := FCaret.LineBytePos; // reset selection
end;
finally

View File

@ -26,7 +26,7 @@ unit SynEditTextBase;
interface
uses
Classes, SysUtils, LCLProc, SynEditTypes;
Classes, SysUtils, LCLProc, SynEditTypes, SynEditMiscProcs, SynEditKeyCmds;
type
TSynEditStrings = class;
@ -37,6 +37,11 @@ type
TPhysicalCharWidths = Array of Shortint;
TSynEditUndoList = class;
TSynEditUndoItem = class;
type
{ TSynEditStrings }
TSynEditStrings = class(TStrings)
@ -52,6 +57,12 @@ type
function GetExpandedString(Index: integer): string; virtual; abstract;
function GetLengthOfLongestLine: integer; virtual; abstract;
procedure SetTextStr(const Value: string); override;
function GetRedoList: TSynEditUndoList; virtual; abstract;
function GetUndoList: TSynEditUndoList; virtual; abstract;
procedure SetIsUndoing(const AValue: Boolean); virtual; abstract;
procedure SendNotification(AReason: TSynEditNotifyReason;
ASender: TSynEditStrings; aIndex, aCount: Integer); virtual; abstract;
public
constructor Create;
procedure DeleteLines(Index, NumLines: integer); virtual; abstract;
@ -77,6 +88,18 @@ type
function PhysicalToLogicalPos(const p: TPoint): TPoint;
function PhysicalToLogicalCol(const Line: string;
Index, PhysicalPos: integer): integer; virtual;
public
procedure EditInsert(LogX, LogY: Integer; AText: String); virtual; abstract;
function EditDelete(LogX, LogY, ByteLen: Integer): String; virtual; abstract;
procedure EditLineBreak(LogX, LogY: Integer); virtual; abstract;
procedure EditLineJoin(LogY: Integer; FillText: String = ''); virtual; abstract;
procedure EditLinesInsert(LogY, ACount: Integer; AText: String = ''); virtual; abstract;
procedure EditLinesDelete(LogY, ACount: Integer); virtual; abstract;
procedure EditUndo(Item: TSynEditUndoItem); virtual; abstract;
procedure EditRedo(Item: TSynEditUndoItem); virtual; abstract;
property UndoList: TSynEditUndoList read GetUndoList;
property RedoList: TSynEditUndoList read GetRedoList;
property IsUndoing: Boolean write SetIsUndoing;
public
property ExpandedStrings[Index: integer]: string read GetExpandedString;
property LengthOfLongestLine: integer read GetLengthOfLongestLine;
@ -101,6 +124,12 @@ type
function GetExpandedString(Index: integer): string; override;
function GetLengthOfLongestLine: integer; override;
procedure SendNotification(AReason: TSynEditNotifyReason;
ASender: TSynEditStrings; aIndex, aCount: Integer); override;
function GetRedoList: TSynEditUndoList; override;
function GetUndoList: TSynEditUndoList; override;
procedure SetIsUndoing(const AValue: Boolean); override;
protected
function GetCount: integer; override;
function GetCapacity: integer;
@ -135,10 +164,114 @@ type
AHandler: TStringListLineCountEvent); override;
function GetPhysicalCharWidths(const Line: String; Index: Integer): TPhysicalCharWidths; override;
public
// LogX, LogY are 1-based
procedure EditInsert(LogX, LogY: Integer; AText: String); override;
function EditDelete(LogX, LogY, ByteLen: Integer): 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;
end;
{ TSynEditUndoItem }
TSynEditUndoItem = class(TObject)
protected
// IsEqual is only needed/implemented for Carets
function IsEqualContent(AnItem: TSynEditUndoItem): Boolean; virtual;
function IsEqual(AnItem: TSynEditUndoItem): Boolean;
public
function IsCaretInfo: Boolean; virtual;
function PerformUndo(Caller: TObject): Boolean; virtual; abstract;
end;
{ TSynEditUndoGroup }
TSynEditUndoGroup = class(TObject)
private
FItems: Array of TSynEditUndoItem;
FCount, FCapacity: Integer;
FReason: TSynEditorCommand;
function GetItem(Index: Integer): TSynEditUndoItem;
procedure Grow;
protected
Function HasUndoInfo: Boolean;
procedure Append(AnUndoGroup: TSynEditUndoItem);
procedure TranferTo(AnUndoGroup: TSynEditUndoGroup);
function CanMergeWith(AnUndoGroup: TSynEditUndoGroup): Boolean;
procedure MergeWith(AnUndoGroup: TSynEditUndoGroup);
public
constructor Create;
Destructor Destroy; override;
procedure Assign(AnUndoGroup: TSynEditUndoGroup);
procedure Add(AnItem: TSynEditUndoItem);
procedure Clear;
procedure Insert(AIndex: Integer; AnItem: TSynEditUndoItem);
function Pop: TSynEditUndoItem;
property Count: Integer read FCount;
property Items [Index: Integer]: TSynEditUndoItem read GetItem;
property Reason: TSynEditorCommand read FReason write FReason;
end;
TSynGetCaretUndoProc = function: TSynEditUndoItem of object;
{ TSynEditUndoList }
TSynEditUndoList = class(TObject)
private
FGroupUndo: Boolean;
FIsInsideRedo: Boolean;
FUndoGroup: TSynEditUndoGroup;
FInGroupCount: integer;
fFullUndoImposible: boolean;
fItems: TList;
fLockCount: integer;
fMaxUndoActions: integer;
fOnAdded: TNotifyEvent;
FOnNeedCaretUndo: TSynGetCaretUndoProc;
fUnModifiedItem: integer;
procedure EnsureMaxEntries;
function GetCanUndo: boolean;
function GetCurrentReason: TSynEditorCommand;
function GetItemCount: integer;
procedure SetCurrentReason(const AValue: TSynEditorCommand);
procedure SetMaxUndoActions(Value: integer);
function RealCount: Integer;
public
constructor Create;
destructor Destroy; override;
procedure AddChange(AChange: TSynEditUndoItem);
procedure AppendToLastChange(AChange: TSynEditUndoItem);
procedure BeginBlock;
procedure EndBlock;
procedure Clear;
procedure Lock;
function PopItem: TSynEditUndoGroup;
procedure Unlock;
function IsLocked: Boolean;
procedure MarkTopAsUnmodified;
function IsTopMarkedAsUnmodified: boolean;
function UnModifiedMarkerExists: boolean;
public
property CanUndo: boolean read GetCanUndo;
property FullUndoImpossible: boolean read fFullUndoImposible;
property ItemCount: integer read GetItemCount;
property MaxUndoActions: integer read fMaxUndoActions
write SetMaxUndoActions;
property IsInsideRedo: Boolean read FIsInsideRedo write FIsInsideRedo;
property OnAddedUndo: TNotifyEvent read fOnAdded write fOnAdded;
property OnNeedCaretUndo : TSynGetCaretUndoProc
read FOnNeedCaretUndo write FOnNeedCaretUndo;
property GroupUndo: Boolean read FGroupUndo write FGroupUndo;
property CurrentGroup: TSynEditUndoGroup read FUndoGroup;
property CurrentReason: TSynEditorCommand read GetCurrentReason
write SetCurrentReason;
end;
implementation
@ -305,6 +438,11 @@ begin
fSynStrings.InsertStrings(Index, NewStrings);
end;
procedure TSynEditStringsLinked.SetIsUndoing(const AValue: Boolean);
begin
fSynStrings.IsUndoing := AValue;
end;
function TSynEditStringsLinked.GetIsUtf8: Boolean;
begin
Result := FSynStrings.IsUtf8;
@ -347,6 +485,16 @@ begin
Result:= fSynStrings.GetLengthOfLongestLine;
end;
function TSynEditStringsLinked.GetRedoList: TSynEditUndoList;
begin
Result := fSynStrings.GetRedoList;
end;
function TSynEditStringsLinked.GetUndoList: TSynEditUndoList;
begin
Result := fSynStrings.GetUndoList;
end;
procedure TSynEditStringsLinked.RegisterAttribute(const Index: TClass; const Size: Word);
begin
fSynStrings.RegisterAttribute(Index, Size);
@ -416,5 +564,433 @@ begin
fSynStrings.EndUpdate;
end;
procedure TSynEditStringsLinked.EditInsert(LogX, LogY: Integer; AText: String);
begin
fSynStrings.EditInsert(LogX, LogY, AText);
end;
function TSynEditStringsLinked.EditDelete(LogX, LogY, ByteLen: Integer): String;
begin
Result := fSynStrings.EditDelete(LogX, LogY, ByteLen);
end;
procedure TSynEditStringsLinked.EditLineBreak(LogX, LogY: Integer);
begin
fSynStrings.EditLineBreak(LogX, LogY);
end;
procedure TSynEditStringsLinked.EditLineJoin(LogY: Integer;
FillText: String = '');
begin
fSynStrings.EditLineJoin(LogY, FillText);
end;
procedure TSynEditStringsLinked.EditLinesInsert(LogY, ACount: Integer; AText: String = '');
begin
fSynStrings.EditLinesInsert(LogY, ACount, AText);
end;
procedure TSynEditStringsLinked.EditLinesDelete(LogY, ACount: Integer);
begin
fSynStrings.EditLinesDelete(LogY, ACount);
end;
procedure TSynEditStringsLinked.EditUndo(Item: TSynEditUndoItem);
begin
fSynStrings.EditUndo(Item);
end;
procedure TSynEditStringsLinked.EditRedo(Item: TSynEditUndoItem);
begin
fSynStrings.EditRedo(Item);
end;
procedure TSynEditStringsLinked.SendNotification(AReason: TSynEditNotifyReason;
ASender: TSynEditStrings; aIndex, aCount: Integer);
begin
fSynStrings.SendNotification(AReason, ASender, aIndex, aCount);
end;
{ TSynEditUndoList }
constructor TSynEditUndoList.Create;
begin
inherited Create;
// Create and keep one undo group => avoids resizing the FItems list
FUndoGroup := TSynEditUndoGroup.Create;
FIsInsideRedo := False;
fItems := TList.Create;
fMaxUndoActions := 1024;
fUnModifiedItem:=-1;
end;
destructor TSynEditUndoList.Destroy;
begin
Clear;
fItems.Free;
FreeAndNil(FUndoGroup);
inherited Destroy;
end;
procedure TSynEditUndoList.AddChange(AChange: TSynEditUndoItem);
var
ugroup: TSynEditUndoGroup;
begin
if fLockCount > 0 then begin
AChange.Free;
exit;
end;
if FInGroupCount > 0 then
FUndoGroup.Add(AChange)
else begin
ugroup := TSynEditUndoGroup.Create;
ugroup.Add(AChange);
fItems.Add(ugroup);
if Assigned(fOnAdded) then
fOnAdded(Self);
end;
EnsureMaxEntries;
end;
procedure TSynEditUndoList.AppendToLastChange(AChange: TSynEditUndoItem);
var
cur: Boolean;
begin
cur := FUndoGroup.HasUndoInfo;
if (fLockCount <> 0) or ((fItems.Count = 0) and not cur) then begin
AChange.Free;
exit;
end;
if cur then
FUndoGroup.Append(AChange)
else
TSynEditUndoGroup(fItems[fItems.Count-1]).Append(AChange);
// Do not callback to synedit, or Redo Info is lost
EnsureMaxEntries;
end;
procedure TSynEditUndoList.BeginBlock;
begin
Inc(FInGroupCount);
if (FInGroupCount = 1) then begin
FUndoGroup.Clear;
if assigned(FOnNeedCaretUndo) then
FUndoGroup.add(FOnNeedCaretUndo());
end
end;
procedure TSynEditUndoList.Clear;
var
i: integer;
begin
for i := 0 to fItems.Count - 1 do
TSynEditUndoGroup(fItems[i]).Free;
fItems.Clear;
fFullUndoImposible := FALSE;
fUnModifiedItem:=-1;
end;
procedure TSynEditUndoList.EndBlock;
var
ugroup: TSynEditUndoGroup;
begin
if FInGroupCount > 0 then begin
Dec(FInGroupCount);
if (FInGroupCount = 0) and FUndoGroup.HasUndoInfo then
begin
// Keep position for REDO; Do not replace if present
if (not FUndoGroup.Items[FUndoGroup.Count - 1].IsCaretInfo)
and assigned(FOnNeedCaretUndo) then
FUndoGroup.Add(FOnNeedCaretUndo());
if (fItems.Count > 0) and FGroupUndo and
FUndoGroup.CanMergeWith(TSynEditUndoGroup(fItems[fItems.Count - 1])) then
begin
FUndoGroup.MergeWith(TSynEditUndoGroup(fItems[fItems.Count - 1]));
FUndoGroup.TranferTo(TSynEditUndoGroup(fItems[fItems.Count - 1]));
end else begin
ugroup := TSynEditUndoGroup.Create;
FUndoGroup.TranferTo(ugroup);
fItems.Add(ugroup);
end;
if Assigned(fOnAdded) then
fOnAdded(Self);
FIsInsideRedo := False;
end;
end;
end;
procedure TSynEditUndoList.EnsureMaxEntries;
var
Item: TSynEditUndoGroup;
begin
if fItems.Count > fMaxUndoActions then begin
fFullUndoImposible := TRUE;
while fItems.Count > fMaxUndoActions do begin
Item := TSynEditUndoGroup(fItems[0]);
Item.Free;
fItems.Delete(0);
if fUnModifiedItem>=0 then dec(fUnModifiedItem);
end;
end;
end;
function TSynEditUndoList.GetCanUndo: boolean;
begin
Result := fItems.Count > 0;
end;
function TSynEditUndoList.GetCurrentReason: TSynEditorCommand;
begin
Result := FUndoGroup.Reason;
end;
function TSynEditUndoList.GetItemCount: integer;
begin
Result := fItems.Count;
end;
procedure TSynEditUndoList.SetCurrentReason(const AValue: TSynEditorCommand);
begin
if FUndoGroup.Reason = ecNone then
FUndoGroup.Reason := AValue;
end;
procedure TSynEditUndoList.Lock;
begin
Inc(fLockCount);
end;
function TSynEditUndoList.PopItem: TSynEditUndoGroup;
var
iLast: integer;
begin
Result := nil;
iLast := fItems.Count - 1;
if iLast >= 0 then begin
Result := TSynEditUndoGroup(fItems[iLast]);
fItems.Delete(iLast);
if fUnModifiedItem>fItems.Count then
fUnModifiedItem:=-1;
end;
end;
procedure TSynEditUndoList.SetMaxUndoActions(Value: integer);
begin
if Value < 0 then
Value := 0;
if Value <> fMaxUndoActions then begin
fMaxUndoActions := Value;
EnsureMaxEntries;
end;
end;
function TSynEditUndoList.RealCount: Integer;
begin
Result := fItems.Count;
if (FInGroupCount > 0) and FUndoGroup.HasUndoInfo then
Result := Result + 1;
end;
procedure TSynEditUndoList.Unlock;
begin
if fLockCount > 0 then
Dec(fLockCount);
end;
function TSynEditUndoList.IsLocked: Boolean;
begin
Result := fLockCount > 0;
end;
procedure TSynEditUndoList.MarkTopAsUnmodified;
begin
fUnModifiedItem := RealCount;
end;
function TSynEditUndoList.IsTopMarkedAsUnmodified: boolean;
begin
Result := (RealCount = fUnModifiedItem);
end;
function TSynEditUndoList.UnModifiedMarkerExists: boolean;
begin
Result := fUnModifiedItem >= 0;
end;
{ TSynEditUndoItem }
function TSynEditUndoItem.IsEqualContent(AnItem: TSynEditUndoItem): Boolean;
begin
Result := False;
end;
function TSynEditUndoItem.IsEqual(AnItem: TSynEditUndoItem): Boolean;
begin
Result := (ClassType = AnItem.ClassType);
if Result then Result := Result and IsEqualContent(AnItem);
end;
function TSynEditUndoItem.IsCaretInfo: Boolean;
begin
Result := False;
end;
(*
function TSynEditUndoItem.ChangeStartPos: TPoint;
begin
if (fChangeStartPos.Y < fChangeEndPos.Y)
or ((fChangeStartPos.Y = fChangeEndPos.Y) and (fChangeStartPos.X < fChangeEndPos.X))
then result := fChangeStartPos
else result := fChangeEndPos;
end;
function TSynEditUndoItem.ChangeEndPos: TPoint;
begin
if (fChangeStartPos.Y < fChangeEndPos.Y)
or ((fChangeStartPos.Y = fChangeEndPos.Y) and (fChangeStartPos.X < fChangeEndPos.X))
then result := fChangeEndPos
else result := fChangeStartPos;
end;
*)
{ TSynEditUndoGroup }
procedure TSynEditUndoGroup.Grow;
begin
FCapacity := FCapacity + Max(10, FCapacity Div 8);
SetLength(FItems, FCapacity);
end;
function TSynEditUndoGroup.HasUndoInfo: Boolean;
var
i: Integer;
begin
i := 0;
while i < FCount do
if FItems[i].IsCaretInfo then
inc(i)
else
exit(true);
Result := False;
end;
procedure TSynEditUndoGroup.Append(AnUndoGroup: TSynEditUndoItem);
var
i: Integer;
begin
i := FCount - 1;
while (i >= 0) and FItems[i].IsCaretInfo do
dec(i);
inc(i);
Insert(i, AnUndoGroup);
end;
procedure TSynEditUndoGroup.TranferTo(AnUndoGroup: TSynEditUndoGroup);
begin
AnUndoGroup.Assign(self);
FCount := 0; // Do not clear; that would free the items
end;
function TSynEditUndoGroup.CanMergeWith(AnUndoGroup: TSynEditUndoGroup): Boolean;
begin
// Check if the other group can be merged to the START of this node
if AnUndoGroup.Count = 0 then exit(True);
Result := (FReason <> ecNone) and (AnUndoGroup.Reason = FReason)
and Items[0].IsCaretInfo
and AnUndoGroup.Items[AnUndoGroup.Count - 1].IsEqual(Items[0]);
end;
procedure TSynEditUndoGroup.MergeWith(AnUndoGroup: TSynEditUndoGroup);
begin
// Merge other group to start
AnUndoGroup.Pop.Free;
if AnUndoGroup.Count > 0 then begin
fItems[0].Free;
fItems[0] := AnUndoGroup.Pop;
end;
while AnUndoGroup.Count > 0 do
Insert(0, AnUndoGroup.Pop);
end;
function TSynEditUndoGroup.GetItem(Index: Integer): TSynEditUndoItem;
begin
Result := FItems[Index];
end;
constructor TSynEditUndoGroup.Create;
begin
FCount := 0;
FCapacity := 0;
end;
destructor TSynEditUndoGroup.Destroy;
begin
Clear;
FItems := nil;
inherited Destroy;
end;
procedure TSynEditUndoGroup.Assign(AnUndoGroup: TSynEditUndoGroup);
begin
Clear;
FCapacity := AnUndoGroup.Count;
FCount := FCapacity;
SetLength(FItems, FCapacity);
if FCapacity = 0 then
exit;
System.Move(AnUndoGroup.FItems[0], FItems[0], FCapacity * SizeOf(TSynEditUndoItem));
FReason := AnUndoGroup.Reason;
end;
procedure TSynEditUndoGroup.Add(AnItem: TSynEditUndoItem);
begin
if (FCount > 0) and AnItem.IsCaretInfo
and FItems[FCount - 1].IsCaretInfo then
begin
FItems[FCount - 1].Free;
FItems[FCount - 1] := AnItem;
exit;
end;
if FCount >= FCapacity then
Grow;
FItems[FCount] := AnItem;
inc (FCount);
end;
procedure TSynEditUndoGroup.Clear;
begin
while FCount > 0 do begin
dec(FCount);
FItems[FCount].Free;
end;
if FCapacity > 100 then begin
FCapacity := 100;
SetLength(FItems, FCapacity);
end;
FReason := ecNone;
end;
procedure TSynEditUndoGroup.Insert(AIndex: Integer; AnItem: TSynEditUndoItem);
begin
if FCount >= FCapacity then
Grow;
System.Move(FItems[AIndex], FItems[AIndex+1],
(FCount - AIndex) * SizeOf(TSynEditUndoItem));
FItems[AIndex] := AnItem;
inc (FCount);
end;
function TSynEditUndoGroup.Pop: TSynEditUndoItem;
begin
if FCount <= 0 then
exit(nil);
dec(FCount);
Result := FItems[FCount];
end;
end.

View File

@ -43,18 +43,13 @@ interface
uses
Classes, SysUtils, SynEditTextBase,
{$IFDEF SYN_LAZARUS}
FileUtil, LCLProc, FPCAdds, LCLIntf, LCLType,
{$ELSE}
Windows,
{$ENDIF}
SynEditTypes, SynEditMiscProcs; //mh 2000-10-19
SynEditTypes, SynEditMiscProcs;
const
NullRange = TSynEditRange(-1);
type
{begin} //mh 2000-10-10
{$IFNDEF SYN_LAZARUS}
TSynEditRange = pointer;
{$ENDIF}
TSynEditRangeClass = class end; // For Register
TSynEditFlagsClass = class end; // For Register
@ -65,26 +60,6 @@ type
);
TSynEditStringFlags = set of TSynEditStringFlag;
TSynChangeReason = (crInsert, crPaste, crDragDropInsert,
// Note: crSelDelete and crDragDropDelete have been deleted, because
// several undo entries can be chained together now via the ChangeNumber
// see also TCustomSynEdit.[Begin|End]UndoBlock methods
crDeleteAfterCursor, crDelete, {crSelDelete, crDragDropDelete, } //mh 2000-11-20
crLineBreak, crIndent, crUnindent,
crSilentDelete, crSilentDeleteAfterCursor, //mh 2000-10-30
crNothing {$IFDEF SYN_LAZARUS}, crTrimSpace, crTrimRealSpace {$ENDIF});
const
SynChangeReasonNames : Array [TSynChangeReason] of string =
('crInsert', 'crPaste', 'crDragDropInsert',
'crDeleteAfterCursor', 'crDelete', {'crSelDelete', 'crDragDropDelete', }
'crLineBreak', 'crIndent', 'crUnindent',
'crSilentDelete', 'crSilentDeleteAfterCursor',
'crNothing' {$IFDEF SYN_LAZARUS}, 'crTrimSpace', 'crTrimRealSpace' {$ENDIF});
NullRange = TSynEditRange(-1);
type
TStringListIndexEvent = procedure(Index: Integer) of object;
TSynEditStringAttribute = record
@ -97,7 +72,7 @@ type
TLineRangeNotificationList = Class(TMethodList)
public
procedure CallRangeNotifyEvents(Sender: TSynEditStrings; aIndex, aCount: Integer);
Procedure CallRangeNotifyEvents(Sender: TSynEditStrings; aIndex, aCount: Integer);
end;
@ -146,6 +121,9 @@ type
fIndexOfLongestLine: integer;
fOnChange: TNotifyEvent;
fOnChanging: TNotifyEvent;
fRedoList: TSynEditUndoList;
fUndoList: TSynEditUndoList;
FIsUndoing: Boolean;
{$IFDEF SYN_LAZARUS}
function GetFlags(Index: Integer): TSynEditStringFlags;
@ -159,6 +137,13 @@ type
fOnCleared: TNotifyEvent;
function GetExpandedString(Index: integer): string; override;
function GetLengthOfLongestLine: integer; override;
function GetRedoList: TSynEditUndoList; override;
function GetUndoList: TSynEditUndoList; override;
procedure SetIsUndoing(const AValue: Boolean); override;
procedure SendNotification(AReason: TSynEditNotifyReason;
ASender: TSynEditStrings; aIndex, aCount: Integer); override;
function GetRange(Index: integer): TSynEditRange; {$IFDEF SYN_LAZARUS}override;{$ENDIF}
procedure PutRange(Index: integer; ARange: TSynEditRange); {$IFDEF SYN_LAZARUS}override;{$ENDIF}
function GetAttribute(const Owner: TClass; const Index: Integer): Pointer; override;
@ -174,6 +159,8 @@ type
procedure SetCapacity(NewCapacity: integer);
{$IFDEF SYN_COMPILER_3_UP} override; {$ENDIF} //mh 2000-10-18
procedure SetUpdateState(Updating: Boolean); override;
procedure UndoEditLinesDelete(LogY, ACount: Integer);
public
constructor Create;
destructor Destroy; override;
@ -191,7 +178,7 @@ type
{$IFDEF SYN_LAZARUS}override;{$ENDIF}
{$IFDEF SYN_LAZARUS}
procedure ClearRanges(ARange: TSynEditRange); override;
procedure MarkModified(AFirst, ALast: Integer; AUndo: Boolean; AReason: TSynChangeReason);
procedure MarkModified(AFirst, ALast: Integer);
procedure MarkSaved;
procedure SetDebugMarks(AFirst, ALast: Integer);
procedure ClearDebugMarks;
@ -212,78 +199,22 @@ type
property Flags[Index: Integer]: TSynEditStringFlags read GetFlags
write SetFlags;
{$ENDIF}
public
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;
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;
end;
ESynEditStringList = class(Exception);
{end} //mh 2000-10-10
{ TSynEditUndoItem }
TSynEditUndoItem = class(TObject)
public
fChangeReason: TSynChangeReason;
fChangeSelMode: TSynSelectionMode;
fChangeStartPos: TPoint; // logical position (byte)
fChangeEndPos: TPoint; // logical position (byte)
fChangeStr: string;
fChangeNumber: integer; //sbs 2000-11-19
{$IFDEF SYN_LAZARUS}
function ChangeStartPos: TPoint; // logical position (byte)
function ChangeEndPos: TPoint; // logical position (byte)
{$ENDIF}
end;
{ TSynEditUndoList }
TSynEditUndoList = class(TObject)
private
fBlockChangeNumber: integer; //sbs 2000-11-19
fBlockCount: integer; //sbs 2000-11-19
fFullUndoImposible: boolean; //mh 2000-10-03
fItems: TList;
fLockCount: integer;
fMaxUndoActions: integer;
fNextChangeNumber: integer; //sbs 2000-11-19
fOnAdded: TNotifyEvent;
{$IFDEF SYN_LAZARUS}
fUnModifiedItem: integer;
{$ENDIF}
procedure EnsureMaxEntries;
function GetCanUndo: boolean;
function GetItemCount: integer;
procedure SetMaxUndoActions(Value: integer);
public
constructor Create;
destructor Destroy; override;
procedure AddChange(AReason: TSynChangeReason; AStart, AEnd: TPoint;
ChangeText: string; SelMode: TSynSelectionMode);
procedure AppendToLastChange(AReason: TSynChangeReason; AStart, AEnd: TPoint;
ChangeText: string; SelMode: TSynSelectionMode);
procedure BeginBlock; //sbs 2000-11-19
procedure Clear;
procedure EndBlock; //sbs 2000-11-19
procedure Lock;
function PeekItem: TSynEditUndoItem;
function PopItem: TSynEditUndoItem;
procedure PushItem(Item: TSynEditUndoItem);
procedure Unlock;
function IsLocked: Boolean;
{$IFDEF SYN_LAZARUS}
procedure MarkTopAsUnmodified;
function IsTopMarkedAsUnmodified: boolean;
function UnModifiedMarkerExists: boolean;
{$ENDIF}
public
property BlockChangeNumber: integer read fBlockChangeNumber //sbs 2000-11-19
write fBlockChangeNumber;
property CanUndo: boolean read GetCanUndo;
property FullUndoImpossible: boolean read fFullUndoImposible; //mh 2000-10-03
property ItemCount: integer read GetItemCount;
property MaxUndoActions: integer read fMaxUndoActions
write SetMaxUndoActions;
property OnAddedUndo: TNotifyEvent read fOnAdded write fOnAdded;
end;
implementation
{$IFNDEF FPC}
@ -297,6 +228,161 @@ const
{$ENDIF}
SListIndexOutOfBounds = 'Invalid stringlist index %d';
type
{ TSynEditUndoTxtInsert }
TSynEditUndoTxtInsert = class(TSynEditUndoItem)
private
FPosX, FPosY, FLen: Integer;
public
constructor Create(APosX, APosY, ALen: Integer);
function PerformUndo(Caller: TObject): Boolean; override;
end;
{ TSynEditUndoTxtDelete }
TSynEditUndoTxtDelete = class(TSynEditUndoItem)
private
FPosX, FPosY: Integer;
FText: String;
public
constructor Create(APosX, APosY: Integer; AText: String);
function PerformUndo(Caller: TObject): Boolean; override;
end;
{ TSynEditUndoTxtLineBreak }
TSynEditUndoTxtLineBreak = class(TSynEditUndoItem)
private
FPosY: Integer;
public
constructor Create(APosY: Integer);
function PerformUndo(Caller: TObject): Boolean; override;
end;
{ TSynEditUndoTxtLineJoin }
TSynEditUndoTxtLineJoin = class(TSynEditUndoItem)
private
FPosX, FPosY: Integer;
public
constructor Create(APosX, APosY: Integer);
function PerformUndo(Caller: TObject): Boolean; override;
end;
{ TSynEditUndoTxtLinesIns }
TSynEditUndoTxtLinesIns = class(TSynEditUndoItem)
private
FPosY, FCount: Integer;
public
constructor Create(ALine, ACount: Integer);
function PerformUndo(Caller: TObject): Boolean; override;
end;
{ TSynEditUndoTxtLinesDel }
TSynEditUndoTxtLinesDel = class(TSynEditUndoItem)
private
FPosY, FCount: Integer;
public
constructor Create(ALine, ACount: Integer);
function PerformUndo(Caller: TObject): Boolean; override;
end;
{ TSynEditUndoTxtInsert }
constructor TSynEditUndoTxtInsert.Create(APosX, APosY, ALen: Integer);
begin
FPosX := APosX;
FPosY := APosY;
FLen := ALen;
end;
function TSynEditUndoTxtInsert.PerformUndo(Caller: TObject): Boolean;
begin
Result := Caller is TSynEditStringList;
if Result then
TSynEditStringList(Caller).EditDelete(FPosX, FPosY, FLen);
end;
{ TSynEditUndoTxtDelete }
constructor TSynEditUndoTxtDelete.Create(APosX, APosY: Integer; AText: String);
begin
FPosX := APosX;
FPosY := APosY;
FText := AText;
end;
function TSynEditUndoTxtDelete.PerformUndo(Caller: TObject): Boolean;
begin
Result := Caller is TSynEditStringList;
if Result then
TSynEditStringList(Caller).EditInsert(FPosX, FPosY, FText);
end;
{ TSynEditUndoTxtLineBreak }
constructor TSynEditUndoTxtLineBreak.Create(APosY: Integer);
begin
FPosY := APosY;
end;
function TSynEditUndoTxtLineBreak.PerformUndo(Caller: TObject): Boolean;
begin
Result := Caller is TSynEditStringList;
if Result then
TSynEditStringList(Caller).EditLineJoin(FPosY)
end;
{ TSynEditUndoTxtLineJoin }
constructor TSynEditUndoTxtLineJoin.Create(APosX, APosY: Integer);
begin
FPosX := APosX;
FPosY := APosY;
end;
function TSynEditUndoTxtLineJoin.PerformUndo(Caller: TObject): Boolean;
begin
Result := Caller is TSynEditStringList;
if Result then
TSynEditStringList(Caller).EditLineBreak(FPosX, FPosY)
end;
{ TSynEditUndoTxtLinesIns }
constructor TSynEditUndoTxtLinesIns.Create(ALine, ACount: Integer);
begin
FPosY := ALine;
FCount := ACount;
end;
function TSynEditUndoTxtLinesIns.PerformUndo(Caller: TObject): Boolean;
begin
Result := Caller is TSynEditStringList;
if Result then
TSynEditStringList(Caller).UndoEditLinesDelete(FPosY, FCount)
end;
{ TSynEditUndoTxtLinesDel }
constructor TSynEditUndoTxtLinesDel.Create(ALine, ACount: Integer);
begin
FPosY := ALine;
FCount := ACount;
end;
function TSynEditUndoTxtLinesDel.PerformUndo(Caller: TObject): Boolean;
begin
Result := Caller is TSynEditStringList;
if Result then
TSynEditStringList(Caller).EditLinesInsert(FPosY, FCount)
end;
{ TSynEditStringList }
procedure ListIndexOutOfBounds(Index: integer);
@ -490,6 +576,24 @@ begin
Result := 0;
end;
function TSynEditStringList.GetRedoList: TSynEditUndoList;
begin
Result := fRedoList;
end;
function TSynEditStringList.GetUndoList: TSynEditUndoList;
begin
if FIsUndoing then
Result := fRedoList
else
Result := fUndoList;
end;
procedure TSynEditStringList.SetIsUndoing(const AValue: Boolean);
begin
FIsUndoing := AValue;
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
function TSynEditStringList.GetPhysicalCharWidths(const Line: String; Index: Integer): TPhysicalCharWidths;
@ -512,8 +616,6 @@ begin
end;
end;
{end} //mh 2000-10-19
function TSynEditStringList.GetObject(Index: integer): TObject;
begin
if (Index >= 0) and (Index < Count) then
@ -726,15 +828,11 @@ begin
Ranges[Index] := ARange;
end;
procedure TSynEditStringList.MarkModified(AFirst, ALast: Integer;
AUndo: Boolean; AReason: TSynChangeReason);
procedure TSynEditStringList.MarkModified(AFirst, ALast: Integer);
var
Index: Integer;
begin
// AUndo = True => this change is also pushed to the undo list, False => to the redo list
// AReason - a reason of change
for Index := AFirst to ALast do
for Index := AFirst - 1 to ALast - 1 do
if (Index >= 0) and (Index < Count) then
Flags[Index] := Flags[Index] + [sfModified] - [sfSaved];
end;
@ -799,257 +897,105 @@ begin
end;
end;
{ TSynEditUndoList }
constructor TSynEditUndoList.Create;
begin
inherited Create;
fItems := TList.Create;
fMaxUndoActions := 1024;
fNextChangeNumber := 1; //sbs 2000-11-19
{$IFDEF SYN_LAZARUS}
fUnModifiedItem:=-1;
{$ENDIF}
end;
destructor TSynEditUndoList.Destroy;
begin
Clear;
fItems.Free;
inherited Destroy;
end;
procedure TSynEditUndoList.AddChange(AReason: TSynChangeReason; AStart,
AEnd: TPoint; ChangeText: string; SelMode: TSynSelectionMode);
procedure TSynEditStringList.EditInsert(LogX, LogY: Integer; AText: String);
var
NewItem: TSynEditUndoItem;
s: string;
begin
if fLockCount = 0 then begin
NewItem := TSynEditUndoItem.Create;
try
with NewItem do begin
fChangeReason := AReason;
fChangeSelMode := SelMode;
fChangeStartPos := AStart;
fChangeEndPos := AEnd;
fChangeStr := ChangeText;
{begin} //sbs 2000-11-19
if fBlockChangeNumber <> 0 then
fChangeNumber := fBlockChangeNumber
else begin
fChangeNumber := fNextChangeNumber;
if fBlockCount = 0 then begin
Inc(fNextChangeNumber);
if fNextChangeNumber = 0 then
Inc(fNextChangeNumber);
end;
end;
{end} //sbs 2000-11-19
end;
(* DebugLn(['TSynEditUndoList.AddChange ChangeNumber=',NewItem.fChangeNumber,
' Reason=', SynChangeReasonNames[AReason],' Astart=',dbgs(AStart),
' AEnd=',dbgs(AEnd),' SelMode=',ord(SelMode)]); *)
PushItem(NewItem);
except
NewItem.Free;
raise;
end;
end;
s := Strings[LogY - 1];
if LogX - 1 > Length(s) then
AText := StringOfChar(' ', LogX - 1 - Length(s)) + AText;
Strings[LogY - 1] := copy(s,1, LogX - 1) + AText + copy(s, LogX, length(s));
UndoList.AddChange(TSynEditUndoTxtInsert.Create(LogX, LogY, Length(AText)));
MarkModified(LogY, LogY);
end;
procedure TSynEditUndoList.AppendToLastChange(AReason: TSynChangeReason; AStart,
AEnd: TPoint; ChangeText: string; SelMode: TSynSelectionMode);
function TSynEditStringList.EditDelete(LogX, LogY, ByteLen: Integer): String;
var
NewItem: TSynEditUndoItem;
s: string;
begin
if (fLockCount = 0) and (PeekItem <> nil) then begin
if (fItems.Count = fUnModifiedItem) then
inc(fUnModifiedItem);
NewItem := TSynEditUndoItem.Create;
try
with NewItem do begin
fChangeReason := AReason;
fChangeSelMode := SelMode;
fChangeStartPos := AStart;
fChangeEndPos := AEnd;
fChangeStr := ChangeText;
fChangeNumber := PeekItem.fChangeNumber;
end;
//PushItem(NewItem);
fItems.Add(NewItem);
EnsureMaxEntries;
except
NewItem.Free;
raise;
end;
end;
s := Strings[LogY - 1];
Result := copy(s, LogX, ByteLen);
Strings[LogY - 1] := copy(s,1, LogX - 1) + copy(s, LogX + ByteLen, length(s));
UndoList.AddChange(TSynEditUndoTxtDelete.Create(LogX, LogY, Result));
MarkModified(LogY, LogY);
end;
{begin} //sbs 2000-11-19
procedure TSynEditUndoList.BeginBlock;
begin
Inc(fBlockCount);
fBlockChangeNumber := fNextChangeNumber;
end;
{end} //sbs 2000-11-19
procedure TSynEditUndoList.Clear;
procedure TSynEditStringList.EditLineBreak(LogX, LogY: Integer);
var
i: integer;
s: string;
begin
for i := 0 to fItems.Count - 1 do
TSynEditUndoItem(fItems[i]).Free;
fItems.Clear;
fFullUndoImposible := FALSE; //mh 2000-10-03
{$IFDEF SYN_LAZARUS}
fUnModifiedItem:=-1;
{$ENDIF}
s := Strings[LogY - 1];
Strings[LogY - 1] := copy(s, 1, LogX - 1);
Insert(LogY, copy(s, LogX, length(s)));
UndoList.AddChange(TSynEditUndoTxtLineBreak.Create(LogY));
MarkModified(LogY, LogY + 1);
end;
{begin} //sbs 2000-11-19
procedure TSynEditUndoList.EndBlock;
begin
if fBlockCount > 0 then begin
Dec(fBlockCount);
if fBlockCount = 0 then begin
fBlockChangeNumber := 0;
Inc(fNextChangeNumber);
if fNextChangeNumber = 0 then
Inc(fNextChangeNumber);
end;
end;
end;
{end} //sbs 2000-11-19
procedure TSynEditUndoList.EnsureMaxEntries;
procedure TSynEditStringList.EditLineJoin(LogY: Integer; FillText: String = '');
var
Item: TSynEditUndoItem;
t: string;
begin
if fItems.Count > fMaxUndoActions then begin //mh 2000-10-03
fFullUndoImposible := TRUE; //mh 2000-10-03
while fItems.Count > fMaxUndoActions do begin
Item := TSynEditUndoItem(fItems[0]);
Item.Free;
fItems.Delete(0);
{$IFDEF SYN_LAZARUS}
if fUnModifiedItem>=0 then dec(fUnModifiedItem);
{$ENDIF}
end;
end;
t := Strings[LogY - 1];
if FillText <> '' then
EditInsert(1 + Length(t), LogY, FillText);
UndoList.AddChange(TSynEditUndoTxtLineJoin.Create(1 + Length(Strings[LogY-1]),
LogY));
Strings[LogY - 1] := t + FillText + Strings[LogY] ;
Delete(LogY);
MarkModified(LogY, LogY);
end;
function TSynEditUndoList.GetCanUndo: boolean;
procedure TSynEditStringList.EditLinesInsert(LogY, ACount: Integer;
AText: String = '');
begin
Result := fItems.Count > 0;
InsertLines(LogY - 1, ACount);
UndoList.AddChange(TSynEditUndoTxtLinesIns.Create(LogY, ACount));
if AText <> '' then
EditInsert(1, LogY, AText);
MarkModified(LogY, LogY + ACount - 1);
end;
function TSynEditUndoList.GetItemCount: integer;
begin
Result := fItems.Count;
end;
procedure TSynEditUndoList.Lock;
begin
Inc(fLockCount);
end;
function TSynEditUndoList.PeekItem: TSynEditUndoItem;
procedure TSynEditStringList.EditLinesDelete(LogY, ACount: Integer);
var
iLast: integer;
i: Integer;
begin
Result := nil;
iLast := fItems.Count - 1;
if iLast >= 0 then
Result := TSynEditUndoItem(fItems[iLast]);
for i := LogY to LogY + ACount - 1 do
EditDelete(1, i, length(Strings[i-1]));
DeleteLines(LogY - 1, ACount);
UndoList.AddChange(TSynEditUndoTxtLinesDel.Create(LogY, ACount));
end;
function TSynEditUndoList.PopItem: TSynEditUndoItem;
var
iLast: integer;
procedure TSynEditStringList.EditUndo(Item: TSynEditUndoItem);
begin
Result := nil;
iLast := fItems.Count - 1;
if iLast >= 0 then begin
Result := TSynEditUndoItem(fItems[iLast]);
fItems.Delete(iLast);
{$IFDEF SYN_LAZARUS}
if fUnModifiedItem>fItems.Count then fUnModifiedItem:=-1;
{$ENDIF}
(*DebugLn(['TSynEditUndoList.PopItem=',Result.fChangeNumber,
' Reason=', SynChangeReasonNames[Result.fChangeReason],' Astart=',dbgs(Result.fChangeStartPos),
' AEnd=',dbgs(result.fChangeEndPos),' SelMode=',ord(result.fChangeSelMode )]);*)
IsUndoing := True;
try
EditRedo(Item);
finally
IsUndoing := False;
end;
end;
procedure TSynEditUndoList.PushItem(Item: TSynEditUndoItem);
procedure TSynEditStringList.UndoEditLinesDelete(LogY, ACount: Integer);
begin
if Assigned(Item) then begin
fItems.Add(Item);
EnsureMaxEntries;
if Assigned(fOnAdded) then
fOnAdded(Self);
UndoList.AddChange(TSynEditUndoTxtLinesDel.Create(LogY, ACount));
DeleteLines(LogY - 1, ACount);
end;
procedure TSynEditStringList.EditRedo(Item: TSynEditUndoItem);
begin
Item.PerformUndo(self);
end;
procedure TSynEditStringList.SendNotification(AReason: TSynEditNotifyReason; ASender: TSynEditStrings; aIndex, aCount: Integer);
begin
case AReason of
senrLineChange:
FLineChangeNotificationList.CallRangeNotifyEvents(ASender, aIndex, aCount);
senrLineCount:
FLineRangeNotificationList.CallRangeNotifyEvents(ASender, aIndex, aCount);
end;
end;
procedure TSynEditUndoList.SetMaxUndoActions(Value: integer);
begin
if Value < 0 then
Value := 0;
if Value <> fMaxUndoActions then begin
fMaxUndoActions := Value;
EnsureMaxEntries;
end;
end;
procedure TSynEditUndoList.Unlock;
begin
if fLockCount > 0 then
Dec(fLockCount);
end;
function TSynEditUndoList.IsLocked: Boolean;
begin
Result := fLockCount > 0;
end;
{$IFDEF SYN_LAZARUS}
procedure TSynEditUndoList.MarkTopAsUnmodified;
begin
fUnModifiedItem:=fItems.Count;
end;
function TSynEditUndoList.IsTopMarkedAsUnmodified: boolean;
begin
Result:=(fItems.Count=fUnModifiedItem);
end;
function TSynEditUndoList.UnModifiedMarkerExists: boolean;
begin
Result:=fUnModifiedItem>=0;
end;
{$ENDIF}
{ TSynEditUndoItem }
{$IFDEF SYN_LAZARUS}
function TSynEditUndoItem.ChangeStartPos: TPoint;
begin
if (fChangeStartPos.Y < fChangeEndPos.Y)
or ((fChangeStartPos.Y = fChangeEndPos.Y) and (fChangeStartPos.X < fChangeEndPos.X))
then result := fChangeStartPos
else result := fChangeEndPos;
end;
function TSynEditUndoItem.ChangeEndPos: TPoint;
begin
if (fChangeStartPos.Y < fChangeEndPos.Y)
or ((fChangeStartPos.Y = fChangeEndPos.Y) and (fChangeStartPos.X < fChangeEndPos.X))
then result := fChangeEndPos
else result := fChangeStartPos;
end;
{$ENDIF}
{ TSynEditStringMemory }
type
PObject = ^TObject;

View File

@ -29,7 +29,7 @@ interface
uses
LCLProc,
Classes, SysUtils, SynEditTypes, SynEditTextBase, SynEditTextBuffer,
SynEditPointClasses;
SynEditPointClasses, SynEditMiscProcs;
type
@ -42,7 +42,6 @@ type
fCaret: TSynEditCaret;
FIsTrimming: Boolean;
FTrimType: TSynEditStringTrimmingType;
fUndoList: TSynEditUndoList;
fSpaces: String;
fLineText: String;
fLineIndex: Integer;
@ -55,9 +54,15 @@ type
procedure SetEnabled(const AValue : Boolean);
procedure SetTrimType(const AValue: TSynEditStringTrimmingType);
function TrimLine(const S : String; Index: Integer; RealUndo: Boolean = False) : String;
procedure StoreSpacesForLine(const Index: Integer; const SpaceStr, LineStr: String);
function Spaces(Index: Integer) : String;
procedure DoLinesChanged(Index, N: integer);
procedure TrimAfterLock;
procedure EditInsertTrim(LogX, LogY: Integer; AText: String);
function EditDeleteTrim(LogX, LogY, ByteLen: Integer): String;
procedure EditMoveToTrim(LogY, Len: Integer);
procedure EditMoveFromTrim(LogY, Len: Integer);
procedure UpdateLineText(LogY: Integer);
protected
function GetExpandedString(Index: integer): string; override;
function GetLengthOfLongestLine: integer; override;
@ -83,16 +88,184 @@ type
procedure Lock;
procedure UnLock;
procedure ForceTrim; // for redo; redo can not wait for UnLock
procedure UndoRealSpaces(Item: TSynEditUndoItem);
property Enabled : Boolean read fEnabled write SetEnabled;
property UndoTrimmedSpaces: Boolean read FUndoTrimmedSpaces write FUndoTrimmedSpaces;
property UndoList: TSynEditUndoList read fUndoList write fUndoList;
property IsTrimming: Boolean read FIsTrimming;
property TrimType: TSynEditStringTrimmingType read FTrimType write SetTrimType;
public
procedure EditInsert(LogX, LogY: Integer; AText: String); override;
Function EditDelete(LogX, LogY, ByteLen: Integer): 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;
end;
implementation
type
{ TSynEditUndoTrimMoveTo }
TSynEditUndoTrimMoveTo = class(TSynEditUndoItem)
private
FPosY, FLen: Integer;
public
constructor Create(APosY, ALen: Integer);
function PerformUndo(Caller: TObject): Boolean; override;
end;
{ TSynEditUndoTrimMoveFrom }
TSynEditUndoTrimMoveFrom = class(TSynEditUndoItem)
private
FPosY, FLen: Integer;
public
constructor Create(APosY, ALen: Integer);
function PerformUndo(Caller: TObject): Boolean; override;
end;
{ TSynEditUndoTrimInsert }
TSynEditUndoTrimInsert = class(TSynEditUndoItem)
private
FPosX, FPosY, FLen: Integer;
public
constructor Create(APosX, APosY, ALen: Integer);
function PerformUndo(Caller: TObject): Boolean; override;
end;
{ TSynEditUndoTrimDelete }
TSynEditUndoTrimDelete = class(TSynEditUndoItem)
private
FPosX, FPosY: Integer;
FText: String;
public
constructor Create(APosX, APosY: Integer; AText: String);
function PerformUndo(Caller: TObject): Boolean; override;
end;
{ TSynEditUndoTrimForget }
TSynEditUndoTrimForget = class(TSynEditUndoItem)
private
FPosY: Integer;
FText: String;
public
constructor Create(APosY: Integer; AText: String);
function PerformUndo(Caller: TObject): Boolean; override;
end;
{ TSynEditUndoTrimMoveTo }
constructor TSynEditUndoTrimMoveTo.Create(APosY, ALen: Integer);
begin
FPosY := APosY;
FLen := ALen;
end;
function TSynEditUndoTrimMoveTo.PerformUndo(Caller: TObject): Boolean;
begin
Result := Caller is TSynEditStringTrimmingList;
if Result then
with TSynEditStringTrimmingList(Caller) do begin
EditMoveFromTrim(FPosY, FLen);
SendNotification(senrLineChange, TSynEditStringTrimmingList(Caller),
FPosY - 1, 1);
end;
end;
{ TSynEditUndoTrimMoveFrom }
constructor TSynEditUndoTrimMoveFrom.Create(APosY, ALen: Integer);
begin
FPosY := APosY;
FLen := ALen;
end;
function TSynEditUndoTrimMoveFrom.PerformUndo(Caller: TObject): Boolean;
begin
Result := Caller is TSynEditStringTrimmingList;
if Result then
with TSynEditStringTrimmingList(Caller) do begin
EditMoveToTrim(FPosY, FLen);
SendNotification(senrLineChange, TSynEditStringTrimmingList(Caller),
FPosY - 1, 1);
end;
end;
{ TSynEditUndoTrimInsert }
constructor TSynEditUndoTrimInsert.Create(APosX, APosY, ALen: Integer);
begin
FPosX := APosX;
FPosY := APosY;
FLen := ALen;
end;
function TSynEditUndoTrimInsert.PerformUndo(Caller: TObject): Boolean;
begin
Result := Caller is TSynEditStringTrimmingList;
if Result then
with TSynEditStringTrimmingList(Caller) do begin
EditDeleteTrim(FPosX, FPosY, FLen);
SendNotification(senrLineChange, TSynEditStringTrimmingList(Caller),
FPosY - 1, 1);
end;
end;
{ TSynEditUndoTrimDelete }
constructor TSynEditUndoTrimDelete.Create(APosX, APosY: Integer; AText: String);
begin
FPosX := APosX;
FPosY := APosY;
FText := AText;
end;
function TSynEditUndoTrimDelete.PerformUndo(Caller: TObject): Boolean;
begin
Result := Caller is TSynEditStringTrimmingList;
if Result then
with TSynEditStringTrimmingList(Caller) do begin
EditInsertTrim(FPosX, FPosY, FText);
SendNotification(senrLineChange, TSynEditStringTrimmingList(Caller),
FPosY - 1, 1);
end;
end;
{ TSynEditUndoTrimForget }
constructor TSynEditUndoTrimForget.Create(APosY: Integer; AText: String);
begin
FPosY := APosY;
FText := AText;
end;
function TSynEditUndoTrimForget.PerformUndo(Caller: TObject): Boolean;
begin
Result := Caller is TSynEditStringTrimmingList;
if Result then
with TSynEditStringTrimmingList(Caller) do begin
UndoList.Lock;
EditInsertTrim(1, FPosY, FText);
UndoList.Unlock;
SendNotification(senrLineChange, TSynEditStringTrimmingList(Caller),
FPosY - 1, 1);
end;
end;
function LastNoneSpacePos(const s: String): Integer;
begin
Result := length(s);
while (Result > 0) and (s[Result] in [#9, ' ']) do dec(Result);
end;
{ TSynEditStringTrimmingList }
@ -138,8 +311,7 @@ begin
s := fSynStrings[fLineIndex];
fSynStrings[fLineIndex] := s; // trigger OnPutted, so the line gets repainted
if (fLineIndex <> TSynEditCaret(Sender).LinePos - 1) then begin
fUndoList.AppendToLastChange(crTrimSpace, Point(1+length(s), fLineIndex+1),
Point(1+length(s)+length(fSpaces), fLineIndex+1), fSpaces, smNormal);
UndoList.AppendToLastChange(TSynEditUndoTrimForget.Create(FLineIndex+1, FSpaces));
fSpaces := '';
end else begin
// same line, only right of caret
@ -150,8 +322,7 @@ begin
j := i - length(s) - 1;
s := copy(FSpaces, j + 1, MaxInt);
FSpaces := copy(FSpaces, 1, j);
fUndoList.AppendToLastChange(crTrimSpace, Point(i, fLineIndex+1),
Point(i + length(s), fLineIndex+1), s, smNormal);
UndoList.AppendToLastChange(TSynEditUndoTrimForget.Create(FLineIndex+1, s));
end;
FIsTrimming := False;
FLineEdited := False;
@ -180,21 +351,6 @@ begin
FTrimType := AValue;
end;
procedure TSynEditStringTrimmingList.UndoRealSpaces(Item: TSynEditUndoItem);
var
i: Integer;
begin
if (not fEnabled) then exit;
if length(fSynStrings.Strings[Item.fChangeStartPos.y-1]) + 1 <> Item.fChangeStartPos.x then
exit;
fSynStrings.Strings[Item.fChangeStartPos.y-1]
:= copy(fSynStrings.Strings[Item.fChangeStartPos.y-1],
1, Item.fChangeStartPos.x-1) + Item.fChangeStr;
if (fLineIndex = Item.fChangeStartPos.y-1) then fSpaces := '';
i := fLockList.IndexOfObject(TObject(Pointer(Item.fChangeStartPos.y-1)));
if i >= 0 then fLockList.Delete(i);
end;
function TSynEditStringTrimmingList.TrimLine(const S: String; Index: Integer;
RealUndo: Boolean = False): String;
var
@ -205,35 +361,40 @@ begin
if RealUndo then begin
temp := fSynStrings.Strings[Index];
l := length(temp);
i:= l;
while (i>0) and (temp[i] in [#9, ' ']) do dec(i);
i := LastNoneSpacePos(temp);
// Add RealSpaceUndo
if i < l then
fUndoList.AddChange(crTrimRealSpace, Point(i+1, Index+1),
Point(l, Index+1), copy(temp, i+1, l-i), smNormal);
EditInsertTrim(1, Index + 1,
inherited EditDelete(1 + i, Index + 1, l - i));
end;
l := length(s);
i := l;
while (i>0) and (s[i] in [#9, ' ']) do dec(i);
i := LastNoneSpacePos(s);
temp := copy(s, i+1, l-i);
if i=l then
result := s // No need to make a copy
else
result := copy(s, 1, i);
StoreSpacesForLine(Index, temp, Result);
end ;
procedure TSynEditStringTrimmingList.StoreSpacesForLine(const Index: Integer; const SpaceStr, LineStr: String);
var
i: LongInt;
begin
if fLockCount > 0 then begin
i := fLockList.IndexOfObject(TObject(pointer(Index)));
if i < 0 then
fLockList.AddObject(temp, TObject(pointer(Index)))
fLockList.AddObject(SpaceStr, TObject(pointer(Index)))
else
fLockList[i] := temp;
end
else if (fLineIndex = Index) then begin
fSpaces := temp;
fLineText:=result;
fLockList[i] := SpaceStr;
end;
end ;
if (fLineIndex = Index) then begin
fSpaces := SpaceStr;
fLineText:= LineStr;
end;
end;
function TSynEditStringTrimmingList.Spaces(Index : Integer) : String;
var
@ -267,7 +428,7 @@ begin
j := Integer(Pointer(fLockList.Objects[i]));
if (j >= Index) and (j < Index - N) then
fLockList.Delete(i)
else if j > Index then
else if j >= Index then
fLockList.Objects[i] := TObject(Pointer(j + N));
end;
end else begin
@ -295,7 +456,7 @@ end;
procedure TSynEditStringTrimmingList.TrimAfterLock;
var
i, index, llen, slen: Integer;
i, index, slen: Integer;
ltext: String;
begin
if (not fEnabled) then exit;
@ -314,10 +475,8 @@ begin
slen := length(fLockList[i]);
if (slen > 0) and (index >= 0) and (index < fSynStrings.Count) then begin
ltext := fSynStrings[index];
llen := length(ltext);
fSynStrings[index] := ltext; // trigger OnPutted, so the line gets repainted
fUndoList.AppendToLastChange(crTrimSpace, Point(1+llen, index+1),
Point(1+llen+slen, index+1), fLockList[i], smNormal);
UndoList.AppendToLastChange(TSynEditUndoTrimForget.Create(Index+1, fLockList[i]));
end;
end;
FIsTrimming := False;
@ -451,5 +610,247 @@ begin
fLineIndex := Index1;
end;
procedure TSynEditStringTrimmingList.EditInsertTrim(LogX, LogY: Integer;
AText: String);
var
s: string;
begin
if AText = '' then
exit;
s := Spaces(LogY - 1);
StoreSpacesForLine(LogY - 1,
copy(s,1, LogX - 1) + AText + copy(s, LogX, length(s)),
fSynStrings.Strings[LogY - 1]);
UndoList.AddChange(TSynEditUndoTrimInsert.Create(LogX, LogY, Length(AText)));
end;
function TSynEditStringTrimmingList.EditDeleteTrim(LogX, LogY, ByteLen:
Integer): String;
var
s: string;
begin
if ByteLen <= 0 then
exit('');
s := Spaces(LogY - 1);
Result := copy(s, LogX, ByteLen);
StoreSpacesForLine(LogY - 1,
copy(s,1, LogX - 1) + copy(s, LogX + ByteLen, length(s)),
fSynStrings.Strings[LogY - 1]);
UndoList.AddChange(TSynEditUndoTrimDelete.Create(LogX, LogY, Result));
end;
procedure TSynEditStringTrimmingList.EditMoveToTrim(LogY, Len: Integer);
var
t, s: String;
begin
if Len <= 0 then
exit;
t := fSynStrings[LogY - 1];
s := copy(t, 1 + length(t) - Len, Len) + Spaces(LogY - 1);
t := copy(t, 1, length(t) - Len);
fSynStrings[LogY - 1] := t;
StoreSpacesForLine(LogY - 1, s, t);
UndoList.AddChange(TSynEditUndoTrimMoveTo.Create(LogY, Len));
end;
procedure TSynEditStringTrimmingList.EditMoveFromTrim(LogY, Len: Integer);
var
t, s: String;
begin
if Len <= 0 then
exit;
s := Spaces(LogY - 1);
t := fSynStrings[LogY - 1] + copy(s, 1, Len);
s := copy(s, 1 + Len, Len);
fSynStrings[LogY - 1] := t;
StoreSpacesForLine(LogY - 1, s, t);
UndoList.AddChange(TSynEditUndoTrimMoveFrom.Create(LogY, Len));
end;
procedure TSynEditStringTrimmingList.UpdateLineText(LogY: Integer);
begin
if LogY - 1 = fLineIndex then
fLineText := fSynStrings[LogY - 1];
end;
procedure TSynEditStringTrimmingList.EditInsert(LogX, LogY: Integer; AText: String);
var
t: String;
Len, LenNS: Integer;
IsSpaces: Boolean;
begin
if (not fEnabled) then begin
fSynStrings.EditInsert(LogX, LogY, AText);
exit;
end;
t := Strings[LogY - 1]; // include trailing
if LogX - 1 > Length(t) then begin
AText := StringOfChar(' ', LogX - 1 - Length(t)) + AText;
LogX := 1 + Length(t);
end;
IsSpaces := LastNoneSpacePos(AText) = 0;
t := fSynStrings[LogY - 1];
Len := length(t);
LenNS := LastNoneSpacePos(t);
if (LenNS < LogX - 1) and not IsSpaces then
LenNs := LogX - 1;
// Trim any existing (commited/real) spaces // skip if we append none-spaces
if (LenNS < Len) and (IsSpaces or (LogX <= len)) then
begin
EditMoveToTrim(LogY, Len - LenNS);
Len := LenNS;
end;
if LogX > len then begin
if IsSpaces then begin
EditInsertTrim(LogX - Len, LogY, AText);
AText := '';
end else begin
// Get Fill Spaces
EditMoveFromTrim(LogY, LogX - 1 - len);
// Trim
Len := length(AText);
LenNS := LastNoneSpacePos(AText);
if LenNS < Len then begin
EditInsertTrim(1, LogY, copy(AText, 1 + LenNS, Len));
AText := copy(AText, 1, LenNS);
end;
end;
end;
if AText <> '' then
inherited EditInsert(LogX, LogY, AText)
else
SendNotification(senrLineChange, self, LogY - 1, 1);
// update spaces
UpdateLineText(LogY);
end;
Function TSynEditStringTrimmingList.EditDelete(LogX, LogY, ByteLen
: Integer): String;
var
t: String;
Len: Integer;
begin
if (not fEnabled) then begin
fSynStrings.EditDelete(LogX, LogY, ByteLen);
exit;
end;
Result := '';
t := fSynStrings[LogY - 1];
Len := length(t);
// Delete uncommited spaces
if LogX + ByteLen > Len + 1 then begin
if LogX > Len + 1 then
ByteLen := ByteLen - (LogX - (Len + 1));
Result := EditDeleteTrim(max(LogX - Len, 1), LogY, LogX - 1 + ByteLen - Len);
ByteLen := Len + 1 - LogX;
end;
if ByteLen > 0 then
Result := inherited EditDelete(LogX, LogY, ByteLen) + Result
else
SendNotification(senrLineChange, self, LogY - 1, 1);
UpdateLineText(LogY);
// Trim any existing (commited/real) spaces
t := fSynStrings[LogY - 1];
EditMoveToTrim(LogY, length(t) - LastNoneSpacePos(t));
end;
procedure TSynEditStringTrimmingList.EditLineBreak(LogX, LogY: Integer);
var
s, t: string;
begin
if (not fEnabled) then begin
fSynStrings.EditLineBreak(LogX, LogY);
exit;
end;
s := Spaces(LogY - 1);
t := fSynStrings[LogY - 1];
if LogX > length(t) then begin
fSynStrings.EditLineBreak(1 + length(t), LogY);
if s <> '' then
s := EditDeleteTrim(LogX - length(t), LogY, length(s) - (LogX - 1 - length(t)));
end
else begin
s := EditDeleteTrim(1, LogY, length(s));
fSynStrings.EditLineBreak(LogX, LogY);
end;
DoLinesChanged(LogY, 1);
UpdateLineText(LogY + 1);
EditInsertTrim(1, LogY + 1, s);
// Trim any existing (commited/real) spaces
s := fSynStrings[LogY - 1];
EditMoveToTrim(LogY, length(s) - LastNoneSpacePos(s));
s := fSynStrings[LogY];
EditMoveToTrim(LogY + 1, length(s) - LastNoneSpacePos(s));
end;
procedure TSynEditStringTrimmingList.EditLineJoin(LogY: Integer;
FillText: String = '');
var
s: String;
begin
if (not fEnabled) then begin
fSynStrings.EditLineJoin(LogY, FillText);
exit;
end;
EditMoveFromTrim(LogY, length(Spaces(LogY - 1)));
s := EditDeleteTrim(1, LogY + 1, length(Spaces(LogY))); // next line
//Todo: if FillText isSpacesOnly AND NextLineIsSpacesOnly => add direct to trailing
fSynStrings.EditLineJoin(LogY, FillText);
DoLinesChanged(LogY - 1, -1);
UpdateLineText(LogY);
EditInsertTrim(1, LogY, s);
// Trim any existing (commited/real) spaces
s := fSynStrings[LogY - 1];
EditMoveToTrim(LogY, length(s) - LastNoneSpacePos(s));
end;
procedure TSynEditStringTrimmingList.EditLinesInsert(LogY, ACount: Integer;
AText: String = '');
var
s: string;
begin
fSynStrings.EditLinesInsert(LogY, ACount, AText);
s := fSynStrings[LogY - 1];
EditMoveToTrim(LogY, length(s) - LastNoneSpacePos(s));
end;
procedure TSynEditStringTrimmingList.EditLinesDelete(LogY, ACount: Integer);
var
i: Integer;
begin
for i := LogY to LogY + ACount - 1 do
EditMoveFromTrim(i, length(Spaces(i - 1)));
fSynStrings.EditLinesDelete(LogY, ACount);
end;
procedure TSynEditStringTrimmingList.EditUndo(Item: TSynEditUndoItem);
begin
IsUndoing := True;
try
EditRedo(Item);
finally
IsUndoing := False;
end;
end;
procedure TSynEditStringTrimmingList.EditRedo(Item: TSynEditUndoItem);
begin
if not Item.PerformUndo(self) then
inherited EditRedo(Item);
end;
end.

View File

@ -62,7 +62,7 @@ type
// to be binary (clipboard) compatible with other (Delphi compiled) synedits
// use {$PACKENUM 1}
{$IFDEF SYN_LAZARUS}{$PACKENUM 1}{$ENDIF SYN_LAZARUS}
TSynSelectionMode = (smNormal, smLine, smColumn);
TSynSelectionMode = (smNormal, smLine, smColumn, smCurrent);
{$IFDEF SYN_LAZARUS}{$PACKENUM 4}{$ENDIF SYN_LAZARUS}
TSynSearchOption = (ssoMatchCase, ssoWholeWord, ssoBackwards,