1mod neovim_backed_binding_test_context;
2mod neovim_backed_test_context;
3mod neovim_connection;
4mod vim_test_context;
5
6use std::sync::Arc;
7
8use command_palette::CommandPalette;
9use editor::DisplayPoint;
10pub use neovim_backed_binding_test_context::*;
11pub use neovim_backed_test_context::*;
12pub use vim_test_context::*;
13
14use indoc::indoc;
15use search::BufferSearchBar;
16
17use crate::{state::Mode, ModeIndicator};
18
19#[gpui::test]
20async fn test_initially_disabled(cx: &mut gpui::TestAppContext) {
21 let mut cx = VimTestContext::new(cx, false).await;
22 cx.simulate_keystrokes(["h", "j", "k", "l"]);
23 cx.assert_editor_state("hjklˇ");
24}
25
26#[gpui::test]
27async fn test_neovim(cx: &mut gpui::TestAppContext) {
28 let mut cx = NeovimBackedTestContext::new(cx).await;
29
30 cx.simulate_shared_keystroke("i").await;
31 cx.assert_state_matches().await;
32 cx.simulate_shared_keystrokes([
33 "shift-T", "e", "s", "t", " ", "t", "e", "s", "t", "escape", "0", "d", "w",
34 ])
35 .await;
36 cx.assert_state_matches().await;
37 cx.assert_editor_state("ˇtest");
38}
39
40#[gpui::test]
41async fn test_toggle_through_settings(cx: &mut gpui::TestAppContext) {
42 let mut cx = VimTestContext::new(cx, true).await;
43
44 cx.simulate_keystroke("i");
45 assert_eq!(cx.mode(), Mode::Insert);
46
47 // Editor acts as though vim is disabled
48 cx.disable_vim();
49 cx.simulate_keystrokes(["h", "j", "k", "l"]);
50 cx.assert_editor_state("hjklˇ");
51
52 // Selections aren't changed if editor is blurred but vim-mode is still disabled.
53 cx.set_state("«hjklˇ»", Mode::Normal);
54 cx.assert_editor_state("«hjklˇ»");
55 cx.update_editor(|_, cx| cx.blur());
56 cx.assert_editor_state("«hjklˇ»");
57 cx.update_editor(|_, cx| cx.focus_self());
58 cx.assert_editor_state("«hjklˇ»");
59
60 // Enabling dynamically sets vim mode again and restores normal mode
61 cx.enable_vim();
62 assert_eq!(cx.mode(), Mode::Normal);
63 cx.simulate_keystrokes(["h", "h", "h", "l"]);
64 assert_eq!(cx.buffer_text(), "hjkl".to_owned());
65 cx.assert_editor_state("hˇjkl");
66 cx.simulate_keystrokes(["i", "T", "e", "s", "t"]);
67 cx.assert_editor_state("hTestˇjkl");
68
69 // Disabling and enabling resets to normal mode
70 assert_eq!(cx.mode(), Mode::Insert);
71 cx.disable_vim();
72 cx.enable_vim();
73 assert_eq!(cx.mode(), Mode::Normal);
74}
75
76#[gpui::test]
77async fn test_buffer_search(cx: &mut gpui::TestAppContext) {
78 let mut cx = VimTestContext::new(cx, true).await;
79
80 cx.set_state(
81 indoc! {"
82 The quick brown
83 fox juˇmps over
84 the lazy dog"},
85 Mode::Normal,
86 );
87 cx.simulate_keystroke("/");
88
89 let search_bar = cx.workspace(|workspace, cx| {
90 workspace
91 .active_pane()
92 .read(cx)
93 .toolbar()
94 .read(cx)
95 .item_of_type::<BufferSearchBar>()
96 .expect("Buffer search bar should be deployed")
97 });
98
99 search_bar.read_with(cx.cx, |bar, cx| {
100 assert_eq!(bar.query(cx), "");
101 })
102}
103
104#[gpui::test]
105async fn test_count_down(cx: &mut gpui::TestAppContext) {
106 let mut cx = VimTestContext::new(cx, true).await;
107
108 cx.set_state(indoc! {"aˇa\nbb\ncc\ndd\nee"}, Mode::Normal);
109 cx.simulate_keystrokes(["2", "down"]);
110 cx.assert_editor_state("aa\nbb\ncˇc\ndd\nee");
111 cx.simulate_keystrokes(["9", "down"]);
112 cx.assert_editor_state("aa\nbb\ncc\ndd\neˇe");
113}
114
115#[gpui::test]
116async fn test_end_of_document_710(cx: &mut gpui::TestAppContext) {
117 let mut cx = VimTestContext::new(cx, true).await;
118
119 // goes to end by default
120 cx.set_state(indoc! {"aˇa\nbb\ncc"}, Mode::Normal);
121 cx.simulate_keystrokes(["shift-g"]);
122 cx.assert_editor_state("aa\nbb\ncˇc");
123
124 // can go to line 1 (https://github.com/zed-industries/community/issues/710)
125 cx.simulate_keystrokes(["1", "shift-g"]);
126 cx.assert_editor_state("aˇa\nbb\ncc");
127}
128
129#[gpui::test]
130async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
131 let mut cx = VimTestContext::new(cx, true).await;
132
133 // works in normal mode
134 cx.set_state(indoc! {"aa\nbˇb\ncc"}, Mode::Normal);
135 cx.simulate_keystrokes([">", ">"]);
136 cx.assert_editor_state("aa\n bˇb\ncc");
137 cx.simulate_keystrokes(["<", "<"]);
138 cx.assert_editor_state("aa\nbˇb\ncc");
139
140 // works in visuial mode
141 cx.simulate_keystrokes(["shift-v", "down", ">"]);
142 cx.assert_editor_state("aa\n b«b\n ccˇ»");
143}
144
145#[gpui::test]
146async fn test_escape_command_palette(cx: &mut gpui::TestAppContext) {
147 let mut cx = VimTestContext::new(cx, true).await;
148
149 cx.set_state("aˇbc\n", Mode::Normal);
150 cx.simulate_keystrokes(["i", "cmd-shift-p"]);
151
152 assert!(cx.workspace(|workspace, _| workspace.modal::<CommandPalette>().is_some()));
153 cx.simulate_keystroke("escape");
154 assert!(!cx.workspace(|workspace, _| workspace.modal::<CommandPalette>().is_some()));
155 cx.assert_state("aˇbc\n", Mode::Insert);
156}
157
158#[gpui::test]
159async fn test_escape_cancels(cx: &mut gpui::TestAppContext) {
160 let mut cx = VimTestContext::new(cx, true).await;
161
162 cx.set_state("aˇbˇc", Mode::Normal);
163 cx.simulate_keystrokes(["escape"]);
164
165 cx.assert_state("aˇbc", Mode::Normal);
166}
167
168#[gpui::test]
169async fn test_selection_on_search(cx: &mut gpui::TestAppContext) {
170 let mut cx = VimTestContext::new(cx, true).await;
171
172 cx.set_state(indoc! {"aa\nbˇb\ncc\ncc\ncc\n"}, Mode::Normal);
173 cx.simulate_keystrokes(["/", "c", "c"]);
174
175 let search_bar = cx.workspace(|workspace, cx| {
176 workspace
177 .active_pane()
178 .read(cx)
179 .toolbar()
180 .read(cx)
181 .item_of_type::<BufferSearchBar>()
182 .expect("Buffer search bar should be deployed")
183 });
184
185 search_bar.read_with(cx.cx, |bar, cx| {
186 assert_eq!(bar.query(cx), "cc");
187 });
188
189 cx.update_editor(|editor, cx| {
190 let highlights = editor.all_text_background_highlights(cx);
191 assert_eq!(3, highlights.len());
192 assert_eq!(
193 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 2),
194 highlights[0].0
195 )
196 });
197 cx.simulate_keystrokes(["enter"]);
198
199 cx.assert_state(indoc! {"aa\nbb\nˇcc\ncc\ncc\n"}, Mode::Normal);
200 cx.simulate_keystrokes(["n"]);
201 cx.assert_state(indoc! {"aa\nbb\ncc\nˇcc\ncc\n"}, Mode::Normal);
202 cx.simulate_keystrokes(["shift-n"]);
203 cx.assert_state(indoc! {"aa\nbb\nˇcc\ncc\ncc\n"}, Mode::Normal);
204}
205
206#[gpui::test]
207async fn test_status_indicator(
208 cx: &mut gpui::TestAppContext,
209 deterministic: Arc<gpui::executor::Deterministic>,
210) {
211 let mut cx = VimTestContext::new(cx, true).await;
212 deterministic.run_until_parked();
213
214 let mode_indicator = cx.workspace(|workspace, cx| {
215 let status_bar = workspace.status_bar().read(cx);
216 let mode_indicator = status_bar.item_of_type::<ModeIndicator>();
217 assert!(mode_indicator.is_some());
218 mode_indicator.unwrap()
219 });
220
221 assert_eq!(
222 cx.workspace(|_, cx| mode_indicator.read(cx).mode),
223 Some(Mode::Normal)
224 );
225
226 // shows the correct mode
227 cx.simulate_keystrokes(["i"]);
228 deterministic.run_until_parked();
229 assert_eq!(
230 cx.workspace(|_, cx| mode_indicator.read(cx).mode),
231 Some(Mode::Insert)
232 );
233
234 // shows even in search
235 cx.simulate_keystrokes(["escape", "v", "/"]);
236 deterministic.run_until_parked();
237 assert_eq!(
238 cx.workspace(|_, cx| mode_indicator.read(cx).mode),
239 Some(Mode::Visual)
240 );
241
242 // hides if vim mode is disabled
243 cx.disable_vim();
244 deterministic.run_until_parked();
245 cx.workspace(|workspace, cx| {
246 let status_bar = workspace.status_bar().read(cx);
247 let mode_indicator = status_bar.item_of_type::<ModeIndicator>().unwrap();
248 assert!(mode_indicator.read(cx).mode.is_none());
249 });
250
251 cx.enable_vim();
252 deterministic.run_until_parked();
253 cx.workspace(|workspace, cx| {
254 let status_bar = workspace.status_bar().read(cx);
255 let mode_indicator = status_bar.item_of_type::<ModeIndicator>().unwrap();
256 assert!(mode_indicator.read(cx).mode.is_some());
257 });
258}
259
260#[gpui::test]
261async fn test_word_characters(cx: &mut gpui::TestAppContext) {
262 let mut cx = VimTestContext::new_typescript(cx).await;
263 cx.set_state(
264 indoc! { "
265 class A {
266 #ˇgoop = 99;
267 $ˇgoop () { return this.#gˇoop };
268 };
269 console.log(new A().$gooˇp())
270 "},
271 Mode::Normal,
272 );
273 cx.simulate_keystrokes(["v", "i", "w"]);
274 cx.assert_state(
275 indoc! {"
276 class A {
277 «#goopˇ» = 99;
278 «$goopˇ» () { return this.«#goopˇ» };
279 };
280 console.log(new A().«$goopˇ»())
281 "},
282 Mode::Visual,
283 )
284}
285
286#[gpui::test]
287async fn test_join_lines(cx: &mut gpui::TestAppContext) {
288 let mut cx = NeovimBackedTestContext::new(cx).await;
289
290 cx.set_shared_state(indoc! {"
291 ˇone
292 two
293 three
294 four
295 five
296 six
297 "})
298 .await;
299 cx.simulate_shared_keystrokes(["shift-j"]).await;
300 cx.assert_shared_state(indoc! {"
301 oneˇ two
302 three
303 four
304 five
305 six
306 "})
307 .await;
308 cx.simulate_shared_keystrokes(["3", "shift-j"]).await;
309 cx.assert_shared_state(indoc! {"
310 one two threeˇ four
311 five
312 six
313 "})
314 .await;
315
316 cx.set_shared_state(indoc! {"
317 ˇone
318 two
319 three
320 four
321 five
322 six
323 "})
324 .await;
325 cx.simulate_shared_keystrokes(["j", "v", "3", "j", "shift-j"])
326 .await;
327 cx.assert_shared_state(indoc! {"
328 one
329 two three fourˇ five
330 six
331 "})
332 .await;
333}
334
335#[gpui::test]
336async fn test_wrapped_lines(cx: &mut gpui::TestAppContext) {
337 let mut cx = NeovimBackedTestContext::new(cx).await;
338
339 cx.set_shared_wrap(12).await;
340 // tests line wrap as follows:
341 // 1: twelve char
342 // twelve char
343 // 2: twelve char
344 cx.set_shared_state(indoc! { "
345 tˇwelve char twelve char
346 twelve char
347 "})
348 .await;
349 cx.simulate_shared_keystrokes(["j"]).await;
350 cx.assert_shared_state(indoc! { "
351 twelve char twelve char
352 tˇwelve char
353 "})
354 .await;
355 cx.simulate_shared_keystrokes(["k"]).await;
356 cx.assert_shared_state(indoc! { "
357 tˇwelve char twelve char
358 twelve char
359 "})
360 .await;
361 cx.simulate_shared_keystrokes(["g", "j"]).await;
362 cx.assert_shared_state(indoc! { "
363 twelve char tˇwelve char
364 twelve char
365 "})
366 .await;
367 cx.simulate_shared_keystrokes(["g", "j"]).await;
368 cx.assert_shared_state(indoc! { "
369 twelve char twelve char
370 tˇwelve char
371 "})
372 .await;
373
374 cx.simulate_shared_keystrokes(["g", "k"]).await;
375 cx.assert_shared_state(indoc! { "
376 twelve char tˇwelve char
377 twelve char
378 "})
379 .await;
380
381 cx.simulate_shared_keystrokes(["g", "^"]).await;
382 cx.assert_shared_state(indoc! { "
383 twelve char ˇtwelve char
384 twelve char
385 "})
386 .await;
387
388 cx.simulate_shared_keystrokes(["^"]).await;
389 cx.assert_shared_state(indoc! { "
390 ˇtwelve char twelve char
391 twelve char
392 "})
393 .await;
394
395 cx.simulate_shared_keystrokes(["g", "$"]).await;
396 cx.assert_shared_state(indoc! { "
397 twelve charˇ twelve char
398 twelve char
399 "})
400 .await;
401 cx.simulate_shared_keystrokes(["$"]).await;
402 cx.assert_shared_state(indoc! { "
403 twelve char twelve chaˇr
404 twelve char
405 "})
406 .await;
407
408 cx.set_shared_state(indoc! { "
409 tˇwelve char twelve char
410 twelve char
411 "})
412 .await;
413 cx.simulate_shared_keystrokes(["enter"]).await;
414 cx.assert_shared_state(indoc! { "
415 twelve char twelve char
416 ˇtwelve char
417 "})
418 .await;
419
420 cx.set_shared_state(indoc! { "
421 twelve char
422 tˇwelve char twelve char
423 twelve char
424 "})
425 .await;
426 cx.simulate_shared_keystrokes(["o", "o", "escape"]).await;
427 cx.assert_shared_state(indoc! { "
428 twelve char
429 twelve char twelve char
430 ˇo
431 twelve char
432 "})
433 .await;
434
435 cx.set_shared_state(indoc! { "
436 twelve char
437 tˇwelve char twelve char
438 twelve char
439 "})
440 .await;
441 cx.simulate_shared_keystrokes(["shift-a", "a", "escape"])
442 .await;
443 cx.assert_shared_state(indoc! { "
444 twelve char
445 twelve char twelve charˇa
446 twelve char
447 "})
448 .await;
449 cx.simulate_shared_keystrokes(["shift-i", "i", "escape"])
450 .await;
451 cx.assert_shared_state(indoc! { "
452 twelve char
453 ˇitwelve char twelve chara
454 twelve char
455 "})
456 .await;
457 cx.simulate_shared_keystrokes(["shift-d"]).await;
458 cx.assert_shared_state(indoc! { "
459 twelve char
460 ˇ
461 twelve char
462 "})
463 .await;
464
465 cx.set_shared_state(indoc! { "
466 twelve char
467 twelve char tˇwelve char
468 twelve char
469 "})
470 .await;
471 cx.simulate_shared_keystrokes(["shift-o", "o", "escape"])
472 .await;
473 cx.assert_shared_state(indoc! { "
474 twelve char
475 ˇo
476 twelve char twelve char
477 twelve char
478 "})
479 .await;
480
481 // line wraps as:
482 // fourteen ch
483 // ar
484 // fourteen ch
485 // ar
486 cx.set_shared_state(indoc! { "
487 fourteen chaˇr
488 fourteen char
489 "})
490 .await;
491
492 cx.simulate_shared_keystrokes(["d", "i", "w"]).await;
493 cx.assert_shared_state(indoc! {"
494 fourteenˇ•
495 fourteen char
496 "})
497 .await;
498 cx.simulate_shared_keystrokes(["j", "shift-f", "e", "f", "r"])
499 .await;
500 cx.assert_shared_state(indoc! {"
501 fourteen•
502 fourteen chaˇr
503 "})
504 .await;
505}
506
507#[gpui::test]
508async fn test_folds(cx: &mut gpui::TestAppContext) {
509 let mut cx = NeovimBackedTestContext::new(cx).await;
510 cx.set_neovim_option("foldmethod=manual").await;
511
512 cx.set_shared_state(indoc! { "
513 fn boop() {
514 ˇbarp()
515 bazp()
516 }
517 "})
518 .await;
519 cx.simulate_shared_keystrokes(["shift-v", "j", "z", "f"])
520 .await;
521
522 // visual display is now:
523 // fn boop () {
524 // [FOLDED]
525 // }
526
527 // TODO: this should not be needed but currently zf does not
528 // return to normal mode.
529 cx.simulate_shared_keystrokes(["escape"]).await;
530
531 // skip over fold downward
532 cx.simulate_shared_keystrokes(["g", "g"]).await;
533 cx.assert_shared_state(indoc! { "
534 ˇfn boop() {
535 barp()
536 bazp()
537 }
538 "})
539 .await;
540
541 cx.simulate_shared_keystrokes(["j", "j"]).await;
542 cx.assert_shared_state(indoc! { "
543 fn boop() {
544 barp()
545 bazp()
546 ˇ}
547 "})
548 .await;
549
550 // skip over fold upward
551 cx.simulate_shared_keystrokes(["2", "k"]).await;
552 cx.assert_shared_state(indoc! { "
553 ˇfn boop() {
554 barp()
555 bazp()
556 }
557 "})
558 .await;
559
560 // yank the fold
561 cx.simulate_shared_keystrokes(["down", "y", "y"]).await;
562 cx.assert_shared_clipboard(" barp()\n bazp()\n").await;
563
564 // re-open
565 cx.simulate_shared_keystrokes(["z", "o"]).await;
566 cx.assert_shared_state(indoc! { "
567 fn boop() {
568 ˇ barp()
569 bazp()
570 }
571 "})
572 .await;
573}
574
575#[gpui::test]
576async fn test_folds_panic(cx: &mut gpui::TestAppContext) {
577 let mut cx = NeovimBackedTestContext::new(cx).await;
578 cx.set_neovim_option("foldmethod=manual").await;
579
580 cx.set_shared_state(indoc! { "
581 fn boop() {
582 ˇbarp()
583 bazp()
584 }
585 "})
586 .await;
587 cx.simulate_shared_keystrokes(["shift-v", "j", "z", "f"])
588 .await;
589 cx.simulate_shared_keystrokes(["escape"]).await;
590 cx.simulate_shared_keystrokes(["g", "g"]).await;
591 cx.simulate_shared_keystrokes(["5", "d", "j"]).await;
592 cx.assert_shared_state(indoc! { "ˇ"}).await;
593}
594
595#[gpui::test]
596async fn test_clear_counts(cx: &mut gpui::TestAppContext) {
597 let mut cx = NeovimBackedTestContext::new(cx).await;
598
599 cx.set_shared_state(indoc! {"
600 The quick brown
601 fox juˇmps over
602 the lazy dog"})
603 .await;
604
605 cx.simulate_shared_keystrokes(["4", "escape", "3", "d", "l"])
606 .await;
607 cx.assert_shared_state(indoc! {"
608 The quick brown
609 fox juˇ over
610 the lazy dog"})
611 .await;
612}
613
614#[gpui::test]
615async fn test_zero(cx: &mut gpui::TestAppContext) {
616 let mut cx = NeovimBackedTestContext::new(cx).await;
617
618 cx.set_shared_state(indoc! {"
619 The quˇick brown
620 fox jumps over
621 the lazy dog"})
622 .await;
623
624 cx.simulate_shared_keystrokes(["0"]).await;
625 cx.assert_shared_state(indoc! {"
626 ˇThe quick brown
627 fox jumps over
628 the lazy dog"})
629 .await;
630
631 cx.simulate_shared_keystrokes(["1", "0", "l"]).await;
632 cx.assert_shared_state(indoc! {"
633 The quick ˇbrown
634 fox jumps over
635 the lazy dog"})
636 .await;
637}
638
639#[gpui::test]
640async fn test_selection_goal(cx: &mut gpui::TestAppContext) {
641 let mut cx = NeovimBackedTestContext::new(cx).await;
642
643 cx.set_shared_state(indoc! {"
644 ;;ˇ;
645 Lorem Ipsum"})
646 .await;
647
648 cx.simulate_shared_keystrokes(["a", "down", "up", ";", "down", "up"])
649 .await;
650 cx.assert_shared_state(indoc! {"
651 ;;;;ˇ
652 Lorem Ipsum"})
653 .await;
654}
655
656#[gpui::test]
657async fn test_wrapped_motions(cx: &mut gpui::TestAppContext) {
658 let mut cx = NeovimBackedTestContext::new(cx).await;
659
660 cx.set_shared_wrap(12).await;
661
662 cx.set_shared_state(indoc! {"
663 aaˇaa
664 😃😃"
665 })
666 .await;
667 cx.simulate_shared_keystrokes(["j"]).await;
668 cx.assert_shared_state(indoc! {"
669 aaaa
670 😃ˇ😃"
671 })
672 .await;
673
674 cx.set_shared_state(indoc! {"
675 123456789012aaˇaa
676 123456789012😃😃"
677 })
678 .await;
679 cx.simulate_shared_keystrokes(["j"]).await;
680 cx.assert_shared_state(indoc! {"
681 123456789012aaaa
682 123456789012😃ˇ😃"
683 })
684 .await;
685
686 cx.set_shared_state(indoc! {"
687 123456789012aaˇaa
688 123456789012😃😃"
689 })
690 .await;
691 cx.simulate_shared_keystrokes(["j"]).await;
692 cx.assert_shared_state(indoc! {"
693 123456789012aaaa
694 123456789012😃ˇ😃"
695 })
696 .await;
697
698 cx.set_shared_state(indoc! {"
699 123456789012aaaaˇaaaaaaaa123456789012
700 wow
701 123456789012😃😃😃😃😃😃123456789012"
702 })
703 .await;
704 cx.simulate_shared_keystrokes(["j", "j"]).await;
705 cx.assert_shared_state(indoc! {"
706 123456789012aaaaaaaaaaaa123456789012
707 wow
708 123456789012😃😃ˇ😃😃😃😃123456789012"
709 })
710 .await;
711}
712
713#[gpui::test]
714async fn test_paragraphs_dont_wrap(cx: &mut gpui::TestAppContext) {
715 let mut cx = NeovimBackedTestContext::new(cx).await;
716
717 cx.set_shared_state(indoc! {"
718 one
719 ˇ
720 two"})
721 .await;
722
723 cx.simulate_shared_keystrokes(["}", "}"]).await;
724 cx.assert_shared_state(indoc! {"
725 one
726
727 twˇo"})
728 .await;
729
730 cx.simulate_shared_keystrokes(["{", "{", "{"]).await;
731 cx.assert_shared_state(indoc! {"
732 ˇone
733
734 two"})
735 .await;
736}
737
738#[gpui::test]
739async fn test_select_all_issue_2170(cx: &mut gpui::TestAppContext) {
740 let mut cx = VimTestContext::new(cx, true).await;
741
742 cx.set_state(
743 indoc! {"
744 defmodule Test do
745 def test(a, ˇ[_, _] = b), do: IO.puts('hi')
746 end
747 "},
748 Mode::Normal,
749 );
750 cx.simulate_keystrokes(["g", "a"]);
751 cx.assert_state(
752 indoc! {"
753 defmodule Test do
754 def test(a, «[ˇ»_, _] = b), do: IO.puts('hi')
755 end
756 "},
757 Mode::Visual,
758 );
759}