1mod neovim_backed_test_context;
2mod neovim_connection;
3mod vim_test_context;
4
5use std::time::Duration;
6
7use collections::HashMap;
8use command_palette::CommandPalette;
9use editor::{actions::DeleteLine, display_map::DisplayRow, DisplayPoint};
10use futures::StreamExt;
11use gpui::{KeyBinding, Modifiers, MouseButton, TestAppContext};
12pub use neovim_backed_test_context::*;
13use settings::SettingsStore;
14pub use vim_test_context::*;
15
16use indoc::indoc;
17use search::BufferSearchBar;
18use workspace::WorkspaceSettings;
19
20use crate::{
21 insert::NormalBefore,
22 motion,
23 state::{Mode, Operator},
24 PushOperator,
25};
26
27#[gpui::test]
28async fn test_initially_disabled(cx: &mut gpui::TestAppContext) {
29 let mut cx = VimTestContext::new(cx, false).await;
30 cx.simulate_keystrokes("h j k l");
31 cx.assert_editor_state("hjklˇ");
32}
33
34#[gpui::test]
35async fn test_neovim(cx: &mut gpui::TestAppContext) {
36 let mut cx = NeovimBackedTestContext::new(cx).await;
37
38 cx.simulate_shared_keystrokes("i").await;
39 cx.shared_state().await.assert_matches();
40 cx.simulate_shared_keystrokes("shift-t e s t space t e s t escape 0 d w")
41 .await;
42 cx.shared_state().await.assert_matches();
43 cx.assert_editor_state("ˇtest");
44}
45
46#[gpui::test]
47async fn test_toggle_through_settings(cx: &mut gpui::TestAppContext) {
48 let mut cx = VimTestContext::new(cx, true).await;
49
50 cx.simulate_keystrokes("i");
51 assert_eq!(cx.mode(), Mode::Insert);
52
53 // Editor acts as though vim is disabled
54 cx.disable_vim();
55 cx.simulate_keystrokes("h j k l");
56 cx.assert_editor_state("hjklˇ");
57
58 // Selections aren't changed if editor is blurred but vim-mode is still disabled.
59 cx.cx.set_state("«hjklˇ»");
60 cx.assert_editor_state("«hjklˇ»");
61 cx.update_editor(|_, cx| cx.blur());
62 cx.assert_editor_state("«hjklˇ»");
63 cx.update_editor(|_, cx| cx.focus_self());
64 cx.assert_editor_state("«hjklˇ»");
65
66 // Enabling dynamically sets vim mode again and restores normal mode
67 cx.enable_vim();
68 assert_eq!(cx.mode(), Mode::Normal);
69 cx.simulate_keystrokes("h h h l");
70 assert_eq!(cx.buffer_text(), "hjkl".to_owned());
71 cx.assert_editor_state("hˇjkl");
72 cx.simulate_keystrokes("i T e s t");
73 cx.assert_editor_state("hTestˇjkl");
74
75 // Disabling and enabling resets to normal mode
76 assert_eq!(cx.mode(), Mode::Insert);
77 cx.disable_vim();
78 cx.enable_vim();
79 assert_eq!(cx.mode(), Mode::Normal);
80}
81
82#[gpui::test]
83async fn test_cancel_selection(cx: &mut gpui::TestAppContext) {
84 let mut cx = VimTestContext::new(cx, true).await;
85
86 cx.set_state(
87 indoc! {"The quick brown fox juˇmps over the lazy dog"},
88 Mode::Normal,
89 );
90 // jumps
91 cx.simulate_keystrokes("v l l");
92 cx.assert_editor_state("The quick brown fox ju«mpsˇ» over the lazy dog");
93
94 cx.simulate_keystrokes("escape");
95 cx.assert_editor_state("The quick brown fox jumpˇs over the lazy dog");
96
97 // go back to the same selection state
98 cx.simulate_keystrokes("v h h");
99 cx.assert_editor_state("The quick brown fox ju«ˇmps» over the lazy dog");
100
101 // Ctrl-[ should behave like Esc
102 cx.simulate_keystrokes("ctrl-[");
103 cx.assert_editor_state("The quick brown fox juˇmps over the lazy dog");
104}
105
106#[gpui::test]
107async fn test_buffer_search(cx: &mut gpui::TestAppContext) {
108 let mut cx = VimTestContext::new(cx, true).await;
109
110 cx.set_state(
111 indoc! {"
112 The quick brown
113 fox juˇmps over
114 the lazy dog"},
115 Mode::Normal,
116 );
117 cx.simulate_keystrokes("/");
118
119 let search_bar = cx.workspace(|workspace, cx| {
120 workspace
121 .active_pane()
122 .read(cx)
123 .toolbar()
124 .read(cx)
125 .item_of_type::<BufferSearchBar>()
126 .expect("Buffer search bar should be deployed")
127 });
128
129 cx.update_view(search_bar, |bar, cx| {
130 assert_eq!(bar.query(cx), "");
131 })
132}
133
134#[gpui::test]
135async fn test_count_down(cx: &mut gpui::TestAppContext) {
136 let mut cx = VimTestContext::new(cx, true).await;
137
138 cx.set_state(indoc! {"aˇa\nbb\ncc\ndd\nee"}, Mode::Normal);
139 cx.simulate_keystrokes("2 down");
140 cx.assert_editor_state("aa\nbb\ncˇc\ndd\nee");
141 cx.simulate_keystrokes("9 down");
142 cx.assert_editor_state("aa\nbb\ncc\ndd\neˇe");
143}
144
145#[gpui::test]
146async fn test_end_of_document_710(cx: &mut gpui::TestAppContext) {
147 let mut cx = VimTestContext::new(cx, true).await;
148
149 // goes to end by default
150 cx.set_state(indoc! {"aˇa\nbb\ncc"}, Mode::Normal);
151 cx.simulate_keystrokes("shift-g");
152 cx.assert_editor_state("aa\nbb\ncˇc");
153
154 // can go to line 1 (https://github.com/zed-industries/zed/issues/5812)
155 cx.simulate_keystrokes("1 shift-g");
156 cx.assert_editor_state("aˇa\nbb\ncc");
157}
158
159#[gpui::test]
160async fn test_end_of_line_with_times(cx: &mut gpui::TestAppContext) {
161 let mut cx = VimTestContext::new(cx, true).await;
162
163 // goes to current line end
164 cx.set_state(indoc! {"ˇaa\nbb\ncc"}, Mode::Normal);
165 cx.simulate_keystrokes("$");
166 cx.assert_editor_state("aˇa\nbb\ncc");
167
168 // goes to next line end
169 cx.simulate_keystrokes("2 $");
170 cx.assert_editor_state("aa\nbˇb\ncc");
171
172 // try to exceed the final line.
173 cx.simulate_keystrokes("4 $");
174 cx.assert_editor_state("aa\nbb\ncˇc");
175}
176
177#[gpui::test]
178async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
179 let mut cx = VimTestContext::new(cx, true).await;
180
181 // works in normal mode
182 cx.set_state(indoc! {"aa\nbˇb\ncc"}, Mode::Normal);
183 cx.simulate_keystrokes("> >");
184 cx.assert_editor_state("aa\n bˇb\ncc");
185 cx.simulate_keystrokes("< <");
186 cx.assert_editor_state("aa\nbˇb\ncc");
187
188 // works in visual mode
189 cx.simulate_keystrokes("shift-v down >");
190 cx.assert_editor_state("aa\n bˇb\n cc");
191
192 // works as operator
193 cx.set_state("aa\nbˇb\ncc\n", Mode::Normal);
194 cx.simulate_keystrokes("> j");
195 cx.assert_editor_state("aa\n bˇb\n cc\n");
196 cx.simulate_keystrokes("< k");
197 cx.assert_editor_state("aa\nbˇb\n cc\n");
198 cx.simulate_keystrokes("> i p");
199 cx.assert_editor_state(" aa\n bˇb\n cc\n");
200 cx.simulate_keystrokes("< i p");
201 cx.assert_editor_state("aa\nbˇb\n cc\n");
202 cx.simulate_keystrokes("< i p");
203 cx.assert_editor_state("aa\nbˇb\ncc\n");
204
205 cx.set_state("ˇaa\nbb\ncc\n", Mode::Normal);
206 cx.simulate_keystrokes("> 2 j");
207 cx.assert_editor_state(" ˇaa\n bb\n cc\n");
208
209 cx.set_state("aa\nbb\nˇcc\n", Mode::Normal);
210 cx.simulate_keystrokes("> 2 k");
211 cx.assert_editor_state(" aa\n bb\n ˇcc\n");
212
213 // works with repeat
214 cx.set_state("a\nb\nccˇc\n", Mode::Normal);
215 cx.simulate_keystrokes("> 2 k");
216 cx.assert_editor_state(" a\n b\n ccˇc\n");
217 cx.simulate_keystrokes(".");
218 cx.assert_editor_state(" a\n b\n ccˇc\n");
219 cx.simulate_keystrokes("v k <");
220 cx.assert_editor_state(" a\n bˇ\n ccc\n");
221 cx.simulate_keystrokes(".");
222 cx.assert_editor_state(" a\nbˇ\nccc\n");
223}
224
225#[gpui::test]
226async fn test_escape_command_palette(cx: &mut gpui::TestAppContext) {
227 let mut cx = VimTestContext::new(cx, true).await;
228
229 cx.set_state("aˇbc\n", Mode::Normal);
230 cx.simulate_keystrokes("i cmd-shift-p");
231
232 assert!(cx.workspace(|workspace, cx| workspace.active_modal::<CommandPalette>(cx).is_some()));
233 cx.simulate_keystrokes("escape");
234 cx.run_until_parked();
235 assert!(!cx.workspace(|workspace, cx| workspace.active_modal::<CommandPalette>(cx).is_some()));
236 cx.assert_state("aˇbc\n", Mode::Insert);
237}
238
239#[gpui::test]
240async fn test_escape_cancels(cx: &mut gpui::TestAppContext) {
241 let mut cx = VimTestContext::new(cx, true).await;
242
243 cx.set_state("aˇbˇc", Mode::Normal);
244 cx.simulate_keystrokes("escape");
245
246 cx.assert_state("aˇbc", Mode::Normal);
247}
248
249#[gpui::test]
250async fn test_selection_on_search(cx: &mut gpui::TestAppContext) {
251 let mut cx = VimTestContext::new(cx, true).await;
252
253 cx.set_state(indoc! {"aa\nbˇb\ncc\ncc\ncc\n"}, Mode::Normal);
254 cx.simulate_keystrokes("/ c c");
255
256 let search_bar = cx.workspace(|workspace, cx| {
257 workspace
258 .active_pane()
259 .read(cx)
260 .toolbar()
261 .read(cx)
262 .item_of_type::<BufferSearchBar>()
263 .expect("Buffer search bar should be deployed")
264 });
265
266 cx.update_view(search_bar, |bar, cx| {
267 assert_eq!(bar.query(cx), "cc");
268 });
269
270 cx.update_editor(|editor, cx| {
271 let highlights = editor.all_text_background_highlights(cx);
272 assert_eq!(3, highlights.len());
273 assert_eq!(
274 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 2),
275 highlights[0].0
276 )
277 });
278 cx.simulate_keystrokes("enter");
279
280 cx.assert_state(indoc! {"aa\nbb\nˇcc\ncc\ncc\n"}, Mode::Normal);
281 cx.simulate_keystrokes("n");
282 cx.assert_state(indoc! {"aa\nbb\ncc\nˇcc\ncc\n"}, Mode::Normal);
283 cx.simulate_keystrokes("shift-n");
284 cx.assert_state(indoc! {"aa\nbb\nˇcc\ncc\ncc\n"}, Mode::Normal);
285}
286
287#[gpui::test]
288async fn test_word_characters(cx: &mut gpui::TestAppContext) {
289 let mut cx = VimTestContext::new_typescript(cx).await;
290 cx.set_state(
291 indoc! { "
292 class A {
293 #ˇgoop = 99;
294 $ˇgoop () { return this.#gˇoop };
295 };
296 console.log(new A().$gooˇp())
297 "},
298 Mode::Normal,
299 );
300 cx.simulate_keystrokes("v i w");
301 cx.assert_state(
302 indoc! {"
303 class A {
304 «#goopˇ» = 99;
305 «$goopˇ» () { return this.«#goopˇ» };
306 };
307 console.log(new A().«$goopˇ»())
308 "},
309 Mode::Visual,
310 )
311}
312
313#[gpui::test]
314async fn test_kebab_case(cx: &mut gpui::TestAppContext) {
315 let mut cx = VimTestContext::new_html(cx).await;
316 cx.set_state(
317 indoc! { r#"
318 <div><a class="bg-rˇed"></a></div>
319 "#},
320 Mode::Normal,
321 );
322 cx.simulate_keystrokes("v i w");
323 cx.assert_state(
324 indoc! { r#"
325 <div><a class="bg-«redˇ»"></a></div>
326 "#
327 },
328 Mode::Visual,
329 )
330}
331
332#[gpui::test]
333async fn test_join_lines(cx: &mut gpui::TestAppContext) {
334 let mut cx = NeovimBackedTestContext::new(cx).await;
335
336 cx.set_shared_state(indoc! {"
337 ˇone
338 two
339 three
340 four
341 five
342 six
343 "})
344 .await;
345 cx.simulate_shared_keystrokes("shift-j").await;
346 cx.shared_state().await.assert_eq(indoc! {"
347 oneˇ two
348 three
349 four
350 five
351 six
352 "});
353 cx.simulate_shared_keystrokes("3 shift-j").await;
354 cx.shared_state().await.assert_eq(indoc! {"
355 one two threeˇ four
356 five
357 six
358 "});
359
360 cx.set_shared_state(indoc! {"
361 ˇone
362 two
363 three
364 four
365 five
366 six
367 "})
368 .await;
369 cx.simulate_shared_keystrokes("j v 3 j shift-j").await;
370 cx.shared_state().await.assert_eq(indoc! {"
371 one
372 two three fourˇ five
373 six
374 "});
375
376 cx.set_shared_state(indoc! {"
377 ˇone
378 two
379 three
380 four
381 five
382 six
383 "})
384 .await;
385 cx.simulate_shared_keystrokes("g shift-j").await;
386 cx.shared_state().await.assert_eq(indoc! {"
387 oneˇtwo
388 three
389 four
390 five
391 six
392 "});
393 cx.simulate_shared_keystrokes("3 g shift-j").await;
394 cx.shared_state().await.assert_eq(indoc! {"
395 onetwothreeˇfour
396 five
397 six
398 "});
399
400 cx.set_shared_state(indoc! {"
401 ˇone
402 two
403 three
404 four
405 five
406 six
407 "})
408 .await;
409 cx.simulate_shared_keystrokes("j v 3 j g shift-j").await;
410 cx.shared_state().await.assert_eq(indoc! {"
411 one
412 twothreefourˇfive
413 six
414 "});
415}
416
417#[cfg(target_os = "macos")]
418#[gpui::test]
419async fn test_wrapped_lines(cx: &mut gpui::TestAppContext) {
420 let mut cx = NeovimBackedTestContext::new(cx).await;
421
422 cx.set_shared_wrap(12).await;
423 // tests line wrap as follows:
424 // 1: twelve char
425 // twelve char
426 // 2: twelve char
427 cx.set_shared_state(indoc! { "
428 tˇwelve char twelve char
429 twelve char
430 "})
431 .await;
432 cx.simulate_shared_keystrokes("j").await;
433 cx.shared_state().await.assert_eq(indoc! {"
434 twelve char twelve char
435 tˇwelve char
436 "});
437 cx.simulate_shared_keystrokes("k").await;
438 cx.shared_state().await.assert_eq(indoc! {"
439 tˇwelve char twelve char
440 twelve char
441 "});
442 cx.simulate_shared_keystrokes("g j").await;
443 cx.shared_state().await.assert_eq(indoc! {"
444 twelve char tˇwelve char
445 twelve char
446 "});
447 cx.simulate_shared_keystrokes("g j").await;
448 cx.shared_state().await.assert_eq(indoc! {"
449 twelve char twelve char
450 tˇwelve char
451 "});
452
453 cx.simulate_shared_keystrokes("g k").await;
454 cx.shared_state().await.assert_eq(indoc! {"
455 twelve char tˇwelve char
456 twelve char
457 "});
458
459 cx.simulate_shared_keystrokes("g ^").await;
460 cx.shared_state().await.assert_eq(indoc! {"
461 twelve char ˇtwelve char
462 twelve char
463 "});
464
465 cx.simulate_shared_keystrokes("^").await;
466 cx.shared_state().await.assert_eq(indoc! {"
467 ˇtwelve char twelve char
468 twelve char
469 "});
470
471 cx.simulate_shared_keystrokes("g $").await;
472 cx.shared_state().await.assert_eq(indoc! {"
473 twelve charˇ twelve char
474 twelve char
475 "});
476 cx.simulate_shared_keystrokes("$").await;
477 cx.shared_state().await.assert_eq(indoc! {"
478 twelve char twelve chaˇr
479 twelve char
480 "});
481
482 cx.set_shared_state(indoc! { "
483 tˇwelve char twelve char
484 twelve char
485 "})
486 .await;
487 cx.simulate_shared_keystrokes("enter").await;
488 cx.shared_state().await.assert_eq(indoc! {"
489 twelve char twelve char
490 ˇtwelve char
491 "});
492
493 cx.set_shared_state(indoc! { "
494 twelve char
495 tˇwelve char twelve char
496 twelve char
497 "})
498 .await;
499 cx.simulate_shared_keystrokes("o o escape").await;
500 cx.shared_state().await.assert_eq(indoc! {"
501 twelve char
502 twelve char twelve char
503 ˇo
504 twelve char
505 "});
506
507 cx.set_shared_state(indoc! { "
508 twelve char
509 tˇwelve char twelve char
510 twelve char
511 "})
512 .await;
513 cx.simulate_shared_keystrokes("shift-a a escape").await;
514 cx.shared_state().await.assert_eq(indoc! {"
515 twelve char
516 twelve char twelve charˇa
517 twelve char
518 "});
519 cx.simulate_shared_keystrokes("shift-i i escape").await;
520 cx.shared_state().await.assert_eq(indoc! {"
521 twelve char
522 ˇitwelve char twelve chara
523 twelve char
524 "});
525 cx.simulate_shared_keystrokes("shift-d").await;
526 cx.shared_state().await.assert_eq(indoc! {"
527 twelve char
528 ˇ
529 twelve char
530 "});
531
532 cx.set_shared_state(indoc! { "
533 twelve char
534 twelve char tˇwelve char
535 twelve char
536 "})
537 .await;
538 cx.simulate_shared_keystrokes("shift-o o escape").await;
539 cx.shared_state().await.assert_eq(indoc! {"
540 twelve char
541 ˇo
542 twelve char twelve char
543 twelve char
544 "});
545
546 // line wraps as:
547 // fourteen ch
548 // ar
549 // fourteen ch
550 // ar
551 cx.set_shared_state(indoc! { "
552 fourteen chaˇr
553 fourteen char
554 "})
555 .await;
556
557 cx.simulate_shared_keystrokes("d i w").await;
558 cx.shared_state().await.assert_eq(indoc! {"
559 fourteenˇ•
560 fourteen char
561 "});
562 cx.simulate_shared_keystrokes("j shift-f e f r").await;
563 cx.shared_state().await.assert_eq(indoc! {"
564 fourteen•
565 fourteen chaˇr
566 "});
567}
568
569#[gpui::test]
570async fn test_folds(cx: &mut gpui::TestAppContext) {
571 let mut cx = NeovimBackedTestContext::new(cx).await;
572 cx.set_neovim_option("foldmethod=manual").await;
573
574 cx.set_shared_state(indoc! { "
575 fn boop() {
576 ˇbarp()
577 bazp()
578 }
579 "})
580 .await;
581 cx.simulate_shared_keystrokes("shift-v j z f").await;
582
583 // visual display is now:
584 // fn boop () {
585 // [FOLDED]
586 // }
587
588 // TODO: this should not be needed but currently zf does not
589 // return to normal mode.
590 cx.simulate_shared_keystrokes("escape").await;
591
592 // skip over fold downward
593 cx.simulate_shared_keystrokes("g g").await;
594 cx.shared_state().await.assert_eq(indoc! {"
595 ˇfn boop() {
596 barp()
597 bazp()
598 }
599 "});
600
601 cx.simulate_shared_keystrokes("j j").await;
602 cx.shared_state().await.assert_eq(indoc! {"
603 fn boop() {
604 barp()
605 bazp()
606 ˇ}
607 "});
608
609 // skip over fold upward
610 cx.simulate_shared_keystrokes("2 k").await;
611 cx.shared_state().await.assert_eq(indoc! {"
612 ˇfn boop() {
613 barp()
614 bazp()
615 }
616 "});
617
618 // yank the fold
619 cx.simulate_shared_keystrokes("down y y").await;
620 cx.shared_clipboard()
621 .await
622 .assert_eq(" barp()\n bazp()\n");
623
624 // re-open
625 cx.simulate_shared_keystrokes("z o").await;
626 cx.shared_state().await.assert_eq(indoc! {"
627 fn boop() {
628 ˇ barp()
629 bazp()
630 }
631 "});
632}
633
634#[gpui::test]
635async fn test_folds_panic(cx: &mut gpui::TestAppContext) {
636 let mut cx = NeovimBackedTestContext::new(cx).await;
637 cx.set_neovim_option("foldmethod=manual").await;
638
639 cx.set_shared_state(indoc! { "
640 fn boop() {
641 ˇbarp()
642 bazp()
643 }
644 "})
645 .await;
646 cx.simulate_shared_keystrokes("shift-v j z f").await;
647 cx.simulate_shared_keystrokes("escape").await;
648 cx.simulate_shared_keystrokes("g g").await;
649 cx.simulate_shared_keystrokes("5 d j").await;
650 cx.shared_state().await.assert_eq("ˇ");
651 cx.set_shared_state(indoc! {"
652 fn boop() {
653 ˇbarp()
654 bazp()
655 }
656 "})
657 .await;
658 cx.simulate_shared_keystrokes("shift-v j j z f").await;
659 cx.simulate_shared_keystrokes("escape").await;
660 cx.simulate_shared_keystrokes("shift-g shift-v").await;
661 cx.shared_state().await.assert_eq(indoc! {"
662 fn boop() {
663 barp()
664 bazp()
665 }
666 ˇ"});
667}
668
669#[gpui::test]
670async fn test_clear_counts(cx: &mut gpui::TestAppContext) {
671 let mut cx = NeovimBackedTestContext::new(cx).await;
672
673 cx.set_shared_state(indoc! {"
674 The quick brown
675 fox juˇmps over
676 the lazy dog"})
677 .await;
678
679 cx.simulate_shared_keystrokes("4 escape 3 d l").await;
680 cx.shared_state().await.assert_eq(indoc! {"
681 The quick brown
682 fox juˇ over
683 the lazy dog"});
684}
685
686#[gpui::test]
687async fn test_zero(cx: &mut gpui::TestAppContext) {
688 let mut cx = NeovimBackedTestContext::new(cx).await;
689
690 cx.set_shared_state(indoc! {"
691 The quˇick brown
692 fox jumps over
693 the lazy dog"})
694 .await;
695
696 cx.simulate_shared_keystrokes("0").await;
697 cx.shared_state().await.assert_eq(indoc! {"
698 ˇThe quick brown
699 fox jumps over
700 the lazy dog"});
701
702 cx.simulate_shared_keystrokes("1 0 l").await;
703 cx.shared_state().await.assert_eq(indoc! {"
704 The quick ˇbrown
705 fox jumps over
706 the lazy dog"});
707}
708
709#[gpui::test]
710async fn test_selection_goal(cx: &mut gpui::TestAppContext) {
711 let mut cx = NeovimBackedTestContext::new(cx).await;
712
713 cx.set_shared_state(indoc! {"
714 ;;ˇ;
715 Lorem Ipsum"})
716 .await;
717
718 cx.simulate_shared_keystrokes("a down up ; down up").await;
719 cx.shared_state().await.assert_eq(indoc! {"
720 ;;;;ˇ
721 Lorem Ipsum"});
722}
723
724#[cfg(target_os = "macos")]
725#[gpui::test]
726async fn test_wrapped_motions(cx: &mut gpui::TestAppContext) {
727 let mut cx = NeovimBackedTestContext::new(cx).await;
728
729 cx.set_shared_wrap(12).await;
730
731 cx.set_shared_state(indoc! {"
732 aaˇaa
733 😃😃"
734 })
735 .await;
736 cx.simulate_shared_keystrokes("j").await;
737 cx.shared_state().await.assert_eq(indoc! {"
738 aaaa
739 😃ˇ😃"
740 });
741
742 cx.set_shared_state(indoc! {"
743 123456789012aaˇaa
744 123456789012😃😃"
745 })
746 .await;
747 cx.simulate_shared_keystrokes("j").await;
748 cx.shared_state().await.assert_eq(indoc! {"
749 123456789012aaaa
750 123456789012😃ˇ😃"
751 });
752
753 cx.set_shared_state(indoc! {"
754 123456789012aaˇaa
755 123456789012😃😃"
756 })
757 .await;
758 cx.simulate_shared_keystrokes("j").await;
759 cx.shared_state().await.assert_eq(indoc! {"
760 123456789012aaaa
761 123456789012😃ˇ😃"
762 });
763
764 cx.set_shared_state(indoc! {"
765 123456789012aaaaˇaaaaaaaa123456789012
766 wow
767 123456789012😃😃😃😃😃😃123456789012"
768 })
769 .await;
770 cx.simulate_shared_keystrokes("j j").await;
771 cx.shared_state().await.assert_eq(indoc! {"
772 123456789012aaaaaaaaaaaa123456789012
773 wow
774 123456789012😃😃ˇ😃😃😃😃123456789012"
775 });
776}
777
778#[gpui::test]
779async fn test_wrapped_delete_end_document(cx: &mut gpui::TestAppContext) {
780 let mut cx = NeovimBackedTestContext::new(cx).await;
781
782 cx.set_shared_wrap(12).await;
783
784 cx.set_shared_state(indoc! {"
785 aaˇaaaaaaaaaaaaaaaaaa
786 bbbbbbbbbbbbbbbbbbbb
787 cccccccccccccccccccc"
788 })
789 .await;
790 cx.simulate_shared_keystrokes("d shift-g i z z z").await;
791 cx.shared_state().await.assert_eq(indoc! {"
792 zzzˇ"
793 });
794}
795
796#[gpui::test]
797async fn test_paragraphs_dont_wrap(cx: &mut gpui::TestAppContext) {
798 let mut cx = NeovimBackedTestContext::new(cx).await;
799
800 cx.set_shared_state(indoc! {"
801 one
802 ˇ
803 two"})
804 .await;
805
806 cx.simulate_shared_keystrokes("} }").await;
807 cx.shared_state().await.assert_eq(indoc! {"
808 one
809
810 twˇo"});
811
812 cx.simulate_shared_keystrokes("{ { {").await;
813 cx.shared_state().await.assert_eq(indoc! {"
814 ˇone
815
816 two"});
817}
818
819#[gpui::test]
820async fn test_select_all_issue_2170(cx: &mut gpui::TestAppContext) {
821 let mut cx = VimTestContext::new(cx, true).await;
822
823 cx.set_state(
824 indoc! {"
825 defmodule Test do
826 def test(a, ˇ[_, _] = b), do: IO.puts('hi')
827 end
828 "},
829 Mode::Normal,
830 );
831 cx.simulate_keystrokes("g a");
832 cx.assert_state(
833 indoc! {"
834 defmodule Test do
835 def test(a, «[ˇ»_, _] = b), do: IO.puts('hi')
836 end
837 "},
838 Mode::Visual,
839 );
840}
841
842#[gpui::test]
843async fn test_jk(cx: &mut gpui::TestAppContext) {
844 let mut cx = NeovimBackedTestContext::new(cx).await;
845
846 cx.update(|cx| {
847 cx.bind_keys([KeyBinding::new(
848 "j k",
849 NormalBefore,
850 Some("vim_mode == insert"),
851 )])
852 });
853 cx.neovim.exec("imap jk <esc>").await;
854
855 cx.set_shared_state("ˇhello").await;
856 cx.simulate_shared_keystrokes("i j o j k").await;
857 cx.shared_state().await.assert_eq("jˇohello");
858}
859
860#[gpui::test]
861async fn test_jk_delay(cx: &mut gpui::TestAppContext) {
862 let mut cx = VimTestContext::new(cx, true).await;
863
864 cx.update(|cx| {
865 cx.bind_keys([KeyBinding::new(
866 "j k",
867 NormalBefore,
868 Some("vim_mode == insert"),
869 )])
870 });
871
872 cx.set_state("ˇhello", Mode::Normal);
873 cx.simulate_keystrokes("i j");
874 cx.executor().advance_clock(Duration::from_millis(500));
875 cx.run_until_parked();
876 cx.assert_state("ˇhello", Mode::Insert);
877 cx.executor().advance_clock(Duration::from_millis(500));
878 cx.run_until_parked();
879 cx.assert_state("jˇhello", Mode::Insert);
880 cx.simulate_keystrokes("k j k");
881 cx.assert_state("jˇkhello", Mode::Normal);
882}
883
884#[gpui::test]
885async fn test_comma_w(cx: &mut gpui::TestAppContext) {
886 let mut cx = NeovimBackedTestContext::new(cx).await;
887
888 cx.update(|cx| {
889 cx.bind_keys([KeyBinding::new(
890 ", w",
891 motion::Down {
892 display_lines: false,
893 },
894 Some("vim_mode == normal"),
895 )])
896 });
897 cx.neovim.exec("map ,w j").await;
898
899 cx.set_shared_state("ˇhello hello\nhello hello").await;
900 cx.simulate_shared_keystrokes("f o ; , w").await;
901 cx.shared_state()
902 .await
903 .assert_eq("hello hello\nhello hellˇo");
904
905 cx.set_shared_state("ˇhello hello\nhello hello").await;
906 cx.simulate_shared_keystrokes("f o ; , i").await;
907 cx.shared_state()
908 .await
909 .assert_eq("hellˇo hello\nhello hello");
910}
911
912#[gpui::test]
913async fn test_rename(cx: &mut gpui::TestAppContext) {
914 let mut cx = VimTestContext::new_typescript(cx).await;
915
916 cx.set_state("const beˇfore = 2; console.log(before)", Mode::Normal);
917 let def_range = cx.lsp_range("const «beforeˇ» = 2; console.log(before)");
918 let tgt_range = cx.lsp_range("const before = 2; console.log(«beforeˇ»)");
919 let mut prepare_request =
920 cx.handle_request::<lsp::request::PrepareRenameRequest, _, _>(move |_, _, _| async move {
921 Ok(Some(lsp::PrepareRenameResponse::Range(def_range)))
922 });
923 let mut rename_request =
924 cx.handle_request::<lsp::request::Rename, _, _>(move |url, params, _| async move {
925 Ok(Some(lsp::WorkspaceEdit {
926 changes: Some(
927 [(
928 url.clone(),
929 vec![
930 lsp::TextEdit::new(def_range, params.new_name.clone()),
931 lsp::TextEdit::new(tgt_range, params.new_name),
932 ],
933 )]
934 .into(),
935 ),
936 ..Default::default()
937 }))
938 });
939
940 cx.simulate_keystrokes("c d");
941 prepare_request.next().await.unwrap();
942 cx.simulate_input("after");
943 cx.simulate_keystrokes("enter");
944 rename_request.next().await.unwrap();
945 cx.assert_state("const afterˇ = 2; console.log(after)", Mode::Normal)
946}
947
948// TODO: this test is flaky on our linux CI machines
949#[cfg(target_os = "macos")]
950#[gpui::test]
951async fn test_remap(cx: &mut gpui::TestAppContext) {
952 let mut cx = VimTestContext::new(cx, true).await;
953
954 // test moving the cursor
955 cx.update(|cx| {
956 cx.bind_keys([KeyBinding::new(
957 "g z",
958 workspace::SendKeystrokes("l l l l".to_string()),
959 None,
960 )])
961 });
962 cx.set_state("ˇ123456789", Mode::Normal);
963 cx.simulate_keystrokes("g z");
964 cx.assert_state("1234ˇ56789", Mode::Normal);
965
966 // test switching modes
967 cx.update(|cx| {
968 cx.bind_keys([KeyBinding::new(
969 "g y",
970 workspace::SendKeystrokes("i f o o escape l".to_string()),
971 None,
972 )])
973 });
974 cx.set_state("ˇ123456789", Mode::Normal);
975 cx.simulate_keystrokes("g y");
976 cx.assert_state("fooˇ123456789", Mode::Normal);
977
978 // test recursion
979 cx.update(|cx| {
980 cx.bind_keys([KeyBinding::new(
981 "g x",
982 workspace::SendKeystrokes("g z g y".to_string()),
983 None,
984 )])
985 });
986 cx.set_state("ˇ123456789", Mode::Normal);
987 cx.simulate_keystrokes("g x");
988 cx.assert_state("1234fooˇ56789", Mode::Normal);
989
990 cx.executor().allow_parking();
991
992 // test command
993 cx.update(|cx| {
994 cx.bind_keys([KeyBinding::new(
995 "g w",
996 workspace::SendKeystrokes(": j enter".to_string()),
997 None,
998 )])
999 });
1000 cx.set_state("ˇ1234\n56789", Mode::Normal);
1001 cx.simulate_keystrokes("g w");
1002 cx.assert_state("1234ˇ 56789", Mode::Normal);
1003
1004 // test leaving command
1005 cx.update(|cx| {
1006 cx.bind_keys([KeyBinding::new(
1007 "g u",
1008 workspace::SendKeystrokes("g w g z".to_string()),
1009 None,
1010 )])
1011 });
1012 cx.set_state("ˇ1234\n56789", Mode::Normal);
1013 cx.simulate_keystrokes("g u");
1014 cx.assert_state("1234 567ˇ89", Mode::Normal);
1015
1016 // test leaving command
1017 cx.update(|cx| {
1018 cx.bind_keys([KeyBinding::new(
1019 "g t",
1020 workspace::SendKeystrokes("i space escape".to_string()),
1021 None,
1022 )])
1023 });
1024 cx.set_state("12ˇ34", Mode::Normal);
1025 cx.simulate_keystrokes("g t");
1026 cx.assert_state("12ˇ 34", Mode::Normal);
1027}
1028
1029#[gpui::test]
1030async fn test_undo(cx: &mut gpui::TestAppContext) {
1031 let mut cx = NeovimBackedTestContext::new(cx).await;
1032
1033 cx.set_shared_state("hello quˇoel world").await;
1034 cx.simulate_shared_keystrokes("v i w s c o escape u").await;
1035 cx.shared_state().await.assert_eq("hello ˇquoel world");
1036 cx.simulate_shared_keystrokes("ctrl-r").await;
1037 cx.shared_state().await.assert_eq("hello ˇco world");
1038 cx.simulate_shared_keystrokes("a o right l escape").await;
1039 cx.shared_state().await.assert_eq("hello cooˇl world");
1040 cx.simulate_shared_keystrokes("u").await;
1041 cx.shared_state().await.assert_eq("hello cooˇ world");
1042 cx.simulate_shared_keystrokes("u").await;
1043 cx.shared_state().await.assert_eq("hello cˇo world");
1044 cx.simulate_shared_keystrokes("u").await;
1045 cx.shared_state().await.assert_eq("hello ˇquoel world");
1046
1047 cx.set_shared_state("hello quˇoel world").await;
1048 cx.simulate_shared_keystrokes("v i w ~ u").await;
1049 cx.shared_state().await.assert_eq("hello ˇquoel world");
1050
1051 cx.set_shared_state("\nhello quˇoel world\n").await;
1052 cx.simulate_shared_keystrokes("shift-v s c escape u").await;
1053 cx.shared_state().await.assert_eq("\nˇhello quoel world\n");
1054
1055 cx.set_shared_state(indoc! {"
1056 ˇ1
1057 2
1058 3"})
1059 .await;
1060
1061 cx.simulate_shared_keystrokes("ctrl-v shift-g ctrl-a").await;
1062 cx.shared_state().await.assert_eq(indoc! {"
1063 ˇ2
1064 3
1065 4"});
1066
1067 cx.simulate_shared_keystrokes("u").await;
1068 cx.shared_state().await.assert_eq(indoc! {"
1069 ˇ1
1070 2
1071 3"});
1072}
1073
1074#[gpui::test]
1075async fn test_mouse_selection(cx: &mut TestAppContext) {
1076 let mut cx = VimTestContext::new(cx, true).await;
1077
1078 cx.set_state("ˇone two three", Mode::Normal);
1079
1080 let start_point = cx.pixel_position("one twˇo three");
1081 let end_point = cx.pixel_position("one ˇtwo three");
1082
1083 cx.simulate_mouse_down(start_point, MouseButton::Left, Modifiers::none());
1084 cx.simulate_mouse_move(end_point, MouseButton::Left, Modifiers::none());
1085 cx.simulate_mouse_up(end_point, MouseButton::Left, Modifiers::none());
1086
1087 cx.assert_state("one «ˇtwo» three", Mode::Visual)
1088}
1089
1090#[gpui::test]
1091async fn test_lowercase_marks(cx: &mut TestAppContext) {
1092 let mut cx = NeovimBackedTestContext::new(cx).await;
1093
1094 cx.set_shared_state("line one\nline ˇtwo\nline three").await;
1095 cx.simulate_shared_keystrokes("m a l ' a").await;
1096 cx.shared_state()
1097 .await
1098 .assert_eq("line one\nˇline two\nline three");
1099 cx.simulate_shared_keystrokes("` a").await;
1100 cx.shared_state()
1101 .await
1102 .assert_eq("line one\nline ˇtwo\nline three");
1103
1104 cx.simulate_shared_keystrokes("^ d ` a").await;
1105 cx.shared_state()
1106 .await
1107 .assert_eq("line one\nˇtwo\nline three");
1108}
1109
1110#[gpui::test]
1111async fn test_lt_gt_marks(cx: &mut TestAppContext) {
1112 let mut cx = NeovimBackedTestContext::new(cx).await;
1113
1114 cx.set_shared_state(indoc!(
1115 "
1116 Line one
1117 Line two
1118 Line ˇthree
1119 Line four
1120 Line five
1121 "
1122 ))
1123 .await;
1124
1125 cx.simulate_shared_keystrokes("v j escape k k").await;
1126
1127 cx.simulate_shared_keystrokes("' <").await;
1128 cx.shared_state().await.assert_eq(indoc! {"
1129 Line one
1130 Line two
1131 ˇLine three
1132 Line four
1133 Line five
1134 "});
1135
1136 cx.simulate_shared_keystrokes("` <").await;
1137 cx.shared_state().await.assert_eq(indoc! {"
1138 Line one
1139 Line two
1140 Line ˇthree
1141 Line four
1142 Line five
1143 "});
1144
1145 cx.simulate_shared_keystrokes("' >").await;
1146 cx.shared_state().await.assert_eq(indoc! {"
1147 Line one
1148 Line two
1149 Line three
1150 ˇLine four
1151 Line five
1152 "
1153 });
1154
1155 cx.simulate_shared_keystrokes("` >").await;
1156 cx.shared_state().await.assert_eq(indoc! {"
1157 Line one
1158 Line two
1159 Line three
1160 Line ˇfour
1161 Line five
1162 "
1163 });
1164
1165 cx.simulate_shared_keystrokes("v i w o escape").await;
1166 cx.simulate_shared_keystrokes("` >").await;
1167 cx.shared_state().await.assert_eq(indoc! {"
1168 Line one
1169 Line two
1170 Line three
1171 Line fouˇr
1172 Line five
1173 "
1174 });
1175 cx.simulate_shared_keystrokes("` <").await;
1176 cx.shared_state().await.assert_eq(indoc! {"
1177 Line one
1178 Line two
1179 Line three
1180 Line ˇfour
1181 Line five
1182 "
1183 });
1184}
1185
1186#[gpui::test]
1187async fn test_caret_mark(cx: &mut TestAppContext) {
1188 let mut cx = NeovimBackedTestContext::new(cx).await;
1189
1190 cx.set_shared_state(indoc!(
1191 "
1192 Line one
1193 Line two
1194 Line three
1195 ˇLine four
1196 Line five
1197 "
1198 ))
1199 .await;
1200
1201 cx.simulate_shared_keystrokes("c w shift-s t r a i g h t space t h i n g escape j j")
1202 .await;
1203
1204 cx.simulate_shared_keystrokes("' ^").await;
1205 cx.shared_state().await.assert_eq(indoc! {"
1206 Line one
1207 Line two
1208 Line three
1209 ˇStraight thing four
1210 Line five
1211 "
1212 });
1213
1214 cx.simulate_shared_keystrokes("` ^").await;
1215 cx.shared_state().await.assert_eq(indoc! {"
1216 Line one
1217 Line two
1218 Line three
1219 Straight thingˇ four
1220 Line five
1221 "
1222 });
1223
1224 cx.simulate_shared_keystrokes("k a ! escape k g i ?").await;
1225 cx.shared_state().await.assert_eq(indoc! {"
1226 Line one
1227 Line two
1228 Line three!?ˇ
1229 Straight thing four
1230 Line five
1231 "
1232 });
1233}
1234
1235#[cfg(target_os = "macos")]
1236#[gpui::test]
1237async fn test_dw_eol(cx: &mut gpui::TestAppContext) {
1238 let mut cx = NeovimBackedTestContext::new(cx).await;
1239
1240 cx.set_shared_wrap(12).await;
1241 cx.set_shared_state("twelve ˇchar twelve char\ntwelve char")
1242 .await;
1243 cx.simulate_shared_keystrokes("d w").await;
1244 cx.shared_state()
1245 .await
1246 .assert_eq("twelve ˇtwelve char\ntwelve char");
1247}
1248
1249#[gpui::test]
1250async fn test_toggle_comments(cx: &mut gpui::TestAppContext) {
1251 let mut cx = VimTestContext::new(cx, true).await;
1252
1253 let language = std::sync::Arc::new(language::Language::new(
1254 language::LanguageConfig {
1255 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
1256 ..Default::default()
1257 },
1258 Some(language::tree_sitter_rust::LANGUAGE.into()),
1259 ));
1260 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
1261
1262 // works in normal model
1263 cx.set_state(
1264 indoc! {"
1265 ˇone
1266 two
1267 three
1268 "},
1269 Mode::Normal,
1270 );
1271 cx.simulate_keystrokes("g c c");
1272 cx.assert_state(
1273 indoc! {"
1274 // ˇone
1275 two
1276 three
1277 "},
1278 Mode::Normal,
1279 );
1280
1281 // works in visual mode
1282 cx.simulate_keystrokes("v j g c");
1283 cx.assert_state(
1284 indoc! {"
1285 // // ˇone
1286 // two
1287 three
1288 "},
1289 Mode::Normal,
1290 );
1291
1292 // works in visual line mode
1293 cx.simulate_keystrokes("shift-v j g c");
1294 cx.assert_state(
1295 indoc! {"
1296 // ˇone
1297 two
1298 three
1299 "},
1300 Mode::Normal,
1301 );
1302
1303 // works with count
1304 cx.simulate_keystrokes("g c 2 j");
1305 cx.assert_state(
1306 indoc! {"
1307 // // ˇone
1308 // two
1309 // three
1310 "},
1311 Mode::Normal,
1312 );
1313
1314 // works with motion object
1315 cx.simulate_keystrokes("shift-g");
1316 cx.simulate_keystrokes("g c g g");
1317 cx.assert_state(
1318 indoc! {"
1319 // one
1320 two
1321 three
1322 ˇ"},
1323 Mode::Normal,
1324 );
1325}
1326
1327#[gpui::test]
1328async fn test_find_multibyte(cx: &mut gpui::TestAppContext) {
1329 let mut cx = NeovimBackedTestContext::new(cx).await;
1330
1331 cx.set_shared_state(r#"<label for="guests">ˇPočet hostů</label>"#)
1332 .await;
1333
1334 cx.simulate_shared_keystrokes("c t < o escape").await;
1335 cx.shared_state()
1336 .await
1337 .assert_eq(r#"<label for="guests">ˇo</label>"#);
1338}
1339
1340#[gpui::test]
1341async fn test_sneak(cx: &mut gpui::TestAppContext) {
1342 let mut cx = VimTestContext::new(cx, true).await;
1343
1344 cx.update(|cx| {
1345 cx.bind_keys([
1346 KeyBinding::new(
1347 "s",
1348 PushOperator(Operator::Sneak { first_char: None }),
1349 Some("vim_mode == normal"),
1350 ),
1351 KeyBinding::new(
1352 "S",
1353 PushOperator(Operator::SneakBackward { first_char: None }),
1354 Some("vim_mode == normal"),
1355 ),
1356 KeyBinding::new(
1357 "S",
1358 PushOperator(Operator::SneakBackward { first_char: None }),
1359 Some("vim_mode == visual"),
1360 ),
1361 ])
1362 });
1363
1364 // Sneak forwards multibyte & multiline
1365 cx.set_state(
1366 indoc! {
1367 r#"<labelˇ for="guests">
1368 Počet hostů
1369 </label>"#
1370 },
1371 Mode::Normal,
1372 );
1373 cx.simulate_keystrokes("s t ů");
1374 cx.assert_state(
1375 indoc! {
1376 r#"<label for="guests">
1377 Počet hosˇtů
1378 </label>"#
1379 },
1380 Mode::Normal,
1381 );
1382
1383 // Visual sneak backwards multibyte & multiline
1384 cx.simulate_keystrokes("v S < l");
1385 cx.assert_state(
1386 indoc! {
1387 r#"«ˇ<label for="guests">
1388 Počet host»ů
1389 </label>"#
1390 },
1391 Mode::Visual,
1392 );
1393
1394 // Sneak backwards repeated
1395 cx.set_state(r#"11 12 13 ˇ14"#, Mode::Normal);
1396 cx.simulate_keystrokes("S space 1");
1397 cx.assert_state(r#"11 12ˇ 13 14"#, Mode::Normal);
1398 cx.simulate_keystrokes(";");
1399 cx.assert_state(r#"11ˇ 12 13 14"#, Mode::Normal);
1400}
1401
1402#[gpui::test]
1403async fn test_plus_minus(cx: &mut gpui::TestAppContext) {
1404 let mut cx = NeovimBackedTestContext::new(cx).await;
1405
1406 cx.set_shared_state(indoc! {
1407 "one
1408 two
1409 thrˇee
1410 "})
1411 .await;
1412
1413 cx.simulate_shared_keystrokes("-").await;
1414 cx.shared_state().await.assert_matches();
1415 cx.simulate_shared_keystrokes("-").await;
1416 cx.shared_state().await.assert_matches();
1417 cx.simulate_shared_keystrokes("+").await;
1418 cx.shared_state().await.assert_matches();
1419}
1420
1421#[gpui::test]
1422async fn test_command_alias(cx: &mut gpui::TestAppContext) {
1423 let mut cx = VimTestContext::new(cx, true).await;
1424 cx.update_global(|store: &mut SettingsStore, cx| {
1425 store.update_user_settings::<WorkspaceSettings>(cx, |s| {
1426 let mut aliases = HashMap::default();
1427 aliases.insert("Q".to_string(), "upper".to_string());
1428 s.command_aliases = Some(aliases)
1429 });
1430 });
1431
1432 cx.set_state("ˇhello world", Mode::Normal);
1433 cx.simulate_keystrokes(": Q");
1434 cx.set_state("ˇHello world", Mode::Normal);
1435}
1436
1437#[gpui::test]
1438async fn test_remap_adjacent_dog_cat(cx: &mut gpui::TestAppContext) {
1439 let mut cx = NeovimBackedTestContext::new(cx).await;
1440 cx.update(|cx| {
1441 cx.bind_keys([
1442 KeyBinding::new(
1443 "d o g",
1444 workspace::SendKeystrokes("🐶".to_string()),
1445 Some("vim_mode == insert"),
1446 ),
1447 KeyBinding::new(
1448 "c a t",
1449 workspace::SendKeystrokes("🐱".to_string()),
1450 Some("vim_mode == insert"),
1451 ),
1452 ])
1453 });
1454 cx.neovim.exec("imap dog 🐶").await;
1455 cx.neovim.exec("imap cat 🐱").await;
1456
1457 cx.set_shared_state("ˇ").await;
1458 cx.simulate_shared_keystrokes("i d o g").await;
1459 cx.shared_state().await.assert_eq("🐶ˇ");
1460
1461 cx.set_shared_state("ˇ").await;
1462 cx.simulate_shared_keystrokes("i d o d o g").await;
1463 cx.shared_state().await.assert_eq("do🐶ˇ");
1464
1465 cx.set_shared_state("ˇ").await;
1466 cx.simulate_shared_keystrokes("i d o c a t").await;
1467 cx.shared_state().await.assert_eq("do🐱ˇ");
1468}
1469
1470#[gpui::test]
1471async fn test_remap_nested_pineapple(cx: &mut gpui::TestAppContext) {
1472 let mut cx = NeovimBackedTestContext::new(cx).await;
1473 cx.update(|cx| {
1474 cx.bind_keys([
1475 KeyBinding::new(
1476 "p i n",
1477 workspace::SendKeystrokes("📌".to_string()),
1478 Some("vim_mode == insert"),
1479 ),
1480 KeyBinding::new(
1481 "p i n e",
1482 workspace::SendKeystrokes("🌲".to_string()),
1483 Some("vim_mode == insert"),
1484 ),
1485 KeyBinding::new(
1486 "p i n e a p p l e",
1487 workspace::SendKeystrokes("🍍".to_string()),
1488 Some("vim_mode == insert"),
1489 ),
1490 ])
1491 });
1492 cx.neovim.exec("imap pin 📌").await;
1493 cx.neovim.exec("imap pine 🌲").await;
1494 cx.neovim.exec("imap pineapple 🍍").await;
1495
1496 cx.set_shared_state("ˇ").await;
1497 cx.simulate_shared_keystrokes("i p i n").await;
1498 cx.executor().advance_clock(Duration::from_millis(1000));
1499 cx.run_until_parked();
1500 cx.shared_state().await.assert_eq("📌ˇ");
1501
1502 cx.set_shared_state("ˇ").await;
1503 cx.simulate_shared_keystrokes("i p i n e").await;
1504 cx.executor().advance_clock(Duration::from_millis(1000));
1505 cx.run_until_parked();
1506 cx.shared_state().await.assert_eq("🌲ˇ");
1507
1508 cx.set_shared_state("ˇ").await;
1509 cx.simulate_shared_keystrokes("i p i n e a p p l e").await;
1510 cx.shared_state().await.assert_eq("🍍ˇ");
1511}
1512
1513#[gpui::test]
1514async fn test_remap_recursion(cx: &mut gpui::TestAppContext) {
1515 let mut cx = NeovimBackedTestContext::new(cx).await;
1516 cx.update(|cx| {
1517 cx.bind_keys([KeyBinding::new(
1518 "x",
1519 workspace::SendKeystrokes("\" _ x".to_string()),
1520 Some("VimControl"),
1521 )]);
1522 cx.bind_keys([KeyBinding::new(
1523 "y",
1524 workspace::SendKeystrokes("2 x".to_string()),
1525 Some("VimControl"),
1526 )])
1527 });
1528 cx.neovim.exec("noremap x \"_x").await;
1529 cx.neovim.exec("map y 2x").await;
1530
1531 cx.set_shared_state("ˇhello").await;
1532 cx.simulate_shared_keystrokes("d l").await;
1533 cx.shared_clipboard().await.assert_eq("h");
1534 cx.simulate_shared_keystrokes("y").await;
1535 cx.shared_clipboard().await.assert_eq("h");
1536 cx.shared_state().await.assert_eq("ˇlo");
1537}
1538
1539#[gpui::test]
1540async fn test_escape_while_waiting(cx: &mut gpui::TestAppContext) {
1541 let mut cx = NeovimBackedTestContext::new(cx).await;
1542 cx.set_shared_state("ˇhi").await;
1543 cx.simulate_shared_keystrokes("\" + escape x").await;
1544 cx.shared_state().await.assert_eq("ˇi");
1545}
1546
1547#[gpui::test]
1548async fn test_ctrl_w_override(cx: &mut gpui::TestAppContext) {
1549 let mut cx = NeovimBackedTestContext::new(cx).await;
1550 cx.update(|cx| {
1551 cx.bind_keys([KeyBinding::new("ctrl-w", DeleteLine, None)]);
1552 });
1553 cx.neovim.exec("map <c-w> D").await;
1554 cx.set_shared_state("ˇhi").await;
1555 cx.simulate_shared_keystrokes("ctrl-w").await;
1556 cx.shared_state().await.assert_eq("ˇ");
1557}
1558
1559#[gpui::test]
1560async fn test_visual_indent_count(cx: &mut gpui::TestAppContext) {
1561 let mut cx = VimTestContext::new(cx, true).await;
1562 cx.set_state("ˇhi", Mode::Normal);
1563 cx.simulate_keystrokes("shift-v 3 >");
1564 cx.assert_state(" ˇhi", Mode::Normal);
1565 cx.simulate_keystrokes("shift-v 2 <");
1566 cx.assert_state(" ˇhi", Mode::Normal);
1567}
1568
1569#[gpui::test]
1570async fn test_record_replay_recursion(cx: &mut gpui::TestAppContext) {
1571 let mut cx = NeovimBackedTestContext::new(cx).await;
1572
1573 cx.set_shared_state("ˇhello world").await;
1574 cx.simulate_shared_keystrokes(">").await;
1575 cx.simulate_shared_keystrokes(".").await;
1576 cx.simulate_shared_keystrokes(".").await;
1577 cx.simulate_shared_keystrokes(".").await;
1578 cx.shared_state().await.assert_eq("ˇhello world");
1579}
1580
1581#[gpui::test]
1582async fn test_blackhole_register(cx: &mut gpui::TestAppContext) {
1583 let mut cx = NeovimBackedTestContext::new(cx).await;
1584
1585 cx.set_shared_state("ˇhello world").await;
1586 cx.simulate_shared_keystrokes("d i w \" _ d a w").await;
1587 cx.simulate_shared_keystrokes("p").await;
1588 cx.shared_state().await.assert_eq("hellˇo");
1589}
1590
1591#[gpui::test]
1592async fn test_sentence_backwards(cx: &mut gpui::TestAppContext) {
1593 let mut cx = NeovimBackedTestContext::new(cx).await;
1594
1595 cx.set_shared_state("one\n\ntwo\nthree\nˇ\nfour").await;
1596 cx.simulate_shared_keystrokes("(").await;
1597 cx.shared_state()
1598 .await
1599 .assert_eq("one\n\nˇtwo\nthree\n\nfour");
1600
1601 cx.set_shared_state("hello.\n\n\nworˇld.").await;
1602 cx.simulate_shared_keystrokes("(").await;
1603 cx.shared_state().await.assert_eq("hello.\n\n\nˇworld.");
1604 cx.simulate_shared_keystrokes("(").await;
1605 cx.shared_state().await.assert_eq("hello.\n\nˇ\nworld.");
1606 cx.simulate_shared_keystrokes("(").await;
1607 cx.shared_state().await.assert_eq("ˇhello.\n\n\nworld.");
1608
1609 cx.set_shared_state("hello. worlˇd.").await;
1610 cx.simulate_shared_keystrokes("(").await;
1611 cx.shared_state().await.assert_eq("hello. ˇworld.");
1612 cx.simulate_shared_keystrokes("(").await;
1613 cx.shared_state().await.assert_eq("ˇhello. world.");
1614
1615 cx.set_shared_state(". helˇlo.").await;
1616 cx.simulate_shared_keystrokes("(").await;
1617 cx.shared_state().await.assert_eq(". ˇhello.");
1618 cx.simulate_shared_keystrokes("(").await;
1619 cx.shared_state().await.assert_eq(". ˇhello.");
1620
1621 cx.set_shared_state(indoc! {
1622 "{
1623 hello_world();
1624 ˇ}"
1625 })
1626 .await;
1627 cx.simulate_shared_keystrokes("(").await;
1628 cx.shared_state().await.assert_eq(indoc! {
1629 "ˇ{
1630 hello_world();
1631 }"
1632 });
1633
1634 cx.set_shared_state(indoc! {
1635 "Hello! World..?
1636
1637 \tHello! World... ˇ"
1638 })
1639 .await;
1640 cx.simulate_shared_keystrokes("(").await;
1641 cx.shared_state().await.assert_eq(indoc! {
1642 "Hello! World..?
1643
1644 \tHello! ˇWorld... "
1645 });
1646 cx.simulate_shared_keystrokes("(").await;
1647 cx.shared_state().await.assert_eq(indoc! {
1648 "Hello! World..?
1649
1650 \tˇHello! World... "
1651 });
1652 cx.simulate_shared_keystrokes("(").await;
1653 cx.shared_state().await.assert_eq(indoc! {
1654 "Hello! World..?
1655 ˇ
1656 \tHello! World... "
1657 });
1658 cx.simulate_shared_keystrokes("(").await;
1659 cx.shared_state().await.assert_eq(indoc! {
1660 "Hello! ˇWorld..?
1661
1662 \tHello! World... "
1663 });
1664}
1665
1666#[gpui::test]
1667async fn test_sentence_forwards(cx: &mut gpui::TestAppContext) {
1668 let mut cx = NeovimBackedTestContext::new(cx).await;
1669
1670 cx.set_shared_state("helˇlo.\n\n\nworld.").await;
1671 cx.simulate_shared_keystrokes(")").await;
1672 cx.shared_state().await.assert_eq("hello.\nˇ\n\nworld.");
1673 cx.simulate_shared_keystrokes(")").await;
1674 cx.shared_state().await.assert_eq("hello.\n\n\nˇworld.");
1675 cx.simulate_shared_keystrokes(")").await;
1676 cx.shared_state().await.assert_eq("hello.\n\n\nworldˇ.");
1677
1678 cx.set_shared_state("helˇlo.\n\n\nworld.").await;
1679}
1680
1681#[gpui::test]
1682async fn test_ctrl_o_visual(cx: &mut gpui::TestAppContext) {
1683 let mut cx = NeovimBackedTestContext::new(cx).await;
1684
1685 cx.set_shared_state("helloˇ world.").await;
1686 cx.simulate_shared_keystrokes("i ctrl-o v b r l").await;
1687 cx.shared_state().await.assert_eq("ˇllllllworld.");
1688 cx.simulate_shared_keystrokes("ctrl-o v f w d").await;
1689 cx.shared_state().await.assert_eq("ˇorld.");
1690}
1691
1692#[gpui::test]
1693async fn test_ctrl_o_position(cx: &mut gpui::TestAppContext) {
1694 let mut cx = NeovimBackedTestContext::new(cx).await;
1695
1696 cx.set_shared_state("helˇlo world.").await;
1697 cx.simulate_shared_keystrokes("i ctrl-o d i w").await;
1698 cx.shared_state().await.assert_eq("ˇ world.");
1699 cx.simulate_shared_keystrokes("ctrl-o p").await;
1700 cx.shared_state().await.assert_eq(" helloˇworld.");
1701}
1702
1703#[gpui::test]
1704async fn test_ctrl_o_dot(cx: &mut gpui::TestAppContext) {
1705 let mut cx = NeovimBackedTestContext::new(cx).await;
1706
1707 cx.set_shared_state("heˇllo world.").await;
1708 cx.simulate_shared_keystrokes("x i ctrl-o .").await;
1709 cx.shared_state().await.assert_eq("heˇo world.");
1710 cx.simulate_shared_keystrokes("l l escape .").await;
1711 cx.shared_state().await.assert_eq("hellˇllo world.");
1712}