diff options
-rw-r--r-- | doc/text.n | 12 | ||||
-rw-r--r-- | generic/tkText.c | 86 | ||||
-rw-r--r-- | generic/tkText.h | 2 | ||||
-rw-r--r-- | library/text.tcl | 96 | ||||
-rw-r--r-- | tests/text.test | 74 |
5 files changed, 264 insertions, 6 deletions
@@ -1307,8 +1307,9 @@ of the widget to \fIboolean\fR. \fIpathName \fBedit redo\fR . When the \fB\-undo\fR option is true, reapplies the last undone edits provided -no other edits were done since then. Generates an error when the redo stack is -empty. Does nothing when the \fB\-undo\fR option is false. +no other edits were done since then, and returns a list of indices indicating +what ranges were changed by the redo operation. Generates an error when the +redo stack is empty. Does nothing when the \fB\-undo\fR option is false. .TP \fIpathName \fBedit reset\fR . @@ -1321,9 +1322,10 @@ Inserts a separator (boundary) on the undo stack. Does nothing when the .TP \fIpathName \fBedit undo\fR . -Undoes the last edit action when the \fB\-undo\fR option is true. An edit -action is defined as all the insert and delete commands that are recorded on -the undo stack in between two separators. Generates an error when the undo +Undoes the last edit action when the \fB\-undo\fR option is true, and returns a +list of indices indicating what ranges were changed by the undo operation. An +edit action is defined as all the insert and delete commands that are recorded +on the undo stack in between two separators. Generates an error when the undo stack is empty. Does nothing when the \fB\-undo\fR option is false. .RE .TP diff --git a/generic/tkText.c b/generic/tkText.c index bea4c62..ea11af1 100644 --- a/generic/tkText.c +++ b/generic/tkText.c @@ -2774,6 +2774,9 @@ TextPushUndoAction( { TkUndoSubAtom *iAtom, *dAtom; int canUndo, canRedo; + char lMarkName[20] = "tk::undoMarkL"; + char rMarkName[20] = "tk::undoMarkR"; + char stringUndoMarkId[7] = ""; /* * Create the helpers. @@ -2784,6 +2787,10 @@ TextPushUndoAction( Tcl_Obj *markSet2InsertObj = NULL; Tcl_Obj *insertCmdObj = Tcl_NewObj(); Tcl_Obj *deleteCmdObj = Tcl_NewObj(); + Tcl_Obj *markSetLUndoMarkCmdObj = Tcl_NewObj(); + Tcl_Obj *markSetRUndoMarkCmdObj = NULL; + Tcl_Obj *markGravityLUndoMarkCmdObj = Tcl_NewObj(); + Tcl_Obj *markGravityRUndoMarkCmdObj = NULL; /* * Get the index positions. @@ -2833,6 +2840,40 @@ TextPushUndoAction( Tcl_ListObjAppendElement(NULL, deleteCmdObj, index1Obj); Tcl_ListObjAppendElement(NULL, deleteCmdObj, index2Obj); + Tcl_ListObjAppendElement(NULL, markSetLUndoMarkCmdObj, + Tcl_NewStringObj(Tk_PathName(textPtr->tkwin), -1)); + Tcl_ListObjAppendElement(NULL, markSetLUndoMarkCmdObj, + Tcl_NewStringObj("mark", 4)); + Tcl_ListObjAppendElement(NULL, markSetLUndoMarkCmdObj, + Tcl_NewStringObj("set", 3)); + markSetRUndoMarkCmdObj = Tcl_DuplicateObj(markSetLUndoMarkCmdObj); + textPtr->sharedTextPtr->undoMarkId++; + sprintf(stringUndoMarkId, "%d", textPtr->sharedTextPtr->undoMarkId); + strcat(lMarkName, stringUndoMarkId); + strcat(rMarkName, stringUndoMarkId); + Tcl_ListObjAppendElement(NULL, markSetLUndoMarkCmdObj, + Tcl_NewStringObj(lMarkName, -1)); + Tcl_ListObjAppendElement(NULL, markSetRUndoMarkCmdObj, + Tcl_NewStringObj(rMarkName, -1)); + Tcl_ListObjAppendElement(NULL, markSetLUndoMarkCmdObj, index1Obj); + Tcl_ListObjAppendElement(NULL, markSetRUndoMarkCmdObj, index2Obj); + + Tcl_ListObjAppendElement(NULL, markGravityLUndoMarkCmdObj, + Tcl_NewStringObj(Tk_PathName(textPtr->tkwin), -1)); + Tcl_ListObjAppendElement(NULL, markGravityLUndoMarkCmdObj, + Tcl_NewStringObj("mark", 4)); + Tcl_ListObjAppendElement(NULL, markGravityLUndoMarkCmdObj, + Tcl_NewStringObj("gravity", 7)); + markGravityRUndoMarkCmdObj = Tcl_DuplicateObj(markGravityLUndoMarkCmdObj); + Tcl_ListObjAppendElement(NULL, markGravityLUndoMarkCmdObj, + Tcl_NewStringObj(lMarkName, -1)); + Tcl_ListObjAppendElement(NULL, markGravityRUndoMarkCmdObj, + Tcl_NewStringObj(rMarkName, -1)); + Tcl_ListObjAppendElement(NULL, markGravityLUndoMarkCmdObj, + Tcl_NewStringObj("left", 4)); + Tcl_ListObjAppendElement(NULL, markGravityRUndoMarkCmdObj, + Tcl_NewStringObj("right", 5)); + /* * Note: we don't wish to use textPtr->widgetCmd in these callbacks * because if we delete the textPtr, but peers still exist, we will then @@ -2850,11 +2891,19 @@ TextPushUndoAction( insertCmdObj, NULL); TkUndoMakeCmdSubAtom(NULL, markSet2InsertObj, iAtom); TkUndoMakeCmdSubAtom(NULL, seeInsertObj, iAtom); + TkUndoMakeCmdSubAtom(NULL, markSetLUndoMarkCmdObj, iAtom); + TkUndoMakeCmdSubAtom(NULL, markSetRUndoMarkCmdObj, iAtom); + TkUndoMakeCmdSubAtom(NULL, markGravityLUndoMarkCmdObj, iAtom); + TkUndoMakeCmdSubAtom(NULL, markGravityRUndoMarkCmdObj, iAtom); dAtom = TkUndoMakeSubAtom(&TextUndoRedoCallback, textPtr->sharedTextPtr, deleteCmdObj, NULL); TkUndoMakeCmdSubAtom(NULL, markSet1InsertObj, dAtom); TkUndoMakeCmdSubAtom(NULL, seeInsertObj, dAtom); + TkUndoMakeCmdSubAtom(NULL, markSetLUndoMarkCmdObj, dAtom); + TkUndoMakeCmdSubAtom(NULL, markSetRUndoMarkCmdObj, dAtom); + TkUndoMakeCmdSubAtom(NULL, markGravityLUndoMarkCmdObj, dAtom); + TkUndoMakeCmdSubAtom(NULL, markGravityRUndoMarkCmdObj, dAtom); Tcl_DecrRefCount(seeInsertObj); Tcl_DecrRefCount(index1Obj); @@ -5069,6 +5118,8 @@ TextEditUndo( TkText *textPtr) /* Overall information about text widget. */ { int status; + Tcl_Obj *cmdObj; + int code; if (!textPtr->sharedTextPtr->undo) { return TCL_OK; @@ -5092,6 +5143,22 @@ TextEditUndo( } textPtr->sharedTextPtr->undo = 1; + /* + * Convert undo/redo temporary marks set by TkUndoRevert() into + * indices left in the interp result. + */ + + cmdObj = Tcl_ObjPrintf("::tk::TextUndoRedoProcessMarks %s", + Tk_PathName(textPtr->tkwin)); + Tcl_IncrRefCount(cmdObj); + code = Tcl_EvalObjEx(textPtr->interp, cmdObj, TCL_EVAL_GLOBAL); + if (code != TCL_OK) { + Tcl_AddErrorInfo(textPtr->interp, + "\n (on undoing)"); + Tcl_BackgroundException(textPtr->interp, code); + } + Tcl_DecrRefCount(cmdObj); + return status; } @@ -5117,6 +5184,8 @@ TextEditRedo( TkText *textPtr) /* Overall information about text widget. */ { int status; + Tcl_Obj *cmdObj; + int code; if (!textPtr->sharedTextPtr->undo) { return TCL_OK; @@ -5139,6 +5208,23 @@ TextEditRedo( textPtr->sharedTextPtr->dirtyMode = TK_TEXT_DIRTY_NORMAL; } textPtr->sharedTextPtr->undo = 1; + + /* + * Convert undo/redo temporary marks set by TkUndoApply() into + * indices left in the interp result. + */ + + cmdObj = Tcl_ObjPrintf("::tk::TextUndoRedoProcessMarks %s", + Tk_PathName(textPtr->tkwin)); + Tcl_IncrRefCount(cmdObj); + code = Tcl_EvalObjEx(textPtr->interp, cmdObj, TCL_EVAL_GLOBAL); + if (code != TCL_OK) { + Tcl_AddErrorInfo(textPtr->interp, + "\n (on undoing)"); + Tcl_BackgroundException(textPtr->interp, code); + } + Tcl_DecrRefCount(cmdObj); + return status; } diff --git a/generic/tkText.h b/generic/tkText.h index 5d88784..430c96b 100644 --- a/generic/tkText.h +++ b/generic/tkText.h @@ -580,6 +580,8 @@ typedef struct TkSharedText { * statements. */ int autoSeparators; /* Non-zero means the separators will be * inserted automatically. */ + int undoMarkId; /* Counts undo marks temporarily used during + undo and redo operations. */ int isDirty; /* Flag indicating the 'dirtyness' of the * text widget. If the flag is not zero, * unsaved modifications have been applied to diff --git a/library/text.tcl b/library/text.tcl index 2bf1b2b..02a8939 100644 --- a/library/text.tcl +++ b/library/text.tcl @@ -1202,3 +1202,99 @@ proc ::tk::TextScanDrag {w x y} { $w scan dragto $x $y } } + +# ::tk::TextUndoRedoProcessMarks -- +# +# This proc is executed after an undo or redo action. +# It processes the list of undo/redo marks temporarily set in the +# text widget to positions delimiting where changes happened, and +# returns a flat list of ranges. The temporary marks are removed +# from the text widget. +# +# Arguments: +# w - The text widget + +proc ::tk::TextUndoRedoProcessMarks {w} { + set indices {} + set undoMarks {} + + # only consider the temporary marks set by an undo/redo action + foreach mark [$w mark names] { + if {[string range $mark 0 11] eq "tk::undoMark"} { + lappend undoMarks $mark + } + } + + # transform marks into indices + # the number of undo/redo marks is always even, each right mark + # completes a left mark to give a range + # this is true because: + # - undo/redo only deals with insertions and deletions of text + # - insertions may move marks but not delete them + # - when deleting text, marks located inside the deleted range + # are not erased but moved to the start of the deletion range + # . this is done in TkBTreeDeleteIndexRange ("This segment + # refuses to die...") + # . because MarkDeleteProc does nothing else than returning + # a value indicating that marks are not deleted by this + # deleteProc + # . mark deletion rather happen through [.text mark unset xxx] + # which was not used _up to this point of the code_ (it + # is a bit later just before exiting the present proc) + set nUndoMarks [llength $undoMarks] + set n [expr {$nUndoMarks / 2}] + set undoMarks [lsort -dictionary $undoMarks] + set Lmarks [lrange $undoMarks 0 [expr {$n - 1}]] + set Rmarks [lrange $undoMarks $n [llength $undoMarks]] + foreach Lmark $Lmarks Rmark $Rmarks { + lappend indices [$w index $Lmark] [$w index $Rmark] + $w mark unset $Lmark $Rmark + } + + # process ranges to: + # - remove those already fully included in another range + # - merge overlapping ranges + set ind [lsort -dictionary -stride 2 $indices] + set indices {} + + for {set i 0} {$i < $nUndoMarks} {incr i 2} { + set il1 [lindex $ind $i] + set ir1 [lindex $ind [expr {$i + 1}]] + lappend indices $il1 $ir1 + + for {set j [expr {$i + 2}]} {$j < $nUndoMarks} {incr j 2} { + set il2 [lindex $ind $j] + set ir2 [lindex $ind [expr {$j + 1}]] + + if {[$w compare $il2 > $ir1]} { + # second range starts after the end of first range + # -> further second ranges do not need to be considered + # because ranges were sorted by increasing first index + set j $nUndoMarks + + } else { + if {[$w compare $ir2 > $ir1]} { + # second range overlaps first range + # -> merge them into a single range + set indices [lreplace $indices end-1 end] + lappend indices $il1 $ir2 + + } else { + # second range is fully included in first range + # -> ignore it + + } + # in both cases above, the second range shall be + # trimmed out from the list of ranges + set ind [lreplace $ind $j [expr {$j + 1}]] + incr j -2 + incr nUndoMarks -2 + + } + + } + + } + + return $indices +} diff --git a/tests/text.test b/tests/text.test index 8723d3d..b4a7a2b 100644 --- a/tests/text.test +++ b/tests/text.test @@ -1458,7 +1458,19 @@ Line 7" rename .t {} rename test.t .t destroy .t -} -result {{edit undo} {delete 2.1 2.4} {mark set insert 2.1} {see insert} {insert 2.1 ef} {mark set insert 2.3} {see insert}} +} -result [list {edit undo} {delete 2.1 2.4} {mark set insert 2.1} {see insert} \ + {mark set tk::undoMarkL3 2.1} {mark set tk::undoMarkR3 2.4} \ + {mark gravity tk::undoMarkL3 left} {mark gravity tk::undoMarkR3 right} \ + {insert 2.1 ef} {mark set insert 2.3} {see insert} \ + {mark set tk::undoMarkL2 2.1} {mark set tk::undoMarkR2 2.3} \ + {mark gravity tk::undoMarkL2 left} {mark gravity tk::undoMarkR2 right} \ + {mark names} \ + {index tk::undoMarkL2} {index tk::undoMarkR2} \ + {mark unset tk::undoMarkL2 tk::undoMarkR2} \ + {index tk::undoMarkL3} {index tk::undoMarkR3} \ + {mark unset tk::undoMarkL3 tk::undoMarkR3} \ + {compare 2.1 > 2.3} {compare 2.6 > 2.3} ] + test text-8.23 {TextWidgetCmd procedure, "replace" option with undo} -setup { text .t } -body { @@ -6577,6 +6589,66 @@ test text-27.25 {<<UndoStack>> virtual event} -setup { } -cleanup { destroy .t } -result {0 0 1 2 3 4 4 5 6 6 7 8 8 9} +test text-27.26 {edit undo and edit redo return ranges} -setup { + destroy .t + set res {} +} -body { + text .t -undo true -autoseparators false + .t insert end "Hello " + .t edit separator + .t insert end "World!\n" + .t insert 1.6 "GREAT " + .t insert end "Another edit here!!" + lappend res [.t edit undo] + lappend res [.t edit redo] + .t edit separator + .t delete 1.6 + .t delete 1.9 1.10 + .t insert 1.9 L + lappend res [.t edit undo] + lappend res [.t edit redo] + .t replace 1.6 1.10 Tcl/Tk + .t replace 2.8 2.12 "one bites the dust" + lappend res [.t edit undo] + lappend res [.t edit redo] +} -cleanup { + destroy .t +} -result [list {1.6 2.0} \ + {1.6 2.19} \ + {1.6 1.7 1.10 1.12} \ + {1.6 1.7 1.9 1.11} \ + {1.6 1.16 2.8 2.19} \ + {1.6 1.16 2.8 2.30} ] +test text-27.27 {edit undo and edit redo return ranges} -setup { + destroy .t + set res {} +} -body { + text .t -undo true -autoseparators false + for {set i 3} {$i >= 1} {incr i -1} { + .t insert 1.0 "Line $i\n" + } + lappend res [.t edit undo] + lappend res [.t edit redo] +} -cleanup { + destroy .t +} -result [list {1.0 2.0} \ + {1.0 4.0} ] +test text-27.28 {edit undo and edit redo do not leave \ + spurious temporary marks behind them} -setup { + destroy .t + set res {} +} -body { + pack [text .t -undo true -autoseparators false] + .t insert end "Hello World.\n" + .t edit separator + .t insert end "Again hello.\n" + .t edit undo + lappend res [lsearch [.t mark names] tk::undoMark*] + .t edit redo + lappend res [lsearch [.t mark names] tk::undoMark*] +} -cleanup { + destroy .t +} -result [list -1 -1] test text-28.1 {bug fix - 624372, ControlUtfProc long lines} -body { |