{------------------------------------------------------------------------------- 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. 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. -------------------------------------------------------------------------------} unit SynPluginSyncronizedEditBase; {$mode objfpc}{$H+} interface uses Classes, SysUtils, Graphics, LCLProc, SynEditMiscClasses, SynEdit, SynEditMarkup, SynEditMiscProcs, LazSynEditText, SynEditTextTrimmer, SynEditKeyCmds, SynEditTextBase; type { TSynPluginSyncronizedEditCell } TSynPluginSyncronizedEditCell = class private FFirstInGroup: Boolean; FLogStart, FLogEnd: TPoint; FGroup: Integer; public constructor Create; procedure Assign(Src: TSynPluginSyncronizedEditCell); reintroduce; property LogStart: TPoint read FLogStart write FLogStart; property LogEnd: TPoint read FLogEnd write FLogEnd; property Group: Integer read FGroup write FGroup; property FirstInGroup: Boolean read FFirstInGroup write FFirstInGroup; end; TSynPluginSyncronizedEditCellChangedEvent = procedure(aIndex: Integer; aOldVal, aNewVal: TSynPluginSyncronizedEditCell) of object; { TSynPluginSyncronizedEditList } TSynPluginSyncronizedEditList = class private FCells: Array of TSynPluginSyncronizedEditCell; FOnCellChange: TSynPluginSyncronizedEditCellChangedEvent; function GetCell(aIndex: Integer): TSynPluginSyncronizedEditCell; function GetGroupCell(aGroup, aIndex: Integer): TSynPluginSyncronizedEditCell; procedure SetCell(aIndex: Integer; const AValue: TSynPluginSyncronizedEditCell); protected property OnCellChange: TSynPluginSyncronizedEditCellChangedEvent // For Markup read FOnCellChange write FOnCellChange; public constructor Create; destructor Destroy; override; procedure Clear; function Add(aCell: TSynPluginSyncronizedEditCell): Integer; function AddNew: TSynPluginSyncronizedEditCell; virtual; procedure Delete(aIndex: Integer); function IndexOf(aCell: TSynPluginSyncronizedEditCell): 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 read GetCell write SetCell; default; property GroupCell[aGroup, aIndex: Integer]: TSynPluginSyncronizedEditCell read GetGroupCell; end; { TSynPluginSyncronizedEditMarkupBase } TSynPluginSyncronizedEditMarkupBase = class(TSynEditMarkup) private FCells: TSynPluginSyncronizedEditList; procedure SetCells(const AValue: TSynPluginSyncronizedEditList); protected procedure CellChanged(aIndex: Integer; aOldVal, aNewVal: TSynPluginSyncronizedEditCell); virtual; abstract; function OwnedByMgr: Boolean; override; procedure DoEnabledChanged(Sender: TObject); override; property Cells: TSynPluginSyncronizedEditList read FCells write SetCells; public constructor Create(ASynEdit: TSynEditBase); destructor Destroy; override; procedure DoInvalidate; end; { TSynPluginSyncronizedEditMarkup } TSynPluginSyncronizedEditMarkup = class(TSynPluginSyncronizedEditMarkupBase) private FCurrentCell: Integer; fMarkupInfoCurrent: TSynSelectedColor; fMarkupInfoSync: TSynSelectedColor; FPreparedRow: Integer; FPreparedCellFrom, FPreparedCellTo: Integer; FPreparedCellTop, FPreparedCellBottom: Integer; procedure SetCurrentCell(const AValue: Integer); protected procedure CellChanged(aIndex: Integer; aOldVal, aNewVal: TSynPluginSyncronizedEditCell); override; property CurrentCell: Integer read FCurrentCell write SetCurrentCell; public constructor Create(ASynEdit: TSynEditBase); destructor Destroy; override; function GetMarkupAttributeAtRowCol(const aRow: Integer; const aStartCol: TLazSynDisplayTokenBound; const AnRtlInfo: TLazSynDisplayRtlInfo): TSynSelectedColor; override; procedure GetNextMarkupColAfterRowCol(const aRow: Integer; const aStartCol: TLazSynDisplayTokenBound; const AnRtlInfo: TLazSynDisplayRtlInfo; out ANextPhys, ANextLog: Integer); override; Procedure PrepareMarkupForRow(aRow : Integer); override; Procedure EndMarkup; override; property MarkupInfoCurrent: TSynSelectedColor read fMarkupInfoCurrent; property MarkupInfoSync: TSynSelectedColor read fMarkupInfoSync; end; { TSynPluginSyncronizedEditMarkupArea } TSynPluginSyncronizedEditMarkupArea = class(TSynPluginSyncronizedEditMarkupBase) private FCellIdForArea: Integer; protected procedure CellChanged(aIndex: Integer; aOldVal, aNewVal: TSynPluginSyncronizedEditCell); override; public function GetMarkupAttributeAtRowCol(const aRow: Integer; const aStartCol: TLazSynDisplayTokenBound; const AnRtlInfo: TLazSynDisplayRtlInfo): TSynSelectedColor; override; procedure GetNextMarkupColAfterRowCol(const aRow: Integer; const aStartCol: TLazSynDisplayTokenBound; const AnRtlInfo: TLazSynDisplayRtlInfo; out ANextPhys, ANextLog: Integer); override; 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 CellIndex: Integer; cLinePos, cBytePos, Count, LineBrkCount: Integer; Text: String; end; { TSynPluginSyncronizedEditChangeList } TSynPluginSyncronizedEditChangeList = class private FList: array of TSynPluginSyncronizedEditChangeAction; FCount: Integer; function GetItems(Index: Integer): TSynPluginSyncronizedEditChangeAction; public procedure Clear; procedure Add(aCellIndex, aLinePos, aBytePos, aCount, aLineBrkCnt: Integer; aText: String); property Count: Integer read FCount; property Items[Index: Integer]: TSynPluginSyncronizedEditChangeAction read GetItems; default; end; { TSynPluginSyncronizedEditBase } TSynPluginSyncronizedEditBase = class(TLazSynEditPlugin) private FActive: Boolean; FCells: TSynPluginSyncronizedEditList; FCurrentCell: Integer; FUndoingCurrentCell: Integer; FDependsOnCurrentCell, FJustStarted: Boolean; FActiveInUndo: Boolean; FChangeList: TSynPluginSyncronizedEditChangeList; FAreaMarkupEnabled: Boolean; FMarkupEnabled: Boolean; FEnabled: Boolean; FEditing: Boolean; // In ApplyChangeList, edit actions are caused by the plugin itself FPaintLock: Integer; FOwnPaintLock: Integer; FTextBufferChanging: Boolean; fMarkupInfo: TSynSelectedColor; fMarkupInfoSync: TSynSelectedColor; fMarkupInfoCurrent: TSynSelectedColor; fMarkupInfoArea: TSynSelectedColor; FOnActivate: TNotifyEvent; 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); procedure SetAreaMarkupEnabled(const AValue: Boolean); procedure SetEnabled(const AValue: Boolean); procedure SetMarkupEnabled(const AValue: Boolean); procedure SetMarkupInfo(AValue: TSynSelectedColor); procedure SetMarkupInfoArea(AValue: TSynSelectedColor); procedure SetMarkupInfoCurrent(AValue: TSynSelectedColor); procedure SetMarkupInfoSync(AValue: TSynSelectedColor); function IsCellFromCaretAmbigious: Boolean; protected FMarkup: TSynPluginSyncronizedEditMarkup; FMarkupArea: TSynPluginSyncronizedEditMarkupArea; procedure MarkupChanged(AMarkup: TObject); function CreateMarkup: TSynPluginSyncronizedEditMarkup; virtual; procedure DoEditorRemoving(AValue: TCustomSynEdit); override; procedure DoEditorAdded(AValue: TCustomSynEdit); override; procedure DoBufferChanging(Sender: TObject); procedure DoBufferChanged(Sender: TObject); procedure DoLinesEdited(Sender: TSynEditStrings; aLinePos, aBytePos, aCount, aLineBrkCnt: Integer; aText: String); procedure ApplyChangeList; procedure DoPreActiveEdit(aX, aY, aCount, aLineBrkCnt: Integer; aUndoRedo: Boolean); virtual; procedure DoBeforeEdit(aX, aY, aCount, aLineBrkCnt: Integer; aUndoRedo: Boolean); virtual; procedure DoAfterEdit(aX, aY: Integer; aUndoRedo: Boolean); virtual; procedure DoPaintLockStarted; virtual; procedure DoPaintLockEnded; virtual; procedure DoClear; virtual; procedure DoOnActivate; virtual; procedure DoOnDeactivate; virtual; procedure DoIncPaintLock(Sender: TObject); procedure DoDecPaintLock(Sender: TObject); property CurrentCell: Integer read FCurrentCell write SetCurrentCell; property Cells: TSynPluginSyncronizedEditList read FCells; property Markup: TSynPluginSyncronizedEditMarkup read FMarkup; property MarkupArea: TSynPluginSyncronizedEditMarkupArea read FMarkupArea; property AreaMarkupEnabled: Boolean read FAreaMarkupEnabled write SetAreaMarkupEnabled; property MarkupEnabled: Boolean read FMarkupEnabled write SetMarkupEnabled; public constructor Create(AOwner: TComponent); override; destructor Destroy; override; procedure Clear; property Enabled: Boolean read FEnabled write SetEnabled; property Active: Boolean read GetActive write SetActive; property PreActive: Boolean read FPreActive write FPreActive; // e.g. collecting words => Reacts to LinesEdited property MarkupInfo: TSynSelectedColor read FMarkupInfo write SetMarkupInfo; property MarkupInfoCurrent: TSynSelectedColor read FMarkupInfoCurrent write SetMarkupInfoCurrent; property MarkupInfoSync: TSynSelectedColor read FMarkupInfoSync write SetMarkupInfoSync; property MarkupInfoArea: TSynSelectedColor read FMarkupInfoArea write SetMarkupInfoArea; property OnActivate: TNotifyEvent read FOnActivate write FOnActivate; property OnDeactivate: TNotifyEvent read FOnDeactivate write FOnDeactivate; end; (* TSynPluginCustomSyncroEdit implements: - Locking of TrimTrailingSpace - CurrentCell follows Caret / LastCell - DeActivate if Edit outside Cell - DeActivate on undo/redo if needed - various helpers, to set the caret/block *) TSynPluginCustomSyncroEdit = class(TSynPluginSyncronizedEditBase) private FLastCell: Integer; FUndoRealCount, FRedoRealCount: Integer; FExternalEditLock: Integer; protected procedure SetUndoStart; // Handle undo/redo stuff procedure DoEditorRemoving(AValue: TCustomSynEdit); override; procedure DoEditorAdded(AValue: TCustomSynEdit); override; procedure DoOnActivate; override; procedure DoOnDeactivate; override; procedure DoBeforeEdit(aX, aY, aCount, aLineBrkCnt: Integer; aUndoRedo: Boolean); override; procedure DoAfterEdit(aX, aY: Integer; aUndoRedo: Boolean); override; procedure DoPaintLockStarted; override; procedure DoPaintLockEnded; override; procedure UpdateCurrentCell; procedure DoCaretChanged(Sender: TObject); property LastCell: Integer read FLastCell; protected procedure SelectCurrentCell(Reverse: Boolean = False); procedure PreviousCell(SetSelect: Boolean = True; SkipSameIndex: Boolean = False; FirstsOnly: Boolean = False); procedure NextCell(SetSelect: Boolean = True; SkipSameIndex: Boolean = False; FirstsOnly: Boolean = False); procedure CellCaretHome; procedure CellCaretEnd; public constructor Create(AOwner: TComponent); override; //destructor Destroy; override; procedure IncExternalEditLock; procedure DecExternalEditLock; end; implementation function CellsAreEqual(c1, c2: TSynPluginSyncronizedEditCell): boolean; begin Result := (CompareCarets(c1.LogStart, c2.LogStart) = 0) and (CompareCarets(c1.LogEnd, c2.LogEnd) = 0) and (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; begin inherited; Clear; end; destructor TSynPluginSyncronizedEditList.Destroy; begin Clear; inherited Destroy; end; procedure TSynPluginSyncronizedEditList.Clear; var i: Integer; begin for i := 0 to length(FCells) - 1 do begin if assigned(FOnCellChange) then FOnCellChange(i, FCells[i], nil); FCells[i].Free; end; SetLength(FCells, 0); end; function TSynPluginSyncronizedEditList.GetCell(aIndex: Integer): TSynPluginSyncronizedEditCell; begin Result := FCells[aIndex]; end; function TSynPluginSyncronizedEditList.GetGroupCell(aGroup, aIndex: Integer): TSynPluginSyncronizedEditCell; var i: Integer; begin i := 0; while i < length(FCells) do begin if FCells[i].Group = aGroup then begin dec(aIndex); if aIndex < 0 then exit(FCells[i]); end; inc(i); end; Result := nil; end; procedure TSynPluginSyncronizedEditList.SetCell(aIndex: Integer; const AValue: TSynPluginSyncronizedEditCell); var OldVal: TSynPluginSyncronizedEditCell; begin OldVal := FCells[aIndex]; if CellsAreEqual(OldVal, AValue) then exit; FCells[aIndex] := AValue; if assigned(FOnCellChange) then FOnCellChange(aIndex, OldVal, AValue); end; function TSynPluginSyncronizedEditList.Add(aCell: TSynPluginSyncronizedEditCell): Integer; var i: Integer; begin i := length(FCells); SetLength(FCells, i + 1); FCells[i] := aCell; Result := i; if assigned(FOnCellChange) then FOnCellChange(i, nil, FCells[i]); end; function TSynPluginSyncronizedEditList.AddNew: TSynPluginSyncronizedEditCell; begin Result := TSynPluginSyncronizedEditCell.Create; Add(Result); end; procedure TSynPluginSyncronizedEditList.Delete(aIndex: Integer); var i: Integer; begin FCells[aIndex].Free; i := length(FCells) - 1; if aIndex < i then System.Move(FCells[aIndex+1], FCells[aIndex], (i-aIndex) * SizeOf(TSynPluginSyncronizedEditCell)); SetLength(FCells, i); if assigned(FOnCellChange) then FOnCellChange(aIndex, FCells[i], nil); end; function TSynPluginSyncronizedEditList.IndexOf(aCell: TSynPluginSyncronizedEditCell): Integer; var i: Integer; begin i := 0; while i < length(FCells) do if CellsAreEqual(FCells[i], aCell) then exit(i); Result := -1; end; 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 a := 0;; for i := 0 to Count -1 do begin if (FCells[i].Group >= 0) and ( (FCells[i].LogStart.Y < aY) or ((FCells[i].LogStart.Y = aY) and (FCells[i].LogStart.X <= aX)) ) and ( (FCells[i].LogEnd.Y > aY) or ((FCells[i].LogEnd.Y = aY) and (FCells[i].LogEnd.X + a > aX)) ) then exit(i); end; 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); end; function TSynPluginSyncronizedEditList.GroupCount(aGroup: Integer): Integer; var i: Integer; begin i := 0; Result := 0; while i < length(FCells) do if FCells[i].Group = aGroup then inc(Result); end; { TSynPluginSyncronizedEditMarkupBase } procedure TSynPluginSyncronizedEditMarkupBase.SetCells(const AValue: TSynPluginSyncronizedEditList); begin if FCells = AValue then exit; if FCells <> nil then FCells.OnCellChange := nil; FCells := AValue; if FCells <> nil then FCells.OnCellChange := @CellChanged; end; function TSynPluginSyncronizedEditMarkupBase.OwnedByMgr: Boolean; begin Result := False; end; procedure TSynPluginSyncronizedEditMarkupBase.DoEnabledChanged(Sender: TObject); var i: Integer; begin if FCells.Count > 100 then InvalidateSynLines(-1, -1) else for i := 0 to FCells.Count - 1 do CellChanged(i, Cells[i], Cells[i]); end; constructor TSynPluginSyncronizedEditMarkupBase.Create(ASynEdit: TSynEditBase); begin FCells := nil; inherited; end; destructor TSynPluginSyncronizedEditMarkupBase.Destroy; begin Cells := nil; inherited Destroy; end; procedure TSynPluginSyncronizedEditMarkupBase.DoInvalidate; begin DoEnabledChanged(nil); end; { TSynPluginSyncronizedEditMarkup } procedure TSynPluginSyncronizedEditMarkup.SetCurrentCell(const AValue: Integer); var i, j: Integer; begin if FCurrentCell = AValue then exit; if (FCurrentCell >= 0) and (FCurrentCell < Cells.Count) then begin j := Cells[FCurrentCell].Group; for i := 0 to Cells.Count -1 do if Cells[i].Group = j then InvalidateSynLines(Cells[i].LogStart.Y, Cells[i].LogEnd.Y); end; FCurrentCell := AValue; if (FCurrentCell >= 0) and (FCurrentCell < Cells.Count) then begin j := Cells[FCurrentCell].Group; for i := 0 to Cells.Count -1 do if Cells[i].Group = j then InvalidateSynLines(Cells[i].LogStart.Y, Cells[i].LogEnd.Y); end; end; procedure TSynPluginSyncronizedEditMarkup.CellChanged(aIndex: Integer; aOldVal, aNewVal: TSynPluginSyncronizedEditCell); begin if (aOldVal <> nil) and (aOldVal.Group >= 0) then InvalidateSynLines(aOldVal.LogStart.Y, aOldVal.LogEnd.Y); if (aNewVal <> nil) and (aNewVal.Group >= 0) then InvalidateSynLines(aNewVal.LogStart.Y, aNewVal.LogEnd.Y); end; constructor TSynPluginSyncronizedEditMarkup.Create(ASynEdit: TSynEditBase); begin inherited Create(ASynEdit); fMarkupInfoCurrent := TSynSelectedColor.Create; fMarkupInfoCurrent.OnChange := @MarkupChanged; fMarkupInfoSync := TSynSelectedColor.Create; fMarkupInfoSync.OnChange := @MarkupChanged; FPreparedRow := -1; end; destructor TSynPluginSyncronizedEditMarkup.Destroy; begin FreeAndNil(fMarkupInfoCurrent); FreeAndNil(fMarkupInfoSync); inherited Destroy; end; function TSynPluginSyncronizedEditMarkup.GetMarkupAttributeAtRowCol(const aRow: Integer; const aStartCol: TLazSynDisplayTokenBound; const AnRtlInfo: TLazSynDisplayRtlInfo): TSynSelectedColor; var i: Integer; s, e: Integer; begin Result := nil; if not RealEnabled then exit; for i := FPreparedCellFrom to FPreparedCellTo do begin if ( ((Cells[i].LogStart.y = aRow) and (Cells[i].LogStart.x <= aStartCol.Logical)) or (Cells[i].LogStart.y < aRow) ) and ( ((Cells[i].LogEnd.y = aRow) and (Cells[i].LogEnd.x > aStartCol.Logical)) or (Cells[i].LogEnd.y > aRow) ) and (Cells[i].Group >= 0) // do not't display negative groups then begin if i = CurrentCell then Result := MarkupInfoCurrent else if (CurrentCell >= 0) and (Cells[i].Group = Cells[CurrentCell].Group) then Result := MarkupInfoSync else Result := MarkupInfo; s := Cells[i].LogStart.x; if Cells[i].LogStart.y < aRow then s := -1; e := Cells[i].LogEnd.x; if Cells[i].LogEnd.y > aRow then e := -1; Result.SetFrameBoundsLog(s, e); break; end; end; end; procedure TSynPluginSyncronizedEditMarkup.GetNextMarkupColAfterRowCol(const aRow: Integer; const aStartCol: TLazSynDisplayTokenBound; const AnRtlInfo: TLazSynDisplayRtlInfo; out ANextPhys, ANextLog: Integer); var i: Integer; begin ANextLog := -1; ANextPhys := -1; if not RealEnabled then exit; for i := FPreparedCellFrom to FPreparedCellTo do begin if Cells[i].Group < 0 then continue; if (Cells[i].LogStart.y = aRow) and (Cells[i].LogStart.x > aStartCol.Logical) and ( (Cells[i].LogStart.x < ANextLog) or (ANextLog < 0) ) then ANextLog := Cells[i].LogStart.x; if (Cells[i].LogEnd.y = aRow) and (Cells[i].LogEnd.x > aStartCol.Logical) and ( (Cells[i].LogEnd.x < ANextLog) or (ANextLog < 0) ) then ANextLog := Cells[i].LogEnd.x; end; end; procedure TSynPluginSyncronizedEditMarkup.PrepareMarkupForRow(aRow: Integer); var i, j, t, b: Integer; begin if not RealEnabled then exit; inherited PrepareMarkupForRow(aRow); if FPreparedRow < 0 then begin i := 0; j := Cells.Count - 1; t := TopLine; b := ScreenRowToRow(LinesInWindow + 1) + 1; while (i <= j) and ((Cells[i].Group < 0 ) or ((Cells[i].LogStart.y < t) and (Cells[i].LogEnd.y < t))) do inc(i); FPreparedCellTop := i; while (i <= j) and ((Cells[j].Group < 0 ) or ((Cells[j].LogStart.y > b) and (Cells[j].LogEnd.y > b))) do dec(j); FPreparedCellBottom := j; end;; i := FPreparedCellTop; j := FPreparedCellBottom; if FPreparedRow >= 0 then begin if FPreparedRow < aRow then i := FPreparedCellFrom else j := FPreparedCellTo; end; FPreparedRow := aRow; while (i <= j) and ((Cells[j].Group < 0 ) or ((Cells[i].LogStart.y < aRow) and (Cells[i].LogEnd.y < aRow))) do inc(i); FPreparedCellFrom := i; while (i <= j) and ((Cells[j].Group < 0 ) or ((Cells[j].LogStart.y > aRow) and (Cells[j].LogEnd.y > aRow))) do dec(j); FPreparedCellTo := j; end; procedure TSynPluginSyncronizedEditMarkup.EndMarkup; begin if not RealEnabled then exit; inherited EndMarkup; FPreparedRow := -1; end; { TSynPluginSyncronizedEditMarkupArea } procedure TSynPluginSyncronizedEditMarkupArea.CellChanged(aIndex: Integer; aOldVal, aNewVal: TSynPluginSyncronizedEditCell); begin if (aOldVal <> nil) and (aOldVal.Group = CellGroupForArea) then InvalidateSynLines(aOldVal.LogStart.Y, aOldVal.LogEnd.Y); if (aNewVal <> nil) and (aNewVal.Group = CellGroupForArea) then InvalidateSynLines(aNewVal.LogStart.Y, aNewVal.LogEnd.Y); end; function TSynPluginSyncronizedEditMarkupArea.GetMarkupAttributeAtRowCol(const aRow: Integer; const aStartCol: TLazSynDisplayTokenBound; const AnRtlInfo: TLazSynDisplayRtlInfo): TSynSelectedColor; var ac: TSynPluginSyncronizedEditCell; begin Result := nil; if MarkupInfo.IsEnabled then begin ac := Cells.GroupCell[CellGroupForArea, 0]; if (ac <> nil) and ( ((ac.LogStart.y = aRow) and (ac.LogStart.x <= aStartCol.Logical)) or (ac.LogStart.y < aRow)) and ( ((ac.LogEnd.y = aRow) and (ac.LogEnd.x > aStartCol.Logical)) or (ac.LogEnd.y > aRow)) then Result := MarkupInfo; end; end; procedure TSynPluginSyncronizedEditMarkupArea.GetNextMarkupColAfterRowCol(const aRow: Integer; const aStartCol: TLazSynDisplayTokenBound; const AnRtlInfo: TLazSynDisplayRtlInfo; out ANextPhys, ANextLog: Integer); var ac: TSynPluginSyncronizedEditCell; begin ANextLog := -1; ANextPhys := -1; if MarkupInfo.IsEnabled then begin ac := Cells.GroupCell[CellGroupForArea, 0]; if ac <> nil then begin if (ac.LogStart.y = aRow) and (ac.LogStart.x > aStartCol.Logical) and ( (ac.LogStart.x < ANextLog) or (ANextLog < 0) ) then ANextLog := ac.LogStart.x; if (ac.LogEnd.y = aRow) and (ac.LogEnd.x > aStartCol.Logical) and ( (ac.LogEnd.x < ANextLog) or (ANextLog < 0) ) then ANextLog := ac.LogEnd.x; end; end; end; { TSynPluginSyncronizedEditChangeList } function TSynPluginSyncronizedEditChangeList.GetItems(Index: Integer): TSynPluginSyncronizedEditChangeAction; begin Result := FList[Index]; end; procedure TSynPluginSyncronizedEditChangeList.Clear; begin FList := nil; FCount := 0; end; procedure TSynPluginSyncronizedEditChangeList.Add(aCellIndex, aLinePos, aBytePos, aCount, aLineBrkCnt: Integer; aText: String); begin if length(FList) <= FCount then SetLength(FList, FCount + 4); FList[FCount].CellIndex := aCellIndex; FList[FCount].cLinePos := aLinePos; FList[FCount].cBytePos := aBytePos; FList[FCount].Count := aCount; FList[FCount].LineBrkCount := aLineBrkCnt; FList[FCount].Text := aText; inc(FCount); end; { TSynPluginSyncronizedEditBase } constructor TSynPluginSyncronizedEditBase.Create(AOwner: TComponent); begin fMarkupInfo := TSynSelectedColor.Create; fMarkupInfo.OnChange := @MarkupChanged; fMarkupInfoSync := TSynSelectedColor.Create; fMarkupInfoSync.OnChange := @MarkupChanged; fMarkupInfoCurrent := TSynSelectedColor.Create; fMarkupInfoCurrent.OnChange := @MarkupChanged; fMarkupInfoArea := TSynSelectedColor.Create; fMarkupInfoArea.OnChange := @MarkupChanged; MarkupInfo.FrameColor := clMaroon; MarkupInfo.Background := clNone; MarkupInfo.Foreground := clNone; MarkupInfoCurrent.FrameColor := clAqua; MarkupInfoCurrent.Background := clNone; MarkupInfoCurrent.Foreground := clNone; MarkupInfoSync.FrameColor := clFuchsia; MarkupInfoSync.Background := clNone; MarkupInfoSync.Foreground := clNone; MarkupInfoArea.FrameColor := clNone; MarkupInfoArea.Background := clNone; MarkupInfoArea.Foreground := clNone; FCells := TSynPluginSyncronizedEditList.Create; CurrentCell := -1; FChangeList := TSynPluginSyncronizedEditChangeList.Create; AreaMarkupEnabled := False; MarkupEnabled := True; inherited Create(AOwner); FEnabled := True; Active := False; FEditing := False; FTextBufferChanging := False; end; destructor TSynPluginSyncronizedEditBase.Destroy; begin Editor := nil; FreeAndNil(FMarkup); FreeAndNil(FMarkupArea); FreeAndNil(FCells); FreeAndNil(fMarkupInfo); FreeAndNil(fMarkupInfoSync); FreeAndNil(fMarkupInfoCurrent); FreeAndNil(fMarkupInfoArea); FreeAndNil(FChangeList); inherited; end; procedure TSynPluginSyncronizedEditBase.Clear; begin if FMarkup <> nil then FMarkup.DoEnabledChanged(nil); // invalidate lines FCells.Clear; CurrentCell := -1; Active := False; DoClear; end; procedure TSynPluginSyncronizedEditBase.DoEditorRemoving(AValue: TCustomSynEdit); begin Active := False; if Editor <> nil then begin if not FTextBufferChanging then 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); if FMarkup <> nil then begin TSynEditMarkupManager(MarkupMgr).RemoveMarkUp(FMarkup); FreeAndNil(FMarkup); end; if FMarkupArea <> nil then begin TSynEditMarkupManager(MarkupMgr).RemoveMarkUp(FMarkupArea); FreeAndNil(FMarkupArea); end; end; inherited DoEditorRemoving(AValue); end; procedure TSynPluginSyncronizedEditBase.DoEditorAdded(AValue: TCustomSynEdit); begin inherited DoEditorAdded(AValue); if Editor <> nil then begin FMarkup := CreateMarkup; FMarkup.Cells := FCells; FMarkup.CurrentCell := FCurrentCell; FMarkup.Enabled := (Active or PreActive) and FMarkupEnabled; TSynEditMarkupManager(MarkupMgr).AddMarkUp(FMarkup); FMarkupArea := TSynPluginSyncronizedEditMarkupArea.Create(Editor); FMarkupArea.Cells := FCells; FMarkupArea.Enabled := Active; TSynEditMarkupManager(MarkupMgr).AddMarkUp(FMarkupArea, True); MarkupChanged(nil); 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); end; end; end; procedure TSynPluginSyncronizedEditBase.DoBufferChanging(Sender: TObject); begin if FTextBufferChanging then exit; FTextBufferChanging := True; if Editor <> nil then DoEditorRemoving(Editor); end; procedure TSynPluginSyncronizedEditBase.DoBufferChanged(Sender: TObject); begin if not FTextBufferChanging then exit; if Editor <> nil then DoEditorAdded(Editor); FTextBufferChanging := False; end; procedure TSynPluginSyncronizedEditBase.SetCurrentCell(const AValue: Integer); begin if FCurrentCell = AValue then exit; FCurrentCell := AValue; if FMarkup <> nil then FMarkup.CurrentCell := FCurrentCell; end; procedure TSynPluginSyncronizedEditBase.SetAreaMarkupEnabled(const AValue: Boolean); begin if FAreaMarkupEnabled = AValue then exit; FAreaMarkupEnabled := AValue; if FMarkupArea <> nil then FMarkupArea.Enabled := Active and FAreaMarkupEnabled; end; procedure TSynPluginSyncronizedEditBase.SetEnabled(const AValue: Boolean); var IsActive: Boolean; begin IsActive := Active; FEnabled := AValue; if FMarkup <> nil then FMarkup.Enabled := (Active or PreActive) and FMarkupEnabled; if FMarkupArea <> nil then FMarkupArea.Enabled := Active and FAreaMarkupEnabled; if IsActive <> Active then begin if Active then DoOnActivate else DoOnDeactivate; end; end; procedure TSynPluginSyncronizedEditBase.SetMarkupEnabled(const AValue: Boolean); begin if FMarkupEnabled = AValue then exit; FMarkupEnabled := AValue; if FMarkup <> nil then FMarkup.Enabled := (Active or PreActive) and FMarkupEnabled; end; procedure TSynPluginSyncronizedEditBase.SetMarkupInfo(AValue: TSynSelectedColor ); begin if FMarkupInfo=AValue then Exit; FMarkupInfo.Assign(AValue); end; procedure TSynPluginSyncronizedEditBase.SetMarkupInfoArea( AValue: TSynSelectedColor); begin if FMarkupInfoArea=AValue then Exit; FMarkupInfoArea.Assign(AValue); end; procedure TSynPluginSyncronizedEditBase.SetMarkupInfoCurrent( AValue: TSynSelectedColor); begin if FMarkupInfoCurrent=AValue then Exit; FMarkupInfoCurrent.Assign(AValue); end; procedure TSynPluginSyncronizedEditBase.SetMarkupInfoSync( AValue: TSynSelectedColor); begin if FMarkupInfoSync=AValue then Exit; 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 FMarkup.MarkupInfo.Assign(fMarkupInfo); FMarkup.MarkupInfoSync.Assign(fMarkupInfoSync); FMarkup.MarkupInfoCurrent.Assign(fMarkupInfoCurrent); end; if FMarkupArea <> nil then FMarkupArea.MarkupInfo.Assign(fMarkupInfoArea); end; function TSynPluginSyncronizedEditBase.CreateMarkup: TSynPluginSyncronizedEditMarkup; begin Result := TSynPluginSyncronizedEditMarkup.Create(Editor); end; function TSynPluginSyncronizedEditBase.GetActive: Boolean; 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; begin IsActive := Active; FActive := AValue; PreActive := False; // change in active, always disables PreActive if FMarkup <> nil then FMarkup.Enabled := (Active or PreActive) and FMarkupEnabled; if FMarkupArea <> nil then FMarkupArea.Enabled := Active and FAreaMarkupEnabled; if IsActive <> Active then begin if Active then DoOnActivate else DoOnDeactivate; end; end; procedure TSynPluginSyncronizedEditBase.DoLinesEdited(Sender: TSynEditStrings; aLinePos, aBytePos, aCount, aLineBrkCnt: Integer; aText: String); var Pos, Pos2: TPoint; function AdjustPoint(aPoint: Tpoint; var Changed: Boolean): TPoint; inline; begin Result := aPoint; if aLineBrkCnt < 0 then begin (* Lines Deleted *) if aPoint.y > aLinePos then begin Result.y := Max(aLinePos, Result.y + aLineBrkCnt); if Result.y = aLinePos then Result.x := Result.x + Pos.x - 1; end; end else if aLineBrkCnt > 0 then begin (* Lines Inserted *) if aPoint.y >= aLinePos then begin if (aPoint.y = aLinePos) and (aPoint.x >= Pos.x) then Result.x := Result.x - Pos.x + 1; Result.y := Result.y + aLineBrkCnt; end; end else if aCount <> 0 then begin (* Chars Insert/Deleted *) if (aPoint.y = aLinePos) and (aPoint.x >= Pos.x) then Result.x := Max(Pos.x, Result.x + aCount); end; Changed := Changed or (aPoint.x <> Result.x) or (aPoint.y <> Result.y); end; var i, a: Integer; CurCell: TSynPluginSyncronizedEditCell; chg: Boolean; edit: Boolean; CellAtPos, CellCnt: Integer; LastCellEndPoint, CurCellStartPos: TPoint; begin if not Active then begin if PreActive then DoPreActiveEdit(aBytePos, aLinePos, aCount, aLineBrkCnt, IsUndoing or IsRedoing); exit; end; Pos := Point(aBytePos, aLinePos); Pos2 := Pos; if not FEditing then DoBeforeEdit(Pos.x, Pos.y, aCount, aLineBrkCnt, IsUndoing or IsRedoing); if not Active then exit; edit := FEditing or IsUndoing or IsRedoing; 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) 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 edit) and (CellAtPos >= 0) and (CellAtPos < Cells.Count) and (CompareCarets(Pos, FCells[CellAtPos].LogStart) <= 0) and (CompareCarets(Pos, FCells[CellAtPos].LogEnd) >= 0) then begin CurCell := FCells[CellAtPos]; Pos.Y := Pos.Y - CurCell.LogStart.y; if Pos.y = 0 then Pos.X := Pos.X - CurCell.LogStart.x else dec(Pos.x); FChangeList.Add(CellAtPos, Pos.Y, Pos.X, aCount, aLineBrkCnt, aText); end; if not FEditing then DoAfterEdit(Pos2.x, Pos2.y, IsUndoing or IsRedoing); if (not FEditing) and (FPaintLock = 0) then DoPaintLockEnded; end; procedure TSynPluginSyncronizedEditBase.ApplyChangeList; var Action: TSynPluginSyncronizedEditChangeAction; a, i: 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 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 X2 := 1 + Action.cBytePos; if (Cell.LogStart.Y + Action.cLinePos < Cell.LogEnd.Y) or ( (Cell.LogStart.Y + Action.cLinePos = Cell.LogEnd.Y) and (X2 <= Cell.LogEnd.X) ) then begin Y2 := Cell.LogStart.Y + Action.cLinePos; if Action.cLinePos = 0 then X2 := Cell.LogStart.X + Action.cBytePos else X2 := 1 + Action.cBytePos; if Action.LineBrkCount = -1 then ViewedTextBuffer.EditLineJoin(Y2) else if Action.LineBrkCount < -1 then ViewedTextBuffer.EditLinesDelete(Y2, -Action.LineBrkCount) else if Action.LineBrkCount = 1 then ViewedTextBuffer.EditLineBreak(X2, Y2) else if Action.LineBrkCount > 1 then ViewedTextBuffer.EditLinesInsert(Y2, Action.LineBrkCount) else if Action.Count < 0 then ViewedTextBuffer.EditDelete(X2, Y2, -Action.Count) else if Action.Count > 0 then 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; end; FChangeList.Clear; end; procedure TSynPluginSyncronizedEditBase.DoPreActiveEdit(aX, aY, aCount, aLineBrkCnt: Integer; aUndoRedo: Boolean); begin // end; procedure TSynPluginSyncronizedEditBase.DoBeforeEdit(aX, aY, aCount, aLineBrkCnt: Integer; aUndoRedo: Boolean); begin (* Do Nothing *); end; procedure TSynPluginSyncronizedEditBase.DoAfterEdit(aX, aY: Integer; aUndoRedo: Boolean); begin (* Do Nothing *); end; procedure TSynPluginSyncronizedEditBase.DoPaintLockStarted; begin FDependsOnCurrentCell := False; FJustStarted := True; end; procedure TSynPluginSyncronizedEditBase.DoPaintLockEnded; begin FJustStarted := False; end; procedure TSynPluginSyncronizedEditBase.DoClear; begin (* Do Nothing *); end; procedure TSynPluginSyncronizedEditBase.DoOnActivate; begin if assigned(FOnActivate) then FOnActivate(self); end; procedure TSynPluginSyncronizedEditBase.DoOnDeactivate; begin if assigned(FOnDeactivate) then FOnDeactivate(self); end; procedure TSynPluginSyncronizedEditBase.DoIncPaintLock(Sender: TObject); begin if FPaintLock = 0 then DoPaintLockStarted; inc(FPaintLock); if Sender = Editor then inc(FOwnPaintLock); end; procedure TSynPluginSyncronizedEditBase.DoDecPaintLock(Sender: TObject); begin dec(FPaintLock); if Sender = Editor then dec(FOwnPaintLock); if FPaintLock = 0 then DoPaintLockEnded; end; { TSynPluginSyncronizedEditCell } constructor TSynPluginSyncronizedEditCell.Create; begin FFirstInGroup := False; end; procedure TSynPluginSyncronizedEditCell.Assign(Src: TSynPluginSyncronizedEditCell); begin if Src = nil then exit; FLogStart := Src.FLogStart; FLogEnd := Src.FLogEnd; FGroup := Src.FGroup; FFirstInGroup := Src.FFirstInGroup; end; { TSynPluginCustomSyncroEdit } procedure TSynPluginCustomSyncroEdit.SetUndoStart; begin ViewedTextBuffer.UndoList.ForceGroupEnd; FUndoRealCount := ViewedTextBuffer.UndoList.RealCount; FRedoRealCount := ViewedTextBuffer.RedoList.RealCount; end; procedure TSynPluginCustomSyncroEdit.DoEditorRemoving(AValue: TCustomSynEdit); begin if Editor <> nil then begin CaretObj.RemoveChangeHandler(@DoCaretChanged); end; inherited DoEditorRemoving(AValue); end; procedure TSynPluginCustomSyncroEdit.DoEditorAdded(AValue: TCustomSynEdit); begin inherited DoEditorAdded(AValue); if Editor <> nil then begin CaretObj.AddChangeHandler(@DoCaretChanged); end; end; procedure TSynPluginCustomSyncroEdit.DoOnActivate; var b: TSynEditStrings; begin inherited; b := ViewedTextBuffer; while b <> nil do begin if b is TSynEditStringTrimmingList then TSynEditStringTrimmingList(b).Lock; if b is TSynEditStringsLinked then b := TSynEditStringsLinked(b).NextLines else b := nil; end; end; procedure TSynPluginCustomSyncroEdit.DoOnDeactivate; var b: TSynEditStrings; begin inherited; FUndoRealCount := -1; FRedoRealCount := -1; b := ViewedTextBuffer; while b <> nil do begin if b is TSynEditStringTrimmingList then TSynEditStringTrimmingList(b).UnLock; if b is TSynEditStringsLinked then b := TSynEditStringsLinked(b).NextLines else b := nil; end; end; procedure TSynPluginCustomSyncroEdit.DoBeforeEdit(aX, aY, aCount, aLineBrkCnt: Integer; aUndoRedo: Boolean); var c1, c2: Integer; begin inherited; if IsUndoing and (FUndoRealCount >= 0) and (ViewedTextBuffer.UndoList.RealCount < FUndoRealCount) then Active := false; if IsRedoing and (FRedoRealCount >= 0) and (ViewedTextBuffer.RedoList.RealCount < FUndoRealCount) then Active := false; if aUndoRedo or not Active then exit; FRedoRealCount := -1; (* TODO / Review - Caret may be outside Cell (eg IdentifierCompletion, TextBetweenPoints) But the edit happens inside a cell => ok - Caret may be in cell, but Codetools inserts text outside the cell => ok - 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, CurrentCell); if aLineBrkCnt < 0 then c2 := Cells.IndexOf(1, aY-aLineBrkCnt, True, CurrentCell) else if aCount < 0 then 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) // Todo, could be just on the edge of a cell ! if (c1 = c2) and (FExternalEditLock > 0) then begin exit; end; // shared editor, outside cells if (FPaintLock > 0) and (FOwnPaintLock = 0) then begin if (c1 < 0) and (c2 < 0) then exit; c1 := -1; // shared Eitor in cell => deactivate end; if (CurrentCell < 0) or (c1 < 0) or (c2 <> c1) then begin Clear; Active := False; end; end; procedure TSynPluginCustomSyncroEdit.DoAfterEdit(aX, aY: Integer; aUndoRedo: Boolean); begin inherited DoAfterEdit(aX, aY, aUndoRedo); if FPaintLock = 0 then UpdateCurrentCell; end; procedure TSynPluginCustomSyncroEdit.DoPaintLockStarted; begin inherited DoPaintLockStarted; end; procedure TSynPluginCustomSyncroEdit.DoPaintLockEnded; begin inherited DoPaintLockEnded; if Active then begin ApplyChangeList; UpdateCurrentCell; end; end; procedure TSynPluginCustomSyncroEdit.UpdateCurrentCell; var i: Integer; begin i := Cells.IndexOf(CaretObj.BytePos, CaretObj.LinePos, True, CurrentCell); if (i <> CurrentCell) and (CurrentCell >= 0) then FLastCell := CurrentCell; CurrentCell := i; end; procedure TSynPluginCustomSyncroEdit.DoCaretChanged(Sender: TObject); begin if not Active then exit; UpdateCurrentCell; end; procedure TSynPluginCustomSyncroEdit.SelectCurrentCell(Reverse: Boolean); begin if (CurrentCell < 0) and (LastCell >= 0) then CurrentCell := LastCell; if (CurrentCell < 0) then exit; if Reverse then begin CaretObj.LineBytePos := Cells[CurrentCell].LogStart; Editor.BlockBegin := Cells[CurrentCell].LogEnd; Editor.BlockEnd := Cells[CurrentCell].LogStart; end else begin CaretObj.LineBytePos := Cells[CurrentCell].LogEnd; Editor.BlockBegin := Cells[CurrentCell].LogStart; Editor.BlockEnd := Cells[CurrentCell].LogEnd; end; end; procedure TSynPluginCustomSyncroEdit.PreviousCell(SetSelect: Boolean; SkipSameIndex: Boolean; FirstsOnly: Boolean); var i, j, x: Integer; Pos: TPoint; begin Pos := CaretObj.LineBytePos; i := Cells.IndexOf(Pos.x, Pos.y, True, CurrentCell); if i < 0 then begin x := -1; i := 0; while (i < Cells.Count) and ((Cells[i].Group < 0) or (CompareCarets(Cells[i].LogEnd, Pos) >= 0)) do inc(i); end else x := Cells[i].Group; j := 0; Repeat dec(i); inc(j); if i < 0 then i := Cells.Count - 1; until (j > Cells.Count) or ( (Cells[i].Group >= 0) and ((not SkipSameIndex) or (Cells[i].Group <> x)) and ((not FirstsOnly) or (Cells[i].FirstInGroup)) ); CurrentCell := i; if CurrentCell < 0 then exit; CaretObj.LineBytePos := Cells[CurrentCell].LogEnd; if SetSelect then SelectCurrentCell else Editor.BlockBegin := Cells[CurrentCell].LogEnd; end; procedure TSynPluginCustomSyncroEdit.NextCell(SetSelect: Boolean; SkipSameIndex: Boolean; FirstsOnly: Boolean); var Pos: TPoint; i, j, x: Integer; begin Pos := CaretObj.LineBytePos; i := Cells.IndexOf(Pos.x, Pos.y, True, CurrentCell); if i < 0 then begin x := -1; i := Cells.Count - 1; while (i >= 0) and ((Cells[i].Group < 0) or (CompareCarets(Cells[i].LogEnd, Pos) <= 0)) do dec(i); end else x := Cells[i].Group; j := 0; Repeat inc(i); inc(j); if i >= Cells.Count then i := 0 until (j > Cells.Count) or ( (Cells[i].Group >= 0) and ((not SkipSameIndex) or (Cells[i].Group <> x)) and ((not FirstsOnly) or (Cells[i].FirstInGroup)) ); CurrentCell := i; if CurrentCell < 0 then exit; CaretObj.LineBytePos := Cells[CurrentCell].LogStart; if SetSelect then SelectCurrentCell(True) else Editor.BlockBegin := Cells[CurrentCell].LogStart; end; procedure TSynPluginCustomSyncroEdit.CellCaretHome; begin if (CurrentCell < 0) and (LastCell >= 0) then CurrentCell := LastCell; if (CurrentCell < 0) then exit; CaretObj.LineBytePos := Cells[CurrentCell].LogStart; Editor.BlockBegin := Cells[CurrentCell].LogStart; end; procedure TSynPluginCustomSyncroEdit.CellCaretEnd; begin if (CurrentCell < 0) and (LastCell >= 0) then CurrentCell := LastCell; if (CurrentCell < 0) then exit; CaretObj.LineBytePos := Cells[CurrentCell].LogEnd; Editor.BlockBegin := Cells[CurrentCell].LogEnd; end; constructor TSynPluginCustomSyncroEdit.Create(AOwner: TComponent); begin FPaintLock := 0; FExternalEditLock := 0; inherited Create(AOwner); end; procedure TSynPluginCustomSyncroEdit.IncExternalEditLock; begin inc(FExternalEditLock); end; procedure TSynPluginCustomSyncroEdit.DecExternalEditLock; begin dec(FExternalEditLock); end; end.