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}