From f2813f60ed7a1be013b639db35f76779c459894d Mon Sep 17 00:00:00 2001 From: Dino Date: Tue, 29 Apr 2025 20:34:51 +0100 Subject: [PATCH] vim: Fix end of paragraph deletion when there's no blank lines (#29490) This Pull Request attempts to fix an issue where using `d}` in vim mode would not delete all characters in case there's no blank lines at the end of the buffer. When calculating the end point for this motion, if there's no blank lines at the end of the buffer, Zed was calculating it to be the last character in the last line. However, if there's a newline at the end of the buffer, it calculates the end point to be the point at the right of the last character. Here's an example, for the following buffer contents: ``` Hello! Hello! ``` If the `d}` command is run at `(0, 0)`, the end point will be set to `(1, 5)`. However, fi the same command is run for this buffer instead: ``` Hello! Hello! ``` The end point will be set to `(1, 6)`, there's a 1 unit difference in the column, which leads to all characters actually being deleted. Closes #29393 Release Notes: - Fixed deleting to the end of paragraph when there's no blank lines --------- Co-authored-by: Conrad Irwin --- crates/vim/src/motion.rs | 10 ++++++++ crates/vim/src/normal/delete.rs | 23 +++++++++++++++++++ .../test_delete_end_of_paragraph.json | 8 +++++++ 3 files changed, 41 insertions(+) create mode 100644 crates/vim/test_data/test_delete_end_of_paragraph.json diff --git a/crates/vim/src/motion.rs b/crates/vim/src/motion.rs index 331d7897f6..f582fea166 100644 --- a/crates/vim/src/motion.rs +++ b/crates/vim/src/motion.rs @@ -1309,6 +1309,16 @@ impl Motion { end_point.row -= 1; end_point.column = 0; selection.end = map.clip_point(map.next_line_boundary(end_point).1, Bias::Left); + } else if let Motion::EndOfParagraph = self { + // Special case: When using the "}" motion, it's possible + // that there's no blank lines after the paragraph the + // cursor is currently on. + // In this situation the `end_point.column` value will be + // greater than 0, so the selection doesn't actually end on + // the first character of a blank line. In that case, we'll + // want to move one column to the right, to actually include + // all characters of the last non-blank line. + selection.end = movement::saturating_right(map, selection.end) } } } else if kind == MotionKind::Inclusive { diff --git a/crates/vim/src/normal/delete.rs b/crates/vim/src/normal/delete.rs index 583e775fc6..f52d9bebe0 100644 --- a/crates/vim/src/normal/delete.rs +++ b/crates/vim/src/normal/delete.rs @@ -351,6 +351,29 @@ mod test { .assert_matches(); } + #[gpui::test] + async fn test_delete_end_of_paragraph(cx: &mut gpui::TestAppContext) { + let mut cx = NeovimBackedTestContext::new(cx).await; + cx.simulate( + "d }", + indoc! {" + ˇhello world. + + hello world."}, + ) + .await + .assert_matches(); + + cx.simulate( + "d }", + indoc! {" + ˇhello world. + hello world."}, + ) + .await + .assert_matches(); + } + #[gpui::test] async fn test_delete_0(cx: &mut gpui::TestAppContext) { let mut cx = NeovimBackedTestContext::new(cx).await; diff --git a/crates/vim/test_data/test_delete_end_of_paragraph.json b/crates/vim/test_data/test_delete_end_of_paragraph.json new file mode 100644 index 0000000000..860fed7a68 --- /dev/null +++ b/crates/vim/test_data/test_delete_end_of_paragraph.json @@ -0,0 +1,8 @@ +{"Put":{"state":"ˇhello world.\n\nhello world."}} +{"Key":"d"} +{"Key":"}"} +{"Get":{"state":"ˇ\nhello world.","mode":"Normal"}} +{"Put":{"state":"ˇhello world.\nhello world."}} +{"Key":"d"} +{"Key":"}"} +{"Get":{"state":"ˇ","mode":"Normal"}}