From 318ea4de21121a8ca0d905289a96cf48d9025816 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Wed, 9 Sep 2026 09:22:54 +0800 Subject: [PATCH] vim-patch:9.2.1054: Virtual Replace mode: BS over multi-byte text eats the padding (#41799) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Problem: In Virtual Replace mode, backspacing over a character that replaced several multi-byte characters deletes the padding that follows it, so the text after the cursor loses its alignment. Solution: In replace_do_bs() advance by the length of the character at the current offset instead of always measuring the first restored character (Volodymyr Chernetskyi). After the original characters are restored, replace_do_bs() adds up their screen width so it knows how much of the alignment padding to drop again. The loop advanced "i" by mb_ptr2len(p) - 1, which always returns the length of the *first* restored character rather than the length of the character at the offset being looked at. One backspace can restore more than one character, because a wide character may have replaced several narrow ones. When those characters do not all have the same byte length the index lands in the middle of a character: chartabsize() then measures a trailing byte, counts it as an unprintable worth four cells, and the inflated width makes the following loop delete padding spaces that should have been kept. call setline(1, 'aé xyz') call feedkeys("gR\u4e00\\e", 'xt') leaves "aéxyz" instead of restoring the original line. Going the other way round ('éa') happens to work, since there the first character is the longer one. This has been wrong since the loop was added in Vim 7.0. closes: vim/vim#21211 https://github.com/vim/vim/commit/5d934b1bdb803c652a521f04481f92b2c2fa029e Co-authored-by: Volodymyr Chernetskyi <19735328+chernetskyi@users.noreply.github.com> --- src/nvim/insert.c | 2 +- test/old/testdir/test_edit.vim | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/nvim/insert.c b/src/nvim/insert.c index 0dc5edde49..d5cd4879e1 100644 --- a/src/nvim/insert.c +++ b/src/nvim/insert.c @@ -2906,7 +2906,7 @@ static void replace_do_bs(int limit_col) int vcol = start_vcol; for (int i = 0; i < ins_len; i++) { vcol += win_chartabsize(curwin, p + i, vcol); - i += utfc_ptr2len(p) - 1; + i += utfc_ptr2len(p + i) - 1; } vcol -= start_vcol; diff --git a/test/old/testdir/test_edit.vim b/test/old/testdir/test_edit.vim index e886089d26..7682f77cb0 100644 --- a/test/old/testdir/test_edit.vim +++ b/test/old/testdir/test_edit.vim @@ -1824,6 +1824,20 @@ func Test_edit_startinsert_in_terminal() bwipe! endfunc +" Backspacing in Virtual Replace mode over a wide character that replaced +" several narrower ones must not eat the padding that follows. +func Test_edit_vreplace_bs_multibyte() + new + call setline(1, 'aé xyz') + call feedkeys("gR\u4e00\\e", 'xt') + call assert_equal('aé xyz', getline(1)) + + call setline(1, 'a€ xyz') + call feedkeys("gR\u4e00\\e", 'xt') + call assert_equal('a€ xyz', getline(1)) + bwipe! +endfunc + " Test for :startreplace and :startgreplace func Test_edit_startreplace() new