diff --git a/ide/text/weditor.pas b/ide/text/weditor.pas index b82b52893c..58973509ab 100644 --- a/ide/text/weditor.pas +++ b/ide/text/weditor.pas @@ -123,6 +123,10 @@ const eaDeleteLine = 4; eaDeleteText = 5; eaSelectionChanged = 6; + LastAction = eaSelectionChanged; + + ActionString : array [0..LastAction] of string[8] = + ('','Move','InsLine','InsText','DelLine','DelText','SelCh'); CIndicator = #2#3#1; CEditor = #33#34#35#36#37#38#39#40#41#42#43#44#45#46#47#48#49; @@ -155,7 +159,10 @@ type PIndicator = ^TIndicator; TIndicator = object(TView) Location: TPoint; - Modified: Boolean; + Modified : Boolean; +{$ifdef debug} + StoreUndo : Boolean; +{$endif debug} constructor Init(var Bounds: TRect); procedure Draw; virtual; function GetPalette: PPalette; virtual; @@ -165,6 +172,21 @@ type procedure Store(var S: TStream); end; +{$ifdef Undo} + PEditorAction = ^TEditorAction; + TEditorAction = object(TObject) + StartPos : TPoint; + EndPos : TPoint; + Text : PString; + Action : byte; + constructor init(act:byte; StartP,EndP:TPoint;Txt:String); + destructor done; virtual; + end; + + PEditorActionCollection = ^TEditorActionCollection; + TEditorActionCollection = object(TCollection) + end; +{$else} PEditorAction = ^TEditorAction; TEditorAction = packed record Action : byte; @@ -177,6 +199,7 @@ type TEditorActionCollection = object(TCollection) procedure FreeItem(Item: Pointer); virtual; end; +{$endif Undo} TSpecSymbolClass = (ssCommentPrefix,ssCommentSingleLinePrefix,ssCommentSuffix,ssStringPrefix,ssStringSuffix, @@ -268,7 +291,8 @@ type LastLocalCmd: word; KeyState : Integer; ErrorMessage: PString; - Actions : PEditorActionCollection; + UndoList : PEditorActionCollection; + RedoList : PEditorActionCollection; Bookmarks : array[0..9] of TEditorBookmark; LockFlag : integer; DrawCalled : boolean; @@ -276,6 +300,7 @@ type function Overwrite: boolean; function GetLine(I: sw_integer): PLine; procedure CheckSels; + procedure UpdateUndoRedo(cm : word; action : byte); function UpdateAttrs(FromLine: sw_integer; Attrs: byte): sw_integer; function UpdateAttrsRange(FromLine, ToLine: sw_integer; Attrs: byte): sw_integer; procedure DrawLines(FirstLine: sw_integer); @@ -396,7 +421,8 @@ const ToClipCmds : TCommandSet = ([cmCut,cmCopy,cmCopyWin]); FromClipCmds : TCommandSet = ([cmPaste,cmPasteWin]); NulClipCmds : TCommandSet = ([cmClear]); - UndoCmds : TCommandSet = ([cmUndo,cmRedo]); + UndoCmd : TCommandSet = ([cmUndo]); + RedoCmd : TCommandSet = ([cmRedo]); function StdEditorDialog(Dialog: Integer; Info: Pointer): word; @@ -672,6 +698,7 @@ begin PointOfs:={longint(P.Y)*MaxLineLength+P.X}PosToOfsP(P); end; +{$ifndef Undo} function NewEditorAction(AAction: byte; AStartPos, AEndPos: TPoint; AText: string): PEditorAction; var P: PEditorAction; begin @@ -693,6 +720,7 @@ begin Dispose(P); end; end; +{$endif ndef Undo} function ExtractTabs(S: string; TabSize: Sw_integer): string; var @@ -1025,6 +1053,10 @@ begin begin if Modified then WordRec (B[0]).Lo := ord('*'); +{$ifdef debug} + if StoreUndo then + WordRec (B[1]).Lo := ord('S'); +{$endif debug} L[0] := Location.Y + 1; L[1] := Location.X + 1; FormatStr(S, ' %d:%d ', L); @@ -1082,8 +1114,13 @@ constructor TCodeEditor.Init(var Bounds: TRect; AHScrollBar, AVScrollBar: PScrollBar; AIndicator: PIndicator; ABufSize:Sw_Word); begin inherited Init(Bounds,AHScrollBar,AVScrollBar); +{$ifndef Undo} StoreUndo:=false; - New(Actions, Init(500,1000)); +{$else Undo} + StoreUndo:=true; +{$endif def Undo} + new(UndoList,init(500,1000)); + new(RedoList,init(500,1000)); New(Lines, Init(500,1000)); { we have always need at least 1 line } Lines^.Insert(NewLine('')); @@ -1227,6 +1264,9 @@ begin begin Indicator^.Location:=CurPos; Indicator^.Modified:=Modified; +{$ifdef debug} + Indicator^.StoreUndo:=StoreUndo; +{$endif debug} Indicator^.DrawView; end; end; @@ -1440,6 +1480,20 @@ begin CurEvent:=OldEvent; end; +procedure TCodeEditor.UpdateUndoRedo(cm : word; action : byte); +var UndoMenu : PMenuItem; +begin + UndoMenu:=PAdvancedMenuBar(MenuBar)^.GetMenuItem(cm); + if assigned(UndoMenu) then + begin + If assigned(UndoMenu^.Param) then + DisposeStr(UndoMenu^.Param); + if action0 then @@ -2252,12 +2326,18 @@ begin S:=GetLineText(CurPos.Y); OI:=LinePosToCharIdx(CurPos.Y,CurPos.X); CI:=LinePosToCharIdx(CurPos.Y,CP); - SetLineText(CurPos.Y,copy(S,1,CI+1-1)+copy(S,OI+1,255)); + SetLineText(CurPos.Y,copy(S,1,CI-1)+copy(S,OI,255)); SetCurPtr(CP,CurPos.Y); +{$ifdef Undo} + StoreUndo:=HoldUndo; + Addaction(eaDeleteText,SCP,CurPos,' '); + StoreUndo:=false; +{$endif Undo} end; UpdateAttrs(CurPos.Y,attrAll); AdjustSelection(CurPos.X-SCP.X,CurPos.Y-SCP.Y); DrawLines(CurPos.Y); + StoreUndo:=HoldUndo; SetModified(true); Unlock; end; @@ -2265,9 +2345,12 @@ end; procedure TCodeEditor.DelChar; var S: string; SDX,SDY,CI : sw_integer; + HoldUndo : boolean; begin if IsReadOnly then Exit; Lock; + HoldUndo:=StoreUndo; + StoreUndo:=false; S:=GetLineText(CurPos.Y); if CurPos.X=length(S) then begin @@ -2284,9 +2367,23 @@ begin { Problem if S[CurPos.X+1]=TAB !! PM } CI:=LinePosToCharIdx(CurPos.Y,CurPos.X); if S[CI]=TAB then - S:=Copy(S,1,CI-1)+CharStr(' ',TabSize-1)+Copy(S,CI+1,255) + begin + S:=Copy(S,1,CI-1)+CharStr(' ',TabSize-1)+Copy(S,CI+1,255); +{$ifdef Undo} + StoreUndo:=HoldUndo; + Addaction(eaDeleteText,CurPos,CurPos,' '); + StoreUndo:=false; +{$endif Undo} + end else - Delete(S,LinePosToCharIdx(CurPos.Y,CurPos.X)+1,1); + begin +{$ifdef Undo} + StoreUndo:=HoldUndo; + Addaction(eaDeleteText,CurPos,CurPos,S[CI]); + StoreUndo:=false; +{$endif Undo} + Delete(S,CI,1); + end; SetLineText(CurPos.Y,S); SDX:=-1; SDY:=0; end; @@ -2294,6 +2391,7 @@ begin UpdateAttrs(CurPos.Y,attrAll); AdjustSelection(SDX,SDY); DrawLines(CurPos.Y); + StoreUndo:=HoldUndo; SetModified(true); Unlock; end; @@ -2324,7 +2422,7 @@ begin S:=GetLineText(CurPos.Y); if (S<>'') and (CurPos.X<>0) then begin - SetLineText(CurPos.Y,copy(S,LinePosToCharIdx(CurPos.Y,CurPos.X)+1,255)); + SetLineText(CurPos.Y,copy(S,LinePosToCharIdx(CurPos.Y,CurPos.X),255)); SetCurPtr(0,CurPos.Y); UpdateAttrs(CurPos.Y,attrAll); DrawLines(CurPos.Y); @@ -2341,7 +2439,7 @@ begin S:=GetLineText(CurPos.Y); if (S<>'') and (CurPos.X<>length(S)) then begin - SetLineText(CurPos.Y,copy(S,1,LinePosToCharIdx(CurPos.Y,CurPos.X))); + SetLineText(CurPos.Y,copy(S,1,LinePosToCharIdx(CurPos.Y,CurPos.X)-1)); SetCurPtr(CurPos.X,CurPos.Y); UpdateAttrs(CurPos.Y,attrAll); DrawLines(CurPos.Y); @@ -2661,11 +2759,14 @@ var S,SC,TabS: string; BI: byte; CI,TabStart : Sw_integer; SP: TPoint; + HoldUndo : boolean; begin if IsReadOnly then Exit; Lock; SP:=CurPos; + HoldUndo:=StoreUndo; + StoreUndo:=false; if (C<>TAB) or ((Flags and efUseTabCharacters)<>0) then SC:=C else if ((Flags and efAutoIndent)=0) then @@ -2704,24 +2805,29 @@ begin else begin if Overwrite and (CIPointOfs(SelEnd) then - if (CurPos.Y=SelEnd.Y) and (CurPos.X0) and (BI>0) then begin + StoreUndo:=HoldUndo; AddChar(CloseBrackets[BI]); + StoreUndo:=false; SetCurPtr(CurPos.X-1,CurPos.Y); end; UpdateAttrs(CurPos.Y,attrAll); AdjustSelection(CurPos.X-SP.X,CurPos.Y-SP.Y); DrawLines(CurPos.Y); + StoreUndo:=HoldUndo; SetModified(true); UnLock; end; @@ -2825,10 +2931,12 @@ begin begin s:=strpas(p2); if not first then - SetCurPtr(0,i); + SetCurPtr(0,i+1); InsertText(s); end; SetCurPtr(StorePos.X,StorePos.Y); + SetModified(true); + Update; { we must free the allocated memory } freemem(p,l); end; @@ -2859,11 +2967,11 @@ begin getmem(p,PCLength); i:=SelStart.Y; s:=GetLineText(i); - str_begin:=LinePosToCharIdx(i,SelStart.X+1); + str_begin:=LinePosToCharIdx(i,SelStart.X); if SelEnd.Y>SelStart.Y then str_end:=255 else - str_end:=LinePosToCharIdx(i,SelEnd.X); + str_end:=LinePosToCharIdx(i,SelEnd.X)-1; s:=copy(s,str_begin,str_end-str_begin+1); strpcopy(p,s); p2:=strend(p); @@ -2876,7 +2984,7 @@ begin end; if SelEnd.Y>SelStart.Y then begin - s:=copy(GetLineText(i),1,LinePosToCharIdx(i,SelEnd.X)); + s:=copy(GetLineText(i),1,LinePosToCharIdx(i,SelEnd.X)-1); strpcopy(p2,EOL+s); end; OK:=WinClipboardSupported; @@ -2891,13 +2999,156 @@ end; {$endif WinClipSupported} procedure TCodeEditor.Undo; +{$ifdef Undo} +var + Temp,Idx : Longint; + SCP : Tpoint; +{$endif Undo} begin +{$ifdef Undo} + StoreUndo := False; + if UndoList^.count > 0 then + begin + Idx:=UndoList^.count-1; + with PEditorAction(UndoList^.At(Idx))^ do + begin + case action of + eaMoveCursor : + begin + { move cursor back to original position } + SetCurPtr(startpos.x,startpos.y); + end; + eaInsertLine : + begin + { move cursor to inserted line, already done by other undo action?} + { delete inserted line} + { move cursor to end of line above } + { insert text that had been moved to line below } + SetCurPtr(EndPos.X,EndPos.Y); + SetDisplayText(EndPos.Y,Copy(GetDisplayText(EndPos.Y),EndPos.X+1,255)); + BackSpace; + end; + eaInsertText : + begin + SetCurPtr(startpos.x,startpos.y); + for Temp := 1 to length(Text^) do + DelChar; + { remove text } + end; + eaDeleteLine : + begin + { reinsert deleted line } + SCP:=CurPos; + SetCurPtr(StartPos.X,StartPos.Y); + InsertLine; + SetCurPtr(StartPos.X,StartPos.Y); + SetLineText(StartPos.Y,GetStr(Text)); + SetCurPtr(SCP.X,SCP.Y); + end; + eaDeleteText : + begin + { reinsert deleted text } + SetCurPtr(startpos.x,startpos.y); + for Temp := 1 to length(Text^) do + AddChar(Text^[Temp]); + end; + eaSelectionChanged : + begin + { move cursor to end of last set selection } + end; + else + { what the 'ell's an undefined action doing round 'ere mate! } + end; { once this lot is done paste into redo and modify to suit needs } + { move item to redo stack } + RedoList^.Insert(UndoList^.At(Idx)); + UpdateUndoRedo(cmRedo,PEditorAction(UndoList^.At(Idx))^.Action); + UndoList^.atDelete(Idx); + If Idx>0 then + UpdateUndoRedo(cmUndo,PEditorAction(UndoList^.At(Idx-1))^.Action) + else + UpdateUndoRedo(cmUndo,0); + if UndoList^.count=0 then + SetCmdState(UndoCmd,false); + SetCmdState(RedoCmd,true); + end; + end; + StoreUndo := True; +{$else} NotImplemented; Exit; +{$endif Undo} end; procedure TCodeEditor.Redo; +{$ifdef Undo} +var + Idx,Temp : Longint; + SCP : Tpoint; +{$endif Undo} begin +{$ifdef Undo} + StoreUndo := False; + if RedoList^.count <> 0 then + begin + Idx:=RedoList^.count-1; + with PEditorAction(RedoList^.At(Idx))^ do + begin + case action of + eaMoveCursor : + begin + { move cursor back to original position } + SetCurPtr(endpos.x,endpos.y); + end; + eaInsertLine : + begin + SetCurPtr(StartPos.X,StartPos.Y); + InsertLine; + { move cursor to inserted line, already done by other undo action?} + { delete inserted line} + { move cursor to end of line above } + { insert text that had been moved to line below } + end; + eaInsertText : + begin + SetCurPtr(startpos.x,startpos.y); + InsertText(GetStr(Text)); + end; + eaDeleteLine : + begin + SetCurPtr(StartPos.X,StartPos.Y); + DeleteLine(EndPos.Y); + { insert line deleted } + end; + eaDeleteText : + begin + SetCurPtr(startpos.x,startpos.y); + for Temp := 1 to length(GetStr(Text)) do + DelChar; + { insert deleted text } + end; + eaSelectionChanged : + begin + { move cursor to end of last set test selection } + end; + else + { what the 'ell's an undefined action doing round 'ere mate! } + end; { once this lot is done paste back into undo and modify to suit needs } + { move item to undo stack } + UndoList^.Insert(RedoList^.At(Idx)); + UpdateUndoRedo(cmUndo,PEditorAction(RedoList^.At(Idx))^.Action); + If Idx>0 then + UpdateUndoRedo(cmRedo,PEditorAction(RedoList^.At(Idx-1))^.Action) + else + UpdateUndoRedo(cmRedo,0); + RedoList^.atDelete(Idx); + if RedoList^.count=0 then + SetCmdState(RedoCmd,false); + SetCmdState(UndoCmd,true); + end; + end; + StoreUndo := True; +{$else} NotImplemented; Exit; +{$endif Undo} end; procedure TCodeEditor.GotoLine; @@ -3644,13 +3895,17 @@ end; function TCodeEditor.InsertText(const S: string): Boolean; var I: sw_integer; OldPos: TPoint; + HoldUndo : boolean; begin Lock; OldPos:=CurPos; + HoldUndo:=StoreUndo; + StoreUndo:=false; for I:=1 to length(S) do AddChar(S[I]); - AddAction(eaInsertText,OldPos,CurPos,S); InsertText:=true; + StoreUndo:=HoldUndo; {te} + AddAction(eaInsertText,OldPos,CurPos,S); UnLock; end; @@ -3754,9 +4009,39 @@ begin end; procedure TCodeEditor.AddAction(AAction: byte; AStartPos, AEndPos: TPoint; AText: string); +var + ActionIntegrated : boolean; + pa : PEditorAction; begin - if (Actions=nil) or (not StoreUndo) then Exit; - Actions^.Insert(NewEditorAction(AAction,AStartPos,AEndPos,AText)); + if (UndoList=nil) or (not StoreUndo) then Exit; + if UndoList^.count>0 then + begin + pa:=PEditorAction(UndoList^.At(UndoList^.count-1)); + if (pa^.action=AAction) and + (pa^.EndPos.X=AStartPos.X) and + (pa^.EndPos.Y=AStartPos.Y) {and + (AAction in []) should we restrict here PM ?? } + then + begin + pa^.EndPos:=AEndPos; + pa^.text:=NewStr(GetStr(pa^.text)+AText); + ActionIntegrated:=true; + end; + end + else + ActionIntegrated:=false; + if not ActionIntegrated then + begin + UndoList^.Insert(New(PEditorAction,Init(AAction,AStartPos,AEndPos,AText))); + UpdateUndoRedo(cmUndo,AAction); + end; + if UndoList^.count <> 0 then + begin + SetCmdState(UndoCmd,true); + SetCmdState(RedoCmd,false); + UpdateUndoRedo(cmRedo,0); + RedoList^.FreeAll; + end; end; function TCodeEditor.ValidBlock: boolean; @@ -3832,6 +4117,8 @@ begin CanPaste:=(Clipboard<>nil) and ((Clipboard^.SelStart.X<>Clipboard^.SelEnd.X) or (Clipboard^.SelStart.Y<>Clipboard^.SelEnd.Y)); SetCmdState(FromClipCmds,CanPaste and (Clipboard<>@Self)); + SetCmdState(UndoCmd,StoreUndo and (UndoList^.count>0)); + SetCmdState(RedoCmd,StoreUndo and (RedoList^.count>0)); Message(Application,evBroadcast,cmCommandSetChanged,nil); DrawView; end; @@ -3865,7 +4152,9 @@ var TS: PSubStream; begin inherited Load(S); - New(Actions, Init(500,1000)); + New(UndoList,init(500,1000)); + New(RedoList,init(500,1000)); + New(Lines, Init(500,1000)); { we have always need at least 1 line } Lines^.Insert(NewLine('')); @@ -4009,15 +4298,32 @@ begin inherited Done; if assigned(Lines) then Dispose(Lines, Done); - If assigned(Actions) then - Dispose(Actions, Done); + If assigned(RedoList) then + Dispose(RedoList,done); + If assigned(UndoList) then + Dispose(UndoList,done); end; +{$ifdef Undo} +constructor TEditorAction.init(act:byte; StartP,EndP:TPoint;Txt:String); +begin + Action := act; + StartPos := StartP; + EndPos := EndP; + Text := NewStr(txt); +end; + +destructor TEditorAction.done; +begin + DisposeStr(Text); +end; +{$else} procedure TEditorActionCollection.FreeItem(Item: Pointer); begin if assigned(Item) then freemem(Item,Sizeof(TEditorAction)); end; +{$endif Undo} constructor TFileEditor.Init(var Bounds: TRect; AHScrollBar, AVScrollBar: PScrollBar; AIndicator: PIndicator;const AFileName: string); @@ -4185,8 +4491,11 @@ constructor TFileEditor.Load(var S: TStream); var P: PString; SSP,SEP,CP,DP: TPoint; HR: TRect; + HoldUndo : boolean; begin inherited Load(S); + HoldUndo:=StoreUndo; + StoreUndo:=False; P:=S.ReadStr; FileName:=GetStr(P); if P<>nil then DisposeStr(P); @@ -4209,6 +4518,7 @@ begin SetModified(false); LimitsChanged; + StoreUndo:=HoldUndo; end; procedure TFileEditor.Store(var S: TStream); @@ -4547,7 +4857,16 @@ end; END. { $Log$ - Revision 1.53 1999-10-14 10:21:48 pierre + Revision 1.54 1999-10-25 16:49:05 pierre + + Undo/Redo by Visa Harvey (great thanks) inserted + (with some modifications) + Moves work correctly + Text insertion/deletion are still buggy ! + * LinePosToCharIndex and reverse function changed to get more + sensible results, dependant code adapted + * several bug fixes + + Revision 1.53 1999/10/14 10:21:48 pierre * more tabs related problems fiwes Revision 1.52 1999/10/12 23:35:18 pierre