vim: Fix Y on last line (#2975)

Conrad Irwin created

For zed-industries/community#2044

Release Notes:

- vim: Fix y in VISUAL LINE mode when last line has no trailing newline
([#2044](https://github.com/zed-industries/community/issues/2044)).

Change summary

assets/keymaps/vim.json                    |  1 +
crates/vim/src/utils.rs                    |  3 ++-
crates/vim/src/visual.rs                   | 22 ++++++++++++++++++++--
crates/vim/test_data/test_visual_yank.json |  6 ++++++
4 files changed, 29 insertions(+), 3 deletions(-)

Detailed changes

assets/keymaps/vim.json 🔗

@@ -455,6 +455,7 @@
       "shift-d": "vim::VisualDelete",
       "shift-x": "vim::VisualDelete",
       "y": "vim::VisualYank",
+      "shift-y": "vim::VisualYank",
       "p": "vim::Paste",
       "shift-p": [
         "vim::Paste",

crates/vim/src/utils.rs 🔗

@@ -26,10 +26,11 @@ pub fn copy_selections_content(editor: &mut Editor, linewise: bool, cx: &mut App
             let is_last_line = linewise
                 && end.row == buffer.max_buffer_row()
                 && buffer.max_point().column > 0
+                && start.row < buffer.max_buffer_row()
                 && start == Point::new(start.row, buffer.line_len(start.row));
 
             if is_last_line {
-                start = Point::new(buffer.max_buffer_row(), 0);
+                start = Point::new(start.row + 1, 0);
             }
             for chunk in buffer.text_for_range(start..end) {
                 text.push_str(chunk);

crates/vim/src/visual.rs 🔗

@@ -12,7 +12,7 @@ use language::{Selection, SelectionGoal};
 use workspace::Workspace;
 
 use crate::{
-    motion::Motion,
+    motion::{start_of_line, Motion},
     object::Object,
     state::{Mode, Operator},
     utils::copy_selections_content,
@@ -326,7 +326,10 @@ pub fn yank(_: &mut Workspace, _: &VisualYank, cx: &mut ViewContext<Workspace>)
             let line_mode = editor.selections.line_mode;
             copy_selections_content(editor, line_mode, cx);
             editor.change_selections(None, cx, |s| {
-                s.move_with(|_, selection| {
+                s.move_with(|map, selection| {
+                    if line_mode {
+                        selection.start = start_of_line(map, false, selection.start);
+                    };
                     selection.collapse_to(selection.start, SelectionGoal::None)
                 });
                 if vim.state().mode == Mode::VisualBlock {
@@ -672,6 +675,21 @@ mod test {
                     the lazy dog"})
             .await;
         cx.assert_clipboard_content(Some("The q"));
+
+        cx.set_shared_state(indoc! {"
+                    The quick brown
+                    fox ˇjumps over
+                    the lazy dog"})
+            .await;
+        cx.simulate_shared_keystrokes(["shift-v", "shift-g", "shift-y"])
+            .await;
+        cx.assert_shared_state(indoc! {"
+                    The quick brown
+                    ˇfox jumps over
+                    the lazy dog"})
+            .await;
+        cx.assert_shared_clipboard("fox jumps over\nthe lazy dog\n")
+            .await;
     }
 
     #[gpui::test]

crates/vim/test_data/test_visual_yank.json 🔗

@@ -27,3 +27,9 @@
 {"Key":"k"}
 {"Key":"y"}
 {"Get":{"state":"ˇThe quick brown\nfox jumps over\nthe lazy dog","mode":"Normal"}}
+{"Put":{"state":"The quick brown\nfox ˇjumps over\nthe lazy dog"}}
+{"Key":"shift-v"}
+{"Key":"shift-g"}
+{"Key":"shift-y"}
+{"Get":{"state":"The quick brown\nˇfox jumps over\nthe lazy dog","mode":"Normal"}}
+{"ReadRegister":{"name":"\"","value":"fox jumps over\nthe lazy dog\n"}}