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::{
10 DisplayPoint, Editor, EditorMode, MultiBuffer, actions::DeleteLine, display_map::DisplayRow,
11 test::editor_test_context::EditorTestContext,
12};
13use futures::StreamExt;
14use gpui::{KeyBinding, Modifiers, MouseButton, TestAppContext};
15use language::Point;
16pub use neovim_backed_test_context::*;
17use settings::SettingsStore;
18pub use vim_test_context::*;
19
20use indoc::indoc;
21use search::BufferSearchBar;
22use workspace::WorkspaceSettings;
23
24use crate::{PushSneak, PushSneakBackward, insert::NormalBefore, motion, state::Mode};
25
26#[gpui::test]
27async fn test_initially_disabled(cx: &mut gpui::TestAppContext) {
28 let mut cx = VimTestContext::new(cx, false).await;
29 cx.simulate_keystrokes("h j k l");
30 cx.assert_editor_state("hjklˇ");
31}
32
33#[gpui::test]
34async fn test_neovim(cx: &mut gpui::TestAppContext) {
35 let mut cx = NeovimBackedTestContext::new(cx).await;
36
37 cx.simulate_shared_keystrokes("i").await;
38 cx.shared_state().await.assert_matches();
39 cx.simulate_shared_keystrokes("shift-t e s t space t e s t escape 0 d w")
40 .await;
41 cx.shared_state().await.assert_matches();
42 cx.assert_editor_state("ˇtest");
43}
44
45#[gpui::test]
46async fn test_toggle_through_settings(cx: &mut gpui::TestAppContext) {
47 let mut cx = VimTestContext::new(cx, true).await;
48
49 cx.simulate_keystrokes("i");
50 assert_eq!(cx.mode(), Mode::Insert);
51
52 // Editor acts as though vim is disabled
53 cx.disable_vim();
54 cx.simulate_keystrokes("h j k l");
55 cx.assert_editor_state("hjklˇ");
56
57 // Selections aren't changed if editor is blurred but vim-mode is still disabled.
58 cx.cx.set_state("«hjklˇ»");
59 cx.assert_editor_state("«hjklˇ»");
60 cx.update_editor(|_, window, _cx| window.blur());
61 cx.assert_editor_state("«hjklˇ»");
62 cx.update_editor(|_, window, cx| cx.focus_self(window));
63 cx.assert_editor_state("«hjklˇ»");
64
65 // Enabling dynamically sets vim mode again and restores normal mode
66 cx.enable_vim();
67 assert_eq!(cx.mode(), Mode::Normal);
68 cx.simulate_keystrokes("h h h l");
69 assert_eq!(cx.buffer_text(), "hjkl".to_owned());
70 cx.assert_editor_state("hˇjkl");
71 cx.simulate_keystrokes("i T e s t");
72 cx.assert_editor_state("hTestˇjkl");
73
74 // Disabling and enabling resets to normal mode
75 assert_eq!(cx.mode(), Mode::Insert);
76 cx.disable_vim();
77 cx.enable_vim();
78 assert_eq!(cx.mode(), Mode::Normal);
79}
80
81#[gpui::test]
82async fn test_cancel_selection(cx: &mut gpui::TestAppContext) {
83 let mut cx = VimTestContext::new(cx, true).await;
84
85 cx.set_state(
86 indoc! {"The quick brown fox juˇmps over the lazy dog"},
87 Mode::Normal,
88 );
89 // jumps
90 cx.simulate_keystrokes("v l l");
91 cx.assert_editor_state("The quick brown fox ju«mpsˇ» over the lazy dog");
92
93 cx.simulate_keystrokes("escape");
94 cx.assert_editor_state("The quick brown fox jumpˇs over the lazy dog");
95
96 // go back to the same selection state
97 cx.simulate_keystrokes("v h h");
98 cx.assert_editor_state("The quick brown fox ju«ˇmps» over the lazy dog");
99
100 // Ctrl-[ should behave like Esc
101 cx.simulate_keystrokes("ctrl-[");
102 cx.assert_editor_state("The quick brown fox juˇmps over the lazy dog");
103}
104
105#[gpui::test]
106async fn test_buffer_search(cx: &mut gpui::TestAppContext) {
107 let mut cx = VimTestContext::new(cx, true).await;
108
109 cx.set_state(
110 indoc! {"
111 The quick brown
112 fox juˇmps over
113 the lazy dog"},
114 Mode::Normal,
115 );
116 cx.simulate_keystrokes("/");
117
118 let search_bar = cx.workspace(|workspace, _, cx| {
119 workspace
120 .active_pane()
121 .read(cx)
122 .toolbar()
123 .read(cx)
124 .item_of_type::<BufferSearchBar>()
125 .expect("Buffer search bar should be deployed")
126 });
127
128 cx.update_entity(search_bar, |bar, _, cx| {
129 assert_eq!(bar.query(cx), "");
130 })
131}
132
133#[gpui::test]
134async fn test_count_down(cx: &mut gpui::TestAppContext) {
135 let mut cx = VimTestContext::new(cx, true).await;
136
137 cx.set_state(indoc! {"aˇa\nbb\ncc\ndd\nee"}, Mode::Normal);
138 cx.simulate_keystrokes("2 down");
139 cx.assert_editor_state("aa\nbb\ncˇc\ndd\nee");
140 cx.simulate_keystrokes("9 down");
141 cx.assert_editor_state("aa\nbb\ncc\ndd\neˇe");
142}
143
144#[gpui::test]
145async fn test_end_of_document_710(cx: &mut gpui::TestAppContext) {
146 let mut cx = VimTestContext::new(cx, true).await;
147
148 // goes to end by default
149 cx.set_state(indoc! {"aˇa\nbb\ncc"}, Mode::Normal);
150 cx.simulate_keystrokes("shift-g");
151 cx.assert_editor_state("aa\nbb\ncˇc");
152
153 // can go to line 1 (https://github.com/zed-industries/zed/issues/5812)
154 cx.simulate_keystrokes("1 shift-g");
155 cx.assert_editor_state("aˇa\nbb\ncc");
156}
157
158#[gpui::test]
159async fn test_end_of_line_with_times(cx: &mut gpui::TestAppContext) {
160 let mut cx = VimTestContext::new(cx, true).await;
161
162 // goes to current line end
163 cx.set_state(indoc! {"ˇaa\nbb\ncc"}, Mode::Normal);
164 cx.simulate_keystrokes("$");
165 cx.assert_editor_state("aˇa\nbb\ncc");
166
167 // goes to next line end
168 cx.simulate_keystrokes("2 $");
169 cx.assert_editor_state("aa\nbˇb\ncc");
170
171 // try to exceed the final line.
172 cx.simulate_keystrokes("4 $");
173 cx.assert_editor_state("aa\nbb\ncˇc");
174}
175
176#[gpui::test]
177async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
178 let mut cx = VimTestContext::new(cx, true).await;
179
180 // works in normal mode
181 cx.set_state(indoc! {"aa\nbˇb\ncc"}, Mode::Normal);
182 cx.simulate_keystrokes("> >");
183 cx.assert_editor_state("aa\n bˇb\ncc");
184 cx.simulate_keystrokes("< <");
185 cx.assert_editor_state("aa\nbˇb\ncc");
186
187 // works in visual mode
188 cx.simulate_keystrokes("shift-v down >");
189 cx.assert_editor_state("aa\n bˇb\n cc");
190
191 // works as operator
192 cx.set_state("aa\nbˇb\ncc\n", Mode::Normal);
193 cx.simulate_keystrokes("> j");
194 cx.assert_editor_state("aa\n bˇb\n cc\n");
195 cx.simulate_keystrokes("< k");
196 cx.assert_editor_state("aa\nbˇb\n cc\n");
197 cx.simulate_keystrokes("> i p");
198 cx.assert_editor_state(" aa\n bˇb\n cc\n");
199 cx.simulate_keystrokes("< i p");
200 cx.assert_editor_state("aa\nbˇb\n cc\n");
201 cx.simulate_keystrokes("< i p");
202 cx.assert_editor_state("aa\nbˇb\ncc\n");
203
204 cx.set_state("ˇaa\nbb\ncc\n", Mode::Normal);
205 cx.simulate_keystrokes("> 2 j");
206 cx.assert_editor_state(" ˇaa\n bb\n cc\n");
207
208 cx.set_state("aa\nbb\nˇcc\n", Mode::Normal);
209 cx.simulate_keystrokes("> 2 k");
210 cx.assert_editor_state(" aa\n bb\n ˇcc\n");
211
212 // works with repeat
213 cx.set_state("a\nb\nccˇc\n", Mode::Normal);
214 cx.simulate_keystrokes("> 2 k");
215 cx.assert_editor_state(" a\n b\n ccˇc\n");
216 cx.simulate_keystrokes(".");
217 cx.assert_editor_state(" a\n b\n ccˇc\n");
218 cx.simulate_keystrokes("v k <");
219 cx.assert_editor_state(" a\n bˇ\n ccc\n");
220 cx.simulate_keystrokes(".");
221 cx.assert_editor_state(" a\nbˇ\nccc\n");
222}
223
224#[gpui::test]
225async fn test_escape_command_palette(cx: &mut gpui::TestAppContext) {
226 let mut cx = VimTestContext::new(cx, true).await;
227
228 cx.set_state("aˇbc\n", Mode::Normal);
229 cx.simulate_keystrokes("i cmd-shift-p");
230
231 assert!(
232 cx.workspace(|workspace, _, cx| workspace.active_modal::<CommandPalette>(cx).is_some())
233 );
234 cx.simulate_keystrokes("escape");
235 cx.run_until_parked();
236 assert!(
237 !cx.workspace(|workspace, _, cx| workspace.active_modal::<CommandPalette>(cx).is_some())
238 );
239 cx.assert_state("aˇbc\n", Mode::Insert);
240}
241
242#[gpui::test]
243async fn test_escape_cancels(cx: &mut gpui::TestAppContext) {
244 let mut cx = VimTestContext::new(cx, true).await;
245
246 cx.set_state("aˇbˇc", Mode::Normal);
247 cx.simulate_keystrokes("escape");
248
249 cx.assert_state("aˇbc", Mode::Normal);
250}
251
252#[gpui::test]
253async fn test_selection_on_search(cx: &mut gpui::TestAppContext) {
254 let mut cx = VimTestContext::new(cx, true).await;
255
256 cx.set_state(indoc! {"aa\nbˇb\ncc\ncc\ncc\n"}, Mode::Normal);
257 cx.simulate_keystrokes("/ c c");
258
259 let search_bar = cx.workspace(|workspace, _, cx| {
260 workspace
261 .active_pane()
262 .read(cx)
263 .toolbar()
264 .read(cx)
265 .item_of_type::<BufferSearchBar>()
266 .expect("Buffer search bar should be deployed")
267 });
268
269 cx.update_entity(search_bar, |bar, _, cx| {
270 assert_eq!(bar.query(cx), "cc");
271 });
272
273 cx.update_editor(|editor, window, cx| {
274 let highlights = editor.all_text_background_highlights(window, cx);
275 assert_eq!(3, highlights.len());
276 assert_eq!(
277 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 2),
278 highlights[0].0
279 )
280 });
281 cx.simulate_keystrokes("enter");
282
283 cx.assert_state(indoc! {"aa\nbb\nˇcc\ncc\ncc\n"}, Mode::Normal);
284 cx.simulate_keystrokes("n");
285 cx.assert_state(indoc! {"aa\nbb\ncc\nˇcc\ncc\n"}, Mode::Normal);
286 cx.simulate_keystrokes("shift-n");
287 cx.assert_state(indoc! {"aa\nbb\nˇcc\ncc\ncc\n"}, Mode::Normal);
288}
289
290#[gpui::test]
291async fn test_word_characters(cx: &mut gpui::TestAppContext) {
292 let mut cx = VimTestContext::new_typescript(cx).await;
293 cx.set_state(
294 indoc! { "
295 class A {
296 #ˇgoop = 99;
297 $ˇgoop () { return this.#gˇoop };
298 };
299 console.log(new A().$gooˇp())
300 "},
301 Mode::Normal,
302 );
303 cx.simulate_keystrokes("v i w");
304 cx.assert_state(
305 indoc! {"
306 class A {
307 «#goopˇ» = 99;
308 «$goopˇ» () { return this.«#goopˇ» };
309 };
310 console.log(new A().«$goopˇ»())
311 "},
312 Mode::Visual,
313 )
314}
315
316#[gpui::test]
317async fn test_kebab_case(cx: &mut gpui::TestAppContext) {
318 let mut cx = VimTestContext::new_html(cx).await;
319 cx.set_state(
320 indoc! { r#"
321 <div><a class="bg-rˇed"></a></div>
322 "#},
323 Mode::Normal,
324 );
325 cx.simulate_keystrokes("v i w");
326 cx.assert_state(
327 indoc! { r#"
328 <div><a class="bg-«redˇ»"></a></div>
329 "#
330 },
331 Mode::Visual,
332 )
333}
334
335#[gpui::test]
336async fn test_join_lines(cx: &mut gpui::TestAppContext) {
337 let mut cx = NeovimBackedTestContext::new(cx).await;
338
339 cx.set_shared_state(indoc! {"
340 ˇone
341 two
342 three
343 four
344 five
345 six
346 "})
347 .await;
348 cx.simulate_shared_keystrokes("shift-j").await;
349 cx.shared_state().await.assert_eq(indoc! {"
350 oneˇ two
351 three
352 four
353 five
354 six
355 "});
356 cx.simulate_shared_keystrokes("3 shift-j").await;
357 cx.shared_state().await.assert_eq(indoc! {"
358 one two threeˇ four
359 five
360 six
361 "});
362
363 cx.set_shared_state(indoc! {"
364 ˇone
365 two
366 three
367 four
368 five
369 six
370 "})
371 .await;
372 cx.simulate_shared_keystrokes("j v 3 j shift-j").await;
373 cx.shared_state().await.assert_eq(indoc! {"
374 one
375 two three fourˇ five
376 six
377 "});
378
379 cx.set_shared_state(indoc! {"
380 ˇone
381 two
382 three
383 four
384 five
385 six
386 "})
387 .await;
388 cx.simulate_shared_keystrokes("g shift-j").await;
389 cx.shared_state().await.assert_eq(indoc! {"
390 oneˇtwo
391 three
392 four
393 five
394 six
395 "});
396 cx.simulate_shared_keystrokes("3 g shift-j").await;
397 cx.shared_state().await.assert_eq(indoc! {"
398 onetwothreeˇfour
399 five
400 six
401 "});
402
403 cx.set_shared_state(indoc! {"
404 ˇone
405 two
406 three
407 four
408 five
409 six
410 "})
411 .await;
412 cx.simulate_shared_keystrokes("j v 3 j g shift-j").await;
413 cx.shared_state().await.assert_eq(indoc! {"
414 one
415 twothreefourˇfive
416 six
417 "});
418}
419
420#[cfg(target_os = "macos")]
421#[gpui::test]
422async fn test_wrapped_lines(cx: &mut gpui::TestAppContext) {
423 let mut cx = NeovimBackedTestContext::new(cx).await;
424
425 cx.set_shared_wrap(12).await;
426 // tests line wrap as follows:
427 // 1: twelve char
428 // twelve char
429 // 2: twelve char
430 cx.set_shared_state(indoc! { "
431 tˇwelve char twelve char
432 twelve char
433 "})
434 .await;
435 cx.simulate_shared_keystrokes("j").await;
436 cx.shared_state().await.assert_eq(indoc! {"
437 twelve char twelve char
438 tˇwelve char
439 "});
440 cx.simulate_shared_keystrokes("k").await;
441 cx.shared_state().await.assert_eq(indoc! {"
442 tˇwelve char twelve char
443 twelve char
444 "});
445 cx.simulate_shared_keystrokes("g j").await;
446 cx.shared_state().await.assert_eq(indoc! {"
447 twelve char tˇwelve char
448 twelve char
449 "});
450 cx.simulate_shared_keystrokes("g j").await;
451 cx.shared_state().await.assert_eq(indoc! {"
452 twelve char twelve char
453 tˇwelve char
454 "});
455
456 cx.simulate_shared_keystrokes("g k").await;
457 cx.shared_state().await.assert_eq(indoc! {"
458 twelve char tˇwelve char
459 twelve char
460 "});
461
462 cx.simulate_shared_keystrokes("g ^").await;
463 cx.shared_state().await.assert_eq(indoc! {"
464 twelve char ˇtwelve char
465 twelve char
466 "});
467
468 cx.simulate_shared_keystrokes("^").await;
469 cx.shared_state().await.assert_eq(indoc! {"
470 ˇtwelve char twelve char
471 twelve char
472 "});
473
474 cx.simulate_shared_keystrokes("g $").await;
475 cx.shared_state().await.assert_eq(indoc! {"
476 twelve charˇ twelve char
477 twelve char
478 "});
479 cx.simulate_shared_keystrokes("$").await;
480 cx.shared_state().await.assert_eq(indoc! {"
481 twelve char twelve chaˇr
482 twelve char
483 "});
484
485 cx.set_shared_state(indoc! { "
486 tˇwelve char twelve char
487 twelve char
488 "})
489 .await;
490 cx.simulate_shared_keystrokes("enter").await;
491 cx.shared_state().await.assert_eq(indoc! {"
492 twelve char twelve char
493 ˇtwelve char
494 "});
495
496 cx.set_shared_state(indoc! { "
497 twelve char
498 tˇwelve char twelve char
499 twelve char
500 "})
501 .await;
502 cx.simulate_shared_keystrokes("o o escape").await;
503 cx.shared_state().await.assert_eq(indoc! {"
504 twelve char
505 twelve char twelve char
506 ˇo
507 twelve char
508 "});
509
510 cx.set_shared_state(indoc! { "
511 twelve char
512 tˇwelve char twelve char
513 twelve char
514 "})
515 .await;
516 cx.simulate_shared_keystrokes("shift-a a escape").await;
517 cx.shared_state().await.assert_eq(indoc! {"
518 twelve char
519 twelve char twelve charˇa
520 twelve char
521 "});
522 cx.simulate_shared_keystrokes("shift-i i escape").await;
523 cx.shared_state().await.assert_eq(indoc! {"
524 twelve char
525 ˇitwelve char twelve chara
526 twelve char
527 "});
528 cx.simulate_shared_keystrokes("shift-d").await;
529 cx.shared_state().await.assert_eq(indoc! {"
530 twelve char
531 ˇ
532 twelve char
533 "});
534
535 cx.set_shared_state(indoc! { "
536 twelve char
537 twelve char tˇwelve char
538 twelve char
539 "})
540 .await;
541 cx.simulate_shared_keystrokes("shift-o o escape").await;
542 cx.shared_state().await.assert_eq(indoc! {"
543 twelve char
544 ˇo
545 twelve char twelve char
546 twelve char
547 "});
548
549 // line wraps as:
550 // fourteen ch
551 // ar
552 // fourteen ch
553 // ar
554 cx.set_shared_state(indoc! { "
555 fourteen chaˇr
556 fourteen char
557 "})
558 .await;
559
560 cx.simulate_shared_keystrokes("d i w").await;
561 cx.shared_state().await.assert_eq(indoc! {"
562 fourteenˇ•
563 fourteen char
564 "});
565 cx.simulate_shared_keystrokes("j shift-f e f r").await;
566 cx.shared_state().await.assert_eq(indoc! {"
567 fourteen•
568 fourteen chaˇr
569 "});
570}
571
572#[gpui::test]
573async fn test_folds(cx: &mut gpui::TestAppContext) {
574 let mut cx = NeovimBackedTestContext::new(cx).await;
575 cx.set_neovim_option("foldmethod=manual").await;
576
577 cx.set_shared_state(indoc! { "
578 fn boop() {
579 ˇbarp()
580 bazp()
581 }
582 "})
583 .await;
584 cx.simulate_shared_keystrokes("shift-v j z f").await;
585
586 // visual display is now:
587 // fn boop () {
588 // [FOLDED]
589 // }
590
591 // TODO: this should not be needed but currently zf does not
592 // return to normal mode.
593 cx.simulate_shared_keystrokes("escape").await;
594
595 // skip over fold downward
596 cx.simulate_shared_keystrokes("g g").await;
597 cx.shared_state().await.assert_eq(indoc! {"
598 ˇfn boop() {
599 barp()
600 bazp()
601 }
602 "});
603
604 cx.simulate_shared_keystrokes("j j").await;
605 cx.shared_state().await.assert_eq(indoc! {"
606 fn boop() {
607 barp()
608 bazp()
609 ˇ}
610 "});
611
612 // skip over fold upward
613 cx.simulate_shared_keystrokes("2 k").await;
614 cx.shared_state().await.assert_eq(indoc! {"
615 ˇfn boop() {
616 barp()
617 bazp()
618 }
619 "});
620
621 // yank the fold
622 cx.simulate_shared_keystrokes("down y y").await;
623 cx.shared_clipboard()
624 .await
625 .assert_eq(" barp()\n bazp()\n");
626
627 // re-open
628 cx.simulate_shared_keystrokes("z o").await;
629 cx.shared_state().await.assert_eq(indoc! {"
630 fn boop() {
631 ˇ barp()
632 bazp()
633 }
634 "});
635}
636
637#[gpui::test]
638async fn test_folds_panic(cx: &mut gpui::TestAppContext) {
639 let mut cx = NeovimBackedTestContext::new(cx).await;
640 cx.set_neovim_option("foldmethod=manual").await;
641
642 cx.set_shared_state(indoc! { "
643 fn boop() {
644 ˇbarp()
645 bazp()
646 }
647 "})
648 .await;
649 cx.simulate_shared_keystrokes("shift-v j z f").await;
650 cx.simulate_shared_keystrokes("escape").await;
651 cx.simulate_shared_keystrokes("g g").await;
652 cx.simulate_shared_keystrokes("5 d j").await;
653 cx.shared_state().await.assert_eq("ˇ");
654 cx.set_shared_state(indoc! {"
655 fn boop() {
656 ˇbarp()
657 bazp()
658 }
659 "})
660 .await;
661 cx.simulate_shared_keystrokes("shift-v j j z f").await;
662 cx.simulate_shared_keystrokes("escape").await;
663 cx.simulate_shared_keystrokes("shift-g shift-v").await;
664 cx.shared_state().await.assert_eq(indoc! {"
665 fn boop() {
666 barp()
667 bazp()
668 }
669 ˇ"});
670}
671
672#[gpui::test]
673async fn test_clear_counts(cx: &mut gpui::TestAppContext) {
674 let mut cx = NeovimBackedTestContext::new(cx).await;
675
676 cx.set_shared_state(indoc! {"
677 The quick brown
678 fox juˇmps over
679 the lazy dog"})
680 .await;
681
682 cx.simulate_shared_keystrokes("4 escape 3 d l").await;
683 cx.shared_state().await.assert_eq(indoc! {"
684 The quick brown
685 fox juˇ over
686 the lazy dog"});
687}
688
689#[gpui::test]
690async fn test_zero(cx: &mut gpui::TestAppContext) {
691 let mut cx = NeovimBackedTestContext::new(cx).await;
692
693 cx.set_shared_state(indoc! {"
694 The quˇick brown
695 fox jumps over
696 the lazy dog"})
697 .await;
698
699 cx.simulate_shared_keystrokes("0").await;
700 cx.shared_state().await.assert_eq(indoc! {"
701 ˇThe quick brown
702 fox jumps over
703 the lazy dog"});
704
705 cx.simulate_shared_keystrokes("1 0 l").await;
706 cx.shared_state().await.assert_eq(indoc! {"
707 The quick ˇbrown
708 fox jumps over
709 the lazy dog"});
710}
711
712#[gpui::test]
713async fn test_selection_goal(cx: &mut gpui::TestAppContext) {
714 let mut cx = NeovimBackedTestContext::new(cx).await;
715
716 cx.set_shared_state(indoc! {"
717 ;;ˇ;
718 Lorem Ipsum"})
719 .await;
720
721 cx.simulate_shared_keystrokes("a down up ; down up").await;
722 cx.shared_state().await.assert_eq(indoc! {"
723 ;;;;ˇ
724 Lorem Ipsum"});
725}
726
727#[cfg(target_os = "macos")]
728#[gpui::test]
729async fn test_wrapped_motions(cx: &mut gpui::TestAppContext) {
730 let mut cx = NeovimBackedTestContext::new(cx).await;
731
732 cx.set_shared_wrap(12).await;
733
734 cx.set_shared_state(indoc! {"
735 aaˇaa
736 😃😃"
737 })
738 .await;
739 cx.simulate_shared_keystrokes("j").await;
740 cx.shared_state().await.assert_eq(indoc! {"
741 aaaa
742 😃ˇ😃"
743 });
744
745 cx.set_shared_state(indoc! {"
746 123456789012aaˇaa
747 123456789012😃😃"
748 })
749 .await;
750 cx.simulate_shared_keystrokes("j").await;
751 cx.shared_state().await.assert_eq(indoc! {"
752 123456789012aaaa
753 123456789012😃ˇ😃"
754 });
755
756 cx.set_shared_state(indoc! {"
757 123456789012aaˇaa
758 123456789012😃😃"
759 })
760 .await;
761 cx.simulate_shared_keystrokes("j").await;
762 cx.shared_state().await.assert_eq(indoc! {"
763 123456789012aaaa
764 123456789012😃ˇ😃"
765 });
766
767 cx.set_shared_state(indoc! {"
768 123456789012aaaaˇaaaaaaaa123456789012
769 wow
770 123456789012😃😃😃😃😃😃123456789012"
771 })
772 .await;
773 cx.simulate_shared_keystrokes("j j").await;
774 cx.shared_state().await.assert_eq(indoc! {"
775 123456789012aaaaaaaaaaaa123456789012
776 wow
777 123456789012😃😃ˇ😃😃😃😃123456789012"
778 });
779}
780
781#[gpui::test]
782async fn test_wrapped_delete_end_document(cx: &mut gpui::TestAppContext) {
783 let mut cx = NeovimBackedTestContext::new(cx).await;
784
785 cx.set_shared_wrap(12).await;
786
787 cx.set_shared_state(indoc! {"
788 aaˇaaaaaaaaaaaaaaaaaa
789 bbbbbbbbbbbbbbbbbbbb
790 cccccccccccccccccccc"
791 })
792 .await;
793 cx.simulate_shared_keystrokes("d shift-g i z z z").await;
794 cx.shared_state().await.assert_eq(indoc! {"
795 zzzˇ"
796 });
797}
798
799#[gpui::test]
800async fn test_paragraphs_dont_wrap(cx: &mut gpui::TestAppContext) {
801 let mut cx = NeovimBackedTestContext::new(cx).await;
802
803 cx.set_shared_state(indoc! {"
804 one
805 ˇ
806 two"})
807 .await;
808
809 cx.simulate_shared_keystrokes("} }").await;
810 cx.shared_state().await.assert_eq(indoc! {"
811 one
812
813 twˇo"});
814
815 cx.simulate_shared_keystrokes("{ { {").await;
816 cx.shared_state().await.assert_eq(indoc! {"
817 ˇone
818
819 two"});
820}
821
822#[gpui::test]
823async fn test_select_all_issue_2170(cx: &mut gpui::TestAppContext) {
824 let mut cx = VimTestContext::new(cx, true).await;
825
826 cx.set_state(
827 indoc! {"
828 defmodule Test do
829 def test(a, ˇ[_, _] = b), do: IO.puts('hi')
830 end
831 "},
832 Mode::Normal,
833 );
834 cx.simulate_keystrokes("g a");
835 cx.assert_state(
836 indoc! {"
837 defmodule Test do
838 def test(a, «[ˇ»_, _] = b), do: IO.puts('hi')
839 end
840 "},
841 Mode::Visual,
842 );
843}
844
845#[gpui::test]
846async fn test_jk(cx: &mut gpui::TestAppContext) {
847 let mut cx = NeovimBackedTestContext::new(cx).await;
848
849 cx.update(|_, cx| {
850 cx.bind_keys([KeyBinding::new(
851 "j k",
852 NormalBefore,
853 Some("vim_mode == insert"),
854 )])
855 });
856 cx.neovim.exec("imap jk <esc>").await;
857
858 cx.set_shared_state("ˇhello").await;
859 cx.simulate_shared_keystrokes("i j o j k").await;
860 cx.shared_state().await.assert_eq("jˇohello");
861}
862
863#[gpui::test]
864async fn test_jk_delay(cx: &mut gpui::TestAppContext) {
865 let mut cx = VimTestContext::new(cx, true).await;
866
867 cx.update(|_, cx| {
868 cx.bind_keys([KeyBinding::new(
869 "j k",
870 NormalBefore,
871 Some("vim_mode == insert"),
872 )])
873 });
874
875 cx.set_state("ˇhello", Mode::Normal);
876 cx.simulate_keystrokes("i j");
877 cx.executor().advance_clock(Duration::from_millis(500));
878 cx.run_until_parked();
879 cx.assert_state("ˇhello", Mode::Insert);
880 cx.executor().advance_clock(Duration::from_millis(500));
881 cx.run_until_parked();
882 cx.assert_state("jˇhello", Mode::Insert);
883 cx.simulate_keystrokes("k j k");
884 cx.assert_state("jˇkhello", Mode::Normal);
885}
886
887#[gpui::test]
888async fn test_comma_w(cx: &mut gpui::TestAppContext) {
889 let mut cx = NeovimBackedTestContext::new(cx).await;
890
891 cx.update(|_, cx| {
892 cx.bind_keys([KeyBinding::new(
893 ", w",
894 motion::Down {
895 display_lines: false,
896 },
897 Some("vim_mode == normal"),
898 )])
899 });
900 cx.neovim.exec("map ,w j").await;
901
902 cx.set_shared_state("ˇhello hello\nhello hello").await;
903 cx.simulate_shared_keystrokes("f o ; , w").await;
904 cx.shared_state()
905 .await
906 .assert_eq("hello hello\nhello hellˇo");
907
908 cx.set_shared_state("ˇhello hello\nhello hello").await;
909 cx.simulate_shared_keystrokes("f o ; , i").await;
910 cx.shared_state()
911 .await
912 .assert_eq("hellˇo hello\nhello hello");
913}
914
915#[gpui::test]
916async fn test_rename(cx: &mut gpui::TestAppContext) {
917 let mut cx = VimTestContext::new_typescript(cx).await;
918
919 cx.set_state("const beˇfore = 2; console.log(before)", Mode::Normal);
920 let def_range = cx.lsp_range("const «beforeˇ» = 2; console.log(before)");
921 let tgt_range = cx.lsp_range("const before = 2; console.log(«beforeˇ»)");
922 let mut prepare_request = cx.set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
923 move |_, _, _| async move { Ok(Some(lsp::PrepareRenameResponse::Range(def_range))) },
924 );
925 let mut rename_request =
926 cx.set_request_handler::<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 PushSneak { first_char: None },
1351 Some("vim_mode == normal"),
1352 ),
1353 KeyBinding::new(
1354 "shift-s",
1355 PushSneakBackward { first_char: None },
1356 Some("vim_mode == normal"),
1357 ),
1358 KeyBinding::new(
1359 "shift-s",
1360 PushSneakBackward { 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}
1715
1716#[gpui::test]
1717async fn test_folded_multibuffer_excerpts(cx: &mut gpui::TestAppContext) {
1718 VimTestContext::init(cx);
1719 cx.update(|cx| {
1720 VimTestContext::init_keybindings(true, cx);
1721 });
1722 let (editor, cx) = cx.add_window_view(|window, cx| {
1723 let multi_buffer = MultiBuffer::build_multi(
1724 [
1725 ("111\n222\n333\n444\n", vec![Point::row_range(0..2)]),
1726 ("aaa\nbbb\nccc\nddd\n", vec![Point::row_range(0..2)]),
1727 ("AAA\nBBB\nCCC\nDDD\n", vec![Point::row_range(0..2)]),
1728 ("one\ntwo\nthr\nfou\n", vec![Point::row_range(0..2)]),
1729 ],
1730 cx,
1731 );
1732 let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
1733
1734 let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
1735 // fold all but the second buffer, so that we test navigating between two
1736 // adjacent folded buffers, as well as folded buffers at the start and
1737 // end the multibuffer
1738 editor.fold_buffer(buffer_ids[0], cx);
1739 editor.fold_buffer(buffer_ids[2], cx);
1740 editor.fold_buffer(buffer_ids[3], cx);
1741
1742 editor
1743 });
1744 let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
1745
1746 cx.assert_excerpts_with_selections(indoc! {"
1747 [EXCERPT]
1748 ˇ[FOLDED]
1749 [EXCERPT]
1750 aaa
1751 bbb
1752 [EXCERPT]
1753 [FOLDED]
1754 [EXCERPT]
1755 [FOLDED]
1756 "
1757 });
1758 cx.simulate_keystroke("j");
1759 cx.assert_excerpts_with_selections(indoc! {"
1760 [EXCERPT]
1761 [FOLDED]
1762 [EXCERPT]
1763 ˇaaa
1764 bbb
1765 [EXCERPT]
1766 [FOLDED]
1767 [EXCERPT]
1768 [FOLDED]
1769 "
1770 });
1771 cx.simulate_keystroke("j");
1772 cx.simulate_keystroke("j");
1773 cx.assert_excerpts_with_selections(indoc! {"
1774 [EXCERPT]
1775 [FOLDED]
1776 [EXCERPT]
1777 aaa
1778 bbb
1779 ˇ[EXCERPT]
1780 [FOLDED]
1781 [EXCERPT]
1782 [FOLDED]
1783 "
1784 });
1785 cx.simulate_keystroke("j");
1786 cx.assert_excerpts_with_selections(indoc! {"
1787 [EXCERPT]
1788 [FOLDED]
1789 [EXCERPT]
1790 aaa
1791 bbb
1792 [EXCERPT]
1793 ˇ[FOLDED]
1794 [EXCERPT]
1795 [FOLDED]
1796 "
1797 });
1798 cx.simulate_keystroke("j");
1799 cx.assert_excerpts_with_selections(indoc! {"
1800 [EXCERPT]
1801 [FOLDED]
1802 [EXCERPT]
1803 aaa
1804 bbb
1805 [EXCERPT]
1806 [FOLDED]
1807 [EXCERPT]
1808 ˇ[FOLDED]
1809 "
1810 });
1811 cx.simulate_keystroke("k");
1812 cx.assert_excerpts_with_selections(indoc! {"
1813 [EXCERPT]
1814 [FOLDED]
1815 [EXCERPT]
1816 aaa
1817 bbb
1818 [EXCERPT]
1819 ˇ[FOLDED]
1820 [EXCERPT]
1821 [FOLDED]
1822 "
1823 });
1824 cx.simulate_keystroke("k");
1825 cx.simulate_keystroke("k");
1826 cx.simulate_keystroke("k");
1827 cx.assert_excerpts_with_selections(indoc! {"
1828 [EXCERPT]
1829 [FOLDED]
1830 [EXCERPT]
1831 ˇaaa
1832 bbb
1833 [EXCERPT]
1834 [FOLDED]
1835 [EXCERPT]
1836 [FOLDED]
1837 "
1838 });
1839 cx.simulate_keystroke("k");
1840 cx.assert_excerpts_with_selections(indoc! {"
1841 [EXCERPT]
1842 ˇ[FOLDED]
1843 [EXCERPT]
1844 aaa
1845 bbb
1846 [EXCERPT]
1847 [FOLDED]
1848 [EXCERPT]
1849 [FOLDED]
1850 "
1851 });
1852 cx.simulate_keystroke("shift-g");
1853 cx.assert_excerpts_with_selections(indoc! {"
1854 [EXCERPT]
1855 [FOLDED]
1856 [EXCERPT]
1857 aaa
1858 bbb
1859 [EXCERPT]
1860 [FOLDED]
1861 [EXCERPT]
1862 ˇ[FOLDED]
1863 "
1864 });
1865 cx.simulate_keystrokes("g g");
1866 cx.assert_excerpts_with_selections(indoc! {"
1867 [EXCERPT]
1868 ˇ[FOLDED]
1869 [EXCERPT]
1870 aaa
1871 bbb
1872 [EXCERPT]
1873 [FOLDED]
1874 [EXCERPT]
1875 [FOLDED]
1876 "
1877 });
1878 cx.update_editor(|editor, _, cx| {
1879 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
1880 editor.fold_buffer(buffer_ids[1], cx);
1881 });
1882
1883 cx.assert_excerpts_with_selections(indoc! {"
1884 [EXCERPT]
1885 ˇ[FOLDED]
1886 [EXCERPT]
1887 [FOLDED]
1888 [EXCERPT]
1889 [FOLDED]
1890 [EXCERPT]
1891 [FOLDED]
1892 "
1893 });
1894 cx.simulate_keystrokes("2 j");
1895 cx.assert_excerpts_with_selections(indoc! {"
1896 [EXCERPT]
1897 [FOLDED]
1898 [EXCERPT]
1899 [FOLDED]
1900 [EXCERPT]
1901 ˇ[FOLDED]
1902 [EXCERPT]
1903 [FOLDED]
1904 "
1905 });
1906}
1907
1908#[gpui::test]
1909async fn test_delete_paragraph_motion(cx: &mut gpui::TestAppContext) {
1910 let mut cx = NeovimBackedTestContext::new(cx).await;
1911 cx.set_shared_state(indoc! {
1912 "ˇhello world.
1913
1914 hello world.
1915 "
1916 })
1917 .await;
1918 cx.simulate_shared_keystrokes("y }").await;
1919 cx.shared_clipboard().await.assert_eq("hello world.\n");
1920 cx.simulate_shared_keystrokes("d }").await;
1921 cx.shared_state().await.assert_eq("ˇ\nhello world.\n");
1922 cx.shared_clipboard().await.assert_eq("hello world.\n");
1923
1924 cx.set_shared_state(indoc! {
1925 "helˇlo world.
1926
1927 hello world.
1928 "
1929 })
1930 .await;
1931 cx.simulate_shared_keystrokes("y }").await;
1932 cx.shared_clipboard().await.assert_eq("lo world.");
1933 cx.simulate_shared_keystrokes("d }").await;
1934 cx.shared_state().await.assert_eq("heˇl\n\nhello world.\n");
1935 cx.shared_clipboard().await.assert_eq("lo world.");
1936}
1937
1938#[gpui::test]
1939async fn test_delete_unmatched_brace(cx: &mut gpui::TestAppContext) {
1940 let mut cx = NeovimBackedTestContext::new(cx).await;
1941 cx.set_shared_state(indoc! {
1942 "fn o(wow: i32) {
1943 othˇ(wow)
1944 oth(wow)
1945 }
1946 "
1947 })
1948 .await;
1949 cx.simulate_shared_keystrokes("d ] }").await;
1950 cx.shared_state().await.assert_eq(indoc! {
1951 "fn o(wow: i32) {
1952 otˇh
1953 }
1954 "
1955 });
1956 cx.shared_clipboard().await.assert_eq("(wow)\n oth(wow)");
1957 cx.set_shared_state(indoc! {
1958 "fn o(wow: i32) {
1959 ˇoth(wow)
1960 oth(wow)
1961 }
1962 "
1963 })
1964 .await;
1965 cx.simulate_shared_keystrokes("d ] }").await;
1966 cx.shared_state().await.assert_eq(indoc! {
1967 "fn o(wow: i32) {
1968 ˇ}
1969 "
1970 });
1971 cx.shared_clipboard()
1972 .await
1973 .assert_eq(" oth(wow)\n oth(wow)\n");
1974}