From b25d8ecb755b785ff4b91bc9b5fd40cfaa68d3de Mon Sep 17 00:00:00 2001 From: Thorsten Ball Date: Fri, 20 Dec 2024 13:48:38 +0100 Subject: [PATCH] vim: Fix deletion with sentence-motion (#22289) Fixes #22151. Turns out Vim also has some weird behavior with sentence deletion in case it's on the first character of a line. Release Notes: - vim: Fixed deleting sentence-wise (i.e. `d(` and `d)`), which would previously delete the whole line instead of just a sentence. --- crates/vim/src/motion.rs | 4 +- crates/vim/src/normal/delete.rs | 86 ++++++++++++++++--- .../vim/test_data/test_delete_sentence.json | 22 +++++ 3 files changed, 98 insertions(+), 14 deletions(-) create mode 100644 crates/vim/test_data/test_delete_sentence.json diff --git a/crates/vim/src/motion.rs b/crates/vim/src/motion.rs index 0e236861b6bcf5688ce5ebe90d8ecc1bda72308c..e90a2c98c0b6936d1b1651c9441601fa2dd78d45 100644 --- a/crates/vim/src/motion.rs +++ b/crates/vim/src/motion.rs @@ -585,8 +585,6 @@ impl Motion { | NextLineStart | PreviousLineStart | StartOfLineDownward - | SentenceBackward - | SentenceForward | StartOfParagraph | EndOfParagraph | WindowTop @@ -611,6 +609,8 @@ impl Motion { | Left | Backspace | Right + | SentenceBackward + | SentenceForward | Space | StartOfLine { .. } | EndOfLineDownward diff --git a/crates/vim/src/normal/delete.rs b/crates/vim/src/normal/delete.rs index e633db1df0aede5954e1e6b45855325f4f2fe2b7..0b9d3b6fcc0fc8d30736d4c2e65d112e53fadc17 100644 --- a/crates/vim/src/normal/delete.rs +++ b/crates/vim/src/normal/delete.rs @@ -28,23 +28,27 @@ impl Vim { original_columns.insert(selection.id, original_head.column()); motion.expand_selection(map, selection, times, true, &text_layout_details); + let start_point = selection.start.to_point(map); + let next_line = map + .buffer_snapshot + .clip_point(Point::new(start_point.row + 1, 0), Bias::Left) + .to_display_point(map); match motion { // Motion::NextWordStart on an empty line should delete it. - Motion::NextWordStart { .. } => { + Motion::NextWordStart { .. } if selection.is_empty() && map .buffer_snapshot - .line_len(MultiBufferRow(selection.start.to_point(map).row)) - == 0 - { - selection.end = map - .buffer_snapshot - .clip_point( - Point::new(selection.start.to_point(map).row + 1, 0), - Bias::Left, - ) - .to_display_point(map) - } + .line_len(MultiBufferRow(start_point.row)) + == 0 => + { + selection.end = next_line + } + // Sentence motions, when done from start of line, include the newline + Motion::SentenceForward | Motion::SentenceBackward + if selection.start.column() == 0 => + { + selection.end = next_line } Motion::EndOfDocument {} => { // Deleting until the end of the document includes the last line, including @@ -604,4 +608,62 @@ mod test { cx.simulate("d t x", "ˇax").await.assert_matches(); cx.simulate("d t x", "aˇx").await.assert_matches(); } + + #[gpui::test] + async fn test_delete_sentence(cx: &mut gpui::TestAppContext) { + let mut cx = NeovimBackedTestContext::new(cx).await; + cx.simulate( + "d )", + indoc! {" + Fiˇrst. Second. Third. + Fourth. + "}, + ) + .await + .assert_matches(); + + cx.simulate( + "d )", + indoc! {" + First. Secˇond. Third. + Fourth. + "}, + ) + .await + .assert_matches(); + + // Two deletes + cx.simulate( + "d ) d )", + indoc! {" + First. Second. Thirˇd. + Fourth. + "}, + ) + .await + .assert_matches(); + + // Should delete whole line if done on first column + cx.simulate( + "d )", + indoc! {" + ˇFirst. + Fourth. + "}, + ) + .await + .assert_matches(); + + // Backwards it should also delete the whole first line + cx.simulate( + "d (", + indoc! {" + First. + ˇSecond. + Fourth. + "}, + ) + .await + .assert_matches(); + } } diff --git a/crates/vim/test_data/test_delete_sentence.json b/crates/vim/test_data/test_delete_sentence.json new file mode 100644 index 0000000000000000000000000000000000000000..ec8edfbbfdc2b6249814aaccf9367a8eeaf533ff --- /dev/null +++ b/crates/vim/test_data/test_delete_sentence.json @@ -0,0 +1,22 @@ +{"Put":{"state":"Fiˇrst. Second. Third.\nFourth.\n"}} +{"Key":"d"} +{"Key":")"} +{"Get":{"state":"FiˇSecond. Third.\nFourth.\n","mode":"Normal"}} +{"Put":{"state":"First. Secˇond. Third.\nFourth.\n"}} +{"Key":"d"} +{"Key":")"} +{"Get":{"state":"First. SecˇThird.\nFourth.\n","mode":"Normal"}} +{"Put":{"state":"First. Second. Thirˇd.\nFourth.\n"}} +{"Key":"d"} +{"Key":")"} +{"Key":"d"} +{"Key":")"} +{"Get":{"state":"First. Second. Thˇi\n","mode":"Normal"}} +{"Put":{"state":"ˇFirst.\nFourth.\n"}} +{"Key":"d"} +{"Key":")"} +{"Get":{"state":"ˇFourth.\n","mode":"Normal"}} +{"Put":{"state":"First.\nˇSecond.\nFourth.\n"}} +{"Key":"d"} +{"Key":"("} +{"Get":{"state":"ˇSecond.\nFourth.\n","mode":"Normal"}}