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