Tk Source Code

Check-in [6c03c35d]
Login
Bounty program for improvements to Tcl and certain Tcl packages.
Tcl 2019 Conference, Houston/TX, US, Nov 4-8
Send your abstracts to tclconference@googlegroups.com
or submit via the online form by Sep 9.

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Return indices making sense at undo/redo return time. The returned ranges are optimized (no duplicates, no overlapping ranges). Works but needs polishing.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | tip-449
Files: files | file ages | folders
SHA1:6c03c35d7b0364a583bd3847ebf9630423889fc5
User & Date: fvogel 2016-07-04 21:26:53
Context
2016-07-05
20:02
Better comments explaining the algorithm in ::tk::TextUndoRedoProcessMarks check-in: 232ed197 user: fvogel tags: tip-449
2016-07-04
21:26
Return indices making sense at undo/redo return time. The returned ranges are optimized (no duplicates, no overlapping ranges). Works but needs polishing. check-in: 6c03c35d user: fvogel tags: tip-449
2016-06-28
18:41
Merged core-8-6-branch check-in: 1cefcd34 user: fvogel tags: tip-449
2016-06-08
09:38
It is not possible to rely only on the interp result. A list of indices has to be built as a return value to undo/redo because there can be several edits between two separators and all such edits have to report which range of text they changed. Note: this commit does not deal with refcounts, it is very likely wrong in that respect. check-in: afa23a02 user: fvogel tags: tip-449
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to generic/tkText.c.

394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
....
2770
2771
2772
2773
2774
2775
2776




2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787



2788
2789
2790
2791
2792
2793
2794
....
2830
2831
2832
2833
2834
2835
2836
2837




2838





2839

2840



















2841
2842
2843
2844
2845
2846
2847
....
2852
2853
2854
2855
2856
2857
2858
2859



2860
2861
2862
2863
2864
2865



2866
2867
2868
2869
2870
2871
2872
....
5059
5060
5061
5062
5063
5064
5065
5066
5067
5068
5069
5070
5071
5072
5073
5074
5075
5076
5077
5078
5079
5080


5081
5082
5083
5084
5085
5086
5087
....
5090
5091
5092
5093
5094
5095
5096
5097
5098
5099
5100
5101
5102
5103
















5104
5105
5106
5107
5108
5109
5110
5111
5112
5113
5114
5115
5116
5117
5118
5119
5120
5121
5122
5123
5124
5125
5126
5127
5128
5129


5130
5131
5132
5133
5134
5135
5136
....
5139
5140
5141
5142
5143
5144
5145
5146
5147
5148
5149
5150
5151

















5152
5153
5154
5155
5156
5157
5158
....
5175
5176
5177
5178
5179
5180
5181
5182
5183
5184
5185
5186
5187
5188
5189
....
5259
5260
5261
5262
5263
5264
5265
5266
5267
5268
5269
5270
5271
5272
5273
5274
5275
5276
5277
5278
5279
....
5296
5297
5298
5299
5300
5301
5302
5303
5304
5305
5306
5307
5308
5309
5310
5311
5312
5313
5314
5315
5316
static int		DumpLine(Tcl_Interp *interp, TkText *textPtr,
			    int what, TkTextLine *linePtr, int start, int end,
			    int lineno, Tcl_Obj *command);
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 Tcl_Obj *	TextGetText(const TkText *textPtr,
			    const TkTextIndex *index1,
			    const TkTextIndex *index2, int visibleOnly);
static void		GenerateModifiedEvent(TkText *textPtr);
static void		GenerateUndoStackEvent(TkText *textPtr);
static void		UpdateDirtyFlag(TkSharedText *sharedPtr);
static void		RunAfterSyncCmd(ClientData clientData);
................................................................................
    const TkTextIndex *index1Ptr,
				/* Index describing first location. */
    const TkTextIndex *index2Ptr)
				/* Index describing second location. */
{
    TkUndoSubAtom *iAtom, *dAtom;
    int canUndo, canRedo;





    /*
     * Create the helpers.
     */

    Tcl_Obj *seeInsertObj = Tcl_NewObj();
    Tcl_Obj *markSet1InsertObj = Tcl_NewObj();
    Tcl_Obj *markSet2InsertObj = NULL;
    Tcl_Obj *insertCmdObj = Tcl_NewObj();
    Tcl_Obj *deleteCmdObj = Tcl_NewObj();
    Tcl_Obj *returnCmdObj = Tcl_NewObj();




    /*
     * Get the index positions.
     */

    Tcl_Obj *index1Obj = TkTextNewIndexObj(NULL, index1Ptr);
    Tcl_Obj *index2Obj = TkTextNewIndexObj(NULL, index2Ptr);
................................................................................
    Tcl_ListObjAppendElement(NULL, insertCmdObj, undoString);

    Tcl_ListObjAppendElement(NULL, deleteCmdObj,
	    Tcl_NewStringObj("delete", 6));
    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);




















    /*
     * 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
     * have references to a non-existent Tcl_Command in the undo stack, which
     * will lead to crashes later. Also, the behaviour of the widget w.r.t.
     * bindings (%W substitutions) always uses the widget path name, so there
................................................................................
     * underlying data shared by all peers.
     */

    iAtom = TkUndoMakeSubAtom(&TextUndoRedoCallback, textPtr->sharedTextPtr,
	    insertCmdObj, NULL);
    TkUndoMakeCmdSubAtom(NULL, markSet2InsertObj, iAtom);
    TkUndoMakeCmdSubAtom(NULL, seeInsertObj, iAtom);
    TkUndoMakeCmdSubAtom(NULL, returnCmdObj, iAtom);




    dAtom = TkUndoMakeSubAtom(&TextUndoRedoCallback, textPtr->sharedTextPtr,
	    deleteCmdObj, NULL);
    TkUndoMakeCmdSubAtom(NULL, markSet1InsertObj, dAtom);
    TkUndoMakeCmdSubAtom(NULL, seeInsertObj, dAtom);
    TkUndoMakeCmdSubAtom(NULL, returnCmdObj, dAtom);




    Tcl_DecrRefCount(seeInsertObj);
    Tcl_DecrRefCount(index1Obj);
    Tcl_DecrRefCount(index2Obj);

    canUndo = TkUndoCanUndo(textPtr->sharedTextPtr->undoStack);
    canRedo = TkUndoCanRedo(textPtr->sharedTextPtr->undoStack);
................................................................................
 *----------------------------------------------------------------------
 *
 * TextEditUndo --
 *
 *	Undo the last change.
 *
 * Results:
 *	The ranges of text that were changed by the undo operation.
 *
 * Side effects:
 *	Apart from manipulating the undo and redo stacks, the state of the
 *	rest of the widget may also change (due to whatever is being undone).
 *
 *----------------------------------------------------------------------
 */

static int
TextEditUndo(
    TkText *textPtr,		/* Overall information about text widget. */
    Tcl_Obj *rangesObj)         /* Ranges of text that were changed. */
{
    int status;



    if (!textPtr->sharedTextPtr->undo) {
	return TCL_OK;
    }

    /*
     * Turn off the undo feature while we revert a compound action, setting
................................................................................
     */

    textPtr->sharedTextPtr->undo = 0;
    if (textPtr->sharedTextPtr->dirtyMode != TK_TEXT_DIRTY_FIXED) {
	textPtr->sharedTextPtr->dirtyMode = TK_TEXT_DIRTY_UNDO;
    }

    status = TkUndoRevert(textPtr->sharedTextPtr->undoStack, rangesObj);

    if (textPtr->sharedTextPtr->dirtyMode != TK_TEXT_DIRTY_FIXED) {
	textPtr->sharedTextPtr->dirtyMode = TK_TEXT_DIRTY_NORMAL;
    }
    textPtr->sharedTextPtr->undo = 1;

















    return status;
}
 
/*
 *----------------------------------------------------------------------
 *
 * TextEditRedo --
 *
 *	Redo the last undone change.
 *
 * Results:
 *	The ranges of text that were changed by the undo operation.
 *
 * Side effects:
 *	Apart from manipulating the undo and redo stacks, the state of the
 *	rest of the widget may also change (due to whatever is being redone).
 *
 *----------------------------------------------------------------------
 */

static int
TextEditRedo(
    TkText *textPtr,		/* Overall information about text widget. */
    Tcl_Obj *rangesObj)         /* Ranges of text that were changed. */
{
    int status;



    if (!textPtr->sharedTextPtr->undo) {
	return TCL_OK;
    }

    /*
     * Turn off the undo feature temporarily while we revert a previously
................................................................................
     */

    textPtr->sharedTextPtr->undo = 0;
    if (textPtr->sharedTextPtr->dirtyMode != TK_TEXT_DIRTY_FIXED) {
	textPtr->sharedTextPtr->dirtyMode = TK_TEXT_DIRTY_REDO;
    }

    status = TkUndoApply(textPtr->sharedTextPtr->undoStack, rangesObj);

    if (textPtr->sharedTextPtr->dirtyMode != TK_TEXT_DIRTY_FIXED) {
	textPtr->sharedTextPtr->dirtyMode = TK_TEXT_DIRTY_NORMAL;
    }
    textPtr->sharedTextPtr->undo = 1;

















    return status;
}
 
/*
 *----------------------------------------------------------------------
 *
 * TextEditCmd --
................................................................................
    Tcl_Interp *interp,		/* Current interpreter. */
    int objc,			/* Number of arguments. */
    Tcl_Obj *const objv[])	/* Argument objects. */
{
    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",
	"undo", NULL
    };
    enum editOptions {
	EDIT_CANUNDO, EDIT_CANREDO, EDIT_MODIFIED, EDIT_REDO, EDIT_RESET,
................................................................................
	break;
    case EDIT_REDO:
	if (objc != 3) {
	    Tcl_WrongNumArgs(interp, 3, objv, NULL);
	    return TCL_ERROR;
	}
	canUndo = TkUndoCanUndo(textPtr->sharedTextPtr->undoStack);
	if (TextEditRedo(textPtr, rangesObj)) {
	    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);
	}
	break;
    case EDIT_RESET:
	if (objc != 3) {
................................................................................
	break;
    case EDIT_UNDO:
	if (objc != 3) {
	    Tcl_WrongNumArgs(interp, 3, objv, NULL);
	    return TCL_ERROR;
	}
	canRedo = TkUndoCanRedo(textPtr->sharedTextPtr->undoStack);
	if (TextEditUndo(textPtr, rangesObj)) {
	    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);
	}
	break;
    }
    return TCL_OK;







|
|







 







>
>
>
>










|
>
>
>







 







|
>
>
>
>
|
>
>
>
>
>
|
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







|
>
>
>





|
>
>
>







 







|










|
<


>
>







 







|






>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>











|










|
<


>
>







 







|





>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







<







 







|



<
<
|







 







|



<
<
|







394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
....
2770
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
2799
2800
2801
....
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847
2848
2849
2850
2851
2852
2853
2854
2855
2856
2857
2858
2859
2860
2861
2862
2863
2864
2865
2866
2867
2868
2869
2870
2871
2872
2873
2874
2875
2876
2877
2878
2879
2880
2881
2882
2883
....
2888
2889
2890
2891
2892
2893
2894
2895
2896
2897
2898
2899
2900
2901
2902
2903
2904
2905
2906
2907
2908
2909
2910
2911
2912
2913
2914
....
5101
5102
5103
5104
5105
5106
5107
5108
5109
5110
5111
5112
5113
5114
5115
5116
5117
5118
5119

5120
5121
5122
5123
5124
5125
5126
5127
5128
5129
5130
....
5133
5134
5135
5136
5137
5138
5139
5140
5141
5142
5143
5144
5145
5146
5147
5148
5149
5150
5151
5152
5153
5154
5155
5156
5157
5158
5159
5160
5161
5162
5163
5164
5165
5166
5167
5168
5169
5170
5171
5172
5173
5174
5175
5176
5177
5178
5179
5180
5181
5182
5183
5184
5185

5186
5187
5188
5189
5190
5191
5192
5193
5194
5195
5196
....
5199
5200
5201
5202
5203
5204
5205
5206
5207
5208
5209
5210
5211
5212
5213
5214
5215
5216
5217
5218
5219
5220
5221
5222
5223
5224
5225
5226
5227
5228
5229
5230
5231
5232
5233
5234
5235
....
5252
5253
5254
5255
5256
5257
5258

5259
5260
5261
5262
5263
5264
5265
....
5335
5336
5337
5338
5339
5340
5341
5342
5343
5344
5345


5346
5347
5348
5349
5350
5351
5352
5353
....
5370
5371
5372
5373
5374
5375
5376
5377
5378
5379
5380


5381
5382
5383
5384
5385
5386
5387
5388
static int		DumpLine(Tcl_Interp *interp, TkText *textPtr,
			    int what, TkTextLine *linePtr, int start, int end,
			    int lineno, Tcl_Obj *command);
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);
static int		TextEditRedo(TkText *textPtr);
static Tcl_Obj *	TextGetText(const TkText *textPtr,
			    const TkTextIndex *index1,
			    const TkTextIndex *index2, int visibleOnly);
static void		GenerateModifiedEvent(TkText *textPtr);
static void		GenerateUndoStackEvent(TkText *textPtr);
static void		UpdateDirtyFlag(TkSharedText *sharedPtr);
static void		RunAfterSyncCmd(ClientData clientData);
................................................................................
    const TkTextIndex *index1Ptr,
				/* Index describing first location. */
    const TkTextIndex *index2Ptr)
				/* Index describing second location. */
{
    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.
     */

    Tcl_Obj *seeInsertObj = Tcl_NewObj();
    Tcl_Obj *markSet1InsertObj = Tcl_NewObj();
    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.
     */

    Tcl_Obj *index1Obj = TkTextNewIndexObj(NULL, index1Ptr);
    Tcl_Obj *index2Obj = TkTextNewIndexObj(NULL, index2Ptr);
................................................................................
    Tcl_ListObjAppendElement(NULL, insertCmdObj, undoString);

    Tcl_ListObjAppendElement(NULL, deleteCmdObj,
	    Tcl_NewStringObj("delete", 6));
    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);
    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
     * because if we delete the textPtr, but peers still exist, we will then
     * have references to a non-existent Tcl_Command in the undo stack, which
     * will lead to crashes later. Also, the behaviour of the widget w.r.t.
     * bindings (%W substitutions) always uses the widget path name, so there
................................................................................
     * underlying data shared by all peers.
     */

    iAtom = TkUndoMakeSubAtom(&TextUndoRedoCallback, textPtr->sharedTextPtr,
	    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);
    Tcl_DecrRefCount(index2Obj);

    canUndo = TkUndoCanUndo(textPtr->sharedTextPtr->undoStack);
    canRedo = TkUndoCanRedo(textPtr->sharedTextPtr->undoStack);
................................................................................
 *----------------------------------------------------------------------
 *
 * TextEditUndo --
 *
 *	Undo the last change.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Apart from manipulating the undo and redo stacks, the state of the
 *	rest of the widget may also change (due to whatever is being undone).
 *
 *----------------------------------------------------------------------
 */

static int
TextEditUndo(
    TkText *textPtr)		/* Overall information about text widget. */

{
    int status;
    Tcl_Obj *cmdObj;
    int code;

    if (!textPtr->sharedTextPtr->undo) {
	return TCL_OK;
    }

    /*
     * Turn off the undo feature while we revert a compound action, setting
................................................................................
     */

    textPtr->sharedTextPtr->undo = 0;
    if (textPtr->sharedTextPtr->dirtyMode != TK_TEXT_DIRTY_FIXED) {
	textPtr->sharedTextPtr->dirtyMode = TK_TEXT_DIRTY_UNDO;
    }

    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;
}
 
/*
 *----------------------------------------------------------------------
 *
 * TextEditRedo --
 *
 *	Redo the last undone change.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Apart from manipulating the undo and redo stacks, the state of the
 *	rest of the widget may also change (due to whatever is being redone).
 *
 *----------------------------------------------------------------------
 */

static int
TextEditRedo(
    TkText *textPtr)		/* Overall information about text widget. */

{
    int status;
    Tcl_Obj *cmdObj;
    int code;

    if (!textPtr->sharedTextPtr->undo) {
	return TCL_OK;
    }

    /*
     * Turn off the undo feature temporarily while we revert a previously
................................................................................
     */

    textPtr->sharedTextPtr->undo = 0;
    if (textPtr->sharedTextPtr->dirtyMode != TK_TEXT_DIRTY_FIXED) {
	textPtr->sharedTextPtr->dirtyMode = TK_TEXT_DIRTY_REDO;
    }

    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;
}
 
/*
 *----------------------------------------------------------------------
 *
 * TextEditCmd --
................................................................................
    Tcl_Interp *interp,		/* Current interpreter. */
    int objc,			/* Number of arguments. */
    Tcl_Obj *const objv[])	/* Argument objects. */
{
    int index, setModified, oldModified;
    int canRedo = 0;
    int canUndo = 0;


    static const char *const editOptionStrings[] = {
	"canundo", "canredo", "modified", "redo", "reset", "separator",
	"undo", NULL
    };
    enum editOptions {
	EDIT_CANUNDO, EDIT_CANREDO, EDIT_MODIFIED, EDIT_REDO, EDIT_RESET,
................................................................................
	break;
    case EDIT_REDO:
	if (objc != 3) {
	    Tcl_WrongNumArgs(interp, 3, objv, NULL);
	    return TCL_ERROR;
	}
	canUndo = TkUndoCanUndo(textPtr->sharedTextPtr->undoStack);
        if (TextEditRedo(textPtr)) {
	    Tcl_SetObjResult(interp, Tcl_NewStringObj("nothing to redo", -1));
	    Tcl_SetErrorCode(interp, "TK", "TEXT", "NO_REDO", NULL);
	    return TCL_ERROR;


	}
	canRedo = TkUndoCanRedo(textPtr->sharedTextPtr->undoStack);
	if (!canUndo || !canRedo) {
	    GenerateUndoStackEvent(textPtr);
	}
	break;
    case EDIT_RESET:
	if (objc != 3) {
................................................................................
	break;
    case EDIT_UNDO:
	if (objc != 3) {
	    Tcl_WrongNumArgs(interp, 3, objv, NULL);
	    return TCL_ERROR;
	}
	canRedo = TkUndoCanRedo(textPtr->sharedTextPtr->undoStack);
	if (TextEditUndo(textPtr)) {
	    Tcl_SetObjResult(interp, Tcl_NewStringObj("nothing to undo", -1));
	    Tcl_SetErrorCode(interp, "TK", "TEXT", "NO_UNDO", NULL);
	    return TCL_ERROR;


	}
	canUndo = TkUndoCanUndo(textPtr->sharedTextPtr->undoStack);
	if (!canRedo || !canUndo) {
	    GenerateUndoStackEvent(textPtr);
	}
	break;
    }
    return TCL_OK;

Changes to generic/tkUndo.c.

552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
...
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
...
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
...
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
 *----------------------------------------------------------------------
 *
 * TkUndoRevert --
 *
 *	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.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

int
TkUndoRevert(
    TkUndoRedoStack *stack,
    Tcl_Obj *retObj)
{
    TkUndoAtom *elem;

    /*
     * Insert a separator on the undo and the redo stack.
     */

................................................................................
    while (elem != NULL && elem->type != TK_UNDO_SEPARATOR) {
	/*
	 * Note that we currently ignore errors thrown here.
	 */

	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);
    }

    /*
     * Insert a separator on the redo stack.
     */
................................................................................
 *----------------------------------------------------------------------
 *
 * TkUndoApply --
 *
 *	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.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

int
TkUndoApply(
    TkUndoRedoStack *stack,
    Tcl_Obj *retObj)
{
    TkUndoAtom *elem;

    /*
     * Insert a separator on the undo stack.
     */

................................................................................
    while (elem != NULL && elem->type != TK_UNDO_SEPARATOR) {
	/*
	 * Note that we currently ignore errors thrown here.
	 */

	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);
    }

    /*
     * Insert a separator on the undo stack.
     */







|
<









|
<







 







<
<
<
<
<
<
<







 







|
<









|
<







 







<
<
<
<
<
<
<







552
553
554
555
556
557
558
559

560
561
562
563
564
565
566
567
568
569

570
571
572
573
574
575
576
...
594
595
596
597
598
599
600







601
602
603
604
605
606
607
...
615
616
617
618
619
620
621
622

623
624
625
626
627
628
629
630
631
632

633
634
635
636
637
638
639
...
656
657
658
659
660
661
662







663
664
665
666
667
668
669
 *----------------------------------------------------------------------
 *
 * TkUndoRevert --
 *
 *	Undo a compound action on the stack.
 *
 * Results:
 *	A Tcl status code

 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

int
TkUndoRevert(
    TkUndoRedoStack *stack)

{
    TkUndoAtom *elem;

    /*
     * Insert a separator on the undo and the redo stack.
     */

................................................................................
    while (elem != NULL && elem->type != TK_UNDO_SEPARATOR) {
	/*
	 * Note that we currently ignore errors thrown here.
	 */

	EvaluateActionList(stack->interp, elem->revert);








	TkUndoPushStack(&stack->redoStack, elem);
	elem = TkUndoPopStack(&stack->undoStack);
    }

    /*
     * Insert a separator on the redo stack.
     */
................................................................................
 *----------------------------------------------------------------------
 *
 * TkUndoApply --
 *
 *	Redo a compound action on the stack.
 *
 * Results:
 *	A Tcl status code

 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

int
TkUndoApply(
    TkUndoRedoStack *stack)

{
    TkUndoAtom *elem;

    /*
     * Insert a separator on the undo stack.
     */

................................................................................
    while (elem != NULL && elem->type != TK_UNDO_SEPARATOR) {
	/*
	 * Note that we currently ignore errors thrown here.
	 */

	EvaluateActionList(stack->interp, elem->apply);








	TkUndoPushStack(&stack->undoStack, elem);
	elem = TkUndoPopStack(&stack->redoStack);
    }

    /*
     * Insert a separator on the undo stack.
     */

Changes to generic/tkUndo.h.

105
106
107
108
109
110
111
112
113
114
115
MODULE_SCOPE TkUndoSubAtom *TkUndoMakeCmdSubAtom(Tcl_Command command,
			    Tcl_Obj *actionScript, TkUndoSubAtom *subAtomList);
MODULE_SCOPE TkUndoSubAtom *TkUndoMakeSubAtom(TkUndoProc *funcPtr,
			    ClientData clientData, Tcl_Obj *actionScript,
			    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);

#endif /* _TKUNDO */







|
|


105
106
107
108
109
110
111
112
113
114
115
MODULE_SCOPE TkUndoSubAtom *TkUndoMakeCmdSubAtom(Tcl_Command command,
			    Tcl_Obj *actionScript, TkUndoSubAtom *subAtomList);
MODULE_SCOPE TkUndoSubAtom *TkUndoMakeSubAtom(TkUndoProc *funcPtr,
			    ClientData clientData, Tcl_Obj *actionScript,
			    TkUndoSubAtom *subAtomList);
MODULE_SCOPE void	TkUndoPushAction(TkUndoRedoStack *stack,
			    TkUndoSubAtom *apply, TkUndoSubAtom *revert);
MODULE_SCOPE int	TkUndoRevert(TkUndoRedoStack *stack);
MODULE_SCOPE int	TkUndoApply(TkUndoRedoStack *stack);

#endif /* _TKUNDO */

Changes to library/text.tcl.

1198
1199
1200
1201
1202
1203
1204



































































    if {($x != $Priv(x)) || ($y != $Priv(y))} {
	set Priv(mouseMoved) 1
    }
    if {[info exists Priv(mouseMoved)] && $Priv(mouseMoved)} {
	$w scan dragto $x $y
    }
}










































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
    if {($x != $Priv(x)) || ($y != $Priv(y))} {
	set Priv(mouseMoved) 1
    }
    if {[info exists Priv(mouseMoved)] && $Priv(mouseMoved)} {
	$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
}