Fix vim selection to include entire range

Co-Authored-By: Max Brunsfeld <maxbrunsfeld@gmail.com>
This commit is contained in:
Conrad Irwin 2023-07-24 23:59:37 -06:00
parent e6f3e0ab9c
commit b53fb8633e
21 changed files with 489 additions and 335 deletions

View file

@ -61,6 +61,9 @@ pub struct NeovimBackedTestContext<'a> {
// bindings are exempted. If None, all bindings are ignored for that insertion text.
exemptions: HashMap<String, Option<HashSet<String>>>,
neovim: NeovimConnection,
last_set_state: Option<String>,
recent_keystrokes: Vec<String>,
}
impl<'a> NeovimBackedTestContext<'a> {
@ -71,6 +74,9 @@ impl<'a> NeovimBackedTestContext<'a> {
cx,
exemptions: Default::default(),
neovim: NeovimConnection::new(function_name).await,
last_set_state: None,
recent_keystrokes: Default::default(),
}
}
@ -102,13 +108,21 @@ impl<'a> NeovimBackedTestContext<'a> {
keystroke_texts: [&str; COUNT],
) -> ContextHandle {
for keystroke_text in keystroke_texts.into_iter() {
self.recent_keystrokes.push(keystroke_text.to_string());
self.neovim.send_keystroke(keystroke_text).await;
}
self.simulate_keystrokes(keystroke_texts)
}
pub async fn set_shared_state(&mut self, marked_text: &str) -> ContextHandle {
let context_handle = self.set_state(marked_text, Mode::Normal);
let mode = if marked_text.contains("»") {
Mode::Visual { line: false }
} else {
Mode::Normal
};
let context_handle = self.set_state(marked_text, mode);
self.last_set_state = Some(marked_text.to_string());
self.recent_keystrokes = Vec::new();
self.neovim.set_state(marked_text).await;
context_handle
}
@ -116,15 +130,25 @@ impl<'a> NeovimBackedTestContext<'a> {
pub async fn assert_shared_state(&mut self, marked_text: &str) {
let neovim = self.neovim_state().await;
if neovim != marked_text {
let initial_state = self
.last_set_state
.as_ref()
.unwrap_or(&"N/A".to_string())
.clone();
panic!(
indoc! {"Test is incorrect (currently expected != neovim state)
# initial state:
{}
# keystrokes:
{}
# currently expected:
{}
# neovim state:
{}
# zed state:
{}"},
initial_state,
self.recent_keystrokes.join(" "),
marked_text,
neovim,
self.editor_state(),
@ -141,28 +165,40 @@ impl<'a> NeovimBackedTestContext<'a> {
)
}
pub async fn neovim_mode(&mut self) -> Mode {
self.neovim.mode().await.unwrap()
}
async fn neovim_selection(&mut self) -> Range<usize> {
let mut neovim_selection = self.neovim.selection().await;
// Zed selections adjust themselves to make the end point visually make sense
if neovim_selection.start > neovim_selection.end {
neovim_selection.start.column += 1;
}
let neovim_selection = self.neovim.selection().await;
neovim_selection.to_offset(&self.buffer_snapshot())
}
pub async fn assert_state_matches(&mut self) {
assert_eq!(
self.neovim.text().await,
self.buffer_text(),
"{}",
self.assertion_context()
);
let neovim = self.neovim_state().await;
let editor = self.editor_state();
let initial_state = self
.last_set_state
.as_ref()
.unwrap_or(&"N/A".to_string())
.clone();
let selections = vec![self.neovim_selection().await];
self.assert_editor_selections(selections);
if let Some(neovim_mode) = self.neovim.mode().await {
assert_eq!(neovim_mode, self.mode(), "{}", self.assertion_context(),);
if neovim != editor {
panic!(
indoc! {"Test failed (zed does not match nvim behaviour)
# initial state:
{}
# keystrokes:
{}
# neovim state:
{}
# zed state:
{}"},
initial_state,
self.recent_keystrokes.join(" "),
neovim,
editor,
)
}
}
@ -207,6 +243,29 @@ impl<'a> NeovimBackedTestContext<'a> {
}
}
pub fn each_marked_position(&self, marked_positions: &str) -> Vec<String> {
let (unmarked_text, cursor_offsets) = marked_text_offsets(marked_positions);
let mut ret = Vec::with_capacity(cursor_offsets.len());
for cursor_offset in cursor_offsets.iter() {
let mut marked_text = unmarked_text.clone();
marked_text.insert(*cursor_offset, 'ˇ');
ret.push(marked_text)
}
ret
}
pub async fn assert_neovim_compatible<const COUNT: usize>(
&mut self,
marked_positions: &str,
keystrokes: [&str; COUNT],
) {
self.set_shared_state(&marked_positions).await;
self.simulate_shared_keystrokes(keystrokes).await;
self.assert_state_matches().await;
}
pub async fn assert_binding_matches_all_exempted<const COUNT: usize>(
&mut self,
keystrokes: [&str; COUNT],

View file

@ -213,6 +213,16 @@ impl NeovimConnection {
);
}
#[cfg(feature = "neovim")]
async fn read_position(&mut self, cmd: &str) -> u32 {
self.nvim
.command_output(cmd)
.await
.unwrap()
.parse::<u32>()
.unwrap()
}
#[cfg(feature = "neovim")]
pub async fn state(&mut self) -> (Option<Mode>, String, Range<Point>) {
let nvim_buffer = self
@ -226,22 +236,11 @@ impl NeovimConnection {
.expect("Could not get buffer text")
.join("\n");
let cursor_row: u32 = self
.nvim
.command_output("echo line('.')")
.await
.unwrap()
.parse::<u32>()
.unwrap()
- 1; // Neovim rows start at 1
let cursor_col: u32 = self
.nvim
.command_output("echo col('.')")
.await
.unwrap()
.parse::<u32>()
.unwrap()
- 1; // Neovim columns start at 1
// nvim columns are 1-based, so -1.
let cursor_row = self.read_position("echo line('.')").await - 1;
let mut cursor_col = self.read_position("echo col('.')").await - 1;
let selection_row = self.read_position("echo line('v')").await - 1;
let mut selection_col = self.read_position("echo col('v')").await - 1;
let nvim_mode_text = self
.nvim
@ -266,46 +265,32 @@ impl NeovimConnection {
_ => None,
};
let (start, end) = if let Some(Mode::Visual { .. }) = mode {
self.nvim
.input("<escape>")
.await
.expect("Could not exit visual mode");
let nvim_buffer = self
.nvim
.get_current_buf()
.await
.expect("Could not get neovim buffer");
let (start_row, start_col) = nvim_buffer
.get_mark("<")
.await
.expect("Could not get selection start");
let (end_row, end_col) = nvim_buffer
.get_mark(">")
.await
.expect("Could not get selection end");
self.nvim
.input("gv")
.await
.expect("Could not reselect visual selection");
if cursor_row == start_row as u32 - 1 && cursor_col == start_col as u32 {
(
Point::new(end_row as u32 - 1, end_col as u32),
Point::new(start_row as u32 - 1, start_col as u32),
)
} else {
(
Point::new(start_row as u32 - 1, start_col as u32),
Point::new(end_row as u32 - 1, end_col as u32),
)
// Vim uses the index of the first and last character in the selection
// Zed uses the index of the positions between the characters, so we need
// to add one to the end in visual mode.
match mode {
Some(Mode::Visual { .. }) => {
if selection_col > cursor_col {
let selection_line_length =
self.read_position("echo strlen(getline(line('v')))").await;
if selection_line_length > 0 {
selection_col += 1;
}
} else {
let cursor_line_length =
self.read_position("echo strlen(getline(line('.')))").await;
if cursor_line_length > 0 {
cursor_col += 1;
}
}
}
} else {
(
Point::new(cursor_row, cursor_col),
Point::new(cursor_row, cursor_col),
)
};
Some(Mode::Insert) | Some(Mode::Normal) | None => {}
}
let (start, end) = (
Point::new(selection_row, selection_col),
Point::new(cursor_row, cursor_col),
);
let state = NeovimData::Get {
mode,

View file

@ -86,12 +86,13 @@ impl<'a> VimTestContext<'a> {
pub fn set_state(&mut self, text: &str, mode: Mode) -> ContextHandle {
let window_id = self.window_id;
let context_handle = self.cx.set_state(text);
self.update_window(window_id, |cx| {
Vim::update(cx, |vim, cx| {
vim.switch_mode(mode, false, cx);
vim.switch_mode(mode, true, cx);
})
});
self.cx.set_state(text)
context_handle
}
#[track_caller]