1mod neovim_backed_binding_test_context;
2mod neovim_backed_test_context;
3mod neovim_connection;
4mod vim_binding_test_context;
5mod vim_test_context;
6
7use std::sync::Arc;
8
9use command_palette::CommandPalette;
10use editor::DisplayPoint;
11pub use neovim_backed_binding_test_context::*;
12pub use neovim_backed_test_context::*;
13pub use vim_binding_test_context::*;
14pub use vim_test_context::*;
15
16use indoc::indoc;
17use search::BufferSearchBar;
18
19use crate::{state::Mode, ModeIndicator};
20
21#[gpui::test]
22async fn test_initially_disabled(cx: &mut gpui::TestAppContext) {
23 let mut cx = VimTestContext::new(cx, false).await;
24 cx.simulate_keystrokes(["h", "j", "k", "l"]);
25 cx.assert_editor_state("hjklˇ");
26}
27
28#[gpui::test]
29async fn test_neovim(cx: &mut gpui::TestAppContext) {
30 let mut cx = NeovimBackedTestContext::new(cx).await;
31
32 cx.simulate_shared_keystroke("i").await;
33 cx.assert_state_matches().await;
34 cx.simulate_shared_keystrokes([
35 "shift-T", "e", "s", "t", " ", "t", "e", "s", "t", "escape", "0", "d", "w",
36 ])
37 .await;
38 cx.assert_state_matches().await;
39 cx.assert_editor_state("ˇtest");
40}
41
42#[gpui::test]
43async fn test_toggle_through_settings(cx: &mut gpui::TestAppContext) {
44 let mut cx = VimTestContext::new(cx, true).await;
45
46 cx.simulate_keystroke("i");
47 assert_eq!(cx.mode(), Mode::Insert);
48
49 // Editor acts as though vim is disabled
50 cx.disable_vim();
51 cx.simulate_keystrokes(["h", "j", "k", "l"]);
52 cx.assert_editor_state("hjklˇ");
53
54 // Selections aren't changed if editor is blurred but vim-mode is still disabled.
55 cx.set_state("«hjklˇ»", Mode::Normal);
56 cx.assert_editor_state("«hjklˇ»");
57 cx.update_editor(|_, cx| cx.blur());
58 cx.assert_editor_state("«hjklˇ»");
59 cx.update_editor(|_, cx| cx.focus_self());
60 cx.assert_editor_state("«hjklˇ»");
61
62 // Enabling dynamically sets vim mode again and restores normal mode
63 cx.enable_vim();
64 assert_eq!(cx.mode(), Mode::Normal);
65 cx.simulate_keystrokes(["h", "h", "h", "l"]);
66 assert_eq!(cx.buffer_text(), "hjkl".to_owned());
67 cx.assert_editor_state("hˇjkl");
68 cx.simulate_keystrokes(["i", "T", "e", "s", "t"]);
69 cx.assert_editor_state("hTestˇjkl");
70
71 // Disabling and enabling resets to normal mode
72 assert_eq!(cx.mode(), Mode::Insert);
73 cx.disable_vim();
74 cx.enable_vim();
75 assert_eq!(cx.mode(), Mode::Normal);
76}
77
78#[gpui::test]
79async fn test_buffer_search(cx: &mut gpui::TestAppContext) {
80 let mut cx = VimTestContext::new(cx, true).await;
81
82 cx.set_state(
83 indoc! {"
84 The quick brown
85 fox juˇmps over
86 the lazy dog"},
87 Mode::Normal,
88 );
89 cx.simulate_keystroke("/");
90
91 let search_bar = cx.workspace(|workspace, cx| {
92 workspace
93 .active_pane()
94 .read(cx)
95 .toolbar()
96 .read(cx)
97 .item_of_type::<BufferSearchBar>()
98 .expect("Buffer search bar should be deployed")
99 });
100
101 search_bar.read_with(cx.cx, |bar, cx| {
102 assert_eq!(bar.query(cx), "");
103 })
104}
105
106#[gpui::test]
107async fn test_count_down(cx: &mut gpui::TestAppContext) {
108 let mut cx = VimTestContext::new(cx, true).await;
109
110 cx.set_state(indoc! {"aˇa\nbb\ncc\ndd\nee"}, Mode::Normal);
111 cx.simulate_keystrokes(["2", "down"]);
112 cx.assert_editor_state("aa\nbb\ncˇc\ndd\nee");
113 cx.simulate_keystrokes(["9", "down"]);
114 cx.assert_editor_state("aa\nbb\ncc\ndd\neˇe");
115}
116
117#[gpui::test]
118async fn test_end_of_document_710(cx: &mut gpui::TestAppContext) {
119 let mut cx = VimTestContext::new(cx, true).await;
120
121 // goes to end by default
122 cx.set_state(indoc! {"aˇa\nbb\ncc"}, Mode::Normal);
123 cx.simulate_keystrokes(["shift-g"]);
124 cx.assert_editor_state("aa\nbb\ncˇc");
125
126 // can go to line 1 (https://github.com/zed-industries/community/issues/710)
127 cx.simulate_keystrokes(["1", "shift-g"]);
128 cx.assert_editor_state("aˇa\nbb\ncc");
129}
130
131#[gpui::test]
132async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
133 let mut cx = VimTestContext::new(cx, true).await;
134
135 // works in normal mode
136 cx.set_state(indoc! {"aa\nbˇb\ncc"}, Mode::Normal);
137 cx.simulate_keystrokes([">", ">"]);
138 cx.assert_editor_state("aa\n bˇb\ncc");
139 cx.simulate_keystrokes(["<", "<"]);
140 cx.assert_editor_state("aa\nbˇb\ncc");
141
142 // works in visuial mode
143 cx.simulate_keystrokes(["shift-v", "down", ">"]);
144 cx.assert_editor_state("aa\n b«b\n ccˇ»");
145}
146
147#[gpui::test]
148async fn test_escape_command_palette(cx: &mut gpui::TestAppContext) {
149 let mut cx = VimTestContext::new(cx, true).await;
150
151 cx.set_state("aˇbc\n", Mode::Normal);
152 cx.simulate_keystrokes(["i", "cmd-shift-p"]);
153
154 assert!(cx.workspace(|workspace, _| workspace.modal::<CommandPalette>().is_some()));
155 cx.simulate_keystroke("escape");
156 assert!(!cx.workspace(|workspace, _| workspace.modal::<CommandPalette>().is_some()));
157 cx.assert_state("aˇbc\n", Mode::Insert);
158}
159
160#[gpui::test]
161async fn test_selection_on_search(cx: &mut gpui::TestAppContext) {
162 let mut cx = VimTestContext::new(cx, true).await;
163
164 cx.set_state(indoc! {"aa\nbˇb\ncc\ncc\ncc\n"}, Mode::Normal);
165 cx.simulate_keystrokes(["/", "c", "c"]);
166
167 let search_bar = cx.workspace(|workspace, cx| {
168 workspace
169 .active_pane()
170 .read(cx)
171 .toolbar()
172 .read(cx)
173 .item_of_type::<BufferSearchBar>()
174 .expect("Buffer search bar should be deployed")
175 });
176
177 search_bar.read_with(cx.cx, |bar, cx| {
178 assert_eq!(bar.query(cx), "cc");
179 });
180
181 // wait for the query editor change event to fire.
182 search_bar.next_notification(&cx).await;
183
184 cx.update_editor(|editor, cx| {
185 let highlights = editor.all_background_highlights(cx);
186 assert_eq!(3, highlights.len());
187 assert_eq!(
188 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 2),
189 highlights[0].0
190 )
191 });
192 cx.simulate_keystrokes(["enter"]);
193
194 cx.assert_state(indoc! {"aa\nbb\nˇcc\ncc\ncc\n"}, Mode::Normal);
195 cx.simulate_keystrokes(["n"]);
196 cx.assert_state(indoc! {"aa\nbb\ncc\nˇcc\ncc\n"}, Mode::Normal);
197 cx.simulate_keystrokes(["shift-n"]);
198 cx.assert_state(indoc! {"aa\nbb\nˇcc\ncc\ncc\n"}, Mode::Normal);
199}
200
201#[gpui::test]
202async fn test_status_indicator(
203 cx: &mut gpui::TestAppContext,
204 deterministic: Arc<gpui::executor::Deterministic>,
205) {
206 let mut cx = VimTestContext::new(cx, true).await;
207 deterministic.run_until_parked();
208
209 let mode_indicator = cx.workspace(|workspace, cx| {
210 let status_bar = workspace.status_bar().read(cx);
211 let mode_indicator = status_bar.item_of_type::<ModeIndicator>();
212 assert!(mode_indicator.is_some());
213 mode_indicator.unwrap()
214 });
215
216 assert_eq!(
217 cx.workspace(|_, cx| mode_indicator.read(cx).mode),
218 Some(Mode::Normal)
219 );
220
221 // shows the correct mode
222 cx.simulate_keystrokes(["i"]);
223 deterministic.run_until_parked();
224 assert_eq!(
225 cx.workspace(|_, cx| mode_indicator.read(cx).mode),
226 Some(Mode::Insert)
227 );
228
229 // shows even in search
230 cx.simulate_keystrokes(["escape", "v", "/"]);
231 deterministic.run_until_parked();
232 assert_eq!(
233 cx.workspace(|_, cx| mode_indicator.read(cx).mode),
234 Some(Mode::Visual { line: false })
235 );
236
237 // hides if vim mode is disabled
238 cx.disable_vim();
239 deterministic.run_until_parked();
240 cx.workspace(|workspace, cx| {
241 let status_bar = workspace.status_bar().read(cx);
242 let mode_indicator = status_bar.item_of_type::<ModeIndicator>().unwrap();
243 assert!(mode_indicator.read(cx).mode.is_none());
244 });
245
246 cx.enable_vim();
247 deterministic.run_until_parked();
248 cx.workspace(|workspace, cx| {
249 let status_bar = workspace.status_bar().read(cx);
250 let mode_indicator = status_bar.item_of_type::<ModeIndicator>().unwrap();
251 assert!(mode_indicator.read(cx).mode.is_some());
252 });
253}