test.rs

  1mod neovim_backed_binding_test_context;
  2mod neovim_backed_test_context;
  3mod neovim_connection;
  4mod vim_test_context;
  5
  6use std::time::Duration;
  7
  8use command_palette::CommandPalette;
  9use editor::DisplayPoint;
 10use futures::StreamExt;
 11use gpui::KeyBinding;
 12pub use neovim_backed_binding_test_context::*;
 13pub use neovim_backed_test_context::*;
 14pub use vim_test_context::*;
 15
 16use indoc::indoc;
 17use search::BufferSearchBar;
 18
 19use crate::{insert::NormalBefore, motion, state::Mode, ModeIndicator};
 20
 21#[gpui::test]
 22async fn test_initially_disabled(cx: &mut gpui::TestAppContext) {
 23    let mut cx = VimTestContext::new(cx, false).await;
 24    cx.simulate_keystrokes(["h", "j", "k", "l"]);
 25    cx.assert_editor_state("hjklˇ");
 26}
 27
 28#[gpui::test]
 29async fn test_neovim(cx: &mut gpui::TestAppContext) {
 30    let mut cx = NeovimBackedTestContext::new(cx).await;
 31
 32    cx.simulate_shared_keystroke("i").await;
 33    cx.assert_state_matches().await;
 34    cx.simulate_shared_keystrokes([
 35        "shift-T", "e", "s", "t", " ", "t", "e", "s", "t", "escape", "0", "d", "w",
 36    ])
 37    .await;
 38    cx.assert_state_matches().await;
 39    cx.assert_editor_state("ˇtest");
 40}
 41
 42#[gpui::test]
 43async fn test_toggle_through_settings(cx: &mut gpui::TestAppContext) {
 44    let mut cx = VimTestContext::new(cx, true).await;
 45
 46    cx.simulate_keystroke("i");
 47    assert_eq!(cx.mode(), Mode::Insert);
 48
 49    // Editor acts as though vim is disabled
 50    cx.disable_vim();
 51    cx.simulate_keystrokes(["h", "j", "k", "l"]);
 52    cx.assert_editor_state("hjklˇ");
 53
 54    // Selections aren't changed if editor is blurred but vim-mode is still disabled.
 55    cx.set_state("«hjklˇ»", Mode::Normal);
 56    cx.assert_editor_state("«hjklˇ»");
 57    cx.update_editor(|_, cx| cx.blur());
 58    cx.assert_editor_state("«hjklˇ»");
 59    cx.update_editor(|_, cx| cx.focus_self());
 60    cx.assert_editor_state("«hjklˇ»");
 61
 62    // Enabling dynamically sets vim mode again and restores normal mode
 63    cx.enable_vim();
 64    assert_eq!(cx.mode(), Mode::Normal);
 65    cx.simulate_keystrokes(["h", "h", "h", "l"]);
 66    assert_eq!(cx.buffer_text(), "hjkl".to_owned());
 67    cx.assert_editor_state("hˇjkl");
 68    cx.simulate_keystrokes(["i", "T", "e", "s", "t"]);
 69    cx.assert_editor_state("hTestˇjkl");
 70
 71    // Disabling and enabling resets to normal mode
 72    assert_eq!(cx.mode(), Mode::Insert);
 73    cx.disable_vim();
 74    cx.enable_vim();
 75    assert_eq!(cx.mode(), Mode::Normal);
 76}
 77
 78#[gpui::test]
 79async fn test_cancel_selection(cx: &mut gpui::TestAppContext) {
 80    let mut cx = VimTestContext::new(cx, true).await;
 81
 82    cx.set_state(
 83        indoc! {"The quick brown fox juˇmps over the lazy dog"},
 84        Mode::Normal,
 85    );
 86    // jumps
 87    cx.simulate_keystrokes(["v", "l", "l"]);
 88    cx.assert_editor_state("The quick brown fox ju«mpsˇ» over the lazy dog");
 89
 90    cx.simulate_keystrokes(["escape"]);
 91    cx.assert_editor_state("The quick brown fox jumpˇs over the lazy dog");
 92
 93    // go back to the same selection state
 94    cx.simulate_keystrokes(["v", "h", "h"]);
 95    cx.assert_editor_state("The quick brown fox ju«ˇmps» over the lazy dog");
 96
 97    // Ctrl-[ should behave like Esc
 98    cx.simulate_keystrokes(["ctrl-["]);
 99    cx.assert_editor_state("The quick brown fox juˇmps over the lazy dog");
100}
101
102#[gpui::test]
103async fn test_buffer_search(cx: &mut gpui::TestAppContext) {
104    let mut cx = VimTestContext::new(cx, true).await;
105
106    cx.set_state(
107        indoc! {"
108            The quick brown
109            fox juˇmps over
110            the lazy dog"},
111        Mode::Normal,
112    );
113    cx.simulate_keystroke("/");
114
115    let search_bar = cx.workspace(|workspace, cx| {
116        workspace
117            .active_pane()
118            .read(cx)
119            .toolbar()
120            .read(cx)
121            .item_of_type::<BufferSearchBar>()
122            .expect("Buffer search bar should be deployed")
123    });
124
125    cx.update_view(search_bar, |bar, cx| {
126        assert_eq!(bar.query(cx), "");
127    })
128}
129
130#[gpui::test]
131async fn test_count_down(cx: &mut gpui::TestAppContext) {
132    let mut cx = VimTestContext::new(cx, true).await;
133
134    cx.set_state(indoc! {"aˇa\nbb\ncc\ndd\nee"}, Mode::Normal);
135    cx.simulate_keystrokes(["2", "down"]);
136    cx.assert_editor_state("aa\nbb\ncˇc\ndd\nee");
137    cx.simulate_keystrokes(["9", "down"]);
138    cx.assert_editor_state("aa\nbb\ncc\ndd\neˇe");
139}
140
141#[gpui::test]
142async fn test_end_of_document_710(cx: &mut gpui::TestAppContext) {
143    let mut cx = VimTestContext::new(cx, true).await;
144
145    // goes to end by default
146    cx.set_state(indoc! {"aˇa\nbb\ncc"}, Mode::Normal);
147    cx.simulate_keystrokes(["shift-g"]);
148    cx.assert_editor_state("aa\nbb\ncˇc");
149
150    // can go to line 1 (https://github.com/zed-industries/zed/issues/5812)
151    cx.simulate_keystrokes(["1", "shift-g"]);
152    cx.assert_editor_state("aˇa\nbb\ncc");
153}
154
155#[gpui::test]
156async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
157    let mut cx = VimTestContext::new(cx, true).await;
158
159    // works in normal mode
160    cx.set_state(indoc! {"aa\nbˇb\ncc"}, Mode::Normal);
161    cx.simulate_keystrokes([">", ">"]);
162    cx.assert_editor_state("aa\n    bˇb\ncc");
163    cx.simulate_keystrokes(["<", "<"]);
164    cx.assert_editor_state("aa\nbˇb\ncc");
165
166    // works in visuial mode
167    cx.simulate_keystrokes(["shift-v", "down", ">"]);
168    cx.assert_editor_state("aa\n    b«b\n    ccˇ»");
169}
170
171#[gpui::test]
172async fn test_escape_command_palette(cx: &mut gpui::TestAppContext) {
173    let mut cx = VimTestContext::new(cx, true).await;
174
175    cx.set_state("aˇbc\n", Mode::Normal);
176    cx.simulate_keystrokes(["i", "cmd-shift-p"]);
177
178    assert!(cx.workspace(|workspace, cx| workspace.active_modal::<CommandPalette>(cx).is_some()));
179    cx.simulate_keystroke("escape");
180    cx.run_until_parked();
181    assert!(!cx.workspace(|workspace, cx| workspace.active_modal::<CommandPalette>(cx).is_some()));
182    cx.assert_state("aˇbc\n", Mode::Insert);
183}
184
185#[gpui::test]
186async fn test_escape_cancels(cx: &mut gpui::TestAppContext) {
187    let mut cx = VimTestContext::new(cx, true).await;
188
189    cx.set_state("aˇbˇc", Mode::Normal);
190    cx.simulate_keystrokes(["escape"]);
191
192    cx.assert_state("aˇbc", Mode::Normal);
193}
194
195#[gpui::test]
196async fn test_selection_on_search(cx: &mut gpui::TestAppContext) {
197    let mut cx = VimTestContext::new(cx, true).await;
198
199    cx.set_state(indoc! {"aa\nbˇb\ncc\ncc\ncc\n"}, Mode::Normal);
200    cx.simulate_keystrokes(["/", "c", "c"]);
201
202    let search_bar = cx.workspace(|workspace, cx| {
203        workspace
204            .active_pane()
205            .read(cx)
206            .toolbar()
207            .read(cx)
208            .item_of_type::<BufferSearchBar>()
209            .expect("Buffer search bar should be deployed")
210    });
211
212    cx.update_view(search_bar, |bar, cx| {
213        assert_eq!(bar.query(cx), "cc");
214    });
215
216    cx.update_editor(|editor, cx| {
217        let highlights = editor.all_text_background_highlights(cx);
218        assert_eq!(3, highlights.len());
219        assert_eq!(
220            DisplayPoint::new(2, 0)..DisplayPoint::new(2, 2),
221            highlights[0].0
222        )
223    });
224    cx.simulate_keystrokes(["enter"]);
225
226    cx.assert_state(indoc! {"aa\nbb\nˇcc\ncc\ncc\n"}, Mode::Normal);
227    cx.simulate_keystrokes(["n"]);
228    cx.assert_state(indoc! {"aa\nbb\ncc\nˇcc\ncc\n"}, Mode::Normal);
229    cx.simulate_keystrokes(["shift-n"]);
230    cx.assert_state(indoc! {"aa\nbb\nˇcc\ncc\ncc\n"}, Mode::Normal);
231}
232
233#[gpui::test]
234async fn test_status_indicator(cx: &mut gpui::TestAppContext) {
235    let mut cx = VimTestContext::new(cx, true).await;
236
237    let mode_indicator = cx.workspace(|workspace, cx| {
238        let status_bar = workspace.status_bar().read(cx);
239        let mode_indicator = status_bar.item_of_type::<ModeIndicator>();
240        assert!(mode_indicator.is_some());
241        mode_indicator.unwrap()
242    });
243
244    assert_eq!(
245        cx.workspace(|_, cx| mode_indicator.read(cx).mode),
246        Some(Mode::Normal)
247    );
248
249    // shows the correct mode
250    cx.simulate_keystrokes(["i"]);
251    assert_eq!(
252        cx.workspace(|_, cx| mode_indicator.read(cx).mode),
253        Some(Mode::Insert)
254    );
255
256    // shows even in search
257    cx.simulate_keystrokes(["escape", "v", "/"]);
258    assert_eq!(
259        cx.workspace(|_, cx| mode_indicator.read(cx).mode),
260        Some(Mode::Visual)
261    );
262
263    // hides if vim mode is disabled
264    cx.disable_vim();
265    cx.run_until_parked();
266    cx.workspace(|workspace, cx| {
267        let status_bar = workspace.status_bar().read(cx);
268        let mode_indicator = status_bar.item_of_type::<ModeIndicator>().unwrap();
269        assert!(mode_indicator.read(cx).mode.is_none());
270    });
271
272    cx.enable_vim();
273    cx.run_until_parked();
274    cx.workspace(|workspace, cx| {
275        let status_bar = workspace.status_bar().read(cx);
276        let mode_indicator = status_bar.item_of_type::<ModeIndicator>().unwrap();
277        assert!(mode_indicator.read(cx).mode.is_some());
278    });
279}
280
281#[gpui::test]
282async fn test_word_characters(cx: &mut gpui::TestAppContext) {
283    let mut cx = VimTestContext::new_typescript(cx).await;
284    cx.set_state(
285        indoc! { "
286        class A {
287            #ˇgoop = 99;
288            $ˇgoop () { return this.#gˇoop };
289        };
290        console.log(new A().$gooˇp())
291    "},
292        Mode::Normal,
293    );
294    cx.simulate_keystrokes(["v", "i", "w"]);
295    cx.assert_state(
296        indoc! {"
297        class A {
298            «#goopˇ» = 99;
299            «$goopˇ» () { return this.«#goopˇ» };
300        };
301        console.log(new A().«$goopˇ»())
302    "},
303        Mode::Visual,
304    )
305}
306
307#[gpui::test]
308async fn test_join_lines(cx: &mut gpui::TestAppContext) {
309    let mut cx = NeovimBackedTestContext::new(cx).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(["shift-j"]).await;
321    cx.assert_shared_state(indoc! {"
322          oneˇ two
323          three
324          four
325          five
326          six
327          "})
328        .await;
329    cx.simulate_shared_keystrokes(["3", "shift-j"]).await;
330    cx.assert_shared_state(indoc! {"
331          one two threeˇ four
332          five
333          six
334          "})
335        .await;
336
337    cx.set_shared_state(indoc! {"
338      ˇone
339      two
340      three
341      four
342      five
343      six
344      "})
345        .await;
346    cx.simulate_shared_keystrokes(["j", "v", "3", "j", "shift-j"])
347        .await;
348    cx.assert_shared_state(indoc! {"
349      one
350      two three fourˇ five
351      six
352      "})
353        .await;
354}
355
356#[gpui::test]
357async fn test_wrapped_lines(cx: &mut gpui::TestAppContext) {
358    let mut cx = NeovimBackedTestContext::new(cx).await;
359
360    cx.set_shared_wrap(12).await;
361    // tests line wrap as follows:
362    //  1: twelve char
363    //     twelve char
364    //  2: twelve char
365    cx.set_shared_state(indoc! { "
366        tˇwelve char twelve char
367        twelve char
368    "})
369        .await;
370    cx.simulate_shared_keystrokes(["j"]).await;
371    cx.assert_shared_state(indoc! { "
372        twelve char twelve char
373        tˇwelve char
374    "})
375        .await;
376    cx.simulate_shared_keystrokes(["k"]).await;
377    cx.assert_shared_state(indoc! { "
378        tˇwelve char twelve char
379        twelve char
380    "})
381        .await;
382    cx.simulate_shared_keystrokes(["g", "j"]).await;
383    cx.assert_shared_state(indoc! { "
384        twelve char tˇwelve char
385        twelve char
386    "})
387        .await;
388    cx.simulate_shared_keystrokes(["g", "j"]).await;
389    cx.assert_shared_state(indoc! { "
390        twelve char twelve char
391        tˇwelve char
392    "})
393        .await;
394
395    cx.simulate_shared_keystrokes(["g", "k"]).await;
396    cx.assert_shared_state(indoc! { "
397        twelve char tˇwelve char
398        twelve char
399    "})
400        .await;
401
402    cx.simulate_shared_keystrokes(["g", "^"]).await;
403    cx.assert_shared_state(indoc! { "
404        twelve char ˇtwelve char
405        twelve char
406    "})
407        .await;
408
409    cx.simulate_shared_keystrokes(["^"]).await;
410    cx.assert_shared_state(indoc! { "
411        ˇtwelve char twelve char
412        twelve char
413    "})
414        .await;
415
416    cx.simulate_shared_keystrokes(["g", "$"]).await;
417    cx.assert_shared_state(indoc! { "
418        twelve charˇ twelve char
419        twelve char
420    "})
421        .await;
422    cx.simulate_shared_keystrokes(["$"]).await;
423    cx.assert_shared_state(indoc! { "
424        twelve char twelve chaˇr
425        twelve char
426    "})
427        .await;
428
429    cx.set_shared_state(indoc! { "
430        tˇwelve char twelve char
431        twelve char
432    "})
433        .await;
434    cx.simulate_shared_keystrokes(["enter"]).await;
435    cx.assert_shared_state(indoc! { "
436            twelve char twelve char
437            ˇtwelve char
438        "})
439        .await;
440
441    cx.set_shared_state(indoc! { "
442        twelve char
443        tˇwelve char twelve char
444        twelve char
445    "})
446        .await;
447    cx.simulate_shared_keystrokes(["o", "o", "escape"]).await;
448    cx.assert_shared_state(indoc! { "
449        twelve char
450        twelve char twelve char
451        ˇo
452        twelve char
453    "})
454        .await;
455
456    cx.set_shared_state(indoc! { "
457        twelve char
458        tˇwelve char twelve char
459        twelve char
460    "})
461        .await;
462    cx.simulate_shared_keystrokes(["shift-a", "a", "escape"])
463        .await;
464    cx.assert_shared_state(indoc! { "
465        twelve char
466        twelve char twelve charˇa
467        twelve char
468    "})
469        .await;
470    cx.simulate_shared_keystrokes(["shift-i", "i", "escape"])
471        .await;
472    cx.assert_shared_state(indoc! { "
473        twelve char
474        ˇitwelve char twelve chara
475        twelve char
476    "})
477        .await;
478    cx.simulate_shared_keystrokes(["shift-d"]).await;
479    cx.assert_shared_state(indoc! { "
480        twelve char
481        ˇ
482        twelve char
483    "})
484        .await;
485
486    cx.set_shared_state(indoc! { "
487        twelve char
488        twelve char tˇwelve char
489        twelve char
490    "})
491        .await;
492    cx.simulate_shared_keystrokes(["shift-o", "o", "escape"])
493        .await;
494    cx.assert_shared_state(indoc! { "
495        twelve char
496        ˇo
497        twelve char twelve char
498        twelve char
499    "})
500        .await;
501
502    // line wraps as:
503    // fourteen ch
504    // ar
505    // fourteen ch
506    // ar
507    cx.set_shared_state(indoc! { "
508        fourteen chaˇr
509        fourteen char
510    "})
511        .await;
512
513    cx.simulate_shared_keystrokes(["d", "i", "w"]).await;
514    cx.assert_shared_state(indoc! {"
515        fourteenˇ•
516        fourteen char
517    "})
518        .await;
519    cx.simulate_shared_keystrokes(["j", "shift-f", "e", "f", "r"])
520        .await;
521    cx.assert_shared_state(indoc! {"
522        fourteen•
523        fourteen chaˇr
524    "})
525        .await;
526}
527
528#[gpui::test]
529async fn test_folds(cx: &mut gpui::TestAppContext) {
530    let mut cx = NeovimBackedTestContext::new(cx).await;
531    cx.set_neovim_option("foldmethod=manual").await;
532
533    cx.set_shared_state(indoc! { "
534        fn boop() {
535          ˇbarp()
536          bazp()
537        }
538    "})
539        .await;
540    cx.simulate_shared_keystrokes(["shift-v", "j", "z", "f"])
541        .await;
542
543    // visual display is now:
544    // fn boop () {
545    //  [FOLDED]
546    // }
547
548    // TODO: this should not be needed but currently zf does not
549    // return to normal mode.
550    cx.simulate_shared_keystrokes(["escape"]).await;
551
552    // skip over fold downward
553    cx.simulate_shared_keystrokes(["g", "g"]).await;
554    cx.assert_shared_state(indoc! { "
555        ˇfn boop() {
556          barp()
557          bazp()
558        }
559    "})
560        .await;
561
562    cx.simulate_shared_keystrokes(["j", "j"]).await;
563    cx.assert_shared_state(indoc! { "
564        fn boop() {
565          barp()
566          bazp()
567        ˇ}
568    "})
569        .await;
570
571    // skip over fold upward
572    cx.simulate_shared_keystrokes(["2", "k"]).await;
573    cx.assert_shared_state(indoc! { "
574        ˇfn boop() {
575          barp()
576          bazp()
577        }
578    "})
579        .await;
580
581    // yank the fold
582    cx.simulate_shared_keystrokes(["down", "y", "y"]).await;
583    cx.assert_shared_clipboard("  barp()\n  bazp()\n").await;
584
585    // re-open
586    cx.simulate_shared_keystrokes(["z", "o"]).await;
587    cx.assert_shared_state(indoc! { "
588        fn boop() {
589        ˇ  barp()
590          bazp()
591        }
592    "})
593        .await;
594}
595
596#[gpui::test]
597async fn test_folds_panic(cx: &mut gpui::TestAppContext) {
598    let mut cx = NeovimBackedTestContext::new(cx).await;
599    cx.set_neovim_option("foldmethod=manual").await;
600
601    cx.set_shared_state(indoc! { "
602        fn boop() {
603          ˇbarp()
604          bazp()
605        }
606    "})
607        .await;
608    cx.simulate_shared_keystrokes(["shift-v", "j", "z", "f"])
609        .await;
610    cx.simulate_shared_keystrokes(["escape"]).await;
611    cx.simulate_shared_keystrokes(["g", "g"]).await;
612    cx.simulate_shared_keystrokes(["5", "d", "j"]).await;
613    cx.assert_shared_state(indoc! { "ˇ"}).await;
614}
615
616#[gpui::test]
617async fn test_clear_counts(cx: &mut gpui::TestAppContext) {
618    let mut cx = NeovimBackedTestContext::new(cx).await;
619
620    cx.set_shared_state(indoc! {"
621        The quick brown
622        fox juˇmps over
623        the lazy dog"})
624        .await;
625
626    cx.simulate_shared_keystrokes(["4", "escape", "3", "d", "l"])
627        .await;
628    cx.assert_shared_state(indoc! {"
629        The quick brown
630        fox juˇ over
631        the lazy dog"})
632        .await;
633}
634
635#[gpui::test]
636async fn test_zero(cx: &mut gpui::TestAppContext) {
637    let mut cx = NeovimBackedTestContext::new(cx).await;
638
639    cx.set_shared_state(indoc! {"
640        The quˇick brown
641        fox jumps over
642        the lazy dog"})
643        .await;
644
645    cx.simulate_shared_keystrokes(["0"]).await;
646    cx.assert_shared_state(indoc! {"
647        ˇThe quick brown
648        fox jumps over
649        the lazy dog"})
650        .await;
651
652    cx.simulate_shared_keystrokes(["1", "0", "l"]).await;
653    cx.assert_shared_state(indoc! {"
654        The quick ˇbrown
655        fox jumps over
656        the lazy dog"})
657        .await;
658}
659
660#[gpui::test]
661async fn test_selection_goal(cx: &mut gpui::TestAppContext) {
662    let mut cx = NeovimBackedTestContext::new(cx).await;
663
664    cx.set_shared_state(indoc! {"
665        ;;ˇ;
666        Lorem Ipsum"})
667        .await;
668
669    cx.simulate_shared_keystrokes(["a", "down", "up", ";", "down", "up"])
670        .await;
671    cx.assert_shared_state(indoc! {"
672        ;;;;ˇ
673        Lorem Ipsum"})
674        .await;
675}
676
677#[gpui::test]
678async fn test_wrapped_motions(cx: &mut gpui::TestAppContext) {
679    let mut cx = NeovimBackedTestContext::new(cx).await;
680
681    cx.set_shared_wrap(12).await;
682
683    cx.set_shared_state(indoc! {"
684                aaˇaa
685                😃😃"
686    })
687    .await;
688    cx.simulate_shared_keystrokes(["j"]).await;
689    cx.assert_shared_state(indoc! {"
690                aaaa
691                😃ˇ😃"
692    })
693    .await;
694
695    cx.set_shared_state(indoc! {"
696                123456789012aaˇaa
697                123456789012😃😃"
698    })
699    .await;
700    cx.simulate_shared_keystrokes(["j"]).await;
701    cx.assert_shared_state(indoc! {"
702        123456789012aaaa
703        123456789012😃ˇ😃"
704    })
705    .await;
706
707    cx.set_shared_state(indoc! {"
708                123456789012aaˇaa
709                123456789012😃😃"
710    })
711    .await;
712    cx.simulate_shared_keystrokes(["j"]).await;
713    cx.assert_shared_state(indoc! {"
714        123456789012aaaa
715        123456789012😃ˇ😃"
716    })
717    .await;
718
719    cx.set_shared_state(indoc! {"
720        123456789012aaaaˇaaaaaaaa123456789012
721        wow
722        123456789012😃😃😃😃😃😃123456789012"
723    })
724    .await;
725    cx.simulate_shared_keystrokes(["j", "j"]).await;
726    cx.assert_shared_state(indoc! {"
727        123456789012aaaaaaaaaaaa123456789012
728        wow
729        123456789012😃😃ˇ😃😃😃😃123456789012"
730    })
731    .await;
732}
733
734#[gpui::test]
735async fn test_paragraphs_dont_wrap(cx: &mut gpui::TestAppContext) {
736    let mut cx = NeovimBackedTestContext::new(cx).await;
737
738    cx.set_shared_state(indoc! {"
739        one
740        ˇ
741        two"})
742        .await;
743
744    cx.simulate_shared_keystrokes(["}", "}"]).await;
745    cx.assert_shared_state(indoc! {"
746        one
747
748        twˇo"})
749        .await;
750
751    cx.simulate_shared_keystrokes(["{", "{", "{"]).await;
752    cx.assert_shared_state(indoc! {"
753        ˇone
754
755        two"})
756        .await;
757}
758
759#[gpui::test]
760async fn test_select_all_issue_2170(cx: &mut gpui::TestAppContext) {
761    let mut cx = VimTestContext::new(cx, true).await;
762
763    cx.set_state(
764        indoc! {"
765        defmodule Test do
766            def test(a, ˇ[_, _] = b), do: IO.puts('hi')
767        end
768    "},
769        Mode::Normal,
770    );
771    cx.simulate_keystrokes(["g", "a"]);
772    cx.assert_state(
773        indoc! {"
774        defmodule Test do
775            def test(a, «[ˇ»_, _] = b), do: IO.puts('hi')
776        end
777    "},
778        Mode::Visual,
779    );
780}
781
782#[gpui::test]
783async fn test_jk(cx: &mut gpui::TestAppContext) {
784    let mut cx = NeovimBackedTestContext::new(cx).await;
785
786    cx.update(|cx| {
787        cx.bind_keys([KeyBinding::new(
788            "j k",
789            NormalBefore,
790            Some("vim_mode == insert"),
791        )])
792    });
793    cx.neovim.exec("imap jk <esc>").await;
794
795    cx.set_shared_state("ˇhello").await;
796    cx.simulate_shared_keystrokes(["i", "j", "o", "j", "k"])
797        .await;
798    cx.assert_shared_state("jˇohello").await;
799}
800
801#[gpui::test]
802async fn test_jk_delay(cx: &mut gpui::TestAppContext) {
803    let mut cx = VimTestContext::new(cx, true).await;
804
805    cx.update(|cx| {
806        cx.bind_keys([KeyBinding::new(
807            "j k",
808            NormalBefore,
809            Some("vim_mode == insert"),
810        )])
811    });
812
813    cx.set_state("ˇhello", Mode::Normal);
814    cx.simulate_keystrokes(["i", "j"]);
815    cx.executor().advance_clock(Duration::from_millis(500));
816    cx.run_until_parked();
817    cx.assert_state("ˇhello", Mode::Insert);
818    cx.executor().advance_clock(Duration::from_millis(500));
819    cx.run_until_parked();
820    cx.assert_state("jˇhello", Mode::Insert);
821    cx.simulate_keystrokes(["k", "j", "k"]);
822    cx.assert_state("jˇkhello", Mode::Normal);
823}
824
825#[gpui::test]
826async fn test_comma_w(cx: &mut gpui::TestAppContext) {
827    let mut cx = NeovimBackedTestContext::new(cx).await;
828
829    cx.update(|cx| {
830        cx.bind_keys([KeyBinding::new(
831            ", w",
832            motion::Down {
833                display_lines: false,
834            },
835            Some("vim_mode == normal"),
836        )])
837    });
838    cx.neovim.exec("map ,w j").await;
839
840    cx.set_shared_state("ˇhello hello\nhello hello").await;
841    cx.simulate_shared_keystrokes(["f", "o", ";", ",", "w"])
842        .await;
843    cx.assert_shared_state("hello hello\nhello hellˇo").await;
844
845    cx.set_shared_state("ˇhello hello\nhello hello").await;
846    cx.simulate_shared_keystrokes(["f", "o", ";", ",", "i"])
847        .await;
848    cx.assert_shared_state("hellˇo hello\nhello hello").await;
849    cx.assert_shared_mode(Mode::Insert).await;
850}
851
852#[gpui::test]
853async fn test_rename(cx: &mut gpui::TestAppContext) {
854    let mut cx = VimTestContext::new_typescript(cx).await;
855
856    cx.set_state("const beˇfore = 2; console.log(before)", Mode::Normal);
857    let def_range = cx.lsp_range("const «beforeˇ» = 2; console.log(before)");
858    let tgt_range = cx.lsp_range("const before = 2; console.log(«beforeˇ»)");
859    let mut prepare_request =
860        cx.handle_request::<lsp::request::PrepareRenameRequest, _, _>(move |_, _, _| async move {
861            Ok(Some(lsp::PrepareRenameResponse::Range(def_range)))
862        });
863    let mut rename_request =
864        cx.handle_request::<lsp::request::Rename, _, _>(move |url, params, _| async move {
865            Ok(Some(lsp::WorkspaceEdit {
866                changes: Some(
867                    [(
868                        url.clone(),
869                        vec![
870                            lsp::TextEdit::new(def_range, params.new_name.clone()),
871                            lsp::TextEdit::new(tgt_range, params.new_name),
872                        ],
873                    )]
874                    .into(),
875                ),
876                ..Default::default()
877            }))
878        });
879
880    cx.simulate_keystrokes(["c", "d"]);
881    prepare_request.next().await.unwrap();
882    cx.simulate_input("after");
883    cx.simulate_keystrokes(["enter"]);
884    rename_request.next().await.unwrap();
885    cx.assert_state("const afterˇ = 2; console.log(after)", Mode::Normal)
886}
887
888#[gpui::test]
889async fn test_remap(cx: &mut gpui::TestAppContext) {
890    let mut cx = VimTestContext::new(cx, true).await;
891
892    // test moving the cursor
893    cx.update(|cx| {
894        cx.bind_keys([KeyBinding::new(
895            "g z",
896            workspace::SendKeystrokes("l l l l".to_string()),
897            None,
898        )])
899    });
900    cx.set_state("ˇ123456789", Mode::Normal);
901    cx.simulate_keystrokes(["g", "z"]);
902    cx.assert_state("1234ˇ56789", Mode::Normal);
903
904    // test switching modes
905    cx.update(|cx| {
906        cx.bind_keys([KeyBinding::new(
907            "g y",
908            workspace::SendKeystrokes("i f o o escape l".to_string()),
909            None,
910        )])
911    });
912    cx.set_state("ˇ123456789", Mode::Normal);
913    cx.simulate_keystrokes(["g", "y"]);
914    cx.assert_state("fooˇ123456789", Mode::Normal);
915
916    // test recursion
917    cx.update(|cx| {
918        cx.bind_keys([KeyBinding::new(
919            "g x",
920            workspace::SendKeystrokes("g z g y".to_string()),
921            None,
922        )])
923    });
924    cx.set_state("ˇ123456789", Mode::Normal);
925    cx.simulate_keystrokes(["g", "x"]);
926    cx.assert_state("1234fooˇ56789", Mode::Normal);
927
928    cx.executor().allow_parking();
929
930    // test command
931    cx.update(|cx| {
932        cx.bind_keys([KeyBinding::new(
933            "g w",
934            workspace::SendKeystrokes(": j enter".to_string()),
935            None,
936        )])
937    });
938    cx.set_state("ˇ1234\n56789", Mode::Normal);
939    cx.simulate_keystrokes(["g", "w"]);
940    cx.assert_state("1234ˇ 56789", Mode::Normal);
941
942    // test leaving command
943    cx.update(|cx| {
944        cx.bind_keys([KeyBinding::new(
945            "g u",
946            workspace::SendKeystrokes("g w g z".to_string()),
947            None,
948        )])
949    });
950    cx.set_state("ˇ1234\n56789", Mode::Normal);
951    cx.simulate_keystrokes(["g", "u"]);
952    cx.assert_state("1234 567ˇ89", Mode::Normal);
953
954    // test leaving command
955    cx.update(|cx| {
956        cx.bind_keys([KeyBinding::new(
957            "g t",
958            workspace::SendKeystrokes("i space escape".to_string()),
959            None,
960        )])
961    });
962    cx.set_state("12ˇ34", Mode::Normal);
963    cx.simulate_keystrokes(["g", "t"]);
964    cx.assert_state("12ˇ 34", Mode::Normal);
965}