mirror of
https://github.com/zed-industries/zed.git
synced 2024-12-29 12:38:02 +00:00
vim: Fix linewise copy of last line with no trailing newline (#2885)
Along the way, delete the VimBindingTestContext by updating the visual tests to no-longer need it. Release Notes: - vim: Fix `y` when on the last line of a file with no trailing newline.
This commit is contained in:
commit
3b6794fe36
7 changed files with 118 additions and 187 deletions
|
@ -1,7 +1,6 @@
|
||||||
mod neovim_backed_binding_test_context;
|
mod neovim_backed_binding_test_context;
|
||||||
mod neovim_backed_test_context;
|
mod neovim_backed_test_context;
|
||||||
mod neovim_connection;
|
mod neovim_connection;
|
||||||
mod vim_binding_test_context;
|
|
||||||
mod vim_test_context;
|
mod vim_test_context;
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
@ -10,7 +9,6 @@ use command_palette::CommandPalette;
|
||||||
use editor::DisplayPoint;
|
use editor::DisplayPoint;
|
||||||
pub use neovim_backed_binding_test_context::*;
|
pub use neovim_backed_binding_test_context::*;
|
||||||
pub use neovim_backed_test_context::*;
|
pub use neovim_backed_test_context::*;
|
||||||
pub use vim_binding_test_context::*;
|
|
||||||
pub use vim_test_context::*;
|
pub use vim_test_context::*;
|
||||||
|
|
||||||
use indoc::indoc;
|
use indoc::indoc;
|
||||||
|
|
|
@ -1,64 +0,0 @@
|
||||||
use std::ops::{Deref, DerefMut};
|
|
||||||
|
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
use super::VimTestContext;
|
|
||||||
|
|
||||||
pub struct VimBindingTestContext<'a, const COUNT: usize> {
|
|
||||||
cx: VimTestContext<'a>,
|
|
||||||
keystrokes_under_test: [&'static str; COUNT],
|
|
||||||
mode_before: Mode,
|
|
||||||
mode_after: Mode,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, const COUNT: usize> VimBindingTestContext<'a, COUNT> {
|
|
||||||
pub fn new(
|
|
||||||
keystrokes_under_test: [&'static str; COUNT],
|
|
||||||
mode_before: Mode,
|
|
||||||
mode_after: Mode,
|
|
||||||
cx: VimTestContext<'a>,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
cx,
|
|
||||||
keystrokes_under_test,
|
|
||||||
mode_before,
|
|
||||||
mode_after,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn binding<const NEW_COUNT: usize>(
|
|
||||||
self,
|
|
||||||
keystrokes_under_test: [&'static str; NEW_COUNT],
|
|
||||||
) -> VimBindingTestContext<'a, NEW_COUNT> {
|
|
||||||
VimBindingTestContext {
|
|
||||||
keystrokes_under_test,
|
|
||||||
cx: self.cx,
|
|
||||||
mode_before: self.mode_before,
|
|
||||||
mode_after: self.mode_after,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn assert(&mut self, initial_state: &str, state_after: &str) {
|
|
||||||
self.cx.assert_binding(
|
|
||||||
self.keystrokes_under_test,
|
|
||||||
initial_state,
|
|
||||||
self.mode_before,
|
|
||||||
state_after,
|
|
||||||
self.mode_after,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, const COUNT: usize> Deref for VimBindingTestContext<'a, COUNT> {
|
|
||||||
type Target = VimTestContext<'a>;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.cx
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, const COUNT: usize> DerefMut for VimBindingTestContext<'a, COUNT> {
|
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
||||||
&mut self.cx
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -8,8 +8,6 @@ use search::{BufferSearchBar, ProjectSearchBar};
|
||||||
|
|
||||||
use crate::{state::Operator, *};
|
use crate::{state::Operator, *};
|
||||||
|
|
||||||
use super::VimBindingTestContext;
|
|
||||||
|
|
||||||
pub struct VimTestContext<'a> {
|
pub struct VimTestContext<'a> {
|
||||||
cx: EditorLspTestContext<'a>,
|
cx: EditorLspTestContext<'a>,
|
||||||
}
|
}
|
||||||
|
@ -126,14 +124,6 @@ impl<'a> VimTestContext<'a> {
|
||||||
assert_eq!(self.mode(), mode_after, "{}", self.assertion_context());
|
assert_eq!(self.mode(), mode_after, "{}", self.assertion_context());
|
||||||
assert_eq!(self.active_operator(), None, "{}", self.assertion_context());
|
assert_eq!(self.active_operator(), None, "{}", self.assertion_context());
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn binding<const COUNT: usize>(
|
|
||||||
mut self,
|
|
||||||
keystrokes: [&'static str; COUNT],
|
|
||||||
) -> VimBindingTestContext<'a, COUNT> {
|
|
||||||
let mode = self.mode();
|
|
||||||
VimBindingTestContext::new(keystrokes, mode, mode, self)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Deref for VimTestContext<'a> {
|
impl<'a> Deref for VimTestContext<'a> {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use editor::{ClipboardSelection, Editor};
|
use editor::{ClipboardSelection, Editor};
|
||||||
use gpui::{AppContext, ClipboardItem};
|
use gpui::{AppContext, ClipboardItem};
|
||||||
|
use language::Point;
|
||||||
|
|
||||||
pub fn copy_selections_content(editor: &mut Editor, linewise: bool, cx: &mut AppContext) {
|
pub fn copy_selections_content(editor: &mut Editor, linewise: bool, cx: &mut AppContext) {
|
||||||
let selections = editor.selections.all_adjusted(cx);
|
let selections = editor.selections.all_adjusted(cx);
|
||||||
|
@ -9,7 +10,7 @@ pub fn copy_selections_content(editor: &mut Editor, linewise: bool, cx: &mut App
|
||||||
{
|
{
|
||||||
let mut is_first = true;
|
let mut is_first = true;
|
||||||
for selection in selections.iter() {
|
for selection in selections.iter() {
|
||||||
let start = selection.start;
|
let mut start = selection.start;
|
||||||
let end = selection.end;
|
let end = selection.end;
|
||||||
if is_first {
|
if is_first {
|
||||||
is_first = false;
|
is_first = false;
|
||||||
|
@ -17,9 +18,25 @@ pub fn copy_selections_content(editor: &mut Editor, linewise: bool, cx: &mut App
|
||||||
text.push_str("\n");
|
text.push_str("\n");
|
||||||
}
|
}
|
||||||
let initial_len = text.len();
|
let initial_len = text.len();
|
||||||
|
|
||||||
|
// if the file does not end with \n, and our line-mode selection ends on
|
||||||
|
// 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_buffer_row()
|
||||||
|
&& buffer.max_point().column > 0
|
||||||
|
&& start == Point::new(start.row, buffer.line_len(start.row));
|
||||||
|
|
||||||
|
if is_last_line {
|
||||||
|
start = Point::new(buffer.max_buffer_row(), 0);
|
||||||
|
}
|
||||||
for chunk in buffer.text_for_range(start..end) {
|
for chunk in buffer.text_for_range(start..end) {
|
||||||
text.push_str(chunk);
|
text.push_str(chunk);
|
||||||
}
|
}
|
||||||
|
if is_last_line {
|
||||||
|
text.push_str("\n");
|
||||||
|
}
|
||||||
clipboard_selections.push(ClipboardSelection {
|
clipboard_selections.push(ClipboardSelection {
|
||||||
len: text.len() - initial_len,
|
len: text.len() - initial_len,
|
||||||
is_entire_line: linewise,
|
is_entire_line: linewise,
|
||||||
|
|
|
@ -563,38 +563,41 @@ mod test {
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_visual_line_delete(cx: &mut gpui::TestAppContext) {
|
async fn test_visual_line_delete(cx: &mut gpui::TestAppContext) {
|
||||||
let mut cx = NeovimBackedTestContext::new(cx)
|
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||||
.await
|
|
||||||
.binding(["shift-v", "x"]);
|
cx.set_shared_state(indoc! {"
|
||||||
cx.assert(indoc! {"
|
|
||||||
The quˇick brown
|
The quˇick brown
|
||||||
fox jumps over
|
fox jumps over
|
||||||
the lazy dog"})
|
the lazy dog"})
|
||||||
.await;
|
.await;
|
||||||
|
cx.simulate_shared_keystrokes(["shift-v", "x"]).await;
|
||||||
|
cx.assert_state_matches().await;
|
||||||
|
|
||||||
// Test pasting code copied on delete
|
// Test pasting code copied on delete
|
||||||
cx.simulate_shared_keystroke("p").await;
|
cx.simulate_shared_keystroke("p").await;
|
||||||
cx.assert_state_matches().await;
|
cx.assert_state_matches().await;
|
||||||
|
|
||||||
cx.assert_all(indoc! {"
|
cx.set_shared_state(indoc! {"
|
||||||
The quick brown
|
The quick brown
|
||||||
fox juˇmps over
|
|
||||||
the laˇzy dog"})
|
|
||||||
.await;
|
|
||||||
let mut cx = cx.binding(["shift-v", "j", "x"]);
|
|
||||||
cx.assert(indoc! {"
|
|
||||||
The quˇick brown
|
|
||||||
fox jumps over
|
fox jumps over
|
||||||
the lazy dog"})
|
|
||||||
.await;
|
|
||||||
// Test pasting code copied on delete
|
|
||||||
cx.simulate_shared_keystroke("p").await;
|
|
||||||
cx.assert_state_matches().await;
|
|
||||||
|
|
||||||
cx.assert_all(indoc! {"
|
|
||||||
The quick brown
|
|
||||||
fox juˇmps over
|
|
||||||
the laˇzy dog"})
|
the laˇzy dog"})
|
||||||
.await;
|
.await;
|
||||||
|
cx.simulate_shared_keystrokes(["shift-v", "x"]).await;
|
||||||
|
cx.assert_state_matches().await;
|
||||||
|
cx.assert_shared_clipboard("the lazy dog\n").await;
|
||||||
|
|
||||||
|
for marked_text in cx.each_marked_position(indoc! {"
|
||||||
|
The quˇick brown
|
||||||
|
fox jumps over
|
||||||
|
the lazy dog"})
|
||||||
|
{
|
||||||
|
cx.set_shared_state(&marked_text).await;
|
||||||
|
cx.simulate_shared_keystrokes(["shift-v", "j", "x"]).await;
|
||||||
|
cx.assert_state_matches().await;
|
||||||
|
// Test pasting code copied on delete
|
||||||
|
cx.simulate_shared_keystroke("p").await;
|
||||||
|
cx.assert_state_matches().await;
|
||||||
|
}
|
||||||
|
|
||||||
cx.set_shared_state(indoc! {"
|
cx.set_shared_state(indoc! {"
|
||||||
The ˇlong line
|
The ˇlong line
|
||||||
|
@ -608,86 +611,57 @@ mod test {
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_visual_yank(cx: &mut gpui::TestAppContext) {
|
async fn test_visual_yank(cx: &mut gpui::TestAppContext) {
|
||||||
let cx = VimTestContext::new(cx, true).await;
|
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||||
let mut cx = cx.binding(["v", "w", "y"]);
|
|
||||||
cx.assert("The quick ˇbrown", "The quick ˇbrown");
|
cx.set_shared_state("The quick ˇbrown").await;
|
||||||
cx.assert_clipboard_content(Some("brown"));
|
cx.simulate_shared_keystrokes(["v", "w", "y"]).await;
|
||||||
let mut cx = cx.binding(["v", "w", "j", "y"]);
|
cx.assert_shared_state("The quick ˇbrown").await;
|
||||||
cx.assert(
|
cx.assert_shared_clipboard("brown").await;
|
||||||
indoc! {"
|
|
||||||
|
cx.set_shared_state(indoc! {"
|
||||||
The ˇquick brown
|
The ˇquick brown
|
||||||
fox jumps over
|
fox jumps over
|
||||||
the lazy dog"},
|
the lazy dog"})
|
||||||
indoc! {"
|
.await;
|
||||||
The ˇquick brown
|
cx.simulate_shared_keystrokes(["v", "w", "j", "y"]).await;
|
||||||
fox jumps over
|
cx.assert_shared_state(indoc! {"
|
||||||
the lazy dog"},
|
The ˇquick brown
|
||||||
);
|
fox jumps over
|
||||||
cx.assert_clipboard_content(Some(indoc! {"
|
the lazy dog"})
|
||||||
quick brown
|
.await;
|
||||||
fox jumps o"}));
|
cx.assert_shared_clipboard(indoc! {"
|
||||||
cx.assert(
|
quick brown
|
||||||
indoc! {"
|
fox jumps o"})
|
||||||
The quick brown
|
.await;
|
||||||
fox jumps over
|
|
||||||
the ˇlazy dog"},
|
cx.set_shared_state(indoc! {"
|
||||||
indoc! {"
|
The quick brown
|
||||||
The quick brown
|
fox jumps over
|
||||||
fox jumps over
|
the ˇlazy dog"})
|
||||||
the ˇlazy dog"},
|
.await;
|
||||||
);
|
cx.simulate_shared_keystrokes(["v", "w", "j", "y"]).await;
|
||||||
cx.assert_clipboard_content(Some("lazy d"));
|
cx.assert_shared_state(indoc! {"
|
||||||
cx.assert(
|
The quick brown
|
||||||
indoc! {"
|
fox jumps over
|
||||||
The quick brown
|
the ˇlazy dog"})
|
||||||
fox jumps ˇover
|
.await;
|
||||||
the lazy dog"},
|
cx.assert_shared_clipboard("lazy d").await;
|
||||||
indoc! {"
|
cx.simulate_shared_keystrokes(["shift-v", "y"]).await;
|
||||||
The quick brown
|
cx.assert_shared_clipboard("the lazy dog\n").await;
|
||||||
fox jumps ˇover
|
|
||||||
the lazy dog"},
|
|
||||||
);
|
|
||||||
cx.assert_clipboard_content(Some(indoc! {"
|
|
||||||
over
|
|
||||||
t"}));
|
|
||||||
let mut cx = cx.binding(["v", "b", "k", "y"]);
|
let mut cx = cx.binding(["v", "b", "k", "y"]);
|
||||||
cx.assert(
|
cx.set_shared_state(indoc! {"
|
||||||
indoc! {"
|
The ˇquick brown
|
||||||
The ˇquick brown
|
fox jumps over
|
||||||
fox jumps over
|
the lazy dog"})
|
||||||
the lazy dog"},
|
.await;
|
||||||
indoc! {"
|
cx.simulate_shared_keystrokes(["v", "b", "k", "y"]).await;
|
||||||
ˇThe quick brown
|
cx.assert_shared_state(indoc! {"
|
||||||
fox jumps over
|
ˇThe quick brown
|
||||||
the lazy dog"},
|
fox jumps over
|
||||||
);
|
the lazy dog"})
|
||||||
|
.await;
|
||||||
cx.assert_clipboard_content(Some("The q"));
|
cx.assert_clipboard_content(Some("The q"));
|
||||||
cx.assert(
|
|
||||||
indoc! {"
|
|
||||||
The quick brown
|
|
||||||
fox jumps over
|
|
||||||
the ˇlazy dog"},
|
|
||||||
indoc! {"
|
|
||||||
The quick brown
|
|
||||||
ˇfox jumps over
|
|
||||||
the lazy dog"},
|
|
||||||
);
|
|
||||||
cx.assert_clipboard_content(Some(indoc! {"
|
|
||||||
fox jumps over
|
|
||||||
the l"}));
|
|
||||||
cx.assert(
|
|
||||||
indoc! {"
|
|
||||||
The quick brown
|
|
||||||
fox jumps ˇover
|
|
||||||
the lazy dog"},
|
|
||||||
indoc! {"
|
|
||||||
The ˇquick brown
|
|
||||||
fox jumps over
|
|
||||||
the lazy dog"},
|
|
||||||
);
|
|
||||||
cx.assert_clipboard_content(Some(indoc! {"
|
|
||||||
quick brown
|
|
||||||
fox jumps o"}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
|
|
|
@ -4,14 +4,11 @@
|
||||||
{"Get":{"state":"fox juˇmps over\nthe lazy dog","mode":"Normal"}}
|
{"Get":{"state":"fox juˇmps over\nthe lazy dog","mode":"Normal"}}
|
||||||
{"Key":"p"}
|
{"Key":"p"}
|
||||||
{"Get":{"state":"fox jumps over\nˇThe quick brown\nthe lazy dog","mode":"Normal"}}
|
{"Get":{"state":"fox jumps over\nˇThe quick brown\nthe lazy dog","mode":"Normal"}}
|
||||||
{"Put":{"state":"The quick brown\nfox juˇmps over\nthe lazy dog"}}
|
|
||||||
{"Key":"shift-v"}
|
|
||||||
{"Key":"x"}
|
|
||||||
{"Get":{"state":"The quick brown\nthe laˇzy dog","mode":"Normal"}}
|
|
||||||
{"Put":{"state":"The quick brown\nfox jumps over\nthe laˇzy dog"}}
|
{"Put":{"state":"The quick brown\nfox jumps over\nthe laˇzy dog"}}
|
||||||
{"Key":"shift-v"}
|
{"Key":"shift-v"}
|
||||||
{"Key":"x"}
|
{"Key":"x"}
|
||||||
{"Get":{"state":"The quick brown\nfox juˇmps over","mode":"Normal"}}
|
{"Get":{"state":"The quick brown\nfox juˇmps over","mode":"Normal"}}
|
||||||
|
{"ReadRegister":{"name":"\"","value":"the lazy dog\n"}}
|
||||||
{"Put":{"state":"The quˇick brown\nfox jumps over\nthe lazy dog"}}
|
{"Put":{"state":"The quˇick brown\nfox jumps over\nthe lazy dog"}}
|
||||||
{"Key":"shift-v"}
|
{"Key":"shift-v"}
|
||||||
{"Key":"j"}
|
{"Key":"j"}
|
||||||
|
@ -19,16 +16,6 @@
|
||||||
{"Get":{"state":"the laˇzy dog","mode":"Normal"}}
|
{"Get":{"state":"the laˇzy dog","mode":"Normal"}}
|
||||||
{"Key":"p"}
|
{"Key":"p"}
|
||||||
{"Get":{"state":"the lazy dog\nˇThe quick brown\nfox jumps over","mode":"Normal"}}
|
{"Get":{"state":"the lazy dog\nˇThe quick brown\nfox jumps over","mode":"Normal"}}
|
||||||
{"Put":{"state":"The quick brown\nfox juˇmps over\nthe lazy dog"}}
|
|
||||||
{"Key":"shift-v"}
|
|
||||||
{"Key":"j"}
|
|
||||||
{"Key":"x"}
|
|
||||||
{"Get":{"state":"The quˇick brown","mode":"Normal"}}
|
|
||||||
{"Put":{"state":"The quick brown\nfox jumps over\nthe laˇzy dog"}}
|
|
||||||
{"Key":"shift-v"}
|
|
||||||
{"Key":"j"}
|
|
||||||
{"Key":"x"}
|
|
||||||
{"Get":{"state":"The quick brown\nfox juˇmps over","mode":"Normal"}}
|
|
||||||
{"Put":{"state":"The ˇlong line\nshould not\ncrash\n"}}
|
{"Put":{"state":"The ˇlong line\nshould not\ncrash\n"}}
|
||||||
{"Key":"shift-v"}
|
{"Key":"shift-v"}
|
||||||
{"Key":"$"}
|
{"Key":"$"}
|
||||||
|
|
29
crates/vim/test_data/test_visual_yank.json
Normal file
29
crates/vim/test_data/test_visual_yank.json
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
{"Put":{"state":"The quick ˇbrown"}}
|
||||||
|
{"Key":"v"}
|
||||||
|
{"Key":"w"}
|
||||||
|
{"Key":"y"}
|
||||||
|
{"Get":{"state":"The quick ˇbrown","mode":"Normal"}}
|
||||||
|
{"ReadRegister":{"name":"\"","value":"brown"}}
|
||||||
|
{"Put":{"state":"The ˇquick brown\nfox jumps over\nthe lazy dog"}}
|
||||||
|
{"Key":"v"}
|
||||||
|
{"Key":"w"}
|
||||||
|
{"Key":"j"}
|
||||||
|
{"Key":"y"}
|
||||||
|
{"Get":{"state":"The ˇquick brown\nfox jumps over\nthe lazy dog","mode":"Normal"}}
|
||||||
|
{"ReadRegister":{"name":"\"","value":"quick brown\nfox jumps o"}}
|
||||||
|
{"Put":{"state":"The quick brown\nfox jumps over\nthe ˇlazy dog"}}
|
||||||
|
{"Key":"v"}
|
||||||
|
{"Key":"w"}
|
||||||
|
{"Key":"j"}
|
||||||
|
{"Key":"y"}
|
||||||
|
{"Get":{"state":"The quick brown\nfox jumps over\nthe ˇlazy dog","mode":"Normal"}}
|
||||||
|
{"ReadRegister":{"name":"\"","value":"lazy d"}}
|
||||||
|
{"Key":"shift-v"}
|
||||||
|
{"Key":"y"}
|
||||||
|
{"ReadRegister":{"name":"\"","value":"the lazy dog\n"}}
|
||||||
|
{"Put":{"state":"The ˇquick brown\nfox jumps over\nthe lazy dog"}}
|
||||||
|
{"Key":"v"}
|
||||||
|
{"Key":"b"}
|
||||||
|
{"Key":"k"}
|
||||||
|
{"Key":"y"}
|
||||||
|
{"Get":{"state":"ˇThe quick brown\nfox jumps over\nthe lazy dog","mode":"Normal"}}
|
Loading…
Reference in a new issue