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