From 1b30deb221045b7381df0fb8667f451113426267 Mon Sep 17 00:00:00 2001 From: "zed-zippy[bot]" <234243425+zed-zippy[bot]@users.noreply.github.com> Date: Fri, 27 Feb 2026 20:35:16 +0000 Subject: [PATCH] Fix panic in vim increment (#50311) (cherry-pick to stable) (#50338) Cherry-pick of #50311 to stable ---- Fixes ZED-59V Release Notes: - vim: Fixed panic when incrementing a number preceded by a multibyte character Co-authored-by: Conrad Irwin --- crates/vim/src/normal/increment.rs | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/crates/vim/src/normal/increment.rs b/crates/vim/src/normal/increment.rs index 9b6707fdb92520e95e874a5be143024beb21b873..9df8721301a82ed26618f7181ba80c43cbc702df 100644 --- a/crates/vim/src/normal/increment.rs +++ b/crates/vim/src/normal/increment.rs @@ -203,20 +203,25 @@ fn find_target( let start_offset = start.to_offset(snapshot); let end_offset = end.to_offset(snapshot); - let mut offset = start_offset; let mut first_char_is_num = snapshot - .chars_at(offset) + .chars_at(start_offset) .next() .map_or(false, |ch| ch.is_ascii_hexdigit()); let mut pre_char = String::new(); - let next_offset = offset + let next_offset = start_offset + snapshot .chars_at(start_offset) .next() .map_or(0, |ch| ch.len_utf8()); - // Backward scan to find the start of the number, but stop at start_offset + // Backward scan to find the start of the number, but stop at start_offset. + // We track `offset` as the start position of the current character. Initialize + // to `next_offset` and decrement at the start of each iteration so that `offset` + // always lands on a valid character boundary (not in the middle of a multibyte char). + let mut offset = next_offset; for ch in snapshot.reversed_chars_at(next_offset) { + offset -= ch.len_utf8(); + // Search boundaries if offset.0 == 0 || ch.is_whitespace() || (need_range && offset <= start_offset) { break; @@ -238,7 +243,6 @@ fn find_target( } pre_char.insert(0, ch); - offset -= ch.len_utf8(); } // The backward scan breaks on whitespace, including newlines. Without this @@ -895,4 +899,15 @@ mod test { .await .assert_eq("# Title\n2. item\nˇ2. item\n3. item"); } + + #[gpui::test] + async fn test_increment_with_multibyte_characters(cx: &mut gpui::TestAppContext) { + let mut cx = VimTestContext::new(cx, true).await; + + // Test cursor after a multibyte character - this would panic before the fix + // because the backward scan would land in the middle of the Korean character + cx.set_state("지ˇ1", Mode::Normal); + cx.simulate_keystrokes("ctrl-a"); + cx.assert_state("지ˇ2", Mode::Normal); + } }