vim: Fix crash when using ‘ge’ motion on multibyte character (#31566)

Smit Barmase created

Closes #30919

- [x] Test

Release Notes:

- Fixed the issue where using the Vim motion `ge` on multibyte character
would cause Zed to crash.

Change summary

crates/vim/src/motion.rs                         | 18 ++++++++++++++++--
crates/vim/test_data/test_previous_word_end.json |  4 ++++
2 files changed, 20 insertions(+), 2 deletions(-)

Detailed changes

crates/vim/src/motion.rs 🔗

@@ -1701,7 +1701,9 @@ fn previous_word_end(
     let mut point = point.to_point(map);
 
     if point.column < map.buffer_snapshot.line_len(MultiBufferRow(point.row)) {
-        point.column += 1;
+        if let Some(ch) = map.buffer_snapshot.chars_at(point).next() {
+            point.column += ch.len_utf8() as u32;
+        }
     }
     for _ in 0..times {
         let new_point = movement::find_preceding_boundary_point(
@@ -1874,7 +1876,9 @@ fn previous_subword_end(
     let mut point = point.to_point(map);
 
     if point.column < map.buffer_snapshot.line_len(MultiBufferRow(point.row)) {
-        point.column += 1;
+        if let Some(ch) = map.buffer_snapshot.chars_at(point).next() {
+            point.column += ch.len_utf8() as u32;
+        }
     }
     for _ in 0..times {
         let new_point = movement::find_preceding_boundary_point(
@@ -3613,6 +3617,16 @@ mod test {
           4;5.6 567 678
           789 890 901
         "});
+
+        // With multi byte char
+        cx.set_shared_state(indoc! {r"
+        bar ˇó
+        "})
+            .await;
+        cx.simulate_shared_keystrokes("g e").await;
+        cx.shared_state().await.assert_eq(indoc! {"
+        baˇr ó
+        "});
     }
 
     #[gpui::test]

crates/vim/test_data/test_previous_word_end.json 🔗

@@ -27,3 +27,7 @@
 {"Key":"g"}
 {"Key":"shift-e"}
 {"Get":{"state":"123 234 34ˇ5\n4;5.6 567 678\n789 890 901\n","mode":"Normal"}}
+{"Put":{"state":"bar ˇó\n"}}
+{"Key":"g"}
+{"Key":"e"}
+{"Get":{"state":"baˇr ó\n","mode":"Normal"}}