test.rs

  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}