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