From bda33ec43691bab087b5b2fa60c651e5d5b9715a Mon Sep 17 00:00:00 2001 From: Peter Finn Date: Tue, 1 Apr 2025 11:21:41 -0700 Subject: [PATCH] vim: Fix space forward bug with non-ASCII characters at EOL (#27860) Closes https://github.com/zed-industries/zed/issues/27619 Fixes issue with right wrapped movement when a multi-byte character is at the end of the line. This is done by grabbing the last character on the current row and using that characters size to calculate the `max_column` variable, which is used to decide if the next right movement should move down the line or not. We did notice a bit of code that could be an issue that we wanted to call out. [Here](https://github.com/zed-industries/zed/blob/main/crates/editor/src/display_map.rs#L1070) inside of `clip_at_line_end` it also does a saturating_sub(1), assuming a single byte character. We didn't run into any issues due to this line but felt like a similar bug. We can apply a similar fix if wanted to pose the question first. Test case: Moving to next line when eol is a multi-byte character https://github.com/user-attachments/assets/1021ab1f-f49d-4986-8f9a-8cfc7e5c91bc Release Notes: - Fixed issue in vim forward spacing when a multi-byte character is at the eol --------- Co-authored-by: KyleBarton --- crates/vim/src/motion.rs | 49 +++++++++++++++---- .../test_backspace_non_ascii_bol.json | 4 ++ .../test_data/test_space_non_ascii_eol.json | 4 ++ .../test_data/test_space_only_ascii_eol.json | 4 ++ 4 files changed, 52 insertions(+), 9 deletions(-) create mode 100644 crates/vim/test_data/test_backspace_non_ascii_bol.json create mode 100644 crates/vim/test_data/test_space_non_ascii_eol.json create mode 100644 crates/vim/test_data/test_space_only_ascii_eol.json diff --git a/crates/vim/src/motion.rs b/crates/vim/src/motion.rs index afec9fe0aa..5ce59cc1e3 100644 --- a/crates/vim/src/motion.rs +++ b/crates/vim/src/motion.rs @@ -1273,16 +1273,19 @@ fn wrapping_right(map: &DisplaySnapshot, mut point: DisplayPoint, times: usize) point } -fn wrapping_right_single(map: &DisplaySnapshot, mut point: DisplayPoint) -> DisplayPoint { - let max_column = map.line_len(point.row()).saturating_sub(1); - if point.column() < max_column { - *point.column_mut() += 1; - point = map.clip_point(point, Bias::Right); - } else if point.row() < map.max_point().row() { - *point.row_mut() += 1; - *point.column_mut() = 0; +fn wrapping_right_single(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPoint { + let mut next_point = point; + *next_point.column_mut() += 1; + next_point = map.clip_point(next_point, Bias::Right); + if next_point == point { + if next_point.row() == map.max_point().row() { + next_point + } else { + DisplayPoint::new(next_point.row().next_row(), 0) + } + } else { + next_point } - point } pub(crate) fn start_of_relative_buffer_row( @@ -3563,4 +3566,32 @@ mod test { cx.simulate_shared_keystrokes("3 space").await; cx.shared_state().await.assert_eq("πππˇππ"); } + + #[gpui::test] + async fn test_space_non_ascii_eol(cx: &mut gpui::TestAppContext) { + let mut cx = NeovimBackedTestContext::new(cx).await; + + cx.set_shared_state(indoc! {" + ππππˇπ + πanotherline"}) + .await; + cx.simulate_shared_keystrokes("4 space").await; + cx.shared_state().await.assert_eq(indoc! {" + πππππ + πanˇotherline"}); + } + + #[gpui::test] + async fn test_backspace_non_ascii_bol(cx: &mut gpui::TestAppContext) { + let mut cx = NeovimBackedTestContext::new(cx).await; + + cx.set_shared_state(indoc! {" + ππππ + πanˇotherline"}) + .await; + cx.simulate_shared_keystrokes("4 backspace").await; + cx.shared_state().await.assert_eq(indoc! {" + πππˇπ + πanotherline"}); + } } diff --git a/crates/vim/test_data/test_backspace_non_ascii_bol.json b/crates/vim/test_data/test_backspace_non_ascii_bol.json new file mode 100644 index 0000000000..88536bc50d --- /dev/null +++ b/crates/vim/test_data/test_backspace_non_ascii_bol.json @@ -0,0 +1,4 @@ +{"Put":{"state":"ππππ\nπanˇotherline"}} +{"Key":"4"} +{"Key":"backspace"} +{"Get":{"state":"πππˇπ\nπanotherline","mode":"Normal"}} diff --git a/crates/vim/test_data/test_space_non_ascii_eol.json b/crates/vim/test_data/test_space_non_ascii_eol.json new file mode 100644 index 0000000000..6c1bb73fa5 --- /dev/null +++ b/crates/vim/test_data/test_space_non_ascii_eol.json @@ -0,0 +1,4 @@ +{"Put":{"state":"ππππˇπ\nπanotherline"}} +{"Key":"4"} +{"Key":"space"} +{"Get":{"state":"πππππ\nπanˇotherline","mode":"Normal"}} diff --git a/crates/vim/test_data/test_space_only_ascii_eol.json b/crates/vim/test_data/test_space_only_ascii_eol.json new file mode 100644 index 0000000000..43a3af7945 --- /dev/null +++ b/crates/vim/test_data/test_space_only_ascii_eol.json @@ -0,0 +1,4 @@ +{"Put":{"state":"aaaaˇaa\nanotherline"}} +{"Key":"4"} +{"Key":"space"} +{"Get":{"state":"aaaaaa\nanˇotherline","mode":"Normal"}}