From 337b9e62d252225a056d369b992f8dd1044a5b3b Mon Sep 17 00:00:00 2001 From: Ben Kunkle Date: Thu, 6 Feb 2025 17:57:24 -0600 Subject: [PATCH] Fix vim full line operations failing when no trailing newline (#24409) Closes #24270 Release Notes: - Fixed an issue where doing line-wise operations in vim mode on the last line of a file with no trailing newline would not work properly --- crates/vim/src/normal.rs | 36 +++++++++++++++++++ crates/vim/src/normal/yank.rs | 15 ++++---- ...d_then_paste_without_trailing_newline.json | 7 ++++ ...st_increment_bin_wrapping_and_padding.json | 4 +-- ...st_increment_hex_wrapping_and_padding.json | 4 +-- .../vim/test_data/test_increment_inline.json | 4 +-- .../test_data/test_increment_sign_change.json | 2 +- .../test_data/test_increment_wrapping.json | 8 ++--- .../test_yank_line_with_trailing_newline.json | 5 +++ ...st_yank_line_without_trailing_newline.json | 5 +++ ...nk_multiline_without_trailing_newline.json | 6 ++++ 11 files changed, 79 insertions(+), 17 deletions(-) create mode 100644 crates/vim/test_data/test_dd_then_paste_without_trailing_newline.json create mode 100644 crates/vim/test_data/test_yank_line_with_trailing_newline.json create mode 100644 crates/vim/test_data/test_yank_line_without_trailing_newline.json create mode 100644 crates/vim/test_data/test_yank_multiline_without_trailing_newline.json diff --git a/crates/vim/src/normal.rs b/crates/vim/src/normal.rs index 7ea8ee899298075947c858b95675cac8e9d4a00a..4d537f8fd7dff14ca0b0050fa107500f31254ad4 100644 --- a/crates/vim/src/normal.rs +++ b/crates/vim/src/normal.rs @@ -1545,4 +1545,40 @@ mod test { cx.simulate_shared_keystrokes("x escape shift-o").await; cx.shared_state().await.assert_eq("// hello\n// ˇ\n// x\n"); } + + #[gpui::test] + async fn test_yank_line_with_trailing_newline(cx: &mut gpui::TestAppContext) { + let mut cx = NeovimBackedTestContext::new(cx).await; + cx.set_shared_state("heˇllo\n").await; + cx.simulate_shared_keystrokes("y y p").await; + cx.shared_state().await.assert_eq("hello\nˇhello\n"); + } + + #[gpui::test] + async fn test_yank_line_without_trailing_newline(cx: &mut gpui::TestAppContext) { + let mut cx = NeovimBackedTestContext::new(cx).await; + cx.set_shared_state("heˇllo").await; + cx.simulate_shared_keystrokes("y y p").await; + cx.shared_state().await.assert_eq("hello\nˇhello"); + } + + #[gpui::test] + async fn test_yank_multiline_without_trailing_newline(cx: &mut gpui::TestAppContext) { + let mut cx = NeovimBackedTestContext::new(cx).await; + cx.set_shared_state("heˇllo\nhello").await; + cx.simulate_shared_keystrokes("2 y y p").await; + cx.shared_state() + .await + .assert_eq("hello\nˇhello\nhello\nhello"); + } + + #[gpui::test] + async fn test_dd_then_paste_without_trailing_newline(cx: &mut gpui::TestAppContext) { + let mut cx = NeovimBackedTestContext::new(cx).await; + cx.set_shared_state("heˇllo").await; + cx.simulate_shared_keystrokes("d d").await; + cx.shared_state().await.assert_eq("ˇ"); + cx.simulate_shared_keystrokes("p p").await; + cx.shared_state().await.assert_eq("\nhello\nˇhello"); + } } diff --git a/crates/vim/src/normal/yank.rs b/crates/vim/src/normal/yank.rs index 0e248037d58e9a83f76358ee5fd3f59b6b24f750..aa521ab8728ed88d823d980d00441f11a700992e 100644 --- a/crates/vim/src/normal/yank.rs +++ b/crates/vim/src/normal/yank.rs @@ -162,13 +162,16 @@ impl Vim { // that line, we will have expanded the start of the selection to ensure it // contains a newline (so that delete works as expected). We undo that change // here. - let is_last_line = linewise - && end.row == buffer.max_row().0 - && buffer.max_point().column > 0 - && start.row < buffer.max_row().0 + let max_point = buffer.max_point(); + let should_adjust_start = linewise + && end.row == max_point.row + && max_point.column > 0 + && start.row < max_point.row && start == Point::new(start.row, buffer.line_len(MultiBufferRow(start.row))); + let should_add_newline = + should_adjust_start || (end == max_point && max_point.column > 0 && linewise); - if is_last_line { + if should_adjust_start { start = Point::new(start.row + 1, 0); } @@ -179,7 +182,7 @@ impl Vim { for chunk in buffer.text_for_range(start..end) { text.push_str(chunk); } - if is_last_line { + if should_add_newline { text.push('\n'); } clipboard_selections.push(ClipboardSelection { diff --git a/crates/vim/test_data/test_dd_then_paste_without_trailing_newline.json b/crates/vim/test_data/test_dd_then_paste_without_trailing_newline.json new file mode 100644 index 0000000000000000000000000000000000000000..5b10a2fe2879b621bca28ce73e7d86e9abe227eb --- /dev/null +++ b/crates/vim/test_data/test_dd_then_paste_without_trailing_newline.json @@ -0,0 +1,7 @@ +{"Put":{"state":"heˇllo"}} +{"Key":"d"} +{"Key":"d"} +{"Get":{"state":"ˇ","mode":"Normal"}} +{"Key":"p"} +{"Key":"p"} +{"Get":{"state":"\nhello\nˇhello","mode":"Normal"}} diff --git a/crates/vim/test_data/test_increment_bin_wrapping_and_padding.json b/crates/vim/test_data/test_increment_bin_wrapping_and_padding.json index 4f1a6aa1d364b7abe19d6b058e6b7053e7aadc2d..69c118c0adc0dc9f2e927de5892d363686728b06 100644 --- a/crates/vim/test_data/test_increment_bin_wrapping_and_padding.json +++ b/crates/vim/test_data/test_increment_bin_wrapping_and_padding.json @@ -1,10 +1,10 @@ {"Put":{"state":"0b111111111111111111111111111111111111111111111111111111111111111111111ˇ1\n"}} {"Key":"ctrl-a"} -{"Get":{"state":"0b000000111111111111111111111111111111111111111111111111111111111111111ˇ1\n", "mode":"Normal"}} +{"Get":{"state":"0b000000111111111111111111111111111111111111111111111111111111111111111ˇ1\n","mode":"Normal"}} {"Key":"ctrl-a"} {"Get":{"state":"0b000000000000000000000000000000000000000000000000000000000000000000000ˇ0\n","mode":"Normal"}} {"Key":"ctrl-a"} {"Get":{"state":"0b000000000000000000000000000000000000000000000000000000000000000000000ˇ1\n","mode":"Normal"}} {"Key":"2"} {"Key":"ctrl-x"} -{"Get":{"state":"0b000000111111111111111111111111111111111111111111111111111111111111111ˇ1\n", "mode":"Normal"}} +{"Get":{"state":"0b000000111111111111111111111111111111111111111111111111111111111111111ˇ1\n","mode":"Normal"}} diff --git a/crates/vim/test_data/test_increment_hex_wrapping_and_padding.json b/crates/vim/test_data/test_increment_hex_wrapping_and_padding.json index 23a561126487c6a20736cedbf156269c50ec411a..562b368812c03354af245b309ce5f39abb34eb82 100644 --- a/crates/vim/test_data/test_increment_hex_wrapping_and_padding.json +++ b/crates/vim/test_data/test_increment_hex_wrapping_and_padding.json @@ -1,10 +1,10 @@ {"Put":{"state":"0xfffffffffffffffffffˇf\n"}} {"Key":"ctrl-a"} -{"Get":{"state":"0x0000fffffffffffffffˇf\n", "mode":"Normal"}} +{"Get":{"state":"0x0000fffffffffffffffˇf\n","mode":"Normal"}} {"Key":"ctrl-a"} {"Get":{"state":"0x0000000000000000000ˇ0\n","mode":"Normal"}} {"Key":"ctrl-a"} {"Get":{"state":"0x0000000000000000000ˇ1\n","mode":"Normal"}} {"Key":"2"} {"Key":"ctrl-x"} -{"Get":{"state":"0x0000fffffffffffffffˇf\n", "mode":"Normal"}} +{"Get":{"state":"0x0000fffffffffffffffˇf\n","mode":"Normal"}} diff --git a/crates/vim/test_data/test_increment_inline.json b/crates/vim/test_data/test_increment_inline.json index 98c4fc280527249f3adc0138fa58791abd2b7390..1e3d8fbd90efd5ee1f9c2c92ff1fafb972e12140 100644 --- a/crates/vim/test_data/test_increment_inline.json +++ b/crates/vim/test_data/test_increment_inline.json @@ -2,9 +2,9 @@ {"Key":"ctrl-a"} {"Get":{"state":"inline0x3ˇau32\n","mode":"Normal"}} {"Key":"ctrl-a"} -{"Get":{"state":"inline0x3ˇbu32\n", "mode":"Normal"}} +{"Get":{"state":"inline0x3ˇbu32\n","mode":"Normal"}} {"Key":"l"} {"Key":"l"} {"Key":"l"} {"Key":"ctrl-a"} -{"Get":{"state":"inline0x3bu3ˇ3\n", "mode":"Normal"}} +{"Get":{"state":"inline0x3bu3ˇ3\n","mode":"Normal"}} diff --git a/crates/vim/test_data/test_increment_sign_change.json b/crates/vim/test_data/test_increment_sign_change.json index 1f4edd57b456af740153636adfba99efc8ab739f..8f2ee7f2f3d513bff143b81d1940a4f5801b3bf0 100644 --- a/crates/vim/test_data/test_increment_sign_change.json +++ b/crates/vim/test_data/test_increment_sign_change.json @@ -3,4 +3,4 @@ {"Get":{"state":"-ˇ1\n","mode":"Normal"}} {"Key":"2"} {"Key":"ctrl-a"} -{"Get":{"state":"ˇ1\n", "mode":"Normal"}} +{"Get":{"state":"ˇ1\n","mode":"Normal"}} diff --git a/crates/vim/test_data/test_increment_wrapping.json b/crates/vim/test_data/test_increment_wrapping.json index 9f84c8cb1145d4cbb0c0de62763f73f4fb55571a..9f189991a6ee67c68a37b2712686ab1108f05a85 100644 --- a/crates/vim/test_data/test_increment_wrapping.json +++ b/crates/vim/test_data/test_increment_wrapping.json @@ -2,12 +2,12 @@ {"Key":"ctrl-a"} {"Get":{"state":"1844674407370955161ˇ5\n","mode":"Normal"}} {"Key":"ctrl-a"} -{"Get":{"state":"-1844674407370955161ˇ5\n", "mode":"Normal"}} +{"Get":{"state":"-1844674407370955161ˇ5\n","mode":"Normal"}} {"Key":"ctrl-a"} -{"Get":{"state":"-1844674407370955161ˇ4\n", "mode":"Normal"}} +{"Get":{"state":"-1844674407370955161ˇ4\n","mode":"Normal"}} {"Key":"3"} {"Key":"ctrl-x"} -{"Get":{"state":"1844674407370955161ˇ4\n", "mode":"Normal"}} +{"Get":{"state":"1844674407370955161ˇ4\n","mode":"Normal"}} {"Key":"2"} {"Key":"ctrl-a"} -{"Get":{"state":"-1844674407370955161ˇ5\n", "mode":"Normal"}} +{"Get":{"state":"-1844674407370955161ˇ5\n","mode":"Normal"}} diff --git a/crates/vim/test_data/test_yank_line_with_trailing_newline.json b/crates/vim/test_data/test_yank_line_with_trailing_newline.json new file mode 100644 index 0000000000000000000000000000000000000000..8b4438737aaa476eb4f3afb817a7494bb464b058 --- /dev/null +++ b/crates/vim/test_data/test_yank_line_with_trailing_newline.json @@ -0,0 +1,5 @@ +{"Put":{"state":"heˇllo\n"}} +{"Key":"y"} +{"Key":"y"} +{"Key":"p"} +{"Get":{"state":"hello\nˇhello\n","mode":"Normal"}} diff --git a/crates/vim/test_data/test_yank_line_without_trailing_newline.json b/crates/vim/test_data/test_yank_line_without_trailing_newline.json new file mode 100644 index 0000000000000000000000000000000000000000..a1158ff2d5b80ff3ee457faa3be9176370475b20 --- /dev/null +++ b/crates/vim/test_data/test_yank_line_without_trailing_newline.json @@ -0,0 +1,5 @@ +{"Put":{"state":"heˇllo"}} +{"Key":"y"} +{"Key":"y"} +{"Key":"p"} +{"Get":{"state":"hello\nˇhello","mode":"Normal"}} diff --git a/crates/vim/test_data/test_yank_multiline_without_trailing_newline.json b/crates/vim/test_data/test_yank_multiline_without_trailing_newline.json new file mode 100644 index 0000000000000000000000000000000000000000..ec38e81f2ed29c2b1f6ac796d06b466a88817a8b --- /dev/null +++ b/crates/vim/test_data/test_yank_multiline_without_trailing_newline.json @@ -0,0 +1,6 @@ +{"Put":{"state":"heˇllo\nhello"}} +{"Key":"2"} +{"Key":"y"} +{"Key":"y"} +{"Key":"p"} +{"Get":{"state":"hello\nˇhello\nhello\nhello","mode":"Normal"}}