From a26d1805deb0e84975ae100fb79a991990e1115d Mon Sep 17 00:00:00 2001 From: martin Date: Sun, 15 Mar 2009 16:56:58 +0000 Subject: [PATCH] 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 - --- components/synedit/synbeautifier.pas | 7 +- components/synedit/synedit.pp | 1165 ++++++++------------ components/synedit/syneditpointclasses.pas | 260 ++--- components/synedit/synedittextbase.pas | 578 +++++++++- components/synedit/synedittextbuffer.pp | 598 +++++----- components/synedit/synedittexttrimmer.pas | 483 +++++++- components/synedit/synedittypes.pp | 2 +- 7 files changed, 1829 insertions(+), 1264 deletions(-) diff --git a/components/synedit/synbeautifier.pas b/components/synedit/synbeautifier.pas index 626df876e1..61fcb79fdd 100644 --- a/components/synedit/synbeautifier.pas +++ b/components/synedit/synbeautifier.pas @@ -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; diff --git a/components/synedit/synedit.pp b/components/synedit/synedit.pp index 3862cf23fa..86d86d5285 100644 --- a/components/synedit/synedit.pp +++ b/components/synedit/synedit.pp @@ -182,7 +182,7 @@ type {$IFDEF SYN_LAZARUS} sfTripleClicked, sfQuadClicked, sfPainting, {$ENDIF} - sfWaitForDragging,{$IFDEF SYN_LAZARUS} sfIsDragging,{$ENDIF} sfInsideRedo + sfWaitForDragging {$IFDEF SYN_LAZARUS}, sfIsDragging{$ENDIF} ); //mh 2000-10-30 TSynStateFlags = set of TSynStateFlag; @@ -376,6 +376,7 @@ type {$ENDIF} fUndoList: TSynEditUndoList; fRedoList: TSynEditUndoList; + FIsUndoing: Boolean; fBookMarks: array[0..9] of TSynEditMark; fMouseDownX: integer; fMouseDownY: integer; @@ -431,6 +432,7 @@ type {$ENDIF} procedure AquirePrimarySelection; + function GetUndoList: TSynEditUndoList; procedure SurrenderPrimarySelection; procedure BookMarkOptionsChanged(Sender: TObject); procedure ComputeCaret(X, Y: Integer); @@ -455,6 +457,7 @@ type {$IFDEF SYN_LAZARUS} function GetCaretX : Integer; function GetCaretY : Integer; + function GetCaretUndo: TSynEditUndoItem; function GetHighlightAllColor : TSynSelectedColor; function GetIncrementColor : TSynSelectedColor; function GetLineHighlightColor: TSynSelectedColor; @@ -516,8 +519,8 @@ type {$ELSE} procedure SetBorderStyle(Value: TBorderStyle); {$ENDIF} - procedure SetCaretAndSelection({$IFDEF SYN_LAZARUS}const {$ENDIF}ptCaret, - ptBefore, ptAfter: TPoint); + procedure SetCaretAndSelection(const ptCaret, ptBefore, ptAfter: TPoint; + Mode: TSynSelectionMode = smCurrent); procedure SetCaretX(Value: Integer); procedure SetCaretY(Value: Integer); procedure SetExtraLineSpacing(const Value: integer); @@ -640,19 +643,20 @@ type procedure EndPaintBuffer(const ClipRect: TRect); {$ENDIF} procedure RecalcCharExtent; - procedure RedoItem; //sbs 2000-11-19 + procedure RedoItem(Item: TSynEditUndoItem); procedure SetCaretXY(Value: TPoint); virtual; procedure CaretChanged(Sender: TObject); procedure SetName(const Value: TComponentName); override; procedure SetReadOnly(Value: boolean); virtual; procedure SetSelTextPrimitive(PasteMode: TSynSelectionMode; Value: PChar; - AddToUndoList: Boolean = false; ChangeReason: TSynChangeReason = crInsert); + AddToUndoList: Boolean = false); procedure ShowCaret; // If the translations requires Data, memory will be allocated for it via a // GetMem call. The client must call FreeMem on Data if it is not NIL. function TranslateKeyCode(Code: word; Shift: TShiftState; var Data: pointer {$IFDEF SYN_LAZARUS};out IsStartOfCombo: boolean{$ENDIF}): TSynEditorCommand; - procedure UndoItem; //sbs 2000-11-19 + procedure UndoItem(Item: TSynEditUndoItem); + property UndoList: TSynEditUndoList read GetUndoList; protected fGutterWidth: Integer; {$IFDEF EnableDoubleBuf} @@ -1105,6 +1109,151 @@ uses {$ENDIF} Clipbrd; +type + + { TSynEditUndoCaret } + + TSynEditUndoCaret = class(TSynEditUndoItem) + private + FCaretPos: TPoint; + protected + function IsEqualContent(AnItem: TSynEditUndoItem): Boolean; override; + public + constructor Create(CaretPos: TPoint); + function IsCaretInfo: Boolean; override; + function PerformUndo(Caller: TObject): Boolean; override; + end; + + { TSynEditUndoSelCaret } + + TSynEditUndoSelCaret = class(TSynEditUndoItem) + private + FCaretPos, FBeginPos, FEndPos: TPoint; + FBlockMode: TSynSelectionMode; + protected + function IsEqualContent(AnItem: TSynEditUndoItem): Boolean; override; + public + function IsCaretInfo: Boolean; override; + constructor Create(CaretPos, BeginPos, EndPos: TPoint; BlockMode: TSynSelectionMode); + function PerformUndo(Caller: TObject): Boolean; override; + end; + + { TSynEditUndoIndent } + + TSynEditUndoIndent = class(TSynEditUndoItem) + public + FPosY1, FPosY2, FCnt: Integer; + public + constructor Create(APosY, EPosY, ACnt: Integer); + function PerformUndo(Caller: TObject): Boolean; override; + end; + + { TSynEditUndoUnIndent } + + TSynEditUndoUnIndent = class(TSynEditUndoItem) + public + FPosY1, FPosY2: Integer; + FText: String; + public + constructor Create(APosY, EPosY: Integer; AText: String); + function PerformUndo(Caller: TObject): Boolean; override; + end; + +{ TSynEditUndoCaret } + +function TSynEditUndoCaret.IsEqualContent(AnItem: TSynEditUndoItem): Boolean; +begin + Result := (FCaretPos.x = TSynEditUndoCaret(AnItem).FCaretPos.x) + and (FCaretPos.y = TSynEditUndoCaret(AnItem).FCaretPos.y); +end; + +constructor TSynEditUndoCaret.Create(CaretPos: TPoint); +begin + FCaretPos := CaretPos; +end; + +function TSynEditUndoCaret.IsCaretInfo: Boolean; +begin + Result := True; +end; + +function TSynEditUndoCaret.PerformUndo(Caller: TObject): Boolean; +begin + Result := Caller is TSynEdit; + if Result then + with TSynEdit(Caller) do begin + CaretXY := FCaretPos; + UndoList.AddChange(TSynEditUndoCaret.Create(FCaretPos)); + end; +end; + +{ TSynEditUndoSelCaret } + +constructor TSynEditUndoSelCaret.Create(CaretPos, BeginPos, EndPos: TPoint; + BlockMode: TSynSelectionMode); +begin + FCaretPos := CaretPos; + FBeginPos := BeginPos; + FEndPos := EndPos; + FBlockMode := BlockMode; +end; + +function TSynEditUndoSelCaret.IsEqualContent(AnItem: TSynEditUndoItem): Boolean; +begin + Result := (FCaretPos.x = TSynEditUndoSelCaret(AnItem).FCaretPos.x) + and (FCaretPos.y = TSynEditUndoSelCaret(AnItem).FCaretPos.y) + and (FBeginPos.x = TSynEditUndoSelCaret(AnItem).FBeginPos.x) + and (FBeginPos.y = TSynEditUndoSelCaret(AnItem).FBeginPos.y) + and (FEndPos.x = TSynEditUndoSelCaret(AnItem).FEndPos.x) + and (FEndPos.y = TSynEditUndoSelCaret(AnItem).FEndPos.y) + and (FBlockMode = TSynEditUndoSelCaret(AnItem).FBlockMode); +end; + +function TSynEditUndoSelCaret.IsCaretInfo: Boolean; +begin + Result := True; +end; + +function TSynEditUndoSelCaret.PerformUndo(Caller: TObject): Boolean; +begin + Result := Caller is TSynEdit; + if Result then + with TSynEdit(Caller) do begin + SetCaretAndSelection(FCaretPos, FBeginPos, FEndPos, FBlockMode); + UndoList.AddChange(TSynEditUndoSelCaret.Create(FCaretPos, FBeginPos, + FEndPos, FBlockMode)); + end; +end; + +{ TSynEditUndoIndent } + +constructor TSynEditUndoIndent.Create(APosY, EPosY, ACnt: Integer); +begin + FPosY1 := APosY; + FPosY2 := EPosY; + FCnt := ACnt; +end; + +function TSynEditUndoIndent.PerformUndo(Caller: TObject): Boolean; +begin + Result := False; +end; + +{ TSynEditUndoUnIndent } + +constructor TSynEditUndoUnIndent.Create(APosY, EPosY: Integer; AText: String); +begin + FPosY1 := APosY; + FPosY2 := EPosY; + FText := AText; +end; + +function TSynEditUndoUnIndent.PerformUndo(Caller: TObject): Boolean; +begin + Result := False; +end; + + {$IFDEF SYN_LAZARUS} var @@ -1414,10 +1563,13 @@ begin fFontDummy := TFont.Create; fUndoList := TSynEditUndoList.Create; fUndoList.OnAddedUndo := {$IFDEF FPC}@{$ENDIF}UndoRedoAdded; + fUndoList.OnNeedCaretUndo := {$IFDEF FPC}@{$ENDIF}GetCaretUndo; fRedoList := TSynEditUndoList.Create; fRedoList.OnAddedUndo := {$IFDEF FPC}@{$ENDIF}UndoRedoAdded; + FIsUndoing := False; - FTrimmedLinesView.UndoList := fUndoList; + TSynEditStringList(FLines).UndoList := fUndoList; + TSynEditStringList(FLines).RedoList := fRedoList; FBlockSelection := TSynEditSelection.Create(FTheLinesView); FBlockSelection.MaxLeftChar := @FMaxLeftChar; @@ -3274,7 +3426,6 @@ var { end local procedures } var - ypos : integer; ColBG : TColor; begin if (AClip.Right < fGutterWidth) then exit; @@ -3451,7 +3602,7 @@ begin // See CopyToClipboard PasteMode := PSynSelectionMode(P)^; inc(P, SizeOf(TSynSelectionMode)); - SetSelTextPrimitive(PasteMode, P, true, crPaste); + SetSelTextPrimitive(PasteMode, P, true); end else raise ESynEditError.Create('Clipboard paste operation failed.'); finally @@ -3548,6 +3699,17 @@ function TCustomSynEdit.GetCaretY : Integer; begin Result:= fCaret.LinePos; end; + +function TCustomSynEdit.GetCaretUndo: TSynEditUndoItem; +begin + if SelAvail then + Result := TSynEditUndoSelCaret.Create(FCaret.LineCharPos, + FBlockSelection.StartLineBytePos, FBlockSelection.EndLineBytePos, + FBlockSelection.ActiveSelectionMode) + else + Result := TSynEditUndoCaret.Create(FCaret.LineCharPos); +end; + {$ENDIF} function TCustomSynEdit.GetMarkup(Index: integer): TSynEditMarkup; @@ -3671,19 +3833,25 @@ end; {$ENDIF} procedure TCustomSynEdit.SetSelTextPrimitive(PasteMode: TSynSelectionMode; - Value: PChar; AddToUndoList: Boolean = false; - ChangeReason: TSynChangeReason = crInsert); + Value: PChar; AddToUndoList: Boolean = false); Begin IncPaintLock; + if not AddToUndoList then begin + fUndoList.Lock; + fRedoList.Lock; + end; try - FBlockSelection.SetSelTextPrimitive(PasteMode, Value, AddToUndoList, - ChangeReason); + FBlockSelection.SetSelTextPrimitive(PasteMode, Value); // Force caret reset CaretXY := CaretXY; fLastCaretX := CaretX; EnsureCursorPosVisible; fMarkupManager.Caret := CaretXY; finally + if not AddToUndoList then begin + fUndoList.Unlock; + fRedoList.Unlock; + end; DecPaintLock; end; end; @@ -4274,14 +4442,28 @@ begin Dec(Result); end; -procedure TCustomSynEdit.LineCountChanged(Sender: TSynEditStrings; AIndex, ACount: Integer); +procedure TCustomSynEdit.LineCountChanged(Sender: TSynEditStrings; + AIndex, ACount: Integer); begin - ScanFrom(AIndex - 1, Max(AIndex, AIndex + ACount)); + if PaintLock>0 then begin + if (fHighlighterNeedsUpdateStartLine<1) + or (fHighlighterNeedsUpdateStartLine>AIndex+1) then + fHighlighterNeedsUpdateStartLine:=AIndex+1; + if (fHighlighterNeedsUpdateEndLine<1) + or (fHighlighterNeedsUpdateEndLine nil then begin - {$IFDEF SYN_LAZARUS} - BeginUndoBlock; - OldChangeNumber := Item.fChangeNumber; - {$ELSE} - OldChangeNumber := fUndoList.BlockChangeNumber; - fUndoList.BlockChangeNumber := Item.fChangeNumber; - {$ENDIF} - try - repeat - RedoItem; - Item := fRedoList.PeekItem; - {$IFDEF SYN_LAZARUS} - until (Item = nil) or (Item.fChangeNumber <> OldChangeNumber); - {$ELSE} - until (Item = nil) or (Item.fChangeNumber <> fUndoList.BlockChangeNumber); - {$ENDIF} - finally - {$IFDEF SYN_LAZARUS} - EndUndoBlock; - {$ELSE} - fUndoList.BlockChangeNumber := OldChangeNumber; - {$ENDIF} + Group := fRedoList.PopItem; + if Group <> nil then begin; + IncPaintLock; + Item := Group.Pop; + if Item <> nil then begin + BeginUndoBlock; + fUndoList.CurrentGroup.Reason := Group.Reason; + fUndoList.IsInsideRedo := True; + try + repeat + RedoItem(Item); + Item := Group.Pop; + until (Item = nil); + finally + EndUndoBlock; + end; end; + Group.Free; + if fRedoList.IsTopMarkedAsUnmodified then + fUndoList.MarkTopAsUnmodified; + DecPaintLock; end; end; -procedure TCustomSynEdit.RedoItem; -{end} //sbs 2000-11-19 +procedure TCustomSynEdit.RedoItem(Item: TSynEditUndoItem); var - Item: TSynEditUndoItem; - OldSelMode: TSynSelectionMode; Run, StrToDelete: PChar; Len, x : integer; TempString: string; - CaretPt: TPoint; - {$IFDEF SYN_LAZARUS} - PhysStartPos: TPoint; - {$ELSE} - e: integer; - {$ENDIF} begin - OldSelMode := FBlockSelection.SelectionMode; - Item := fRedoList.PopItem; - if Assigned(Item) then try - SelectionMode := Item.fChangeSelMode; - IncPaintLock; - FCaret.ForcePastEOL := True; - Include(fStateFlags, sfInsideRedo); //mh 2000-10-30 - {$IFDEF SYN_LAZARUS} - PhysStartPos:=LogicalToPhysicalPos(Item.fChangeStartPos); - {$ENDIF} - case Item.fChangeReason of - crInsert, crPaste, crDragDropInsert: - begin - SetCaretAndSelection(LogicalToPhysicalPos(Item.ChangeStartPos), - Item.ChangeStartPos, Item.ChangeStartPos); - SetSelTextPrimitive(Item.fChangeSelMode, PChar(Item.fChangeStr)); - CaretXY := LogicalToPhysicalPos(Item.fChangeEndPos); - fUndoList.AddChange(Item.fChangeReason, Item.fChangeStartPos, - Item.fChangeEndPos, GetSelText, Item.fChangeSelMode); -{begin} //mh 2000-11-20 - if Item.fChangeReason = crDragDropInsert then begin - SetCaretAndSelection(PhysStartPos, - Item.fChangeStartPos, Item.fChangeEndPos); - end; -{end} //mh 2000-11-20 + if Assigned(Item) then + try + FCaret.IncForcePastEOL; + if Item.ClassType = TSynEditUndoIndent then + begin // re-insert the column + SetCaretAndSelection(LogicalToPhysicalPos(Point(1,TSynEditUndoIndent(Item).FPosY1)), + Point(1, TSynEditUndoIndent(Item).FPosY1), Point(2, TSynEditUndoIndent(Item).FPosY2), + smNormal); + x := fBlockIndent; + fBlockIndent := TSynEditUndoIndent(Item).FCnt; + DoBlockIndent; + fBlockIndent := x; + end + else + if Item.ClassType = TSynEditUndoUnIndent then + begin // re-delete the (raggered) column + // add to undo list + fUndoList.AddChange(TSynEditUndoUnIndent.Create(TSynEditUndoUnIndent(Item).FPosY1, + TSynEditUndoUnIndent(Item).FPosY2, TSynEditUndoUnIndent(Item).FText)); + // Delete string + StrToDelete := PChar(TSynEditUndoUnIndent(Item).FText); + CaretY := TSynEditUndoUnIndent(Item).FPosY1; + x := -1; + repeat + Run := GetEOL(StrToDelete); + Len := Run - StrToDelete; + if x < 0 then + x:= Len; + if Len > 0 then begin + TempString := FTheLinesView[CaretY - 1]; + Delete(TempString, 1, Len); + FTheLinesView[CaretY - 1] := TempString; end; - crDeleteAfterCursor, crSilentDeleteAfterCursor: //mh 2000-10-30 - begin - SetCaretAndSelection(PhysStartPos, - Item.fChangeStartPos, Item.fChangeEndPos); - TempString := GetSelText; - SetSelTextPrimitive(Item.fChangeSelMode, PChar(Item.fChangeStr)); - fUndoList.AddChange(Item.fChangeReason, Item.fChangeStartPos, - Item.fChangeEndPos, TempString, Item.fChangeSelMode); - {$IFDEF SYN_LAZARUS} - CaretXY := LogicalToPhysicalPos(Item.ChangeStartPos); - {$ELSE} - CaretXY := Item.fChangeStartPos; - {$ENDIF} + if Run^ in [#10,#13] then begin + if (Run[1] in [#10,#13]) and (Run^<>Run[1]) then + Inc(Run,2) + else + Inc(Run); + CaretY := CaretY + 1; end; - crDelete, {crDragDropDelete, crSelDelete, }crSilentDelete: //mh 2000-10-30, 2000-11-20 - begin - SetCaretAndSelection( - {$IFDEF SYN_LAZARUS}PhysStartPos{$ELSE}Item.fChangeStartPos{$ENDIF}, - Item.fChangeStartPos, {$IFDEF SYN_LAZARUS}Item.fChangeEndPos{$ELSE}Item.fChangeStartPos{$ENDIF} - ); - TempString := GetSelText;; - SetSelTextPrimitive(Item.fChangeSelMode, PChar(Item.fChangeStr)); - fUndoList.AddChange(Item.fChangeReason, Item.fChangeStartPos, - Item.fChangeEndPos, TempString, Item.fChangeSelMode); - {$IFDEF SYN_LAZARUS} - CaretXY := PhysStartPos; - {$ELSE} - CaretXY := LogicalToPhysicalPos(Item.ChangeStartPos); - {$ENDIF} -{begin} //mh 2000-11-20 -(* - // process next entry? This is awkward, and should be replaced by - // undoitems maintaining a single linked list of connected items... - ItemNext := fRedoList.PeekItem; - if {(Item.fChangeReason = crSelDelete) or } - ((Item.fChangeReason = crDragDropDelete) and Assigned(ItemNext) - and (ItemNext.fChangeReason = crDragDropInsert)) - then - Redo; -*) -{end} //mh 2000-11-20 - end; - {$IFDEF SYN_LAZARUS} - crTrimSpace: - begin - FTrimmedLinesView.ForceTrim; - UpdateModified; - end; - {$ENDIF} - crTrimRealSpace: - begin - fUndoList.AddChange(Item.fChangeReason, Item.fChangeStartPos, - Item.fChangeEndPos, Item.fChangeStr, Item.fChangeSelMode); - end; - crLineBreak: -{begin} //sbs 2000-11-20 -// CommandProcessor(ecLineBreak, #13, nil); - begin - {$IFDEF SYN_LAZARUS} - SetCaretAndSelection(PhysStartPos, Item.fChangeStartPos, - Item.fChangeStartPos); - {$ELSE} - CaretPt := Item.fChangeStartPos; - SetCaretAndSelection(CaretPt, CaretPt, CaretPt); - {$ENDIF} - CommandProcessor(ecLineBreak, #13, nil); - end; -{end} //sbs 2000-11-20 - crIndent: - begin // re-insert the column - SetCaretAndSelection(LogicalToPhysicalPos(Item.fChangeEndPos), - Item.fChangeStartPos, Item.fChangeEndPos); - x := fBlockIndent; - fBlockIndent := ord(Item.fChangeStr[1]); - SelectionMode := Item.fChangeSelMode; - DoBlockIndent; - fBlockIndent := x; - end; - crUnindent : - begin // re-delete the (raggered) column - // add to undo list - fUndoList.AddChange(Item.fChangeReason, Item.fChangeStartPos, - Item.fChangeEndPos, Item.fChangeStr, Item.fChangeSelMode); - // Delete string - StrToDelete := PChar(Item.fChangeStr); - {$IFDEF SYN_LAZARUS} - CaretY := Item.ChangeStartPos.Y; - x := -1; - {$ELSE} - CaretY := Item.fChangeStartPos.Y; - {$ENDIF} - repeat - Run := GetEOL(StrToDelete); - {$IFDEF SYN_LAZARUS} - Len := Run - StrToDelete; - if x < 0 then - x:= Len; - if Len > 0 then begin - TempString := FTheLinesView[CaretY - 1]; - Delete(TempString, 1, Len); - FTheLinesView[CaretY - 1] := TempString; - end; - if Run^ in [#10,#13] then begin - if (Run[1] in [#10,#13]) and (Run^<>Run[1]) then - Inc(Run,2) - else - Inc(Run); - CaretY := CaretY + 1; - end; - {$ELSE} - if Run <> StrToDelete then begin - Len := Run - StrToDelete; - TempString := Lines[CaretY - 1]; - if Len > 0 then - Delete(TempString, 1, Len); - Lines[CaretY - 1] := TempString; - end else - Len := 0; - if Run^ = #13 then begin - Inc(Run); - if Run^ = #10 then - Inc(Run); - Inc(fCaretY); - end; - {$ENDIF} - StrToDelete := Run; - until Run^ = #0; - // restore selection - {$IFDEF SYN_LAZARUS} - if (Item.fChangeStartPos.y > Item.fChangeEndPos.y) - or ((Item.fChangeStartPos.y = Item.fChangeEndPos.y) and (Item.fChangeStartPos.x > Item.fChangeEndPos.x)) - then SwapInt(Len, x); - CaretPt := Point(Item.fChangeEndPos.x - Len, Item.fChangeEndPos.y); - SetCaretAndSelection(LogicalToPhysicalPos(CaretPt), - Point(Item.fChangeStartPos.x - x, Item.fChangeStartPos.y), CaretPt - ); - {$ELSE} - CaretPt := Point(Item.fChangeStartPos.x - fTabWidth, - Item.fChangeStartPos.y); - SetCaretAndSelection(CaretPt, CaretPt, - Point(Item.fChangeEndPos.x - Len, Item.fChangeEndPos.y) - ); - {$ENDIF} - end; - end; + StrToDelete := Run; + until Run^ = #0; + end + else + if not Item.PerformUndo(self) then + FTheLinesView.EditRedo(Item); finally - FBlockSelection.SelectionMode := OldSelMode; - FBlockSelection.ActiveSelectionMode := Item.fChangeSelMode; - Exclude(fStateFlags, sfInsideRedo); //mh 2000-10-30 - FCaret.ForcePastEOL := False; + FCaret.DecForcePastEOL; Item.Free; - DecPaintLock; - {$IFDEF SYN_LAZARUS} - if fRedoList.IsTopMarkedAsUnmodified then - fUndoList.MarkTopAsUnmodified; - {$ENDIF} end; end; procedure TCustomSynEdit.Undo; -{begin} //sbs 2000-11-19 var Item: TSynEditUndoItem; - OldChangeNumber: integer; + Group: TSynEditUndoGroup; begin - Item := fUndoList.PeekItem; - if Item <> nil then begin - {$IFDEF SYN_LAZARUS} - BeginUndoBlock(fRedoList); - OldChangeNumber := Item.fChangeNumber; - {$ELSE} - OldChangeNumber := fRedoList.BlockChangeNumber; - fRedoList.BlockChangeNumber := Item.fChangeNumber; - {$ENDIF} - fUndoList.Lock; - try - repeat - UndoItem; - Item := fUndoList.PeekItem; - {$IFDEF SYN_LAZARUS} - until (Item = nil) or (Item.fChangeNumber <> OldChangeNumber); - {$ELSE} - until (Item = nil) or (Item.fChangeNumber <> fRedoList.BlockChangeNumber); - {$ENDIF} - finally - // Todo: Decide what do to, If there are any trimable spaces. - FTrimmedLinesView.ForceTrim; - fUndoList.UnLock; - {$IFDEF SYN_LAZARUS} - EndUndoBlock(fRedoList); - {$ELSE} - fRedoList.BlockChangeNumber := OldChangeNumber; - {$ENDIF} + Group := fUndoList.PopItem; + if Group <> nil then begin; + IncPaintLock; + FIsUndoing := True; + Item := Group.Pop; + if Item <> nil then begin + BeginUndoBlock(fRedoList); + fRedoList.CurrentGroup.Reason := Group.Reason; + fUndoList.Lock; + try + repeat + UndoItem(Item); + Item := Group.Pop; + until (Item = nil); + finally + // Todo: Decide what do to, If there are any trimable spaces. + FTrimmedLinesView.ForceTrim; + fUndoList.UnLock; + EndUndoBlock(fRedoList); + end; end; + FIsUndoing := False; + Group.Free; + if fUndoList.IsTopMarkedAsUnmodified then + fRedoList.MarkTopAsUnmodified; + DecPaintLock; end; end; +procedure TCustomSynEdit.UndoItem(Item: TSynEditUndoItem); +begin + if Assigned(Item) then try + FCaret.IncForcePastEOL; + if Item.ClassType = TSynEditUndoIndent then + begin + // remove the column that was inserted // select the inserted column + BlockBegin := Point(1, TSynEditUndoIndent(Item).FPosY1); + BlockEnd := Point(1 + TSynEditUndoIndent(Item).FCnt, TSynEditUndoIndent(Item).FPosY2); + FBlockSelection.ActiveSelectionMode := smColumn; + // add to redo list + fRedoList.AddChange(TSynEditUndoIndent.Create(TSynEditUndoIndent(Item).FPosY1, + TSynEditUndoIndent(Item).FPosY2, TSynEditUndoIndent(Item).FCnt)); + // remove the column + SetSelTextPrimitive(smNormal, nil); + end + else + if Item.ClassType = TSynEditUndoUnIndent then + begin + fRedoList.AddChange(TSynEditUndoUnIndent.Create(TSynEditUndoUnIndent(Item).FPosY1, + TSynEditUndoUnIndent(Item).FPosY2, TSynEditUndoUnIndent(Item).FText)); + // reinsert the string + InsertBlock(Point(1, TSynEditUndoUnIndent(Item).FPosY1), + PChar(TSynEditUndoUnIndent(Item).FText)); + end + else + if not Item.PerformUndo(self) then + FTheLinesView.EditUndo(Item); + finally + FTrimmedLinesView.UndoTrimmedSpaces := False; + FCaret.DecForcePastEOL; + Item.Free; + end; +end; + +function TCustomSynEdit.GetUndoList: TSynEditUndoList; +begin + if FIsUndoing then + Result := fRedoList + else + Result := fUndoList; +end; + function TCustomSynEdit.GetLineState(ALine: Integer): TSynLineState; begin with TSynEditStringList(fLines) do @@ -5003,160 +5077,11 @@ end; procedure TCustomSynEdit.SetDebugMarks(AFirst, ALast: Integer); begin TSynEditStringList(fLines).SetDebugMarks(AFirst, ALast); - InvalidateGutterLines(AFirst, ALast); end; procedure TCustomSynEdit.ClearDebugMarks; begin TSynEditStringList(fLines).ClearDebugMarks; - InvalidateGutter; -end; - -procedure TCustomSynEdit.UndoItem; -{end} //sbs 2000-11-19 -var - Item: TSynEditUndoItem; - OldSelMode: TSynSelectionMode; - TmpPos: TPoint; - TmpStr: string; - {$IFDEF SYN_LAZARUS} - PhysStartPos: TPoint; - {$ENDIF} -begin - OldSelMode := FBlockSelection.SelectionMode; - Item := fUndoList.PopItem; - if Assigned(Item) then try - FBlockSelection.SelectionMode := Item.fChangeSelMode; // Default and Active SelectionMode - IncPaintLock; - FCaret.ForcePastEOL := True; - {$IFDEF SYN_LAZARUS} - PhysStartPos:=LogicalToPhysicalPos(Item.fChangeStartPos); - {$ENDIF} - case Item.fChangeReason of - crInsert, crPaste, crDragDropInsert: - begin - SetCaretAndSelection( - {$IFDEF SYN_LAZARUS}PhysStartPos{$ELSE}Item.fChangeStartPos{$ENDIF}, - Item.fChangeStartPos, Item.fChangeEndPos); - fRedoList.AddChange(Item.fChangeReason, Item.fChangeStartPos, - Item.fChangeEndPos, GetSelText, Item.fChangeSelMode); - SetSelTextPrimitive(Item.fChangeSelMode, PChar(Item.fChangeStr)); - CaretXY := {$IFDEF SYN_LAZARUS}PhysStartPos - {$ELSE}Item.fChangeStartPos{$ENDIF}; - end; - crDeleteAfterCursor, crDelete, {crDragDropDelete, crSelDelete, } //mh 2000-11-20 - {$IFDEF SYN_LAZARUS}crTrimSpace,{$ENDIF} - crSilentDelete, crSilentDeleteAfterCursor: //mh 2000-10-30 - begin - if Item.fChangeReason = crTrimSpace then - FTrimmedLinesView.UndoTrimmedSpaces := true; - // If there's no selection, we have to set - // the Caret's position manually. - if Item.fChangeSelMode = smColumn then - TmpPos := Point(Min(Item.fChangeStartPos.X, Item.fChangeEndPos.X), - Min(Item.fChangeStartPos.Y, Item.fChangeEndPos.Y)) - else - TmpPos := minPoint(Item.fChangeStartPos, Item.fChangeEndPos); - if (Item.fChangeReason in [crDeleteAfterCursor, - crSilentDeleteAfterCursor]) and (TmpPos.Y > FTheLinesView.Count) //mh 2000-10-30 - then begin - CaretXY := Point(1, FTheLinesView.Count); - // this stinks!!! - CommandProcessor(ecLineBreak, #13, nil); - end; - SetCaretAndSelection(LogicalToPhysicalPos(TmpPos), TmpPos, TmpPos); - SetSelTextPrimitive(Item.fChangeSelMode, PChar(Item.fChangeStr)); - if Item.fChangeReason in [crSilentDelete, crSilentDeleteAfterCursor - {$IFDEF SYN_LAZARUS}, crTrimSpace{$ENDIF} ] - then - CaretXY := LogicalToPhysicalPos(Item.fChangeEndPos) - else begin - SetCaretAndSelection(LogicalToPhysicalPos(Item.fChangeEndPos), - Item.fChangeStartPos, Item.fChangeEndPos); - end; - fRedoList.AddChange(Item.fChangeReason, Item.fChangeStartPos, - Item.fChangeEndPos, '', Item.fChangeSelMode); - EnsureCursorPosVisible; - FTrimmedLinesView.UndoTrimmedSpaces := False; - end; - crTrimRealSpace: - begin - FTrimmedLinesView.UndoRealSpaces(Item); - fRedoList.AddChange(Item.fChangeReason, Item.fChangeStartPos, - Item.fChangeEndPos, Item.fChangeStr, Item.fChangeSelMode); - end; - crLineBreak: - begin - // If there's no selection, we have to set - // the Caret's position manualy. - CaretXY := PhysStartPos; - fRedoList.AddChange(Item.fChangeReason, Item.fChangeStartPos, - Item.fChangeEndPos, '', Item.fChangeSelMode); - TmpStr := FTheLinesView.Strings[CaretY - 1]; - FTheLinesView.Delete(Item.fChangeEndPos.y); - {$IFDEF SYN_LAZARUS} - if Item.fChangeStr <> '' then - FTheLinesView[CaretY - 1] := TmpStr + Item.fChangeStr; - {$ELSE} - TrimmedSetLine(CaretY - 1, TmpStr + Item.fChangeStr); - {$ENDIF} - DoLinesDeleted(CaretY, 1); - end; - crIndent: // remove the column that was inserted - begin - // select the inserted column - {$IFDEF SYN_LAZARUS} - BlockBegin := Point(1, Item.ChangeStartPos.y); - TmpPos := Item.ChangeEndPos; - if TmpPos.x = 1 then - Dec(TmpPos.y); - TmpPos.x := ord(Item.fChangeStr[1])+1; - BlockEnd := TmpPos; - {$ELSE} - BlockBegin := Point(1, Item.fChangeStartPos.y); - TmpPos := Item.fChangeEndPos; - if TmpPos.x = 1 then - Dec(TmpPos.y); - TmpPos.x := fTabWidth+1; - BlockEnd := TmpPos; - {$ENDIF} - FBlockSelection.ActiveSelectionMode := smColumn; - // add to redo list - fRedoList.AddChange(Item.fChangeReason, Item.fChangeStartPos, - Item.fChangeEndPos, {$IFDEF SYN_LAZARUS}Item.fChangeStr{$ELSE}GetSelText{$ENDIF}, Item.fChangeSelMode); - // remove the column - SetSelTextPrimitive(Item.fChangeSelMode, nil); - // restore the selection - SetCaretAndSelection( - {$IFDEF SYN_LAZARUS} - LogicalToPhysicalPos(Item.fChangeEndPos), - {$ELSE} - Item.fChangeEndPos, - {$ENDIF} - Item.fChangeStartPos, Item.fChangeEndPos); - end; - crUnindent: // reinsert the (raggered) column that was deleted - begin - fRedoList.AddChange(Item.fChangeReason, Item.fChangeStartPos, - Item.fChangeEndPos, Item.fChangeStr, Item.fChangeSelMode); - // reinsert the string - InsertBlock(Point(1, Item.ChangeStartPos.y), PChar(Item.fChangeStr)); - SetCaretAndSelection(LogicalToPhysicalPos(Item.fChangeEndPos), - Item.fChangeStartPos, Item.fChangeEndPos); - end; - end; - finally - FTrimmedLinesView.UndoTrimmedSpaces := False; - FBlockSelection.SelectionMode := OldSelMode; - FBlockSelection.ActiveSelectionMode := Item.fChangeSelMode; - FCaret.ForcePastEOL := False; - Item.Free; - DecPaintLock; - {$IFDEF SYN_LAZARUS} - if fUndoList.IsTopMarkedAsUnmodified then - fRedoList.MarkTopAsUnmodified; - {$ENDIF} - end; end; {$IFDEF SYN_LAZARUS} @@ -5377,16 +5302,16 @@ begin end; end; // insert the selected text - FCaret.ForcePastEOL := True; + FCaret.IncForcePastEOL; try CaretXY := NewCaret; BlockBegin := NewCaret; if Source = Self then - SetSelTextPrimitive(smNormal, PChar(DragDropText), true, crDragDropInsert) + SetSelTextPrimitive(smNormal, PChar(DragDropText), true) else - SetSelTextPrimitive(smNormal, PChar(DragDropText), true, crInsert); + SetSelTextPrimitive(smNormal, PChar(DragDropText), true); finally - FCaret.ForcePastEOL := False; + FCaret.DecForcePastEOL; end; BlockBegin := {$IFDEF SYN_LAZARUS}PhysicalToLogicalPos(NewCaret) {$ELSE}NewCaret{$ENDIF}; @@ -5791,7 +5716,6 @@ var Temp: string; Temp2: string; Helper: string; - StartOfBlock: TPoint; bCaretAdjust: Boolean; moveBkm: boolean; WP: TPoint; @@ -5799,23 +5723,22 @@ var CaretNew: TPoint; OldSelMode: TSynSelectionMode; {$IFDEF SYN_MBCSSUPPORT} + StartOfBlock: TPoint; i: integer; s: string; {$ENDIF} counter: Integer; InsDelta: integer; - {$IFDEF SYN_LAZARUS} LogCounter: integer; LogCaretXY: TPoint; - LogCaret: TPoint; - LastUndoItem:TSynEditUndoItem; CY: Integer; - {$ENDIF} begin IncPaintLock; bCaretAdjust := FCaret.AdjustToNextChar; try + fUndoList.CurrentReason := Command; + if Command in [ecSelColCmdRangeStart..ecSelColCmdRangeEnd] then FBlockSelection.ActiveSelectionMode := smColumn; if Command in [ecSelCmdRangeStart..ecSelCmdRangeEnd] then @@ -6005,37 +5928,23 @@ begin else begin Temp := LineText; Len := Length(Temp); - {$IFDEF SYN_LAZARUS} LogCaretXY:=PhysicalToLogicalPos(CaretXY); - LogCaret:=LogCaretXY; - {$ENDIF} Caret := CaretXY; //debugln('ecDeleteLastChar B Temp="',DbgStr(Temp),'" CaretX=',dbgs(CaretX),' LogCaretXY=',dbgs(LogCaretXY)); - if {$IFDEF SYN_LAZARUS}LogCaretXY.X{$ELSE}CaretX{$ENDIF} > Len +1 + if LogCaretXY.X > Len +1 then begin - // only move caret one column - Helper := ' '; - FCaret.ForcePastEOL := True; + // past EOL; only move caret one column + FCaret.IncForcePastEOL; CaretX := CaretX - 1; - FCaret.ForcePastEOL := False; - {$IFDEF SYN_LAZARUS} - // behind EOL, there was no char to delete, this wa a simple cursor move, do not undo - Caret := CaretXY; - {$ENDIF} + FCaret.DecForcePastEOL; end else if CaretX = 1 then begin // join this line with the last line if possible if CaretY > 1 then begin CaretY := CaretY - 1; CaretX := PhysicalLineLength(FTheLinesView[CaretY - 1], CaretY - 1) + 1; - FTheLinesView.Delete(CaretY); + FTheLinesView.EditLineJoin(CaretY); DoLinesDeleted(CaretY, 1); - {$IFNDEF SYN_LAZARUS} - if eoTrimTrailingSpaces in Options then - Temp := TrimRight(Temp); - {$ENDIF} - LineText := LineText + Temp; - Helper := {$IFDEF SYN_LAZARUS}LineEnding{$ELSE}#13#10{$ENDIF}; end; end else begin // delete text before the caret @@ -6043,72 +5952,25 @@ begin FBeautifier.CanUnindent(Self, Temp, CaretX) then begin // unindent - Temp := FBeautifier.UnIndentLine(Self, Temp, ViewedTextBuffer, - CaretXY, Helper, Temp2, CX); // TODO: Temp2 must be added as crInsert to undolist - FTheLinesView[CaretY - 1] := Temp; + FBeautifier.UnIndentLine(Self, Temp, ViewedTextBuffer, + CaretXY, Helper, Temp2, CX); CaretX := CX; fLastCaretX := CaretX; StatusChanged([scCaretX]); end else begin // delete char - counter := 1; - {$IFDEF SYN_LAZARUS} - {$IFDEF USE_UTF8BIDI_LCL} - CaretX := CaretX - counter; - Helper := Copy(Temp, CaretX, counter); - VDelete(Temp, CaretX, counter, drLTR); - {$ELSE USE_UTF8BIDI_LCL} - LogCaretXY.X:=PhysicalToLogicalCol(Temp, CaretY-1, CaretX-counter); - LogCounter:=GetCharLen(Temp,LogCaretXY.X); - CaretX := LogicalToPhysicalCol(Temp, CaretY-1, LogCaretXY.X); - Helper := Copy(Temp, LogCaretXY.X, LogCounter); - System.Delete(Temp, LogCaretXY.X, LogCounter); - //debugln('ecDeleteLastChar delete char CaretX=',dbgs(CaretX), - // ' Helper="',DbgStr(Helper),'" Temp="',DbgStr(Temp),'"'); - {$ENDIF USE_UTF8BIDI_LCL} - FTheLinesView[CaretY - 1] := Temp; - {$ELSE} - {$IFDEF SYN_MBCSSUPPORT} - if ByteType(Temp, CaretX - 2) = mbLeadByte then - Inc(counter); - {$ENDIF} - CaretX := CaretX - counter; - Helper := Copy(Temp, CaretX, counter); - Delete(Temp, CaretX, counter); - TrimmedSetLine(CaretY - 1, Temp); - {$ENDIF} + {$IFDEF USE_UTF8BIDI_LCL} + CaretX := CaretX - 1; + FTheLinesView.EditDelete(CaretX, LogCaretXY.Y, 1); + {$ELSE USE_UTF8BIDI_LCL} + LogCaretXY.X:=PhysicalToLogicalCol(Temp, CaretY-1, CaretX - 1); + LogCounter:=GetCharLen(Temp,LogCaretXY.X); + CaretX := LogicalToPhysicalCol(Temp, CaretY-1, LogCaretXY.X); + FTheLinesView.EditDelete(FCaret.BytePos, LogCaretXY.Y, LogCounter); + {$ENDIF USE_UTF8BIDI_LCL} end; end; - if (Caret.X <> CaretX) or (Caret.Y <> CaretY) then begin - {$IFDEF SYN_LAZARUS} - if eoGroupUndo in Options then begin - LastUndoItem := fUndoList.PeekItem; - if (LastUndoItem <> nil) - and (LastUndoItem.fChangeReason = crSilentDelete) - and (LastUndoItem.fChangeStartPos.Y = LastUndoItem.fChangeEndPos.Y) - and (PhysicalToLogicalPos(CaretXY).Y = LogCaret.Y) - and (LastUndoItem.fChangeStartPos.X = LogCaret.X) - then begin // Share the undo item with the delete char action before - LastUndoItem.fChangeStartPos.X := PhysicalToLogicalPos(CaretXY).X; - LastUndoItem.fChangeStr := Helper + LastUndoItem.fChangeStr; - end - else - begin - fUndoList.AddChange(crSilentDelete, - PhysicalToLogicalPos(CaretXY), LogCaret, - Helper, smNormal); - end; - end else begin - //debugln('ecDeleteLastChar AddChange CaretXY=',dbgs(CaretXY), - // ' LogCaret=',dbgs(LogCaret),' Helper="',DbgStr(Helper),'" Temp="',DbgStr(Temp),'"'); - fUndoList.AddChange(crSilentDelete, CaretXY, Caret, - Helper, smNormal); - end; - {$ELSE} - fUndoList.AddChange(crSilentDelete,CaretXY,Caret,Helper,smNormal); - {$ENDIF} - end; end; end; ecDeleteChar: @@ -6118,55 +5980,21 @@ begin else begin Temp := LineText; Len := Length(Temp); - {$IFDEF SYN_LAZARUS} LogCaretXY:=PhysicalToLogicalPos(CaretXY); - {$ENDIF} - if {$IFDEF SYN_LAZARUS}LogCaretXY.X{$ELSE}CaretX{$ENDIF} <= Len then + if LogCaretXY.X <= Len then begin // delete char - {$IFDEF SYN_LAZARUS} - Counter:=GetCharLen(Temp,LogCaretXY.X); - Helper := Copy(Temp, LogCaretXY.X, Counter); - Caret.X := LogicalToPhysicalCol(Temp, CaretY-1, LogCaretXY.X+Counter); - Caret.Y := CaretY; -{$IFDEF USE_UTF8BIDI_LCL} - VDelete(Temp, LogCaretXY.X, Counter, drLTR); -{$ELSE USE_UTF8BIDI_LCL} - System.Delete(Temp, LogCaretXY.X, Counter); -{$ENDIF USE_UTF8BIDI_LCL} - FTheLinesView[CaretY - 1] := Temp; - {$ELSE} - counter := 1; - {$IFDEF SYN_MBCSSUPPORT} - if ByteType(Temp, CaretX) = mbLeadByte then - Inc(counter); - {$ENDIF} - Helper := Copy(Temp, CaretX, counter); - Caret := Point(CaretX + counter, CaretY); - Delete(Temp, CaretX, counter); - TrimmedSetLine(CaretY - 1, Temp); - {$ENDIF} + Counter:=GetCharLen(Temp,LogCaretXY.X); + FTheLinesView.EditDelete(LogCaretXY.X, CaretY, Counter); + DoLinesDeleted(CaretY - 1, 1); end else begin // join line with the line after if CaretY < FTheLinesView.Count then begin Helper := StringOfChar(' ', LogCaretXY.X - 1 - Len); - FTheLinesView[CaretY - 1] := Temp + Helper + FTheLinesView[CaretY]; - FTheLinesView.Delete(CaretY); - if helper <> '' then begin - Caret := Point(Len+1, CaretY); // logical - fUndoList.AddChange(crInsert, PhysicalToLogicalPos(CaretXY), - Caret, '', smNormal); - end; - Caret := Point(1, CaretY + 1); - Helper := {$IFDEF SYN_LAZARUS}LineEnding{$ELSE}#13#10{$ENDIF}; + FTheLinesView.EditLineJoin(CaretY, Helper); DoLinesDeleted(CaretY - 1, 1); end; end; - if (Caret.X <> CaretX) or (Caret.Y <> CaretY) then begin - fUndoList.AddChange(crSilentDeleteAfterCursor, - PhysicalToLogicalPos(Caret), PhysicalToLogicalPos(CaretXY), - Helper, smNormal); - end; end; end; ecDeleteWord, ecDeleteEOL: @@ -6177,28 +6005,24 @@ begin if Command = ecDeleteWord then begin if CaretX > Len + 1 then begin Helper := StringOfChar(' ', CaretX - 1 - Len); - Caret.X := Caret.X - (CaretX - 1 - Len); + CaretX := 1 + Len; end; - WP := NextWordPos{$IFDEF SYN_LAZARUS}(True){$ENDIF}; + WP := NextWordPos(True); end else WP := Point(Len + 1, CaretY); if (WP.X <> CaretX) or (WP.Y <> CaretY) then begin - if Helper <> '' then begin - LineText := LineText + Helper; - Caret := PhysicalToLogicalPos(Point(Len+1, CaretY)); - fUndoList.AddChange(crInsert, PhysicalToLogicalPos(CaretXY), - Caret, '', smNormal); - end; OldSelMode := FBlockSelection.ActiveSelectionMode; try SetBlockBegin(PhysicalToLogicalPos(WP)); SetBlockEnd(PhysicalToLogicalPos(CaretXY)); FBlockSelection.ActiveSelectionMode := smNormal; - SetSelTextPrimitive(smNormal, nil, true, crSilentDeleteAfterCursor) + SetSelTextPrimitive(smNormal, nil, true); + if Helper <> '' then + FTabbedLinesView.EditInsert(CaretX, CaretY, Helper); finally FBlockSelection.ActiveSelectionMode := OldSelMode; end; - CaretXY := CaretXY; + CaretXY := Caret; end; end; ecDeleteLastWord, ecDeleteBOL: @@ -6213,7 +6037,7 @@ begin SetBlockBegin(PhysicalToLogicalPos(WP)); SetBlockEnd(PhysicalToLogicalPos(CaretXY)); FBlockSelection.ActiveSelectionMode := smNormal; - SetSelTextPrimitive(smNormal, nil, true, crSilentDelete) + SetSelTextPrimitive(smNormal, nil, true) finally FBlockSelection.ActiveSelectionMode := OldSelMode; end; @@ -6225,32 +6049,13 @@ begin if not ReadOnly and not ((FTheLinesView.Count = 1) and (Length(FTheLinesView[0]) = 0)) then begin if SelAvail then - SetBlockBegin({$IFDEF SYN_LAZARUS}PhysicalToLogicalPos(CaretXY) - {$ELSE}CaretXY{$ENDIF}); - if FTheLinesView.Count = 1 then begin - fUndoList.AddChange(crDeleteAfterCursor, - {$IFDEF SYN_LAZARUS} - PhysicalToLogicalPos(Point(1, CaretY)), - PhysicalToLogicalPos(CaretXY), - {$ELSE} - Point(1, CaretY), CaretXY, - {$ENDIF} - LineText, smNormal); - FTheLinesView[0] := ''; - end else begin - fUndoList.AddChange(crDeleteAfterCursor, - {$IFDEF SYN_LAZARUS} - PhysicalToLogicalPos(Point(1, CaretY)), - PhysicalToLogicalPos(CaretXY), - LineText + LineEnding, - {$ELSE} - Point(1, CaretY), CaretXY, - LineText + #13#10, - {$ENDIF} - smNormal); - FTheLinesView.Delete(CaretY - 1); + SetBlockBegin(PhysicalToLogicalPos(CaretXY)); + if FTheLinesView.Count = 1 then + FTheLinesView.EditDelete(1, 1, length(FTheLinesView[0])) + else begin + FTheLinesView.EditLinesDelete(CaretY, 1); + DoLinesDeleted(CaretY - 1, 1); end; - DoLinesDeleted(CaretY - 1, 1); CaretXY := Point(1, CaretY); // like seen in the Delphi editor end; ecClearAll: @@ -6260,6 +6065,9 @@ begin ecInsertLine, ecLineBreak: if not ReadOnly then begin + // current line is empty (len = 0) + if FTheLinesView.Count = 0 then + FTheLinesView.Add(''); if SelAvail then begin SetSelTextExternal(''); end; @@ -6267,60 +6075,25 @@ begin Temp2 := Temp; //LineText; // This is sloppy, but the Right Thing would be to track the column of markers // too, so they could be moved depending on whether they are after the caret... - InsDelta := Ord(CaretX = 1); + InsDelta := Ord( (CaretX = 1) AND (Command = ecLineBreak) ); LogCaretXY:=PhysicalToLogicalPos(CaretXY); Len := Length(Temp); - if Len >= LogCaretXY.X then begin - if LogCaretXY.X > 1 then begin - // break line in two - LineText := LineText; // TrimRealSpaces - FTheLinesView.Insert(CaretY - 1, Copy(Temp, 1, LogCaretXY.X - 1)); - Delete(Temp2, 1, LogCaretXY.X - 1); - fUndoList.AddChange(crLineBreak, LogCaretXY, LogCaretXY, - Temp2, smNormal); - if (eoAutoIndent in fOptions) then begin - Caret := CaretXY; - Caret.Y := Caret.Y + 1; - Temp2 := FBeautifier.IndentLine(Self, Temp2, ViewedTextBuffer, - Caret, Helper, Temp, CX, False); - end else - CX := 1; - FTheLinesView[CaretY] := Temp2; - if Command = ecLineBreak then - CaretXY := Point(CX, CaretY + 1); - end else begin - // move the whole line - LineText := LineText; // TrimRealSpaces - FTheLinesView.Insert(CaretY - 1, ''); - fUndoList.AddChange(crLineBreak, - LogCaretXY, LogCaretXY, - Temp2, smNormal); - if Command = ecLineBreak then - CaretY := CaretY + 1; - end; - end else begin - // current line is empty (len = 0) - if FTheLinesView.Count = 0 then - FTheLinesView.Add(''); - // linebreak after end of line - LineText := LineText; // TrimRealSpaces - fUndoList.AddChange(crLineBreak, - LogCaretXY, LogCaretXY, - '', smNormal); - Temp2 := ''; - if (eoAutoIndent in fOptions) then begin - Caret := CaretXY; - Caret.Y := Caret.Y + 1; - Temp2 := FBeautifier.IndentLine(Self, Temp2, ViewedTextBuffer, - Caret, Helper, Temp, CX, False); - end else - CX := 1; - FTheLinesView.Insert(CaretY, Temp); - if Command = ecLineBreak then begin - FCaret.ForcePastEOL := True; - CaretXY := Point(CX, CaretY + 1); - FCaret.ForcePastEOL := False; - end; + if LogCaretXY.X > Len + 1 then + LogCaretXY.X := Len + 1; + FTheLinesView.EditLineBreak(LogCaretXY.X, LogCaretXY.Y); + if (eoAutoIndent in fOptions) and (LogCaretXY.X > 1) then begin + Caret := CaretXY; + Caret.Y := Caret.Y + 1; + FTheLinesView.EditInsert(1, LogCaretXY.Y + 1, + FBeautifier.IndentLine + (Self, FTheLinesView[CaretY], ViewedTextBuffer, + Caret, Helper, Temp, CX, False)); + end else + CX := 1; + if Command = ecLineBreak then begin + FCaret.IncForcePastEOL; + CaretXY := Point(CX, CaretY + 1); + FCaret.DecForcePastEOL; end; DoLinesInserted(CaretY - InsDelta, 1); EnsureCursorPosVisible; //JGF 2000-09-23 @@ -6339,83 +6112,45 @@ begin ecChar: if not ReadOnly and (AChar >= #32) and (AChar <> #127) then begin if SelAvail then begin - BeginUndoBlock; - try - SetSelTextExternal(AChar); - //debugln('ecChar SelAvail StartOfBlock=',dbgs(StartOfBlock),' fBlockEnd=',dbgs(fBlockEnd)); - finally - EndUndoBlock; - end; + SetSelTextExternal(AChar); end else begin - Temp := LineText; -// Added the check for whether or not we're in insert mode. -// If we are, we append one less space than we would in overwrite mode. -// This is because in overwrite mode we have to put in a final space -// character which will be overwritten with the typed character. If we put the -// extra space in in insert mode, it would be left at the end of the line and -// cause problems unless eoTrimTrailingSpaces is set. - LogCaretXY:=PhysicalToLogicalPos(CaretXY); - {debugln('ecChar CaretXY=',dbgs(CaretXY), - ' LogCaretXY=',dbgs(PhysicalToLogicalPos(CaretXY)), - ' Adjusted LogCaretXY=',dbgs(LogCaretXY), - ' fInserting=',dbgs(fInserting), - ' Temp=',dbgstr(Temp), - '" UseUTF8=',dbgs(UseUTF8));} try - FCaret.ForcePastEOL := True; - StartOfBlock := LogCaretXY; - if fInserting then begin - // insert mode - {$IFDEF USE_UTF8BIDI_LCL} - // TODO: improve utf8bidi for tabs - Len := VLength(Temp, drLTR); - if Len < CaretX then begin - Temp := Temp + StringOfChar(' ', CaretX - Len); - end; - CaretX := InsertChar(aChar, Temp, CaretX, drLTR); - FTheLinesView[CaretY - 1] := Temp; - {$ELSE} - Len := Length(Temp); - if Len < LogCaretXY.X - 1 then - Temp := Temp + StringOfChar(' ', LogCaretXY.X - 1 - Len); - System.Insert(AChar, Temp, LogCaretXY.X); - //debugln('ecChar Temp=',DbgStr(Temp),' AChar=',DbgStr(AChar)); - FTheLinesView[CaretY - 1] := Temp; - FCaret.AdjustToNextChar := True; - CaretX := CaretX + 1; - FCaret.AdjustToNextChar := bCaretAdjust; - {$ENDIF} - fUndoList.AddChange(crInsert, StartOfBlock, - PhysicalToLogicalPos(CaretXY), '', smNormal); - end else begin - // overwrite mode - Counter := GetCharLen(Temp,LogCaretXY.X); - Helper := Copy(Temp, LogCaretXY.X, Counter); - {$IFDEF USE_UTF8BIDI_LCL} - CaretNew.X := CaretX; - // TODO: improve utf8bidi for tabs - //utf8bidi.insert(Temp,AChar,CaretNew.X); - CaretX := CaretNew.X; - {$ELSE} - Len := Length(Temp); - if LogCaretXY.X<=Len then - Temp:=copy(Temp,1,LogCaretXY.X-1)+AChar - +copy(Temp,LogCaretXY.X+Counter,length(Temp)) - else - Temp:=Temp+StringOfChar(' ', LogCaretXY.X-1-Len)+AChar; - {$ENDIF} - FTheLinesView[CaretY - 1] := Temp; - FCaret.AdjustToNextChar := True; - CaretX := CaretX + 1; - FCaret.AdjustToNextChar := bCaretAdjust; - fUndoList.AddChange(crInsert, - StartOfBlock, FCaret.LineBytePos, - Helper, smNormal); + FCaret.IncForcePastEOL; + FCaret.AdjustToNextChar := True; + + LogCaretXY := FCaret.LineBytePos; + Temp := LineText; + Len := Length(Temp); + if (not fInserting) and (LogCaretXY.X - 1<= Len) then begin + counter := GetCharLen(Temp,LogCaretXY.X); + FTheLinesView.EditDelete(LogCaretXY.X, LogCaretXY.Y, counter); + Len := Len - counter; end; + + {$IFDEF USE_UTF8BIDI_LCL} + // TODO: improve utf8bidi for tabs + Len := VLength(LineText, drLTR); + (*if Len < CaretX then + Temp := StringOfChar(' ', CaretX - Len) + else + Temp := '' *) + FTheLinesView.EditInsert(CaretX, LogCaretXY.Y, (*Temp +*) AChar); + {$ELSE} + (*if Len < LogCaretXY.X - 1 then begin + Temp := StringOfChar(' ', LogCaretXY.X - 1 - Len); + LogCaretXY.X := Len + 1; + end + else + temp := '';*) + FTheLinesView.EditInsert(LogCaretXY.X, LogCaretXY.Y, (*Temp +*) AChar); + {$ENDIF} + + CaretX := CaretX + 1; if CaretX >= LeftChar + fCharsInWindow then LeftChar := LeftChar + Min(25, fCharsInWindow - 1); finally - FCaret.ForcePastEOL := False; + FCaret.AdjustToNextChar := bCaretAdjust; + FCaret.DecForcePastEOL; end; end; end; @@ -6532,7 +6267,7 @@ begin if Len < CaretX then Temp := Temp + StringOfChar(' ', CaretX - Len); try - FCaret.ForcePastEOL := True; + FCaret.IncForcePastEOL; StartOfBlock := CaretXY; // Processing of case character covers on LeadByte. Len := Length(s); @@ -6551,16 +6286,12 @@ begin if fInserting then Helper := ''; fUndoList.AddChange(crInsert, StartOfBlock, - {$IFDEF SYN_LAZARUS} PhysicalToLogicalPos(CaretXY), - {$ELSE} - CaretXY, - {$ENDIF} Helper, smNormal); if CaretX >= LeftChar + fCharsInWindow then LeftChar := LeftChar + min(25, fCharsInWindow - 1); finally - FCaret.ForcePastEOL := False; + FCaret.DecForcePastEOL; end; end; end; @@ -7532,7 +7263,7 @@ begin {$ENDIF} if (eoNoSelection in ChangedOptions) then FBlockSelection.Enabled := eoNoSelection in fOptions; - FBlockSelection.SpacesToTabs := eoSpacesToTabs in fOptions; + fUndoList.GroupUndo := eoGroupUndo in fOptions; end; end; @@ -7571,6 +7302,7 @@ begin SetCaretRespondToFocus(Handle,not (eoPersistentCaret in fOptions)); {$ENDIF} EnsureCursorPosVisible; + fUndoList.GroupUndo := eoGroupUndo in fOptions; end; end; @@ -7642,7 +7374,7 @@ begin // adjust selection IncPaintLock; - FCaret.ForcePastEOL := True; + FCaret.IncForcePastEOL; if SelectionCommand then begin //debugln('TCustomSynEdit.MoveCaretHorz A CaretXY=',dbgs(CaretXY),' NewCaret=',dbgs(NewCaret)); if not FBlockSelection.SelCanContinue(FCaret) then @@ -7654,7 +7386,7 @@ begin SetBlockBegin(FInternalCaret.LineBytePos); // commit new caret CaretXY := FInternalCaret.LineCharPos; - FCaret.ForcePastEOL := False; + FCaret.DecForcePastEOL; DecPaintLock; end; {$ELSE} @@ -7827,8 +7559,8 @@ begin end; {$ENDIF} -procedure TCustomSynEdit.SetCaretAndSelection( - {$IFDEF SYN_LAZARUS}const {$ENDIF}ptCaret, ptBefore, ptAfter: TPoint); +procedure TCustomSynEdit.SetCaretAndSelection(const ptCaret, ptBefore, + ptAfter: TPoint; Mode: TSynSelectionMode = smCurrent); // caret is physical (screen) // Before, After is logical (byte) begin @@ -7836,9 +7568,9 @@ begin CaretXY := ptCaret; SetBlockBegin(ptBefore); SetBlockEnd(ptAfter); - {$IFDEF SYN_LAZARUS} + if Mode <> smCurrent then + FBlockSelection.ActiveSelectionMode := Mode; AquirePrimarySelection; - {$ENDIF} DecPaintLock; end; @@ -8057,13 +7789,13 @@ begin StrPCopy(Run, Spaces); InsertBlock(Point(1,BB.y), StrToInsert); + + fUndoList.AddChange(TSynEditUndoIndent.Create(BB.Y, e, fBlockIndent)); + TSynEditStringList(FLines).MarkModified(BB.Y, e); {$IFDEF SYN_LAZARUS} if BlockBackward then SwapPoint(BB, BE); {$ENDIF} - fUndoList.AddChange(crIndent, BB, BE, - {$IFDEF SYN_LAZARUS}chr(fBlockIndent){$ELSE}''{$ENDIF}, - FBlockSelection.ActiveSelectionMode); finally StrDispose(StrToInsert); end; @@ -8214,15 +7946,14 @@ begin StrToDelete := Run; until Run^ = #0; LastIndent := Len; + fUndoList.AddChange(TSynEditUndoUnIndent.Create(BB.Y, e, FullStrToDelete)); + TSynEditStringList(FLines).MarkModified(BB.Y, e); {$IFDEF SYN_LAZARUS} if BlockBackward then Begin SwapPoint(BB, BE); SwapInt(FirstIndent, LastIndent); end; {$ENDIF} - fUndoList.AddChange(crUnindent, BB, BE, - {$IFDEF SYN_LAZARUS}FullStrToDelete{$ELSE}StrToDelete{$ENDIF}, - FBlockSelection.ActiveSelectionMode); end; {$IFDEF SYN_LAZARUS} if FirstIndent = -1 then begin @@ -9032,18 +8763,12 @@ begin end; procedure TCustomSynEdit.UndoRedoAdded(Sender: TObject); -var - Item: TSynEditUndoItem; begin IncreaseChangeStamp; - Item := TSynEditUndoList(Sender).PeekItem; - if Item <> nil then - TSynEditStringList(fLines).MarkModified(Item.ChangeStartPos.y - 1, - Item.ChangeEndPos.y - 1, Sender = fUndoList, Item.fChangeReason); UpdateModified; // we have to clear the redo information, since adding undo info removes // the necessary context to undo earlier edit actions - if (Sender = fUndoList) and not (sfInsideRedo in fStateFlags) then //mh 2000-10-30 + if (Sender = fUndoList) and not (fUndoList.IsInsideRedo) then //mh 2000-10-30 fRedoList.Clear; if Assigned(fOnChange) then fOnChange(Self); diff --git a/components/synedit/syneditpointclasses.pas b/components/synedit/syneditpointclasses.pas index 1286199a92..8594ab70c7 100644 --- a/components/synedit/syneditpointclasses.pas +++ b/components/synedit/syneditpointclasses.pas @@ -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 diff --git a/components/synedit/synedittextbase.pas b/components/synedit/synedittextbase.pas index 17ad6807bd..fad16fe991 100644 --- a/components/synedit/synedittextbase.pas +++ b/components/synedit/synedittextbase.pas @@ -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. diff --git a/components/synedit/synedittextbuffer.pp b/components/synedit/synedittextbuffer.pp index 7318f58c7b..59ec84dc2a 100644 --- a/components/synedit/synedittextbuffer.pp +++ b/components/synedit/synedittextbuffer.pp @@ -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; diff --git a/components/synedit/synedittexttrimmer.pas b/components/synedit/synedittexttrimmer.pas index e2666a44b5..5d10fd4abb 100644 --- a/components/synedit/synedittexttrimmer.pas +++ b/components/synedit/synedittexttrimmer.pas @@ -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. diff --git a/components/synedit/synedittypes.pp b/components/synedit/synedittypes.pp index 8764e1d20d..c7f05e1792 100644 --- a/components/synedit/synedittypes.pp +++ b/components/synedit/synedittypes.pp @@ -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,