vim: Fix visual line yank on newline char (#28005)

5brian created

Problem:
When yanking in visual line on the newline char, the next line gets
yanked as well:


https://github.com/user-attachments/assets/40f332dd-19f5-445f-a30f-39d50167c46f

Changes:
Similar to visual delete, exclude the newline char from the selection in
line mode.

Release Notes:

- vim: Fixed visual line yank while on the newline character yanking
following line

Change summary

crates/vim/src/visual.rs                   | 30 ++++++++++++++++++++++++
crates/vim/test_data/test_visual_yank.json |  6 ++++
2 files changed, 36 insertions(+)

Detailed changes

crates/vim/src/visual.rs 🔗

@@ -598,6 +598,23 @@ impl Vim {
         self.store_visual_marks(window, cx);
         self.update_editor(window, cx, |vim, editor, window, cx| {
             let line_mode = line_mode || editor.selections.line_mode;
+
+            // For visual line mode, adjust selections to avoid yanking the next line when on \n
+            if line_mode && vim.mode != Mode::VisualBlock {
+                editor.change_selections(None, window, cx, |s| {
+                    s.move_with(|map, selection| {
+                        let start = selection.start.to_point(map);
+                        let end = selection.end.to_point(map);
+                        if end.column == 0 && end > start {
+                            let row = end.row.saturating_sub(1);
+                            selection.end =
+                                Point::new(row, map.buffer_snapshot.line_len(MultiBufferRow(row)))
+                                    .to_display_point(map);
+                        }
+                    });
+                });
+            }
+
             editor.selections.line_mode = line_mode;
             let kind = if line_mode {
                 MotionKind::Linewise
@@ -1132,6 +1149,19 @@ mod test {
         cx.shared_clipboard()
             .await
             .assert_eq("fox jumps over\nthe lazy dog\n");
+
+        cx.set_shared_state(indoc! {"
+                    The quick brown
+                    fox ˇjumps over
+                    the lazy dog"})
+            .await;
+        cx.simulate_shared_keystrokes("shift-v shift-4 shift-y")
+            .await;
+        cx.shared_state().await.assert_eq(indoc! {"
+                    The quick brown
+                    ˇfox jumps over
+                    the lazy dog"});
+        cx.shared_clipboard().await.assert_eq("fox jumps over\n");
     }
 
     #[gpui::test]

crates/vim/test_data/test_visual_yank.json 🔗

@@ -34,3 +34,9 @@
 {"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"}}
+{"Put":{"state":"The quick brown\nfox ˇjumps over\nthe lazy dog"}}
+{"Key":"shift-v"}
+{"Key":"shift-4"}
+{"Key":"shift-y"}
+{"Get":{"state":"The quick brown\nˇfox jumps over\nthe lazy dog","mode":"Normal"}}
+{"ReadRegister":{"name":"\"","value":"fox jumps over\n"}}