patch 9.2.0408: Insert-mode <Cmd> edits can corrupt undo

Commit: 
https://github.com/vim/vim/commit/e47daed4423182968f64b80c3d7613f0a98a50d4
Author: Jaehwang Jung <[email protected]>
Date:   Tue Apr 28 19:04:39 2026 +0000

    patch 9.2.0408: Insert-mode <Cmd> edits can corrupt undo
    
    Problem:  A <Cmd> command in Insert mode can edit the current buffer,
              e.g., with setline(). That edit appends to the current undo
              block, but Insert mode does not know that the cursor line may
              need to be saved again before the next typed edit. If the next
              typed edit is a <BS> at the start of a line, it can join away
              the line that was changed by the <Cmd> command before Insert
              mode saves that updated line. The newest undo entry can then
              still refer to the joined-away line, so undo sees a range past
              the end of the buffer and fails with E438.
    Solution: If a <Cmd> command in Insert mode changes the buffer, set
              ins_need_undo so stop_arrow() refreshes Insstart. This lets
              the next edit properly decide whether a new undo entry is
              needed (Jaehwang Jung)
    
    closes: #20087
    AI-assisted: Codex
    
    Signed-off-by: Jaehwang Jung <[email protected]>
    Signed-off-by: Christian Brabandt <[email protected]>

diff --git a/src/edit.c b/src/edit.c
index f15cc55f3..1db9c1307 100644
--- a/src/edit.c
+++ b/src/edit.c
@@ -1132,6 +1132,10 @@ doESCkey:
        case K_COMMAND:             // <Cmd>command<CR>
        case K_SCRIPT_COMMAND:      // <ScriptCmd>command<CR>
            {
+               bufref_T    save_curbuf;
+               varnumber_T tick = CHANGEDTICK(curbuf);
+
+               set_bufref(&save_curbuf, curbuf);
                do_cmdkey_command(c, 0);
 
 #ifdef FEAT_TERMINAL
@@ -1139,10 +1143,15 @@ doESCkey:
                    // Started a terminal that gets the input, exit Insert mode.
                    goto doESCkey;
 #endif
-               if (curbuf->b_u_synced)
-                   // The command caused undo to be synced.  Need to save the
-                   // line for undo before inserting the next char.
+               if (curbuf->b_u_synced
+                       || (bufref_valid(&save_curbuf)
+                           && curbuf == save_curbuf.br_buf
+                           && tick != CHANGEDTICK(curbuf)))
+               {
+                   // The command synced undo or changed this buffer.
+                   // Save the cursor line before the next typed edit.
                    ins_need_undo = TRUE;
+               }
            }
            break;
 
@@ -2503,7 +2512,9 @@ stop_arrow(void)
     {
        if (u_save_cursor() == OK)
        {
-           // A command or event may have moved the cursor after syncing undo.
+           // A command or event may have moved the cursor or edited the
+           // buffer. Update Insstart so that later edits can properly decide
+           // whether an extra undo entry is needed.
            Insstart = curwin->w_cursor;
            Insstart_textlen = (colnr_T)linetabsize_str(ml_get_curline());
            ins_need_undo = FALSE;
diff --git a/src/testdir/test_undo.vim b/src/testdir/test_undo.vim
index 97b77f423..f03d3ef09 100644
--- a/src/testdir/test_undo.vim
+++ b/src/testdir/test_undo.vim
@@ -939,4 +939,32 @@ func 
Test_undo_line_backspace_after_insert_cmd_cursor_movement()
   bwipe!
 endfunc
 
+func Test_undo_line_backspace_after_insert_func_edit()
+  new
+  setlocal backspace=eol undolevels=100
+
+  let v:errmsg = ''
+  call feedkeys("i\<CR>"
+        \ .. "\<Cmd>call setline(2, 'abc')\<CR>"
+        \ .. "\<BS>\<Esc>u", 'xt')
+
+  call assert_equal('', v:errmsg)
+  call assert_equal([''], getline(1, '$'))
+  bwipe!
+endfunc
+
+func Test_undo_line_backspace_after_insert_cmd_edit()
+  new
+  setlocal backspace=eol undolevels=100
+
+  let v:errmsg = ''
+  call feedkeys("i\<CR>"
+        \ .. "\<Cmd>s/.*/abc/\<CR>"
+        \ .. "\<BS>\<Esc>u", 'xt')
+
+  call assert_equal('', v:errmsg)
+  call assert_equal([''], getline(1, '$'))
+  bwipe!
+endfunc
+
 " vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/version.c b/src/version.c
index b6cbb51aa..406bccd54 100644
--- a/src/version.c
+++ b/src/version.c
@@ -729,6 +729,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    408,
 /**/
     407,
 /**/

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion visit 
https://groups.google.com/d/msgid/vim_dev/E1wHnth-0068W3-CR%40256bit.org.

Raspunde prin e-mail lui