From 77f4d07ef5cfb838fee58aeadf50fa3d948d273d Mon Sep 17 00:00:00 2001 From: fvogel Date: Mon, 4 Jul 2016 21:26:53 +0000 Subject: Return indices making sense at undo/redo return time. The returned ranges are optimized (no duplicates, no overlapping ranges). Works but needs polishing. --- generic/tkText.c | 124 +++++++++++++++++++++++++++++++++++++++++++------------ generic/tkUndo.c | 26 ++---------- generic/tkUndo.h | 4 +- library/text.tcl | 67 ++++++++++++++++++++++++++++++ 4 files changed, 171 insertions(+), 50 deletions(-) diff --git a/generic/tkText.c b/generic/tkText.c index 8698825..f34b221 100644 --- a/generic/tkText.c +++ b/generic/tkText.c @@ -398,8 +398,8 @@ static int DumpSegment(TkText *textPtr, Tcl_Interp *interp, const char *key, const char *value, Tcl_Obj *command, const TkTextIndex *index, int what); -static int TextEditUndo(TkText *textPtr, Tcl_Obj *rangesObj); -static int TextEditRedo(TkText *textPtr, Tcl_Obj *rangesObj); +static int TextEditUndo(TkText *textPtr); +static int TextEditRedo(TkText *textPtr); static Tcl_Obj * TextGetText(const TkText *textPtr, const TkTextIndex *index1, const TkTextIndex *index2, int visibleOnly); @@ -2774,6 +2774,10 @@ TextPushUndoAction( { TkUndoSubAtom *iAtom, *dAtom; int canUndo, canRedo; + static int undoMarkId = -1; + char lMarkName[20] = "tk::undoMarkL"; + char rMarkName[20] = "tk::undoMarkR"; + char stringUndoMarkId[7] = ""; /* * Create the helpers. @@ -2784,7 +2788,10 @@ TextPushUndoAction( Tcl_Obj *markSet2InsertObj = NULL; Tcl_Obj *insertCmdObj = Tcl_NewObj(); Tcl_Obj *deleteCmdObj = Tcl_NewObj(); - Tcl_Obj *returnCmdObj = Tcl_NewObj(); + Tcl_Obj *markSetLUndoMarkCmdObj = Tcl_NewObj(); + Tcl_Obj *markSetRUndoMarkCmdObj = NULL; + Tcl_Obj *markGravityLUndoMarkCmdObj = Tcl_NewObj(); + Tcl_Obj *markGravityRUndoMarkCmdObj = NULL; /* * Get the index positions. @@ -2834,10 +2841,39 @@ TextPushUndoAction( Tcl_ListObjAppendElement(NULL, deleteCmdObj, index1Obj); Tcl_ListObjAppendElement(NULL, deleteCmdObj, index2Obj); - Tcl_ListObjAppendElement(NULL, returnCmdObj, - Tcl_NewStringObj("list", 4)); - Tcl_ListObjAppendElement(NULL, returnCmdObj, index1Obj); - Tcl_ListObjAppendElement(NULL, returnCmdObj, 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); + undoMarkId++; + sprintf(stringUndoMarkId, "%d", 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 @@ -2856,13 +2892,19 @@ TextPushUndoAction( insertCmdObj, NULL); TkUndoMakeCmdSubAtom(NULL, markSet2InsertObj, iAtom); TkUndoMakeCmdSubAtom(NULL, seeInsertObj, iAtom); - TkUndoMakeCmdSubAtom(NULL, returnCmdObj, 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, returnCmdObj, dAtom); + TkUndoMakeCmdSubAtom(NULL, markSetLUndoMarkCmdObj, dAtom); + TkUndoMakeCmdSubAtom(NULL, markSetRUndoMarkCmdObj, dAtom); + TkUndoMakeCmdSubAtom(NULL, markGravityLUndoMarkCmdObj, dAtom); + TkUndoMakeCmdSubAtom(NULL, markGravityRUndoMarkCmdObj, dAtom); Tcl_DecrRefCount(seeInsertObj); Tcl_DecrRefCount(index1Obj); @@ -5063,7 +5105,7 @@ DumpSegment( * Undo the last change. * * Results: - * The ranges of text that were changed by the undo operation. + * None. * * Side effects: * Apart from manipulating the undo and redo stacks, the state of the @@ -5074,10 +5116,11 @@ DumpSegment( static int TextEditUndo( - TkText *textPtr, /* Overall information about text widget. */ - Tcl_Obj *rangesObj) /* Ranges of text that were changed. */ + TkText *textPtr) /* Overall information about text widget. */ { int status; + Tcl_Obj *cmdObj; + int code; if (!textPtr->sharedTextPtr->undo) { return TCL_OK; @@ -5094,13 +5137,29 @@ TextEditUndo( textPtr->sharedTextPtr->dirtyMode = TK_TEXT_DIRTY_UNDO; } - status = TkUndoRevert(textPtr->sharedTextPtr->undoStack, rangesObj); + status = TkUndoRevert(textPtr->sharedTextPtr->undoStack); if (textPtr->sharedTextPtr->dirtyMode != TK_TEXT_DIRTY_FIXED) { textPtr->sharedTextPtr->dirtyMode = TK_TEXT_DIRTY_NORMAL; } 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; } @@ -5112,7 +5171,7 @@ TextEditUndo( * Redo the last undone change. * * Results: - * The ranges of text that were changed by the undo operation. + * None. * * Side effects: * Apart from manipulating the undo and redo stacks, the state of the @@ -5123,10 +5182,11 @@ TextEditUndo( static int TextEditRedo( - TkText *textPtr, /* Overall information about text widget. */ - Tcl_Obj *rangesObj) /* Ranges of text that were changed. */ + TkText *textPtr) /* Overall information about text widget. */ { int status; + Tcl_Obj *cmdObj; + int code; if (!textPtr->sharedTextPtr->undo) { return TCL_OK; @@ -5143,12 +5203,29 @@ TextEditRedo( textPtr->sharedTextPtr->dirtyMode = TK_TEXT_DIRTY_REDO; } - status = TkUndoApply(textPtr->sharedTextPtr->undoStack, rangesObj); + status = TkUndoApply(textPtr->sharedTextPtr->undoStack); if (textPtr->sharedTextPtr->dirtyMode != TK_TEXT_DIRTY_FIXED) { 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; } @@ -5179,7 +5256,6 @@ TextEditCmd( int index, setModified, oldModified; int canRedo = 0; int canUndo = 0; - Tcl_Obj *rangesObj = Tcl_NewObj(); static const char *const editOptionStrings[] = { "canundo", "canredo", "modified", "redo", "reset", "separator", @@ -5263,13 +5339,11 @@ TextEditCmd( return TCL_ERROR; } canUndo = TkUndoCanUndo(textPtr->sharedTextPtr->undoStack); - if (TextEditRedo(textPtr, rangesObj)) { + if (TextEditRedo(textPtr)) { Tcl_SetObjResult(interp, Tcl_NewStringObj("nothing to redo", -1)); Tcl_SetErrorCode(interp, "TK", "TEXT", "NO_REDO", NULL); return TCL_ERROR; - } else { - Tcl_SetObjResult(interp, rangesObj); - } + } canRedo = TkUndoCanRedo(textPtr->sharedTextPtr->undoStack); if (!canUndo || !canRedo) { GenerateUndoStackEvent(textPtr); @@ -5300,13 +5374,11 @@ TextEditCmd( return TCL_ERROR; } canRedo = TkUndoCanRedo(textPtr->sharedTextPtr->undoStack); - if (TextEditUndo(textPtr, rangesObj)) { + if (TextEditUndo(textPtr)) { Tcl_SetObjResult(interp, Tcl_NewStringObj("nothing to undo", -1)); Tcl_SetErrorCode(interp, "TK", "TEXT", "NO_UNDO", NULL); return TCL_ERROR; - } else { - Tcl_SetObjResult(interp, rangesObj); - } + } canUndo = TkUndoCanUndo(textPtr->sharedTextPtr->undoStack); if (!canRedo || !canUndo) { GenerateUndoStackEvent(textPtr); diff --git a/generic/tkUndo.c b/generic/tkUndo.c index f364326..c66905d 100644 --- a/generic/tkUndo.c +++ b/generic/tkUndo.c @@ -556,8 +556,7 @@ TkUndoInsertUndoSeparator( * Undo a compound action on the stack. * * Results: - * A Tcl status code. Also, the passed Tcl_(List)Obj is appended by the - * interp results from evaluation of each element of the undo stack. + * A Tcl status code * * Side effects: * None. @@ -567,8 +566,7 @@ TkUndoInsertUndoSeparator( int TkUndoRevert( - TkUndoRedoStack *stack, - Tcl_Obj *retObj) + TkUndoRedoStack *stack) { TkUndoAtom *elem; @@ -600,13 +598,6 @@ TkUndoRevert( EvaluateActionList(stack->interp, elem->revert); - /* - * The interp result is appended to the returned object for each - * element of the undo stack (not for each sub-atom). - */ - - Tcl_ListObjAppendList(NULL, retObj, Tcl_GetObjResult(stack->interp)); - TkUndoPushStack(&stack->redoStack, elem); elem = TkUndoPopStack(&stack->undoStack); } @@ -628,8 +619,7 @@ TkUndoRevert( * Redo a compound action on the stack. * * Results: - * A Tcl status code. Also, the passed Tcl_(List)Obj is appended by the - * interp results from evaluation of each element of the redo stack. + * A Tcl status code * * Side effects: * None. @@ -639,8 +629,7 @@ TkUndoRevert( int TkUndoApply( - TkUndoRedoStack *stack, - Tcl_Obj *retObj) + TkUndoRedoStack *stack) { TkUndoAtom *elem; @@ -671,13 +660,6 @@ TkUndoApply( EvaluateActionList(stack->interp, elem->apply); - /* - * The interp result is appended to the returned object for each - * element of the redo stack (not for each sub-atom). - */ - - Tcl_ListObjAppendList(NULL, retObj, Tcl_GetObjResult(stack->interp)); - TkUndoPushStack(&stack->undoStack, elem); elem = TkUndoPopStack(&stack->redoStack); } diff --git a/generic/tkUndo.h b/generic/tkUndo.h index 505e0f0..490ede9 100644 --- a/generic/tkUndo.h +++ b/generic/tkUndo.h @@ -109,7 +109,7 @@ MODULE_SCOPE TkUndoSubAtom *TkUndoMakeSubAtom(TkUndoProc *funcPtr, TkUndoSubAtom *subAtomList); MODULE_SCOPE void TkUndoPushAction(TkUndoRedoStack *stack, TkUndoSubAtom *apply, TkUndoSubAtom *revert); -MODULE_SCOPE int TkUndoRevert(TkUndoRedoStack *stack, Tcl_Obj *retObj); -MODULE_SCOPE int TkUndoApply(TkUndoRedoStack *stack, Tcl_Obj *retObj); +MODULE_SCOPE int TkUndoRevert(TkUndoRedoStack *stack); +MODULE_SCOPE int TkUndoApply(TkUndoRedoStack *stack); #endif /* _TKUNDO */ diff --git a/library/text.tcl b/library/text.tcl index 2bf1b2b..cc78bc2 100644 --- a/library/text.tcl +++ b/library/text.tcl @@ -1202,3 +1202,70 @@ 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 + } + } + # the number of undo/redo marks is always even + 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 + } +#puts "Unoptimized indices: $indices" + # 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 +#puts " range1: $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}]] +#puts " range2: $il2 $ir2" + if {[$w compare $il2 > $ir1]} { + # second range starts after the end of first range + set j $nUndoMarks + } else { + if {[$w compare $ir2 > $ir1]} { + # second range overlaps with first range + set indices [lreplace $indices end-1 end] + lappend indices $il1 $ir2 + } else { + # second range is fully included in first range + } + set ind [lreplace $ind $j [expr {$j + 1}]] + incr j -2 + incr nUndoMarks -2 + } +#puts " indices: $indices" + } + } +#puts "OPTIMIZED indices: $indices" + return $indices +} -- cgit v0.12