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 cx.update_view(search_bar, |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, cx| workspace.active_modal::<CommandPalette>(cx).is_some()));
153 cx.simulate_keystroke("escape");
154 cx.run_until_parked();
155 assert!(!cx.workspace(|workspace, cx| workspace.active_modal::<CommandPalette>(cx).is_some()));
156 cx.assert_state("aˇbc\n", Mode::Insert);
157}
158
159#[gpui::test]
160async fn test_escape_cancels(cx: &mut gpui::TestAppContext) {
161 let mut cx = VimTestContext::new(cx, true).await;
162
163 cx.set_state("aˇbˇc", Mode::Normal);
164 cx.simulate_keystrokes(["escape"]);
165
166 cx.assert_state("aˇbc", Mode::Normal);
167}
168
169#[gpui::test]
170async fn test_selection_on_search(cx: &mut gpui::TestAppContext) {
171 let mut cx = VimTestContext::new(cx, true).await;
172
173 cx.set_state(indoc! {"aa\nbˇb\ncc\ncc\ncc\n"}, Mode::Normal);
174 cx.simulate_keystrokes(["/", "c", "c"]);
175
176 let search_bar = cx.workspace(|workspace, cx| {
177 workspace
178 .active_pane()
179 .read(cx)
180 .toolbar()
181 .read(cx)
182 .item_of_type::<BufferSearchBar>()
183 .expect("Buffer search bar should be deployed")
184 });
185
186 cx.update_view(search_bar, |bar, cx| {
187 assert_eq!(bar.query(cx), "cc");
188 });
189
190 cx.update_editor(|editor, cx| {
191 let highlights = editor.all_text_background_highlights(cx);
192 assert_eq!(3, highlights.len());
193 assert_eq!(
194 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 2),
195 highlights[0].0
196 )
197 });
198 cx.simulate_keystrokes(["enter"]);
199
200 cx.assert_state(indoc! {"aa\nbb\nˇcc\ncc\ncc\n"}, Mode::Normal);
201 cx.simulate_keystrokes(["n"]);
202 cx.assert_state(indoc! {"aa\nbb\ncc\nˇcc\ncc\n"}, Mode::Normal);
203 cx.simulate_keystrokes(["shift-n"]);
204 cx.assert_state(indoc! {"aa\nbb\nˇcc\ncc\ncc\n"}, Mode::Normal);
205}
206
207#[gpui::test]
208async fn test_status_indicator(cx: &mut gpui::TestAppContext) {
209 let mut cx = VimTestContext::new(cx, true).await;
210
211 let mode_indicator = cx.workspace(|workspace, cx| {
212 let status_bar = workspace.status_bar().read(cx);
213 let mode_indicator = status_bar.item_of_type::<ModeIndicator>();
214 assert!(mode_indicator.is_some());
215 mode_indicator.unwrap()
216 });
217
218 assert_eq!(
219 cx.workspace(|_, cx| mode_indicator.read(cx).mode),
220 Some(Mode::Normal)
221 );
222
223 // shows the correct mode
224 cx.simulate_keystrokes(["i"]);
225 assert_eq!(
226 cx.workspace(|_, cx| mode_indicator.read(cx).mode),
227 Some(Mode::Insert)
228 );
229
230 // shows even in search
231 cx.simulate_keystrokes(["escape", "v", "/"]);
232 assert_eq!(
233 cx.workspace(|_, cx| mode_indicator.read(cx).mode),
234 Some(Mode::Visual)
235 );
236
237 // hides if vim mode is disabled
238 cx.disable_vim();
239 cx.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 cx.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}
254
255#[gpui::test]
256async fn test_word_characters(cx: &mut gpui::TestAppContext) {
257 let mut cx = VimTestContext::new_typescript(cx).await;
258 cx.set_state(
259 indoc! { "
260 class A {
261 #ˇgoop = 99;
262 $ˇgoop () { return this.#gˇoop };
263 };
264 console.log(new A().$gooˇp())
265 "},
266 Mode::Normal,
267 );
268 cx.simulate_keystrokes(["v", "i", "w"]);
269 cx.assert_state(
270 indoc! {"
271 class A {
272 «#goopˇ» = 99;
273 «$goopˇ» () { return this.«#goopˇ» };
274 };
275 console.log(new A().«$goopˇ»())
276 "},
277 Mode::Visual,
278 )
279}
280
281#[gpui::test]
282async fn test_join_lines(cx: &mut gpui::TestAppContext) {
283 let mut cx = NeovimBackedTestContext::new(cx).await;
284
285 cx.set_shared_state(indoc! {"
286 ˇone
287 two
288 three
289 four
290 five
291 six
292 "})
293 .await;
294 cx.simulate_shared_keystrokes(["shift-j"]).await;
295 cx.assert_shared_state(indoc! {"
296 oneˇ two
297 three
298 four
299 five
300 six
301 "})
302 .await;
303 cx.simulate_shared_keystrokes(["3", "shift-j"]).await;
304 cx.assert_shared_state(indoc! {"
305 one two threeˇ four
306 five
307 six
308 "})
309 .await;
310
311 cx.set_shared_state(indoc! {"
312 ˇone
313 two
314 three
315 four
316 five
317 six
318 "})
319 .await;
320 cx.simulate_shared_keystrokes(["j", "v", "3", "j", "shift-j"])
321 .await;
322 cx.assert_shared_state(indoc! {"
323 one
324 two three fourˇ five
325 six
326 "})
327 .await;
328}
329
330#[gpui::test]
331async fn test_wrapped_lines(cx: &mut gpui::TestAppContext) {
332 let mut cx = NeovimBackedTestContext::new(cx).await;
333
334 cx.set_shared_wrap(12).await;
335 // tests line wrap as follows:
336 // 1: twelve char
337 // twelve char
338 // 2: twelve char
339 cx.set_shared_state(indoc! { "
340 tˇwelve char twelve char
341 twelve char
342 "})
343 .await;
344 cx.simulate_shared_keystrokes(["j"]).await;
345 cx.assert_shared_state(indoc! { "
346 twelve char twelve char
347 tˇwelve char
348 "})
349 .await;
350 cx.simulate_shared_keystrokes(["k"]).await;
351 cx.assert_shared_state(indoc! { "
352 tˇwelve char twelve char
353 twelve char
354 "})
355 .await;
356 cx.simulate_shared_keystrokes(["g", "j"]).await;
357 cx.assert_shared_state(indoc! { "
358 twelve char tˇwelve char
359 twelve char
360 "})
361 .await;
362 cx.simulate_shared_keystrokes(["g", "j"]).await;
363 cx.assert_shared_state(indoc! { "
364 twelve char twelve char
365 tˇwelve char
366 "})
367 .await;
368
369 cx.simulate_shared_keystrokes(["g", "k"]).await;
370 cx.assert_shared_state(indoc! { "
371 twelve char tˇwelve char
372 twelve char
373 "})
374 .await;
375
376 cx.simulate_shared_keystrokes(["g", "^"]).await;
377 cx.assert_shared_state(indoc! { "
378 twelve char ˇtwelve char
379 twelve char
380 "})
381 .await;
382
383 cx.simulate_shared_keystrokes(["^"]).await;
384 cx.assert_shared_state(indoc! { "
385 ˇtwelve char twelve char
386 twelve char
387 "})
388 .await;
389
390 cx.simulate_shared_keystrokes(["g", "$"]).await;
391 cx.assert_shared_state(indoc! { "
392 twelve charˇ twelve char
393 twelve char
394 "})
395 .await;
396 cx.simulate_shared_keystrokes(["$"]).await;
397 cx.assert_shared_state(indoc! { "
398 twelve char twelve chaˇr
399 twelve char
400 "})
401 .await;
402
403 cx.set_shared_state(indoc! { "
404 tˇwelve char twelve char
405 twelve char
406 "})
407 .await;
408 cx.simulate_shared_keystrokes(["enter"]).await;
409 cx.assert_shared_state(indoc! { "
410 twelve char twelve char
411 ˇtwelve char
412 "})
413 .await;
414
415 cx.set_shared_state(indoc! { "
416 twelve char
417 tˇwelve char twelve char
418 twelve char
419 "})
420 .await;
421 cx.simulate_shared_keystrokes(["o", "o", "escape"]).await;
422 cx.assert_shared_state(indoc! { "
423 twelve char
424 twelve char twelve char
425 ˇo
426 twelve char
427 "})
428 .await;
429
430 cx.set_shared_state(indoc! { "
431 twelve char
432 tˇwelve char twelve char
433 twelve char
434 "})
435 .await;
436 cx.simulate_shared_keystrokes(["shift-a", "a", "escape"])
437 .await;
438 cx.assert_shared_state(indoc! { "
439 twelve char
440 twelve char twelve charˇa
441 twelve char
442 "})
443 .await;
444 cx.simulate_shared_keystrokes(["shift-i", "i", "escape"])
445 .await;
446 cx.assert_shared_state(indoc! { "
447 twelve char
448 ˇitwelve char twelve chara
449 twelve char
450 "})
451 .await;
452 cx.simulate_shared_keystrokes(["shift-d"]).await;
453 cx.assert_shared_state(indoc! { "
454 twelve char
455 ˇ
456 twelve char
457 "})
458 .await;
459
460 cx.set_shared_state(indoc! { "
461 twelve char
462 twelve char tˇwelve char
463 twelve char
464 "})
465 .await;
466 cx.simulate_shared_keystrokes(["shift-o", "o", "escape"])
467 .await;
468 cx.assert_shared_state(indoc! { "
469 twelve char
470 ˇo
471 twelve char twelve char
472 twelve char
473 "})
474 .await;
475
476 // line wraps as:
477 // fourteen ch
478 // ar
479 // fourteen ch
480 // ar
481 cx.set_shared_state(indoc! { "
482 fourteen chaˇr
483 fourteen char
484 "})
485 .await;
486
487 cx.simulate_shared_keystrokes(["d", "i", "w"]).await;
488 cx.assert_shared_state(indoc! {"
489 fourteenˇ•
490 fourteen char
491 "})
492 .await;
493 cx.simulate_shared_keystrokes(["j", "shift-f", "e", "f", "r"])
494 .await;
495 cx.assert_shared_state(indoc! {"
496 fourteen•
497 fourteen chaˇr
498 "})
499 .await;
500}
501
502#[gpui::test]
503async fn test_folds(cx: &mut gpui::TestAppContext) {
504 let mut cx = NeovimBackedTestContext::new(cx).await;
505 cx.set_neovim_option("foldmethod=manual").await;
506
507 cx.set_shared_state(indoc! { "
508 fn boop() {
509 ˇbarp()
510 bazp()
511 }
512 "})
513 .await;
514 cx.simulate_shared_keystrokes(["shift-v", "j", "z", "f"])
515 .await;
516
517 // visual display is now:
518 // fn boop () {
519 // [FOLDED]
520 // }
521
522 // TODO: this should not be needed but currently zf does not
523 // return to normal mode.
524 cx.simulate_shared_keystrokes(["escape"]).await;
525
526 // skip over fold downward
527 cx.simulate_shared_keystrokes(["g", "g"]).await;
528 cx.assert_shared_state(indoc! { "
529 ˇfn boop() {
530 barp()
531 bazp()
532 }
533 "})
534 .await;
535
536 cx.simulate_shared_keystrokes(["j", "j"]).await;
537 cx.assert_shared_state(indoc! { "
538 fn boop() {
539 barp()
540 bazp()
541 ˇ}
542 "})
543 .await;
544
545 // skip over fold upward
546 cx.simulate_shared_keystrokes(["2", "k"]).await;
547 cx.assert_shared_state(indoc! { "
548 ˇfn boop() {
549 barp()
550 bazp()
551 }
552 "})
553 .await;
554
555 // yank the fold
556 cx.simulate_shared_keystrokes(["down", "y", "y"]).await;
557 cx.assert_shared_clipboard(" barp()\n bazp()\n").await;
558
559 // re-open
560 cx.simulate_shared_keystrokes(["z", "o"]).await;
561 cx.assert_shared_state(indoc! { "
562 fn boop() {
563 ˇ barp()
564 bazp()
565 }
566 "})
567 .await;
568}
569
570#[gpui::test]
571async fn test_folds_panic(cx: &mut gpui::TestAppContext) {
572 let mut cx = NeovimBackedTestContext::new(cx).await;
573 cx.set_neovim_option("foldmethod=manual").await;
574
575 cx.set_shared_state(indoc! { "
576 fn boop() {
577 ˇbarp()
578 bazp()
579 }
580 "})
581 .await;
582 cx.simulate_shared_keystrokes(["shift-v", "j", "z", "f"])
583 .await;
584 cx.simulate_shared_keystrokes(["escape"]).await;
585 cx.simulate_shared_keystrokes(["g", "g"]).await;
586 cx.simulate_shared_keystrokes(["5", "d", "j"]).await;
587 cx.assert_shared_state(indoc! { "ˇ"}).await;
588}
589
590#[gpui::test]
591async fn test_clear_counts(cx: &mut gpui::TestAppContext) {
592 let mut cx = NeovimBackedTestContext::new(cx).await;
593
594 cx.set_shared_state(indoc! {"
595 The quick brown
596 fox juˇmps over
597 the lazy dog"})
598 .await;
599
600 cx.simulate_shared_keystrokes(["4", "escape", "3", "d", "l"])
601 .await;
602 cx.assert_shared_state(indoc! {"
603 The quick brown
604 fox juˇ over
605 the lazy dog"})
606 .await;
607}
608
609#[gpui::test]
610async fn test_zero(cx: &mut gpui::TestAppContext) {
611 let mut cx = NeovimBackedTestContext::new(cx).await;
612
613 cx.set_shared_state(indoc! {"
614 The quˇick brown
615 fox jumps over
616 the lazy dog"})
617 .await;
618
619 cx.simulate_shared_keystrokes(["0"]).await;
620 cx.assert_shared_state(indoc! {"
621 ˇThe quick brown
622 fox jumps over
623 the lazy dog"})
624 .await;
625
626 cx.simulate_shared_keystrokes(["1", "0", "l"]).await;
627 cx.assert_shared_state(indoc! {"
628 The quick ˇbrown
629 fox jumps over
630 the lazy dog"})
631 .await;
632}
633
634#[gpui::test]
635async fn test_selection_goal(cx: &mut gpui::TestAppContext) {
636 let mut cx = NeovimBackedTestContext::new(cx).await;
637
638 cx.set_shared_state(indoc! {"
639 ;;ˇ;
640 Lorem Ipsum"})
641 .await;
642
643 cx.simulate_shared_keystrokes(["a", "down", "up", ";", "down", "up"])
644 .await;
645 cx.assert_shared_state(indoc! {"
646 ;;;;ˇ
647 Lorem Ipsum"})
648 .await;
649}
650
651#[gpui::test]
652async fn test_wrapped_motions(cx: &mut gpui::TestAppContext) {
653 let mut cx = NeovimBackedTestContext::new(cx).await;
654
655 cx.set_shared_wrap(12).await;
656
657 cx.set_shared_state(indoc! {"
658 aaˇaa
659 😃😃"
660 })
661 .await;
662 cx.simulate_shared_keystrokes(["j"]).await;
663 cx.assert_shared_state(indoc! {"
664 aaaa
665 😃ˇ😃"
666 })
667 .await;
668
669 cx.set_shared_state(indoc! {"
670 123456789012aaˇaa
671 123456789012😃😃"
672 })
673 .await;
674 cx.simulate_shared_keystrokes(["j"]).await;
675 cx.assert_shared_state(indoc! {"
676 123456789012aaaa
677 123456789012😃ˇ😃"
678 })
679 .await;
680
681 cx.set_shared_state(indoc! {"
682 123456789012aaˇaa
683 123456789012😃😃"
684 })
685 .await;
686 cx.simulate_shared_keystrokes(["j"]).await;
687 cx.assert_shared_state(indoc! {"
688 123456789012aaaa
689 123456789012😃ˇ😃"
690 })
691 .await;
692
693 cx.set_shared_state(indoc! {"
694 123456789012aaaaˇaaaaaaaa123456789012
695 wow
696 123456789012😃😃😃😃😃😃123456789012"
697 })
698 .await;
699 cx.simulate_shared_keystrokes(["j", "j"]).await;
700 cx.assert_shared_state(indoc! {"
701 123456789012aaaaaaaaaaaa123456789012
702 wow
703 123456789012😃😃ˇ😃😃😃😃123456789012"
704 })
705 .await;
706}
707
708#[gpui::test]
709async fn test_paragraphs_dont_wrap(cx: &mut gpui::TestAppContext) {
710 let mut cx = NeovimBackedTestContext::new(cx).await;
711
712 cx.set_shared_state(indoc! {"
713 one
714 ˇ
715 two"})
716 .await;
717
718 cx.simulate_shared_keystrokes(["}", "}"]).await;
719 cx.assert_shared_state(indoc! {"
720 one
721
722 twˇo"})
723 .await;
724
725 cx.simulate_shared_keystrokes(["{", "{", "{"]).await;
726 cx.assert_shared_state(indoc! {"
727 ˇone
728
729 two"})
730 .await;
731}
732
733#[gpui::test]
734async fn test_select_all_issue_2170(cx: &mut gpui::TestAppContext) {
735 let mut cx = VimTestContext::new(cx, true).await;
736
737 cx.set_state(
738 indoc! {"
739 defmodule Test do
740 def test(a, ˇ[_, _] = b), do: IO.puts('hi')
741 end
742 "},
743 Mode::Normal,
744 );
745 cx.simulate_keystrokes(["g", "a"]);
746 cx.assert_state(
747 indoc! {"
748 defmodule Test do
749 def test(a, «[ˇ»_, _] = b), do: IO.puts('hi')
750 end
751 "},
752 Mode::Visual,
753 );
754}