diff --git a/crates/vim/src/normal/paste.rs b/crates/vim/src/normal/paste.rs index 68b0acefac..f16f442705 100644 --- a/crates/vim/src/normal/paste.rs +++ b/crates/vim/src/normal/paste.rs @@ -124,7 +124,20 @@ impl Vim { } let display_range = if !selection.is_empty() { - selection.start..selection.end + // If vim is in VISUAL LINE mode and the column for the + // selection's end point is 0, that means that the + // cursor is at the newline character (\n) at the end of + // the line. In this situation we'll want to move one + // position to the left, ensuring we don't join the last + // line of the selection with the line directly below. + let end_point = + if vim.mode == Mode::VisualLine && selection.end.column() == 0 { + movement::left(&display_map, selection.end) + } else { + selection.end + }; + + selection.start..end_point } else if line_mode { let point = if before { movement::line_beginning(&display_map, selection.start, false) @@ -553,6 +566,17 @@ mod test { ˇfox jumps over the lazy dog"}); cx.shared_clipboard().await.assert_eq("The quick brown\n"); + + // Copy line and paste in visual mode, with cursor on newline character. + cx.set_shared_state(indoc! {" + ˇThe quick brown + fox jumps over + the lazy dog"}) + .await; + cx.simulate_shared_keystrokes("y y shift-v j $ p").await; + cx.shared_state().await.assert_eq(indoc! {" + ˇThe quick brown + the lazy dog"}); } #[gpui::test] diff --git a/crates/vim/test_data/test_paste_visual.json b/crates/vim/test_data/test_paste_visual.json index c5597ba0f3..fb10f94782 100644 --- a/crates/vim/test_data/test_paste_visual.json +++ b/crates/vim/test_data/test_paste_visual.json @@ -41,3 +41,11 @@ {"Key":"p"} {"Get":{"state":"ˇfox jumps over\nthe lazy dog","mode":"Normal"}} {"ReadRegister":{"name":"\"","value":"The quick brown\n"}} +{"Put":{"state":"ˇThe quick brown\nfox jumps over\nthe lazy dog"}} +{"Key":"y"} +{"Key":"y"} +{"Key":"shift-v"} +{"Key":"j"} +{"Key":"$"} +{"Key":"p"} +{"Get":{"state":"ˇThe quick brown\nthe lazy dog","mode":"Normal"}}