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