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}