ZIm/crates/vim/src/test.rs
Conrad Irwin 847bd35bd9
vim remap 2 (#15193)
Release Notes:

- N/A
2024-07-25 09:00:53 -06:00

1440 lines
40 KiB
Rust

mod neovim_backed_test_context;
mod neovim_connection;
mod vim_test_context;
use std::time::Duration;
use collections::HashMap;
use command_palette::CommandPalette;
use editor::{actions::DeleteLine, display_map::DisplayRow, DisplayPoint};
use futures::StreamExt;
use gpui::{KeyBinding, Modifiers, MouseButton, TestAppContext};
pub use neovim_backed_test_context::*;
use settings::SettingsStore;
pub use vim_test_context::*;
use indoc::indoc;
use search::BufferSearchBar;
use workspace::WorkspaceSettings;
use crate::{insert::NormalBefore, motion, state::Mode, ModeIndicator};
#[gpui::test]
async fn test_initially_disabled(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, false).await;
cx.simulate_keystrokes("h j k l");
cx.assert_editor_state("hjklˇ");
}
#[gpui::test]
async fn test_neovim(cx: &mut gpui::TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await;
cx.simulate_shared_keystrokes("i").await;
cx.shared_state().await.assert_matches();
cx.simulate_shared_keystrokes("shift-t e s t space t e s t escape 0 d w")
.await;
cx.shared_state().await.assert_matches();
cx.assert_editor_state("ˇtest");
}
#[gpui::test]
async fn test_toggle_through_settings(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
cx.simulate_keystrokes("i");
assert_eq!(cx.mode(), Mode::Insert);
// Editor acts as though vim is disabled
cx.disable_vim();
cx.simulate_keystrokes("h j k l");
cx.assert_editor_state("hjklˇ");
// Selections aren't changed if editor is blurred but vim-mode is still disabled.
cx.set_state("«hjklˇ»", Mode::Normal);
cx.assert_editor_state("«hjklˇ»");
cx.update_editor(|_, cx| cx.blur());
cx.assert_editor_state("«hjklˇ»");
cx.update_editor(|_, cx| cx.focus_self());
cx.assert_editor_state("«hjklˇ»");
// Enabling dynamically sets vim mode again and restores normal mode
cx.enable_vim();
assert_eq!(cx.mode(), Mode::Normal);
cx.simulate_keystrokes("h h h l");
assert_eq!(cx.buffer_text(), "hjkl".to_owned());
cx.assert_editor_state("hˇjkl");
cx.simulate_keystrokes("i T e s t");
cx.assert_editor_state("hTestˇjkl");
// Disabling and enabling resets to normal mode
assert_eq!(cx.mode(), Mode::Insert);
cx.disable_vim();
cx.enable_vim();
assert_eq!(cx.mode(), Mode::Normal);
}
#[gpui::test]
async fn test_cancel_selection(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
cx.set_state(
indoc! {"The quick brown fox juˇmps over the lazy dog"},
Mode::Normal,
);
// jumps
cx.simulate_keystrokes("v l l");
cx.assert_editor_state("The quick brown fox ju«mpsˇ» over the lazy dog");
cx.simulate_keystrokes("escape");
cx.assert_editor_state("The quick brown fox jumpˇs over the lazy dog");
// go back to the same selection state
cx.simulate_keystrokes("v h h");
cx.assert_editor_state("The quick brown fox ju«ˇmps» over the lazy dog");
// Ctrl-[ should behave like Esc
cx.simulate_keystrokes("ctrl-[");
cx.assert_editor_state("The quick brown fox juˇmps over the lazy dog");
}
#[gpui::test]
async fn test_buffer_search(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
cx.set_state(
indoc! {"
The quick brown
fox juˇmps over
the lazy dog"},
Mode::Normal,
);
cx.simulate_keystrokes("/");
let search_bar = cx.workspace(|workspace, cx| {
workspace
.active_pane()
.read(cx)
.toolbar()
.read(cx)
.item_of_type::<BufferSearchBar>()
.expect("Buffer search bar should be deployed")
});
cx.update_view(search_bar, |bar, cx| {
assert_eq!(bar.query(cx), "");
})
}
#[gpui::test]
async fn test_count_down(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
cx.set_state(indoc! {"aˇa\nbb\ncc\ndd\nee"}, Mode::Normal);
cx.simulate_keystrokes("2 down");
cx.assert_editor_state("aa\nbb\ncˇc\ndd\nee");
cx.simulate_keystrokes("9 down");
cx.assert_editor_state("aa\nbb\ncc\ndd\neˇe");
}
#[gpui::test]
async fn test_end_of_document_710(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
// goes to end by default
cx.set_state(indoc! {"aˇa\nbb\ncc"}, Mode::Normal);
cx.simulate_keystrokes("shift-g");
cx.assert_editor_state("aa\nbb\ncˇc");
// can go to line 1 (https://github.com/zed-industries/zed/issues/5812)
cx.simulate_keystrokes("1 shift-g");
cx.assert_editor_state("aˇa\nbb\ncc");
}
#[gpui::test]
async fn test_end_of_line_with_times(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
// goes to current line end
cx.set_state(indoc! {"ˇaa\nbb\ncc"}, Mode::Normal);
cx.simulate_keystrokes("$");
cx.assert_editor_state("aˇa\nbb\ncc");
// goes to next line end
cx.simulate_keystrokes("2 $");
cx.assert_editor_state("aa\nbˇb\ncc");
// try to exceed the final line.
cx.simulate_keystrokes("4 $");
cx.assert_editor_state("aa\nbb\ncˇc");
}
#[gpui::test]
async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
// works in normal mode
cx.set_state(indoc! {"aa\nbˇb\ncc"}, Mode::Normal);
cx.simulate_keystrokes("> >");
cx.assert_editor_state("aa\n bˇb\ncc");
cx.simulate_keystrokes("< <");
cx.assert_editor_state("aa\nbˇb\ncc");
// works in visual mode
cx.simulate_keystrokes("shift-v down >");
cx.assert_editor_state("aa\n bˇb\n cc");
// works as operator
cx.set_state("aa\nbˇb\ncc\n", Mode::Normal);
cx.simulate_keystrokes("> j");
cx.assert_editor_state("aa\n bˇb\n cc\n");
cx.simulate_keystrokes("< k");
cx.assert_editor_state("aa\nbˇb\n cc\n");
cx.simulate_keystrokes("> i p");
cx.assert_editor_state(" aa\n bˇb\n cc\n");
cx.simulate_keystrokes("< i p");
cx.assert_editor_state("aa\nbˇb\n cc\n");
cx.simulate_keystrokes("< i p");
cx.assert_editor_state("aa\nbˇb\ncc\n");
cx.set_state("ˇaa\nbb\ncc\n", Mode::Normal);
cx.simulate_keystrokes("> 2 j");
cx.assert_editor_state(" ˇaa\n bb\n cc\n");
cx.set_state("aa\nbb\nˇcc\n", Mode::Normal);
cx.simulate_keystrokes("> 2 k");
cx.assert_editor_state(" aa\n bb\n ˇcc\n");
// works with repeat
cx.set_state("a\nb\nccˇc\n", Mode::Normal);
cx.simulate_keystrokes("> 2 k");
cx.assert_editor_state(" a\n b\n ccˇc\n");
cx.simulate_keystrokes(".");
cx.assert_editor_state(" a\n b\n ccˇc\n");
cx.simulate_keystrokes("v k <");
cx.assert_editor_state(" a\n\n ccc\n");
cx.simulate_keystrokes(".");
cx.assert_editor_state(" a\n\nccc\n");
}
#[gpui::test]
async fn test_escape_command_palette(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
cx.set_state("aˇbc\n", Mode::Normal);
cx.simulate_keystrokes("i cmd-shift-p");
assert!(cx.workspace(|workspace, cx| workspace.active_modal::<CommandPalette>(cx).is_some()));
cx.simulate_keystrokes("escape");
cx.run_until_parked();
assert!(!cx.workspace(|workspace, cx| workspace.active_modal::<CommandPalette>(cx).is_some()));
cx.assert_state("aˇbc\n", Mode::Insert);
}
#[gpui::test]
async fn test_escape_cancels(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
cx.set_state("aˇbˇc", Mode::Normal);
cx.simulate_keystrokes("escape");
cx.assert_state("aˇbc", Mode::Normal);
}
#[gpui::test]
async fn test_selection_on_search(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
cx.set_state(indoc! {"aa\nbˇb\ncc\ncc\ncc\n"}, Mode::Normal);
cx.simulate_keystrokes("/ c c");
let search_bar = cx.workspace(|workspace, cx| {
workspace
.active_pane()
.read(cx)
.toolbar()
.read(cx)
.item_of_type::<BufferSearchBar>()
.expect("Buffer search bar should be deployed")
});
cx.update_view(search_bar, |bar, cx| {
assert_eq!(bar.query(cx), "cc");
});
cx.update_editor(|editor, cx| {
let highlights = editor.all_text_background_highlights(cx);
assert_eq!(3, highlights.len());
assert_eq!(
DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 2),
highlights[0].0
)
});
cx.simulate_keystrokes("enter");
cx.assert_state(indoc! {"aa\nbb\nˇcc\ncc\ncc\n"}, Mode::Normal);
cx.simulate_keystrokes("n");
cx.assert_state(indoc! {"aa\nbb\ncc\nˇcc\ncc\n"}, Mode::Normal);
cx.simulate_keystrokes("shift-n");
cx.assert_state(indoc! {"aa\nbb\nˇcc\ncc\ncc\n"}, Mode::Normal);
}
#[gpui::test]
async fn test_status_indicator(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
let mode_indicator = cx.workspace(|workspace, cx| {
let status_bar = workspace.status_bar().read(cx);
let mode_indicator = status_bar.item_of_type::<ModeIndicator>();
assert!(mode_indicator.is_some());
mode_indicator.unwrap()
});
assert_eq!(
cx.workspace(|_, cx| mode_indicator.read(cx).mode),
Some(Mode::Normal)
);
// shows the correct mode
cx.simulate_keystrokes("i");
assert_eq!(
cx.workspace(|_, cx| mode_indicator.read(cx).mode),
Some(Mode::Insert)
);
cx.simulate_keystrokes("escape shift-r");
assert_eq!(
cx.workspace(|_, cx| mode_indicator.read(cx).mode),
Some(Mode::Replace)
);
// shows even in search
cx.simulate_keystrokes("escape v /");
assert_eq!(
cx.workspace(|_, cx| mode_indicator.read(cx).mode),
Some(Mode::Visual)
);
// hides if vim mode is disabled
cx.disable_vim();
cx.run_until_parked();
cx.workspace(|workspace, cx| {
let status_bar = workspace.status_bar().read(cx);
let mode_indicator = status_bar.item_of_type::<ModeIndicator>().unwrap();
assert!(mode_indicator.read(cx).mode.is_none());
});
cx.enable_vim();
cx.run_until_parked();
cx.workspace(|workspace, cx| {
let status_bar = workspace.status_bar().read(cx);
let mode_indicator = status_bar.item_of_type::<ModeIndicator>().unwrap();
assert!(mode_indicator.read(cx).mode.is_some());
});
}
#[gpui::test]
async fn test_word_characters(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new_typescript(cx).await;
cx.set_state(
indoc! { "
class A {
#ˇgoop = 99;
$ˇgoop () { return this.#gˇoop };
};
console.log(new A().$gooˇp())
"},
Mode::Normal,
);
cx.simulate_keystrokes("v i w");
cx.assert_state(
indoc! {"
class A {
«#goopˇ» = 99;
«$goopˇ» () { return this.«#goopˇ» };
};
console.log(new A().«$goopˇ»())
"},
Mode::Visual,
)
}
#[gpui::test]
async fn test_join_lines(cx: &mut gpui::TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await;
cx.set_shared_state(indoc! {"
ˇone
two
three
four
five
six
"})
.await;
cx.simulate_shared_keystrokes("shift-j").await;
cx.shared_state().await.assert_eq(indoc! {"
oneˇ two
three
four
five
six
"});
cx.simulate_shared_keystrokes("3 shift-j").await;
cx.shared_state().await.assert_eq(indoc! {"
one two threeˇ four
five
six
"});
cx.set_shared_state(indoc! {"
ˇone
two
three
four
five
six
"})
.await;
cx.simulate_shared_keystrokes("j v 3 j shift-j").await;
cx.shared_state().await.assert_eq(indoc! {"
one
two three fourˇ five
six
"});
}
#[cfg(target_os = "macos")]
#[gpui::test]
async fn test_wrapped_lines(cx: &mut gpui::TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await;
cx.set_shared_wrap(12).await;
// tests line wrap as follows:
// 1: twelve char
// twelve char
// 2: twelve char
cx.set_shared_state(indoc! { "
tˇwelve char twelve char
twelve char
"})
.await;
cx.simulate_shared_keystrokes("j").await;
cx.shared_state().await.assert_eq(indoc! {"
twelve char twelve char
tˇwelve char
"});
cx.simulate_shared_keystrokes("k").await;
cx.shared_state().await.assert_eq(indoc! {"
tˇwelve char twelve char
twelve char
"});
cx.simulate_shared_keystrokes("g j").await;
cx.shared_state().await.assert_eq(indoc! {"
twelve char tˇwelve char
twelve char
"});
cx.simulate_shared_keystrokes("g j").await;
cx.shared_state().await.assert_eq(indoc! {"
twelve char twelve char
tˇwelve char
"});
cx.simulate_shared_keystrokes("g k").await;
cx.shared_state().await.assert_eq(indoc! {"
twelve char tˇwelve char
twelve char
"});
cx.simulate_shared_keystrokes("g ^").await;
cx.shared_state().await.assert_eq(indoc! {"
twelve char ˇtwelve char
twelve char
"});
cx.simulate_shared_keystrokes("^").await;
cx.shared_state().await.assert_eq(indoc! {"
ˇtwelve char twelve char
twelve char
"});
cx.simulate_shared_keystrokes("g $").await;
cx.shared_state().await.assert_eq(indoc! {"
twelve charˇ twelve char
twelve char
"});
cx.simulate_shared_keystrokes("$").await;
cx.shared_state().await.assert_eq(indoc! {"
twelve char twelve chaˇr
twelve char
"});
cx.set_shared_state(indoc! { "
tˇwelve char twelve char
twelve char
"})
.await;
cx.simulate_shared_keystrokes("enter").await;
cx.shared_state().await.assert_eq(indoc! {"
twelve char twelve char
ˇtwelve char
"});
cx.set_shared_state(indoc! { "
twelve char
tˇwelve char twelve char
twelve char
"})
.await;
cx.simulate_shared_keystrokes("o o escape").await;
cx.shared_state().await.assert_eq(indoc! {"
twelve char
twelve char twelve char
ˇo
twelve char
"});
cx.set_shared_state(indoc! { "
twelve char
tˇwelve char twelve char
twelve char
"})
.await;
cx.simulate_shared_keystrokes("shift-a a escape").await;
cx.shared_state().await.assert_eq(indoc! {"
twelve char
twelve char twelve charˇa
twelve char
"});
cx.simulate_shared_keystrokes("shift-i i escape").await;
cx.shared_state().await.assert_eq(indoc! {"
twelve char
ˇitwelve char twelve chara
twelve char
"});
cx.simulate_shared_keystrokes("shift-d").await;
cx.shared_state().await.assert_eq(indoc! {"
twelve char
ˇ
twelve char
"});
cx.set_shared_state(indoc! { "
twelve char
twelve char tˇwelve char
twelve char
"})
.await;
cx.simulate_shared_keystrokes("shift-o o escape").await;
cx.shared_state().await.assert_eq(indoc! {"
twelve char
ˇo
twelve char twelve char
twelve char
"});
// line wraps as:
// fourteen ch
// ar
// fourteen ch
// ar
cx.set_shared_state(indoc! { "
fourteen chaˇr
fourteen char
"})
.await;
cx.simulate_shared_keystrokes("d i w").await;
cx.shared_state().await.assert_eq(indoc! {"
fourteenˇ•
fourteen char
"});
cx.simulate_shared_keystrokes("j shift-f e f r").await;
cx.shared_state().await.assert_eq(indoc! {"
fourteen•
fourteen chaˇr
"});
}
#[gpui::test]
async fn test_folds(cx: &mut gpui::TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await;
cx.set_neovim_option("foldmethod=manual").await;
cx.set_shared_state(indoc! { "
fn boop() {
ˇbarp()
bazp()
}
"})
.await;
cx.simulate_shared_keystrokes("shift-v j z f").await;
// visual display is now:
// fn boop () {
// [FOLDED]
// }
// TODO: this should not be needed but currently zf does not
// return to normal mode.
cx.simulate_shared_keystrokes("escape").await;
// skip over fold downward
cx.simulate_shared_keystrokes("g g").await;
cx.shared_state().await.assert_eq(indoc! {"
ˇfn boop() {
barp()
bazp()
}
"});
cx.simulate_shared_keystrokes("j j").await;
cx.shared_state().await.assert_eq(indoc! {"
fn boop() {
barp()
bazp()
ˇ}
"});
// skip over fold upward
cx.simulate_shared_keystrokes("2 k").await;
cx.shared_state().await.assert_eq(indoc! {"
ˇfn boop() {
barp()
bazp()
}
"});
// yank the fold
cx.simulate_shared_keystrokes("down y y").await;
cx.shared_clipboard()
.await
.assert_eq(" barp()\n bazp()\n");
// re-open
cx.simulate_shared_keystrokes("z o").await;
cx.shared_state().await.assert_eq(indoc! {"
fn boop() {
ˇ barp()
bazp()
}
"});
}
#[gpui::test]
async fn test_folds_panic(cx: &mut gpui::TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await;
cx.set_neovim_option("foldmethod=manual").await;
cx.set_shared_state(indoc! { "
fn boop() {
ˇbarp()
bazp()
}
"})
.await;
cx.simulate_shared_keystrokes("shift-v j z f").await;
cx.simulate_shared_keystrokes("escape").await;
cx.simulate_shared_keystrokes("g g").await;
cx.simulate_shared_keystrokes("5 d j").await;
cx.shared_state().await.assert_eq("ˇ");
cx.set_shared_state(indoc! {"
fn boop() {
ˇbarp()
bazp()
}
"})
.await;
cx.simulate_shared_keystrokes("shift-v j j z f").await;
cx.simulate_shared_keystrokes("escape").await;
cx.simulate_shared_keystrokes("shift-g shift-v").await;
cx.shared_state().await.assert_eq(indoc! {"
fn boop() {
barp()
bazp()
}
ˇ"});
}
#[gpui::test]
async fn test_clear_counts(cx: &mut gpui::TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await;
cx.set_shared_state(indoc! {"
The quick brown
fox juˇmps over
the lazy dog"})
.await;
cx.simulate_shared_keystrokes("4 escape 3 d l").await;
cx.shared_state().await.assert_eq(indoc! {"
The quick brown
fox juˇ over
the lazy dog"});
}
#[gpui::test]
async fn test_zero(cx: &mut gpui::TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await;
cx.set_shared_state(indoc! {"
The quˇick brown
fox jumps over
the lazy dog"})
.await;
cx.simulate_shared_keystrokes("0").await;
cx.shared_state().await.assert_eq(indoc! {"
ˇThe quick brown
fox jumps over
the lazy dog"});
cx.simulate_shared_keystrokes("1 0 l").await;
cx.shared_state().await.assert_eq(indoc! {"
The quick ˇbrown
fox jumps over
the lazy dog"});
}
#[gpui::test]
async fn test_selection_goal(cx: &mut gpui::TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await;
cx.set_shared_state(indoc! {"
;;ˇ;
Lorem Ipsum"})
.await;
cx.simulate_shared_keystrokes("a down up ; down up").await;
cx.shared_state().await.assert_eq(indoc! {"
;;;;ˇ
Lorem Ipsum"});
}
#[cfg(target_os = "macos")]
#[gpui::test]
async fn test_wrapped_motions(cx: &mut gpui::TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await;
cx.set_shared_wrap(12).await;
cx.set_shared_state(indoc! {"
aaˇaa
😃😃"
})
.await;
cx.simulate_shared_keystrokes("j").await;
cx.shared_state().await.assert_eq(indoc! {"
aaaa
😃ˇ😃"
});
cx.set_shared_state(indoc! {"
123456789012aaˇaa
123456789012😃😃"
})
.await;
cx.simulate_shared_keystrokes("j").await;
cx.shared_state().await.assert_eq(indoc! {"
123456789012aaaa
123456789012😃ˇ😃"
});
cx.set_shared_state(indoc! {"
123456789012aaˇaa
123456789012😃😃"
})
.await;
cx.simulate_shared_keystrokes("j").await;
cx.shared_state().await.assert_eq(indoc! {"
123456789012aaaa
123456789012😃ˇ😃"
});
cx.set_shared_state(indoc! {"
123456789012aaaaˇaaaaaaaa123456789012
wow
123456789012😃😃😃😃😃😃123456789012"
})
.await;
cx.simulate_shared_keystrokes("j j").await;
cx.shared_state().await.assert_eq(indoc! {"
123456789012aaaaaaaaaaaa123456789012
wow
123456789012😃😃ˇ😃😃😃😃123456789012"
});
}
#[gpui::test]
async fn test_paragraphs_dont_wrap(cx: &mut gpui::TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await;
cx.set_shared_state(indoc! {"
one
ˇ
two"})
.await;
cx.simulate_shared_keystrokes("} }").await;
cx.shared_state().await.assert_eq(indoc! {"
one
twˇo"});
cx.simulate_shared_keystrokes("{ { {").await;
cx.shared_state().await.assert_eq(indoc! {"
ˇone
two"});
}
#[gpui::test]
async fn test_select_all_issue_2170(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
cx.set_state(
indoc! {"
defmodule Test do
def test(a, ˇ[_, _] = b), do: IO.puts('hi')
end
"},
Mode::Normal,
);
cx.simulate_keystrokes("g a");
cx.assert_state(
indoc! {"
defmodule Test do
def test(a, «[ˇ»_, _] = b), do: IO.puts('hi')
end
"},
Mode::Visual,
);
}
#[gpui::test]
async fn test_jk(cx: &mut gpui::TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await;
cx.update(|cx| {
cx.bind_keys([KeyBinding::new(
"j k",
NormalBefore,
Some("vim_mode == insert"),
)])
});
cx.neovim.exec("imap jk <esc>").await;
cx.set_shared_state("ˇhello").await;
cx.simulate_shared_keystrokes("i j o j k").await;
cx.shared_state().await.assert_eq("jˇohello");
}
#[gpui::test]
async fn test_jk_delay(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
cx.update(|cx| {
cx.bind_keys([KeyBinding::new(
"j k",
NormalBefore,
Some("vim_mode == insert"),
)])
});
cx.set_state("ˇhello", Mode::Normal);
cx.simulate_keystrokes("i j");
cx.executor().advance_clock(Duration::from_millis(500));
cx.run_until_parked();
cx.assert_state("ˇhello", Mode::Insert);
cx.executor().advance_clock(Duration::from_millis(500));
cx.run_until_parked();
cx.assert_state("jˇhello", Mode::Insert);
cx.simulate_keystrokes("k j k");
cx.assert_state("jˇkhello", Mode::Normal);
}
#[gpui::test]
async fn test_comma_w(cx: &mut gpui::TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await;
cx.update(|cx| {
cx.bind_keys([KeyBinding::new(
", w",
motion::Down {
display_lines: false,
},
Some("vim_mode == normal"),
)])
});
cx.neovim.exec("map ,w j").await;
cx.set_shared_state("ˇhello hello\nhello hello").await;
cx.simulate_shared_keystrokes("f o ; , w").await;
cx.shared_state()
.await
.assert_eq("hello hello\nhello hellˇo");
cx.set_shared_state("ˇhello hello\nhello hello").await;
cx.simulate_shared_keystrokes("f o ; , i").await;
cx.shared_state()
.await
.assert_eq("hellˇo hello\nhello hello");
}
#[gpui::test]
async fn test_rename(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new_typescript(cx).await;
cx.set_state("const beˇfore = 2; console.log(before)", Mode::Normal);
let def_range = cx.lsp_range("const «beforeˇ» = 2; console.log(before)");
let tgt_range = cx.lsp_range("const before = 2; console.log(«beforeˇ»)");
let mut prepare_request =
cx.handle_request::<lsp::request::PrepareRenameRequest, _, _>(move |_, _, _| async move {
Ok(Some(lsp::PrepareRenameResponse::Range(def_range)))
});
let mut rename_request =
cx.handle_request::<lsp::request::Rename, _, _>(move |url, params, _| async move {
Ok(Some(lsp::WorkspaceEdit {
changes: Some(
[(
url.clone(),
vec![
lsp::TextEdit::new(def_range, params.new_name.clone()),
lsp::TextEdit::new(tgt_range, params.new_name),
],
)]
.into(),
),
..Default::default()
}))
});
cx.simulate_keystrokes("c d");
prepare_request.next().await.unwrap();
cx.simulate_input("after");
cx.simulate_keystrokes("enter");
rename_request.next().await.unwrap();
cx.assert_state("const afterˇ = 2; console.log(after)", Mode::Normal)
}
// TODO: this test is flaky on our linux CI machines
#[cfg(target_os = "macos")]
#[gpui::test]
async fn test_remap(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
// test moving the cursor
cx.update(|cx| {
cx.bind_keys([KeyBinding::new(
"g z",
workspace::SendKeystrokes("l l l l".to_string()),
None,
)])
});
cx.set_state("ˇ123456789", Mode::Normal);
cx.simulate_keystrokes("g z");
cx.assert_state("1234ˇ56789", Mode::Normal);
// test switching modes
cx.update(|cx| {
cx.bind_keys([KeyBinding::new(
"g y",
workspace::SendKeystrokes("i f o o escape l".to_string()),
None,
)])
});
cx.set_state("ˇ123456789", Mode::Normal);
cx.simulate_keystrokes("g y");
cx.assert_state("fooˇ123456789", Mode::Normal);
// test recursion
cx.update(|cx| {
cx.bind_keys([KeyBinding::new(
"g x",
workspace::SendKeystrokes("g z g y".to_string()),
None,
)])
});
cx.set_state("ˇ123456789", Mode::Normal);
cx.simulate_keystrokes("g x");
cx.assert_state("1234fooˇ56789", Mode::Normal);
cx.executor().allow_parking();
// test command
cx.update(|cx| {
cx.bind_keys([KeyBinding::new(
"g w",
workspace::SendKeystrokes(": j enter".to_string()),
None,
)])
});
cx.set_state("ˇ1234\n56789", Mode::Normal);
cx.simulate_keystrokes("g w");
cx.assert_state("1234ˇ 56789", Mode::Normal);
// test leaving command
cx.update(|cx| {
cx.bind_keys([KeyBinding::new(
"g u",
workspace::SendKeystrokes("g w g z".to_string()),
None,
)])
});
cx.set_state("ˇ1234\n56789", Mode::Normal);
cx.simulate_keystrokes("g u");
cx.assert_state("1234 567ˇ89", Mode::Normal);
// test leaving command
cx.update(|cx| {
cx.bind_keys([KeyBinding::new(
"g t",
workspace::SendKeystrokes("i space escape".to_string()),
None,
)])
});
cx.set_state("12ˇ34", Mode::Normal);
cx.simulate_keystrokes("g t");
cx.assert_state("12ˇ 34", Mode::Normal);
}
#[gpui::test]
async fn test_undo(cx: &mut gpui::TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await;
cx.set_shared_state("hello quˇoel world").await;
cx.simulate_shared_keystrokes("v i w s c o escape u").await;
cx.shared_state().await.assert_eq("hello ˇquoel world");
cx.simulate_shared_keystrokes("ctrl-r").await;
cx.shared_state().await.assert_eq("hello ˇco world");
cx.simulate_shared_keystrokes("a o right l escape").await;
cx.shared_state().await.assert_eq("hello cooˇl world");
cx.simulate_shared_keystrokes("u").await;
cx.shared_state().await.assert_eq("hello cooˇ world");
cx.simulate_shared_keystrokes("u").await;
cx.shared_state().await.assert_eq("hello cˇo world");
cx.simulate_shared_keystrokes("u").await;
cx.shared_state().await.assert_eq("hello ˇquoel world");
cx.set_shared_state("hello quˇoel world").await;
cx.simulate_shared_keystrokes("v i w ~ u").await;
cx.shared_state().await.assert_eq("hello ˇquoel world");
cx.set_shared_state("\nhello quˇoel world\n").await;
cx.simulate_shared_keystrokes("shift-v s c escape u").await;
cx.shared_state().await.assert_eq("\nˇhello quoel world\n");
cx.set_shared_state(indoc! {"
ˇ1
2
3"})
.await;
cx.simulate_shared_keystrokes("ctrl-v shift-g ctrl-a").await;
cx.shared_state().await.assert_eq(indoc! {"
ˇ2
3
4"});
cx.simulate_shared_keystrokes("u").await;
cx.shared_state().await.assert_eq(indoc! {"
ˇ1
2
3"});
}
#[gpui::test]
async fn test_mouse_selection(cx: &mut TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
cx.set_state("ˇone two three", Mode::Normal);
let start_point = cx.pixel_position("one twˇo three");
let end_point = cx.pixel_position("one ˇtwo three");
cx.simulate_mouse_down(start_point, MouseButton::Left, Modifiers::none());
cx.simulate_mouse_move(end_point, MouseButton::Left, Modifiers::none());
cx.simulate_mouse_up(end_point, MouseButton::Left, Modifiers::none());
cx.assert_state("one «ˇtwo» three", Mode::Visual)
}
#[gpui::test]
async fn test_lowercase_marks(cx: &mut TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await;
cx.set_shared_state("line one\nline ˇtwo\nline three").await;
cx.simulate_shared_keystrokes("m a l ' a").await;
cx.shared_state()
.await
.assert_eq("line one\nˇline two\nline three");
cx.simulate_shared_keystrokes("` a").await;
cx.shared_state()
.await
.assert_eq("line one\nline ˇtwo\nline three");
cx.simulate_shared_keystrokes("^ d ` a").await;
cx.shared_state()
.await
.assert_eq("line one\nˇtwo\nline three");
}
#[gpui::test]
async fn test_lt_gt_marks(cx: &mut TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await;
cx.set_shared_state(indoc!(
"
Line one
Line two
Line ˇthree
Line four
Line five
"
))
.await;
cx.simulate_shared_keystrokes("v j escape k k").await;
cx.simulate_shared_keystrokes("' <").await;
cx.shared_state().await.assert_eq(indoc! {"
Line one
Line two
ˇLine three
Line four
Line five
"});
cx.simulate_shared_keystrokes("` <").await;
cx.shared_state().await.assert_eq(indoc! {"
Line one
Line two
Line ˇthree
Line four
Line five
"});
cx.simulate_shared_keystrokes("' >").await;
cx.shared_state().await.assert_eq(indoc! {"
Line one
Line two
Line three
ˇLine four
Line five
"
});
cx.simulate_shared_keystrokes("` >").await;
cx.shared_state().await.assert_eq(indoc! {"
Line one
Line two
Line three
Line ˇfour
Line five
"
});
cx.simulate_shared_keystrokes("v i w o escape").await;
cx.simulate_shared_keystrokes("` >").await;
cx.shared_state().await.assert_eq(indoc! {"
Line one
Line two
Line three
Line fouˇr
Line five
"
});
cx.simulate_shared_keystrokes("` <").await;
cx.shared_state().await.assert_eq(indoc! {"
Line one
Line two
Line three
Line ˇfour
Line five
"
});
}
#[gpui::test]
async fn test_caret_mark(cx: &mut TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await;
cx.set_shared_state(indoc!(
"
Line one
Line two
Line three
ˇLine four
Line five
"
))
.await;
cx.simulate_shared_keystrokes("c w shift-s t r a i g h t space t h i n g escape j j")
.await;
cx.simulate_shared_keystrokes("' ^").await;
cx.shared_state().await.assert_eq(indoc! {"
Line one
Line two
Line three
ˇStraight thing four
Line five
"
});
cx.simulate_shared_keystrokes("` ^").await;
cx.shared_state().await.assert_eq(indoc! {"
Line one
Line two
Line three
Straight thingˇ four
Line five
"
});
cx.simulate_shared_keystrokes("k a ! escape k g i ?").await;
cx.shared_state().await.assert_eq(indoc! {"
Line one
Line two
Line three!?ˇ
Straight thing four
Line five
"
});
}
#[cfg(target_os = "macos")]
#[gpui::test]
async fn test_dw_eol(cx: &mut gpui::TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await;
cx.set_shared_wrap(12).await;
cx.set_shared_state("twelve ˇchar twelve char\ntwelve char")
.await;
cx.simulate_shared_keystrokes("d w").await;
cx.shared_state()
.await
.assert_eq("twelve ˇtwelve char\ntwelve char");
}
#[gpui::test]
async fn test_toggle_comments(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
let language = std::sync::Arc::new(language::Language::new(
language::LanguageConfig {
line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
..Default::default()
},
Some(language::tree_sitter_rust::language()),
));
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
// works in normal model
cx.set_state(
indoc! {"
ˇone
two
three
"},
Mode::Normal,
);
cx.simulate_keystrokes("g c c");
cx.assert_state(
indoc! {"
// ˇone
two
three
"},
Mode::Normal,
);
// works in visual mode
cx.simulate_keystrokes("v j g c");
cx.assert_state(
indoc! {"
// // ˇone
// two
three
"},
Mode::Normal,
);
// works in visual line mode
cx.simulate_keystrokes("shift-v j g c");
cx.assert_state(
indoc! {"
// ˇone
two
three
"},
Mode::Normal,
);
// works with count
cx.simulate_keystrokes("g c 2 j");
cx.assert_state(
indoc! {"
// // ˇone
// two
// three
"},
Mode::Normal,
);
// works with motion object
cx.simulate_keystrokes("shift-g");
cx.simulate_keystrokes("g c g g");
cx.assert_state(
indoc! {"
// one
two
three
ˇ"},
Mode::Normal,
);
}
#[gpui::test]
async fn test_find_multibyte(cx: &mut gpui::TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await;
cx.set_shared_state(r#"<label for="guests">ˇPočet hostů</label>"#)
.await;
cx.simulate_shared_keystrokes("c t < o escape").await;
cx.shared_state()
.await
.assert_eq(r#"<label for="guests">ˇo</label>"#);
}
#[gpui::test]
async fn test_plus_minus(cx: &mut gpui::TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await;
cx.set_shared_state(indoc! {
"one
two
thrˇee
"})
.await;
cx.simulate_shared_keystrokes("-").await;
cx.shared_state().await.assert_matches();
cx.simulate_shared_keystrokes("-").await;
cx.shared_state().await.assert_matches();
cx.simulate_shared_keystrokes("+").await;
cx.shared_state().await.assert_matches();
}
#[gpui::test]
async fn test_command_alias(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
cx.update_global(|store: &mut SettingsStore, cx| {
store.update_user_settings::<WorkspaceSettings>(cx, |s| {
let mut aliases = HashMap::default();
aliases.insert("Q".to_string(), "upper".to_string());
s.command_aliases = Some(aliases)
});
});
cx.set_state("ˇhello world", Mode::Normal);
cx.simulate_keystrokes(": Q");
cx.set_state("ˇHello world", Mode::Normal);
}
#[gpui::test]
async fn test_remap_adjacent_dog_cat(cx: &mut gpui::TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await;
cx.update(|cx| {
cx.bind_keys([
KeyBinding::new(
"d o g",
workspace::SendKeystrokes("🐶".to_string()),
Some("vim_mode == insert"),
),
KeyBinding::new(
"c a t",
workspace::SendKeystrokes("🐱".to_string()),
Some("vim_mode == insert"),
),
])
});
cx.neovim.exec("imap dog 🐶").await;
cx.neovim.exec("imap cat 🐱").await;
cx.set_shared_state("ˇ").await;
cx.simulate_shared_keystrokes("i d o g").await;
cx.shared_state().await.assert_eq("🐶ˇ");
cx.set_shared_state("ˇ").await;
cx.simulate_shared_keystrokes("i d o d o g").await;
cx.shared_state().await.assert_eq("do🐶ˇ");
cx.set_shared_state("ˇ").await;
cx.simulate_shared_keystrokes("i d o c a t").await;
cx.shared_state().await.assert_eq("do🐱ˇ");
}
#[gpui::test]
async fn test_remap_nested_pineapple(cx: &mut gpui::TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await;
cx.update(|cx| {
cx.bind_keys([
KeyBinding::new(
"p i n",
workspace::SendKeystrokes("📌".to_string()),
Some("vim_mode == insert"),
),
KeyBinding::new(
"p i n e",
workspace::SendKeystrokes("🌲".to_string()),
Some("vim_mode == insert"),
),
KeyBinding::new(
"p i n e a p p l e",
workspace::SendKeystrokes("🍍".to_string()),
Some("vim_mode == insert"),
),
])
});
cx.neovim.exec("imap pin 📌").await;
cx.neovim.exec("imap pine 🌲").await;
cx.neovim.exec("imap pineapple 🍍").await;
cx.set_shared_state("ˇ").await;
cx.simulate_shared_keystrokes("i p i n").await;
cx.executor().advance_clock(Duration::from_millis(1000));
cx.run_until_parked();
cx.shared_state().await.assert_eq("📌ˇ");
cx.set_shared_state("ˇ").await;
cx.simulate_shared_keystrokes("i p i n e").await;
cx.executor().advance_clock(Duration::from_millis(1000));
cx.run_until_parked();
cx.shared_state().await.assert_eq("🌲ˇ");
cx.set_shared_state("ˇ").await;
cx.simulate_shared_keystrokes("i p i n e a p p l e").await;
cx.shared_state().await.assert_eq("🍍ˇ");
}
#[gpui::test]
async fn test_escape_while_waiting(cx: &mut gpui::TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await;
cx.set_shared_state("ˇhi").await;
cx.simulate_shared_keystrokes("\" + escape x").await;
cx.shared_state().await.assert_eq("ˇi");
}
#[gpui::test]
async fn test_ctrl_w_override(cx: &mut gpui::TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await;
cx.update(|cx| {
cx.bind_keys([KeyBinding::new("ctrl-w", DeleteLine, None)]);
});
cx.neovim.exec("map <c-w> D").await;
cx.set_shared_state("ˇhi").await;
cx.simulate_shared_keystrokes("ctrl-w").await;
cx.shared_state().await.assert_eq("ˇ");
}