From c8dbb04da96dd200165690cba253c8b832bc388a Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sat, 23 May 2026 08:57:36 +0800 Subject: [PATCH] vim-patch:9.2.0510: setline() mapping may trigger autoindent (#39961) Problem: setline() insert mode mapping may trigger autoindent, corrupting the newly inserted line content (Evgeni Chasnovski) Solution: Only strip autoindent whitespace when the rest of the line is all whitespace (glepnir). fixes: vim/vim#19363 closes: vim/vim#20290 https://github.com/vim/vim/commit/e3dedac77b2076210e7027b14a5927622434f014 Co-authored-by: glepnir --- src/nvim/edit.c | 59 ++++++++++++++++++++-------------- test/old/testdir/test_edit.vim | 24 ++++++++++++++ 2 files changed, 58 insertions(+), 25 deletions(-) diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 85fd450a10..dd2354b14a 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -2295,33 +2295,42 @@ static void stop_insert(pos_T *end_insert_pos, int esc, int nomove) curwin->w_cursor = *end_insert_pos; check_cursor_col(curwin); // make sure it is not past the line - while (true) { - if (gchar_cursor() == NUL && curwin->w_cursor.col > 0) { - curwin->w_cursor.col--; - } - cc = gchar_cursor(); - if (!ascii_iswhite(cc)) { - break; - } - if (del_char(true) == FAIL) { - break; // should not happen - } - } - if (curwin->w_cursor.lnum != tpos.lnum) { - curwin->w_cursor = tpos; - } else if (curwin->w_cursor.col < prev_col) { - // reset tpos, could have been invalidated in the loop above - tpos = curwin->w_cursor; - tpos.col++; - if (cc != NUL && gchar_pos(&tpos) == NUL) { - curwin->w_cursor.col++; // put cursor back on the NUL - } + + // Where the loop would actually start (back up if NUL). + colnr_T strip_col = curwin->w_cursor.col; + if (gchar_cursor() == NUL && strip_col > 0) { + strip_col--; } - // may have started Visual mode, adjust the position for - // deleted characters. - if (VIsual_active) { - check_visual_pos(); + // Don't strip if non-whitespace follows: setline() from a + // mapping or CursorHoldI autocmd may have inserted content. + if (*skipwhite(get_cursor_line_ptr() + strip_col) == NUL) { + curwin->w_cursor.col = strip_col; + while (true) { + cc = gchar_cursor(); + if (!ascii_iswhite(cc)) { + break; + } + if (del_char(true) == FAIL) { + break; // should not happen + } + } + if (curwin->w_cursor.lnum != tpos.lnum) { + curwin->w_cursor = tpos; + } else if (curwin->w_cursor.col < prev_col) { + // reset tpos, could have been invalidated in the loop above + tpos = curwin->w_cursor; + tpos.col++; + if (cc != NUL && gchar_pos(&tpos) == NUL) { + curwin->w_cursor.col++; // put cursor back on the NUL + } + } + + // may have started Visual mode, adjust the position for + // deleted characters. + if (VIsual_active) { + check_visual_pos(); + } } } } diff --git a/test/old/testdir/test_edit.vim b/test/old/testdir/test_edit.vim index 7abae9c849..4530e2116f 100644 --- a/test/old/testdir/test_edit.vim +++ b/test/old/testdir/test_edit.vim @@ -2474,4 +2474,28 @@ func Test_edit_CAR_with_completion() bw! endfunc +func Test_autoindent_no_strip_after_cmd_setline() + new + setlocal autoindent + inoremap call setline('.', 'v v')call cursor(line('.'), 2) + call feedkeys("Go\\", 'tx') + call assert_equal('v v', getline(2)) + bwipe! +endfunc + +func Test_autoindent_no_strip_after_cursorholdi() + CheckFeature timers + new + setlocal autoindent + set updatetime=50 + au CursorHoldI call setline('.', 'v v') + call setline(1, ' x') + call cursor(1, 2) + call timer_start(120, {-> feedkeys("\", 't')}) + call feedkeys("o", 'tx!') + call assert_equal('v v', getline(2)) + set updatetime& + bwipe! +endfunc + " vim: shiftwidth=2 sts=2 expandtab