@@ -289,6 +289,50 @@ UndoToggleGravityInspect(
289289 return AppendName (UndoToggleGravityGetCommand (sharedTextPtr , item ), sharedTextPtr , token -> markPtr );
290290}
291291
292+ /*
293+ *----------------------------------------------------------------------
294+ *
295+ * ReplayTarget --
296+ *
297+ * Find the mark a token has to act on. The referenced mark may have
298+ * been unlinked meanwhile (deleted by [delete -marks], unset, or
299+ * superseded by a living homonym): in this case the living mark of
300+ * the same name is the target, replaying the operation on it - this
301+ * is the same adoption the redo of a [mark set] performs.
302+ *
303+ * Results:
304+ * The mark to act on, or NULL if the name is not alive anymore; in
305+ * the latter case the token has to be a no-op.
306+ *
307+ * Side effects:
308+ * None.
309+ *
310+ *----------------------------------------------------------------------
311+ */
312+
313+ static TkTextSegment *
314+ ReplayTarget (
315+ const TkSharedText * sharedTextPtr , /* Handle to shared text resource. */
316+ TkTextSegment * markPtr ) /* The mark referenced by the token. */
317+ {
318+ Tcl_HashEntry * hPtr ;
319+
320+ assert (markPtr );
321+ assert (TkTextIsNormalMark (markPtr ));
322+
323+ if (markPtr -> sectionPtr ) {
324+ return markPtr ;
325+ }
326+ if (!IS_PRESERVED (markPtr )) {
327+ return NULL ;
328+ }
329+ if (!(hPtr = Tcl_FindHashEntry (& ((TkSharedText * ) sharedTextPtr )-> markTable ,
330+ GET_NAME (markPtr )))) {
331+ return NULL ;
332+ }
333+ return (TkTextSegment * )Tcl_GetHashValue (hPtr );
334+ }
335+
292336static void
293337UndoToggleGravityPerform (
294338 TkSharedText * sharedTextPtr ,
@@ -299,12 +343,17 @@ UndoToggleGravityPerform(
299343 UndoTokenToggleGravity * token = (UndoTokenToggleGravity * ) undoInfo -> token ;
300344 const Tk_SegType * newTypePtr ;
301345 const Tk_SegType * oldTypePtr ;
346+ TkTextSegment * markPtr ;
302347
303348 assert (!token -> markPtr -> body .mark .changePtr );
304349
305- oldTypePtr = token -> markPtr -> typePtr ;
350+ if (!(markPtr = ReplayTarget (sharedTextPtr , token -> markPtr ))) {
351+ return ; /* the mark is gone, nothing to toggle */
352+ }
353+
354+ oldTypePtr = markPtr -> typePtr ;
306355 newTypePtr = (oldTypePtr == & tkTextRightMarkType ) ? & tkTextLeftMarkType : & tkTextRightMarkType ;
307- ChangeGravity (sharedTextPtr , NULL , token -> markPtr , newTypePtr , NULL );
356+ ChangeGravity (sharedTextPtr , NULL , markPtr , newTypePtr , NULL );
308357
309358 if (redoInfo ) {
310359 redoInfo -> token = undoInfo -> token ;
@@ -335,24 +384,29 @@ UndoMoveMarkPerform(
335384{
336385 UndoTokenMoveMark * token = (UndoTokenMoveMark * ) undoInfo -> token ;
337386 TkTextUndoIndex index = token -> index ;
387+ TkTextSegment * markPtr ;
338388
339389 assert (!token -> markPtr -> body .mark .changePtr );
340390
391+ if (!(markPtr = ReplayTarget (sharedTextPtr , token -> markPtr ))) {
392+ return ; /* the mark is gone, nothing to move */
393+ }
394+
341395 if (redoInfo ) {
342396 TkTextUndoIndex redoIndex ;
343397
344398 /*
345399 * Don't clobber 'index': the mark must be re-inserted at the saved
346400 * position, whereas the redo token receives the current one.
347401 */
348- TkBTreeMakeUndoIndex (sharedTextPtr , token -> markPtr , & redoIndex );
402+ TkBTreeMakeUndoIndex (sharedTextPtr , markPtr , & redoIndex );
349403 token -> index = redoIndex ;
350404 redoInfo -> token = undoInfo -> token ;
351405 redoInfo -> token -> undoType = isRedo ? & undoTokenMoveMarkType : & redoTokenMoveMarkType ;
352406 }
353407
354- TkBTreeUnlinkSegment (sharedTextPtr , token -> markPtr );
355- TkBTreeReInsertSegment (sharedTextPtr , & index , token -> markPtr );
408+ TkBTreeUnlinkSegment (sharedTextPtr , markPtr );
409+ TkBTreeReInsertSegment (sharedTextPtr , & index , markPtr );
356410}
357411
358412static void
@@ -412,6 +466,10 @@ UndoSetMarkPerform(
412466 const UndoTokenSetMark * token = (const UndoTokenSetMark * ) undoInfo -> token ;
413467 TkTextSegment * markPtr = (TkTextSegment * )GET_POINTER (token -> markPtr );
414468
469+ if (!(markPtr = ReplayTarget (sharedTextPtr , markPtr ))) {
470+ return ; /* the name is gone, nothing to unset */
471+ }
472+
415473 assert (!markPtr -> body .mark .changePtr );
416474 UnsetMark (sharedTextPtr , markPtr , redoInfo );
417475 if (redoInfo && !isRedo ) {
@@ -447,8 +505,35 @@ RedoSetMarkPerform(
447505 assert (TkTextIsNormalMark (markPtr ));
448506
449507 if (IS_PRESERVED (markPtr )) {
508+ Tcl_HashEntry * hPtr = Tcl_FindHashEntry (& sharedTextPtr -> markTable , GET_NAME (markPtr ));
509+
510+ if (hPtr ) {
511+ /*
512+ * The name is alive again, undo/redo interleavings can revive an
513+ * homonym (the redo of a deletion does not delete the marks
514+ * again). The mark of the token owns the name back, with its
515+ * recorded gravity and position: unset the homonym, without
516+ * recording - the further undoes revive it through the chain of
517+ * the deletion which preserved it, and its restoration then
518+ * finds the name taken (MarkRestoreProc drops the duplicate).
519+ */
520+
521+ TkTextSegment * livingPtr = (TkTextSegment * )Tcl_GetHashValue (hPtr );
522+
523+ assert (livingPtr != markPtr );
524+ assert (TkTextIsNormalMark (livingPtr ));
525+ UnsetMark (sharedTextPtr , livingPtr , NULL );
526+ }
527+
450528 ReactivateMark (sharedTextPtr , markPtr );
451529 sharedTextPtr -> numMarks += 1 ;
530+ } else if (markPtr -> sectionPtr ) {
531+ /*
532+ * The mark is alive and linked again (an undone deletion has
533+ * restored it): setting an existing mark is a move, so unlink it
534+ * before it is re-inserted at the recorded position.
535+ */
536+ TkBTreeUnlinkSegment (sharedTextPtr , markPtr );
452537 }
453538
454539 TkBTreeReInsertSegment (sharedTextPtr , & token -> index , markPtr );
@@ -1834,6 +1919,24 @@ SetMark(
18341919 }
18351920
18361921 if ((segPtr = TkTextIndexGetSegment (indexPtr )) == markPtr ) {
1922+ if (typePtr && typePtr != markPtr -> typePtr ) {
1923+ /*
1924+ * The index resolves to the mark itself (e.g. [mark set m
1925+ * end right] with the mark heading the last line, or [mark
1926+ * set m m right]), but an explicit direction still has to
1927+ * be applied - it used to be skipped on this path.
1928+ */
1929+
1930+ TkTextUndoInfo undoInfo ;
1931+ TkTextUndoInfo * undoInfoPtr = NULL ;
1932+
1933+ if (sharedTextPtr -> steadyMarks
1934+ && TkTextIsNormalMark (markPtr )
1935+ && !TkTextUndoUndoStackIsFull (sharedTextPtr -> undoStack )) {
1936+ undoInfoPtr = & undoInfo ;
1937+ }
1938+ ChangeGravity (sharedTextPtr , textPtr , markPtr , typePtr , undoInfoPtr );
1939+ }
18371940 return markPtr ;
18381941 }
18391942
@@ -2281,6 +2384,18 @@ MarkDeleteProc(
22812384 return 0 ;
22822385 }
22832386
2387+ if ((flags & DELETE_RELEASE ) && segPtr -> sectionPtr ) {
2388+ /*
2389+ * The caller destroys a token whose chain referenced this mark, but
2390+ * the mark is still alive inside the tree: release the reference,
2391+ * the mark is not ours to delete - preserving it here would remove
2392+ * its hash entry and leave a preserved segment inside the tree.
2393+ */
2394+ assert (segPtr -> refCount > 1 );
2395+ segPtr -> refCount -= 1 ;
2396+ return 1 ;
2397+ }
2398+
22842399 assert (segPtr -> body .mark .ptr );
22852400
22862401 if (segPtr -> body .mark .changePtr ) {
@@ -2446,7 +2561,15 @@ MarkRestoreProc(
24462561 }
24472562
24482563 hPtr = Tcl_CreateHashEntry (& sharedTextPtr -> markTable , GET_NAME (segPtr ), & isNew );
2449- assert (isNew );
2564+ if (!isNew ) {
2565+ /*
2566+ * A mark of this name is alive again (undo/redo interleavings
2567+ * can revive an homonym): the living one wins, discard the
2568+ * preserved duplicate like the branch above does.
2569+ */
2570+ MarkDeleteProc (sharedTextPtr , segPtr , DELETE_CLEANUP );
2571+ return 0 ;
2572+ }
24502573 Tcl_SetHashValue (hPtr , segPtr );
24512574 sharedTextPtr -> numMarks += 1 ;
24522575 Tcl_Free (GET_NAME (segPtr ));
0 commit comments