Fix yank around paragraph missing newline (#43583)

Use `MotionKind::LineWise` in both
`vim::normal::change::Vim.change_object` and
`vim::normal::yank::Vim.yank_object` when dealing with objects that
target `Mode::VisualLine`, for example, paragraphs. This fixes an issue
where yanking and changing paragraphs would not include the trailing
newline character.

Closes #28804

Release Notes:

- Fixed linewise text object operations (`yap`, `cap`, etc.) omitting
trailing blank line in vim mode

---------

Co-authored-by: dino <dinojoaocosta@gmail.com>
This commit is contained in:
Ramon
2025-12-03 18:11:53 +01:00
committed by GitHub
parent 85ccd7c98b
commit 575ea49aad
5 changed files with 101 additions and 2 deletions

View File

@@ -121,7 +121,11 @@ impl Vim {
});
});
if objects_found {
vim.copy_selections_content(editor, MotionKind::Exclusive, window, cx);
let kind = match object.target_visual_mode(vim.mode, around) {
Mode::VisualLine => MotionKind::Linewise,
_ => MotionKind::Exclusive,
};
vim.copy_selections_content(editor, kind, window, cx);
editor.insert("", window, cx);
editor.refresh_edit_prediction(true, false, window, cx);
}

View File

@@ -81,7 +81,11 @@ impl Vim {
start_positions.insert(selection.id, start_position);
});
});
vim.yank_selections_content(editor, MotionKind::Exclusive, window, cx);
let kind = match object.target_visual_mode(vim.mode, around) {
Mode::VisualLine => MotionKind::Linewise,
_ => MotionKind::Exclusive,
};
vim.yank_selections_content(editor, kind, window, cx);
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
s.move_with(|_, selection| {
let (head, goal) = start_positions.remove(&selection.id).unwrap();

View File

@@ -2253,6 +2253,79 @@ async fn test_paragraph_multi_delete(cx: &mut gpui::TestAppContext) {
cx.shared_state().await.assert_eq(indoc! {"ˇ"});
}
#[perf]
#[gpui::test]
async fn test_yank_paragraph_with_paste(cx: &mut gpui::TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await;
cx.set_shared_state(indoc! {
"
first paragraph
ˇstill first
second paragraph
still second
third paragraph
"
})
.await;
cx.simulate_shared_keystrokes("y a p").await;
cx.shared_clipboard()
.await
.assert_eq("first paragraph\nstill first\n\n");
cx.simulate_shared_keystrokes("j j p").await;
cx.shared_state().await.assert_eq(indoc! {
"
first paragraph
still first
ˇfirst paragraph
still first
second paragraph
still second
third paragraph
"
});
}
#[perf]
#[gpui::test]
async fn test_change_paragraph(cx: &mut gpui::TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await;
cx.set_shared_state(indoc! {
"
first paragraph
ˇstill first
second paragraph
still second
third paragraph
"
})
.await;
cx.simulate_shared_keystrokes("c a p").await;
cx.shared_clipboard()
.await
.assert_eq("first paragraph\nstill first\n\n");
cx.simulate_shared_keystrokes("escape").await;
cx.shared_state().await.assert_eq(indoc! {
"
ˇ
second paragraph
still second
third paragraph
"
});
}
#[perf]
#[gpui::test]
async fn test_multi_cursor_replay(cx: &mut gpui::TestAppContext) {

View File

@@ -0,0 +1,8 @@
{"Put":{"state":"first paragraph\nˇstill first\n\nsecond paragraph\nstill second\n\nthird paragraph\n"}}
{"Key":"c"}
{"Key":"a"}
{"Key":"p"}
{"Get":{"state":"ˇ\nsecond paragraph\nstill second\n\nthird paragraph\n","mode":"Insert"}}
{"ReadRegister":{"name":"\"","value":"first paragraph\nstill first\n\n"}}
{"Key":"escape"}
{"Get":{"state":"ˇ\nsecond paragraph\nstill second\n\nthird paragraph\n","mode":"Normal"}}

View File

@@ -0,0 +1,10 @@
{"Put":{"state":"first paragraph\nˇstill first\n\nsecond paragraph\nstill second\n\nthird paragraph\n"}}
{"Key":"y"}
{"Key":"a"}
{"Key":"p"}
{"Get":{"state":"ˇfirst paragraph\nstill first\n\nsecond paragraph\nstill second\n\nthird paragraph\n","mode":"Normal"}}
{"ReadRegister":{"name":"\"","value":"first paragraph\nstill first\n\n"}}
{"Key":"j"}
{"Key":"j"}
{"Key":"p"}
{"Get":{"state":"first paragraph\nstill first\n\nˇfirst paragraph\nstill first\n\nsecond paragraph\nstill second\n\nthird paragraph\n","mode":"Normal"}}