mod neovim_backed_test_context; mod neovim_connection; mod vim_test_context; use std::time::Duration; use command_palette::CommandPalette; use editor::{display_map::DisplayRow, DisplayPoint}; use futures::StreamExt; use gpui::{KeyBinding, Modifiers, MouseButton, TestAppContext}; pub use neovim_backed_test_context::*; pub use vim_test_context::*; use indoc::indoc; use search::BufferSearchBar; 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::() .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 bˇ\n ccc\n"); cx.simulate_keystrokes("."); cx.assert_editor_state(" a\nbˇ\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::(cx).is_some())); cx.simulate_keystrokes("escape"); cx.run_until_parked(); assert!(!cx.workspace(|workspace, cx| workspace.active_modal::(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::() .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::(); 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::().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::().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 "}); } #[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"}); } #[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 ").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::(move |_, _, _| async move { Ok(Some(lsp::PrepareRenameResponse::Range(def_range))) }); let mut rename_request = cx.handle_request::(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) } #[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 " }); } #[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 " }); }