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