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