test.rs

  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}