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    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}