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_escape_cancels(cx: &mut gpui::TestAppContext) {
162 let mut cx = VimTestContext::new(cx, true).await;
163
164 cx.set_state("aˇbˇc", Mode::Normal);
165 cx.simulate_keystrokes(["escape"]);
166
167 cx.assert_state("aˇbc", Mode::Normal);
168}
169
170#[gpui::test]
171async fn test_selection_on_search(cx: &mut gpui::TestAppContext) {
172 let mut cx = VimTestContext::new(cx, true).await;
173
174 cx.set_state(indoc! {"aa\nbˇb\ncc\ncc\ncc\n"}, Mode::Normal);
175 cx.simulate_keystrokes(["/", "c", "c"]);
176
177 let search_bar = cx.workspace(|workspace, cx| {
178 workspace
179 .active_pane()
180 .read(cx)
181 .toolbar()
182 .read(cx)
183 .item_of_type::<BufferSearchBar>()
184 .expect("Buffer search bar should be deployed")
185 });
186
187 search_bar.read_with(cx.cx, |bar, cx| {
188 assert_eq!(bar.query(cx), "cc");
189 });
190
191 // wait for the query editor change event to fire.
192 search_bar.next_notification(&cx).await;
193
194 cx.update_editor(|editor, cx| {
195 let highlights = editor.all_background_highlights(cx);
196 assert_eq!(3, highlights.len());
197 assert_eq!(
198 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 2),
199 highlights[0].0
200 )
201 });
202 cx.simulate_keystrokes(["enter"]);
203
204 cx.assert_state(indoc! {"aa\nbb\nˇcc\ncc\ncc\n"}, Mode::Normal);
205 cx.simulate_keystrokes(["n"]);
206 cx.assert_state(indoc! {"aa\nbb\ncc\nˇcc\ncc\n"}, Mode::Normal);
207 cx.simulate_keystrokes(["shift-n"]);
208 cx.assert_state(indoc! {"aa\nbb\nˇcc\ncc\ncc\n"}, Mode::Normal);
209}
210
211#[gpui::test]
212async fn test_status_indicator(
213 cx: &mut gpui::TestAppContext,
214 deterministic: Arc<gpui::executor::Deterministic>,
215) {
216 let mut cx = VimTestContext::new(cx, true).await;
217 deterministic.run_until_parked();
218
219 let mode_indicator = cx.workspace(|workspace, cx| {
220 let status_bar = workspace.status_bar().read(cx);
221 let mode_indicator = status_bar.item_of_type::<ModeIndicator>();
222 assert!(mode_indicator.is_some());
223 mode_indicator.unwrap()
224 });
225
226 assert_eq!(
227 cx.workspace(|_, cx| mode_indicator.read(cx).mode),
228 Some(Mode::Normal)
229 );
230
231 // shows the correct mode
232 cx.simulate_keystrokes(["i"]);
233 deterministic.run_until_parked();
234 assert_eq!(
235 cx.workspace(|_, cx| mode_indicator.read(cx).mode),
236 Some(Mode::Insert)
237 );
238
239 // shows even in search
240 cx.simulate_keystrokes(["escape", "v", "/"]);
241 deterministic.run_until_parked();
242 assert_eq!(
243 cx.workspace(|_, cx| mode_indicator.read(cx).mode),
244 Some(Mode::Visual { line: false })
245 );
246
247 // hides if vim mode is disabled
248 cx.disable_vim();
249 deterministic.run_until_parked();
250 cx.workspace(|workspace, cx| {
251 let status_bar = workspace.status_bar().read(cx);
252 let mode_indicator = status_bar.item_of_type::<ModeIndicator>().unwrap();
253 assert!(mode_indicator.read(cx).mode.is_none());
254 });
255
256 cx.enable_vim();
257 deterministic.run_until_parked();
258 cx.workspace(|workspace, cx| {
259 let status_bar = workspace.status_bar().read(cx);
260 let mode_indicator = status_bar.item_of_type::<ModeIndicator>().unwrap();
261 assert!(mode_indicator.read(cx).mode.is_some());
262 });
263}