SynEdit: improved syncro/template edit to deal with adjacent cells. (no text between cells)

git-svn-id: trunk@60079 -
This commit is contained in:
martin 2019-01-14 01:53:46 +00:00
parent 02479b4a1f
commit 507784f308
4 changed files with 254 additions and 28 deletions

View File

@ -68,7 +68,9 @@ type
senrBeforeDecPaintLock,
senrAfterDecPaintLock,
senrTextBufferChanging, // About to change
senrTextBufferChanged
senrTextBufferChanged,
senrBeginUndoRedo,
senrEndUndoRedo
);
TPhysicalCharWidth = Byte;
@ -998,7 +1000,7 @@ end;
procedure TSynEditStrings.AddNotifyHandler(AReason: TSynEditNotifyReason;
AHandler: TNotifyEvent);
begin
assert(AReason in [senrCleared..senrTextBufferChanged], 'AddNotifyHandler');
assert(AReason in [senrCleared..senrEndUndoRedo], 'AddNotifyHandler');
AddGenericHandler(AReason, TMethod(AHandler));
end;
@ -1019,7 +1021,7 @@ end;
procedure TSynEditStrings.RemoveNotifyHandler(AReason: TSynEditNotifyReason;
AHandler: TNotifyEvent);
begin
assert(AReason in [senrCleared..senrTextBufferChanged], 'RemoveNotifyHandler');
assert(AReason in [senrCleared..senrEndUndoRedo], 'RemoveNotifyHandler');
RemoveGenericHandler(AReason, TMethod(AHandler));
end;

View File

@ -802,7 +802,16 @@ end;
procedure TSynEditStringList.SetIsUndoing(const AValue: Boolean);
begin
if FIsUndoing = AValue then
exit;
if not AValue then
SendNotification(senrEndUndoRedo, Self); // before UNDO ends
FIsUndoing := AValue;
if AValue then
SendNotification(senrBeginUndoRedo, Self); // after UNDO started
end;
function TSynEditStringList.GetIsUndoing: Boolean;
@ -812,7 +821,16 @@ end;
procedure TSynEditStringList.SetIsRedoing(const AValue: Boolean);
begin
if FIsRedoing = AValue then
exit;
if not AValue then
SendNotification(senrEndUndoRedo, Self); // before UNDO ends
FIsRedoing := AValue;
if AValue then
SendNotification(senrBeginUndoRedo, Self); // after UNDO started
end;
function TSynEditStringList.GetIsRedoing: Boolean;

View File

@ -28,7 +28,7 @@ interface
uses
Classes, SysUtils, Graphics, LCLProc,
SynEditMiscClasses, SynEdit, SynEditMarkup, SynEditMiscProcs, LazSynEditText,
SynEditTextTrimmer, SynEditKeyCmds;
SynEditTextTrimmer, SynEditKeyCmds, SynEditTextBase;
type
@ -72,7 +72,8 @@ type
function AddNew: TSynPluginSyncronizedEditCell; virtual;
procedure Delete(aIndex: Integer);
function IndexOf(aCell: TSynPluginSyncronizedEditCell): Integer;
function IndexOf(aX, aY: Integer; IncludeLast: Boolean = False): Integer;
function IndexOf(aX, aY: Integer; IncludeLast: Boolean = False; aCurrentCellIdx: Integer = -1): Integer;
function IsInCell(aX, aY, ACellIdx: Integer; IncludeLast: Boolean = False): Boolean;
function Count: Integer;
function GroupCount(aGroup: Integer): Integer;
property Cell[aIndex: Integer]: TSynPluginSyncronizedEditCell
@ -148,6 +149,23 @@ type
property CellGroupForArea: Integer read FCellIdForArea write FCellIdForArea;
end;
TSynPluginSyncronizedEditBase = class;
{ TSynPluginSyncronizedEditUndoCurrentCell }
TSynPluginSyncronizedEditUndoCurrentCell = class(TSynEditUndoItem)
private
FCurrentCell: Integer;
FOwner: TSynPluginSyncronizedEditBase;
protected
function IsEqualContent(AnItem: TSynEditUndoItem): Boolean; override;
function DebugString: String; override;
public
//function IsCaretInfo: Boolean; override; // TODO: maybe indicator for TSynEditUndoGroup.HasUndoInfo
constructor Create(ACurrentCell: Integer; AOwner: TSynPluginSyncronizedEditBase);
function PerformUndo(Caller: TObject): Boolean; override;
end;
{ TSynPluginSyncronizedEditChangeAction }
TSynPluginSyncronizedEditChangeAction = record
@ -179,6 +197,9 @@ type
FActive: Boolean;
FCells: TSynPluginSyncronizedEditList;
FCurrentCell: Integer;
FUndoingCurrentCell: Integer;
FDependsOnCurrentCell, FJustStarted: Boolean;
FActiveInUndo: Boolean;
FChangeList: TSynPluginSyncronizedEditChangeList;
FAreaMarkupEnabled: Boolean;
FMarkupEnabled: Boolean;
@ -196,6 +217,8 @@ type
FOnDeactivate: TNotifyEvent;
FPreActive: Boolean;
procedure DoUndoBegining(Sender: TObject);
procedure DoUndoEnding(Sender: TObject);
function GetActive: Boolean;
procedure SetActive(const AValue: Boolean);
procedure SetCurrentCell(const AValue: Integer);
@ -206,6 +229,7 @@ type
procedure SetMarkupInfoArea(AValue: TSynSelectedColor);
procedure SetMarkupInfoCurrent(AValue: TSynSelectedColor);
procedure SetMarkupInfoSync(AValue: TSynSelectedColor);
function IsCellFromCaretAmbigious: Boolean;
protected
FMarkup: TSynPluginSyncronizedEditMarkup;
FMarkupArea: TSynPluginSyncronizedEditMarkupArea;
@ -298,6 +322,55 @@ begin
(c1.Group = c2.Group);
end;
{ TSynPluginSyncronizedEditUndoCurrentCell }
function TSynPluginSyncronizedEditUndoCurrentCell.IsEqualContent(
AnItem: TSynEditUndoItem): Boolean;
begin
Result := (FCurrentCell = TSynPluginSyncronizedEditUndoCurrentCell(AnItem).FCurrentCell) and
(FOwner = TSynPluginSyncronizedEditUndoCurrentCell(AnItem).FOwner);
end;
function TSynPluginSyncronizedEditUndoCurrentCell.DebugString: String;
begin
Result := ClassName + ' CurCell=' + IntToStr(FCurrentCell);
end;
constructor TSynPluginSyncronizedEditUndoCurrentCell.Create(
ACurrentCell: Integer; AOwner: TSynPluginSyncronizedEditBase);
begin
FCurrentCell := ACurrentCell;
FOwner := AOwner;
end;
function TSynPluginSyncronizedEditUndoCurrentCell.PerformUndo(Caller: TObject
): Boolean;
var
i: Integer;
begin
i := TCustomSynEdit(Caller).PluginCount;
Result := False;
while (i > 0) and (not Result) do begin
dec(i);
Result := (TCustomSynEdit(Caller).Plugin[i] = FOwner);
end;
if not (Result and
(TObject(FOwner) is TSynPluginSyncronizedEditBase) and // in case a new plugin is at the same address
(FOwner.Active) // at the current point of undo-history only the real owner can be active
)
then
exit;
if (FOwner.FUndoingCurrentCell >= 0) and
(FOwner.FUndoingCurrentCell <> FCurrentCell)
then
FOwner.ViewedTextBuffer.CurUndoList.AddChange(
TSynPluginSyncronizedEditUndoCurrentCell.Create(FOwner.FUndoingCurrentCell, FOwner));
FOwner.FUndoingCurrentCell := FCurrentCell;
FOwner.FCurrentCell := FCurrentCell;
end;
{ TSynPluginSyncronizedEditList }
constructor TSynPluginSyncronizedEditList.Create;
@ -398,10 +471,17 @@ begin
Result := -1;
end;
function TSynPluginSyncronizedEditList.IndexOf(aX, aY: Integer; IncludeLast: Boolean): Integer;
function TSynPluginSyncronizedEditList.IndexOf(aX, aY: Integer;
IncludeLast: Boolean; aCurrentCellIdx: Integer): Integer;
var
a, i: Integer;
begin
// If 2 cells touch, prefer current cell
if (aCurrentCellIdx >= 0) and IsInCell(aX, aY, aCurrentCellIdx, IncludeLast) then begin
Result := aCurrentCellIdx;
exit;
end;
if IncludeLast then
a := 1
else
@ -418,6 +498,23 @@ begin
Result := -1;
end;
function TSynPluginSyncronizedEditList.IsInCell(aX, aY, ACellIdx: Integer;
IncludeLast: Boolean): Boolean;
var
a: Integer;
begin
if IncludeLast then
a := 1
else
a := 0;;
Result :=
(FCells[ACellIdx].Group >= 0) and
( (FCells[ACellIdx].LogStart.Y < aY) or
((FCells[ACellIdx].LogStart.Y = aY) and (FCells[ACellIdx].LogStart.X <= aX)) ) and
( (FCells[ACellIdx].LogEnd.Y > aY) or
((FCells[ACellIdx].LogEnd.Y = aY) and (FCells[ACellIdx].LogEnd.X + a > aX)) );
end;
function TSynPluginSyncronizedEditList.Count: Integer;
begin
Result := length(FCells);
@ -777,6 +874,8 @@ begin
ViewedTextBuffer.RemoveNotifyHandler(senrTextBufferChanging, @DoBufferChanging);
ViewedTextBuffer.RemoveNotifyHandler(senrTextBufferChanged, @DoBufferChanged);
end;
ViewedTextBuffer.RemoveNotifyHandler(senrBeginUndoRedo, @DoUndoBegining);
ViewedTextBuffer.RemoveNotifyHandler(senrEndUndoRedo, @DoUndoEnding);
ViewedTextBuffer.RemoveEditHandler(@DoLinesEdited);
ViewedTextBuffer.RemoveNotifyHandler(senrAfterIncPaintLock, @DoIncPaintLock);
ViewedTextBuffer.RemoveNotifyHandler(senrBeforeDecPaintLock, @DoDecPaintLock);
@ -809,6 +908,8 @@ begin
ViewedTextBuffer.AddEditHandler(@DoLinesEdited);
ViewedTextBuffer.AddNotifyHandler(senrAfterIncPaintLock, @DoIncPaintLock);
ViewedTextBuffer.AddNotifyHandler(senrBeforeDecPaintLock, @DoDecPaintLock);
ViewedTextBuffer.AddNotifyHandler(senrBeginUndoRedo, @DoUndoBegining);
ViewedTextBuffer.AddNotifyHandler(senrEndUndoRedo, @DoUndoEnding);
if not FTextBufferChanging then begin
ViewedTextBuffer.AddNotifyHandler(senrTextBufferChanging, @DoBufferChanging);
ViewedTextBuffer.AddNotifyHandler(senrTextBufferChanged, @DoBufferChanged);
@ -903,6 +1004,26 @@ begin
FMarkupInfoSync.Assign(AValue);
end;
function TSynPluginSyncronizedEditBase.IsCellFromCaretAmbigious: Boolean;
var
c: TSynPluginSyncronizedEditCell;
p: TPoint;
begin
p := CaretObj.LineBytePos;
if (CurrentCell < 0) then
Result := Cells.IndexOf(p.x, p.y, True) >= 0
else begin
c := Cells[CurrentCell];
Result := (
(CurrentCell > 0) and (CompareCarets(c.LogStart, p) = 0) and
(CompareCarets(Cells[CurrentCell - 1].LogEnd, p) = 0)
) or (
(CurrentCell < Cells.Count-1) and (CompareCarets(c.LogEnd, p) = 0) and
(CompareCarets(Cells[CurrentCell + 1].LogStart, p) = 0)
);
end;
end;
procedure TSynPluginSyncronizedEditBase.MarkupChanged(AMarkup: TObject);
begin
if FMarkup <> nil then begin
@ -924,6 +1045,32 @@ begin
Result := FActive and FEnabled and (Editor <> nil);
end;
procedure TSynPluginSyncronizedEditBase.DoUndoBegining(Sender: TObject);
begin
FActiveInUndo := Active;
if not FActiveInUndo then
exit;
ViewedTextBuffer.CurUndoList.BeginBlock;
FUndoingCurrentCell := -1;
end;
procedure TSynPluginSyncronizedEditBase.DoUndoEnding(Sender: TObject);
begin
if not FActiveInUndo then
exit;
if FUndoingCurrentCell >= 0 then begin
// AppendToLastChange: Add before the caret undo info, otherwise another caret info will be added (at this point the current undo group must have some items)
ViewedTextBuffer.CurUndoList.AppendToLastChange(
TSynPluginSyncronizedEditUndoCurrentCell.Create(FUndoingCurrentCell, Self));
end;
FDependsOnCurrentCell := False; // Prevent ApplyChangeList from adding undo info
FUndoingCurrentCell := -1;
ViewedTextBuffer.CurUndoList.EndBlock;
end;
procedure TSynPluginSyncronizedEditBase.SetActive(const AValue: Boolean);
var
IsActive: Boolean;
@ -981,7 +1128,8 @@ var
CurCell: TSynPluginSyncronizedEditCell;
chg: Boolean;
edit: Boolean;
CellAtPos: Integer;
CellAtPos, CellCnt: Integer;
LastCellEndPoint, CurCellStartPos: TPoint;
begin
if not Active then begin
if PreActive then DoPreActiveEdit(aBytePos, aLinePos, aCount, aLineBrkCnt, IsUndoing or IsRedoing);
@ -991,25 +1139,59 @@ begin
Pos2 := Pos;
if not FEditing then
DoBeforeEdit(Pos.x, Pos.y, aCount, aLineBrkCnt, IsUndoing or IsRedoing);
CellAtPos := Cells.IndexOf(Pos.x, Pos.y, True);
// Todo: need do add undo info (start/stop flag),
// so we know which group (if any) this applies to
edit := FEditing or IsUndoing or IsRedoing;
for i := 0 to FCells.Count - 1 do begin
if (not edit) then begin
CellAtPos := Cells.IndexOf(Pos.x, Pos.y, True, CurrentCell);
if FJustStarted and IsCellFromCaretAmbigious // and not IsUndoing
then
FDependsOnCurrentCell := True;
end;
FJustStarted := False;
CurCellStartPos := Point(-1, -1);
if FCurrentCell >= 0 then
CurCellStartPos := Cells[FCurrentCell].LogStart;
LastCellEndPoint := Point(-1, -1);
CellCnt := FCells.Count - 1;
for i := 0 to CellCnt do begin
CurCell := Cells[i];
chg := False;
a := CompareCarets(Pos, CurCell.LogStart);
if (a = 0) and (CompareCarets(LastCellEndPoint, CurCell.LogStart) = 0) then begin
a := 1;
FDependsOnCurrentCell := True;
end;
if (a > 0) then
CurCell.LogStart := AdjustPoint(CurCell.LogStart, chg);
a := CompareCarets(Pos, CurCell.LogEnd);
if (a > 0) or ((a = 0) and ((i = FCurrentCell) or (CurCell.Group = -1) or edit)) then
if (a = 0) then begin
if (CompareCarets(CurCell.LogEnd, CurCellStartPos) <> 0) or
(CurCell.Group=-1)
then
inc(a)
else
begin
FDependsOnCurrentCell := True;
if (i >= CurrentCell) then
inc(a);
end;
end;
if (a > 0) //or
then begin
if CurCell.Group <> -1 then
LastCellEndPoint := CurCell.LogEnd; // If this is adjusted, the start of the next cell (if equal to this) must be adjusted too
CurCell.LogEnd := AdjustPoint(CurCell.LogEnd, chg);
end;
if chg then
Cells[i] := CurCell;
end;
if (not (FEditing or IsUndoing or IsRedoing)) and
if (not edit) and
(CellAtPos >= 0) and (CellAtPos < Cells.Count) and
(CompareCarets(Pos, FCells[CellAtPos].LogStart) <= 0) and
(CompareCarets(Pos, FCells[CellAtPos].LogEnd) >= 0)
@ -1033,23 +1215,35 @@ procedure TSynPluginSyncronizedEditBase.ApplyChangeList;
var
Action: TSynPluginSyncronizedEditChangeAction;
a, i: Integer;
Group, Y2, X2: Integer;
Group, Y2, X2, CurCell, LastUndoCurCell: Integer;
Cell: TSynPluginSyncronizedEditCell;
begin
LastUndoCurCell := -1;
if FDependsOnCurrentCell then begin
ViewedTextBuffer.CurUndoList.AddChange(
TSynPluginSyncronizedEditUndoCurrentCell.Create(FCurrentCell, Self));
LastUndoCurCell := FCurrentCell;
end;
if FChangeList.Count = 0 then
exit;
FEditing := True;
ViewedTextBuffer.BeginUpdate;
CaretObj.IncAutoMoveOnEdit;
CurCell := FCurrentCell; // direct access / markup does not need to know
try
for a := 0 to FChangeList.Count - 1 do begin
Action := FChangeList[a];
Group := FCells[Action.CellIndex].Group;
for i := 0 to FCells.Count - 1 do begin
Cell := FCells[i];
for i := 0 to FCells.Count - 1 do begin
FDependsOnCurrentCell := False;
Cell := FCells[i];
for a := 0 to FChangeList.Count - 1 do begin
Action := FChangeList[a];
Group := FCells[Action.CellIndex].Group;
if (i = Action.CellIndex) or (Cell.Group <> Group) then
continue;
FCurrentCell := i; // direct access / markup does not need to know
if Cell.LogStart.Y = Cell.LogEnd.Y then
X2 := Cell.LogStart.X + Action.cBytePos
else
@ -1083,8 +1277,19 @@ begin
ViewedTextBuffer.EditInsert(X2, Y2, Action.Text);
end;
end;
if FDependsOnCurrentCell then begin
ViewedTextBuffer.CurUndoList.AddChange(
TSynPluginSyncronizedEditUndoCurrentCell.Create(FCurrentCell, Self));
LastUndoCurCell := FCurrentCell;
end;
end;
finally
FCurrentCell := CurCell; // direct access / markup does not need to know
// for redo
if IsCellFromCaretAmbigious and (FCurrentCell <> LastUndoCurCell) then
ViewedTextBuffer.CurUndoList.AddChange(
TSynPluginSyncronizedEditUndoCurrentCell.Create(FCurrentCell, Self));
FEditing := False;
CaretObj.DecAutoMoveOnEdit;
ViewedTextBuffer.EndUpdate;
@ -1110,12 +1315,13 @@ end;
procedure TSynPluginSyncronizedEditBase.DoPaintLockStarted;
begin
(* Do Nothing *);
FDependsOnCurrentCell := False;
FJustStarted := True;
end;
procedure TSynPluginSyncronizedEditBase.DoPaintLockEnded;
begin
(* Do Nothing *);
FJustStarted := False;
end;
procedure TSynPluginSyncronizedEditBase.DoClear;
@ -1247,11 +1453,11 @@ begin
- User edit outside a cell (both locations will be outside the cell => deactivate
TODO: Hook SynEdits Lock, and check Caret before locking only
*)
c1 := Cells.IndexOf(aX, aY, True);
c1 := Cells.IndexOf(aX, aY, True, CurrentCell);
if aLineBrkCnt < 0 then
c2 := Cells.IndexOf(1, aY-aLineBrkCnt, True)
c2 := Cells.IndexOf(1, aY-aLineBrkCnt, True, CurrentCell)
else if aCount < 0 then
c2 := Cells.IndexOf(aX - aCount, aY, True)
c2 := Cells.IndexOf(aX - aCount, aY, True, CurrentCell)
else
c2 := c1;
// allow edit outside cell? (only if not partly cell / part cell updates are not allowed at all)
@ -1297,7 +1503,7 @@ procedure TSynPluginCustomSyncroEdit.UpdateCurrentCell;
var
i: Integer;
begin
i := Cells.IndexOf(CaretObj.BytePos, CaretObj.LinePos, True);
i := Cells.IndexOf(CaretObj.BytePos, CaretObj.LinePos, True, CurrentCell);
if (i <> CurrentCell) and (CurrentCell >= 0) then
FLastCell := CurrentCell;
CurrentCell := i;
@ -1333,7 +1539,7 @@ var
Pos: TPoint;
begin
Pos := CaretObj.LineBytePos;
i := Cells.IndexOf(Pos.x, Pos.y, True);
i := Cells.IndexOf(Pos.x, Pos.y, True, CurrentCell);
if i < 0 then begin
x := -1;
i := 0;
@ -1374,7 +1580,7 @@ var
i, j, x: Integer;
begin
Pos := CaretObj.LineBytePos;
i := Cells.IndexOf(Pos.x, Pos.y, True);
i := Cells.IndexOf(Pos.x, Pos.y, True, CurrentCell);
if i < 0 then begin
x := -1;
i := Cells.Count - 1;

View File

@ -225,7 +225,7 @@ var
i: Integer;
begin
Pos := CaretObj.LineBytePos;
i := Cells.IndexOf(Pos.x, Pos.y, True);
i := Cells.IndexOf(Pos.x, Pos.y, True, CurrentCell);
if i < 0 then begin
i := Cells.Count - 1;
while (i >= 0) and