vim: Fix `d shift-g` not deleting until EOD if soft-wrap is on (#20160)

Thorsten Ball created

This previously didn't work: `d G` would delete to the end of the "first
of the soft-wrapped lines" of the last line.

To fix it, we special case the delete behavior for `shift-g`, which is
what Neovim also seems to do.


Release Notes:

- Fixed `d G` in Vim mode not deleting until the actual end of the
document if soft-wrap is turned on.

Change summary

crates/vim/src/normal/delete.rs                            | 41 ++++---
crates/vim/src/test.rs                                     | 18 +++
crates/vim/test_data/test_wrapped_delete_end_document.json | 10 +
3 files changed, 51 insertions(+), 18 deletions(-)

Detailed changes

crates/vim/src/normal/delete.rs đź”—

@@ -28,25 +28,30 @@ impl Vim {
                         original_columns.insert(selection.id, original_head.column());
                         motion.expand_selection(map, selection, times, true, &text_layout_details);
 
-                        // Motion::NextWordStart on an empty line should delete it.
-                        if let Motion::NextWordStart {
-                            ignore_punctuation: _,
-                        } = motion
-                        {
-                            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)
+                        match motion {
+                            // Motion::NextWordStart on an empty line should delete it.
+                            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)
+                                }
+                            }
+                            Motion::EndOfDocument {} => {
+                                // Deleting until the end of the document includes the last line, including
+                                // soft-wrapped lines.
+                                selection.end = map.max_point()
                             }
+                            _ => {}
                         }
                     });
                 });

crates/vim/src/test.rs đź”—

@@ -730,6 +730,24 @@ async fn test_wrapped_motions(cx: &mut gpui::TestAppContext) {
     });
 }
 
+#[gpui::test]
+async fn test_wrapped_delete_end_document(cx: &mut gpui::TestAppContext) {
+    let mut cx = NeovimBackedTestContext::new(cx).await;
+
+    cx.set_shared_wrap(12).await;
+
+    cx.set_shared_state(indoc! {"
+                aaˇaaaaaaaaaaaaaaaaaa
+                bbbbbbbbbbbbbbbbbbbb
+                cccccccccccccccccccc"
+    })
+    .await;
+    cx.simulate_shared_keystrokes("d shift-g i z z z").await;
+    cx.shared_state().await.assert_eq(indoc! {"
+                zzzˇ"
+    });
+}
+
 #[gpui::test]
 async fn test_paragraphs_dont_wrap(cx: &mut gpui::TestAppContext) {
     let mut cx = NeovimBackedTestContext::new(cx).await;

crates/vim/test_data/test_wrapped_delete_end_document.json đź”—

@@ -0,0 +1,10 @@
+{"SetOption":{"value":"wrap"}}
+{"SetOption":{"value":"columns=12"}}
+{"Put":{"state":"aaˇaaaaaaaaaaaaaaaaaa\nbbbbbbbbbbbbbbbbbbbb\ncccccccccccccccccccc"}}
+{"Key":"d"}
+{"Key":"shift-g"}
+{"Key":"i"}
+{"Key":"z"}
+{"Key":"z"}
+{"Key":"z"}
+{"Get":{"state":"zzzˇ","mode":"Insert"}}