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 = cx.set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
921 move |_, _, _| async move { Ok(Some(lsp::PrepareRenameResponse::Range(def_range))) },
922 );
923 let mut rename_request =
924 cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, params, _| async move {
925 Ok(Some(lsp::WorkspaceEdit {
926 changes: Some(
927 [(
928 url.clone(),
929 vec![
930 lsp::TextEdit::new(def_range, params.new_name.clone()),
931 lsp::TextEdit::new(tgt_range, params.new_name),
932 ],
933 )]
934 .into(),
935 ),
936 ..Default::default()
937 }))
938 });
939
940 cx.simulate_keystrokes("c d");
941 prepare_request.next().await.unwrap();
942 cx.simulate_input("after");
943 cx.simulate_keystrokes("enter");
944 rename_request.next().await.unwrap();
945 cx.assert_state("const afterˇ = 2; console.log(after)", Mode::Normal)
946}
947
948// TODO: this test is flaky on our linux CI machines
949#[cfg(target_os = "macos")]
950#[gpui::test]
951async fn test_remap(cx: &mut gpui::TestAppContext) {
952 let mut cx = VimTestContext::new(cx, true).await;
953
954 // test moving the cursor
955 cx.update(|_, cx| {
956 cx.bind_keys([KeyBinding::new(
957 "g z",
958 workspace::SendKeystrokes("l l l l".to_string()),
959 None,
960 )])
961 });
962 cx.set_state("ˇ123456789", Mode::Normal);
963 cx.simulate_keystrokes("g z");
964 cx.assert_state("1234ˇ56789", Mode::Normal);
965
966 // test switching modes
967 cx.update(|_, cx| {
968 cx.bind_keys([KeyBinding::new(
969 "g y",
970 workspace::SendKeystrokes("i f o o escape l".to_string()),
971 None,
972 )])
973 });
974 cx.set_state("ˇ123456789", Mode::Normal);
975 cx.simulate_keystrokes("g y");
976 cx.assert_state("fooˇ123456789", Mode::Normal);
977
978 // test recursion
979 cx.update(|_, cx| {
980 cx.bind_keys([KeyBinding::new(
981 "g x",
982 workspace::SendKeystrokes("g z g y".to_string()),
983 None,
984 )])
985 });
986 cx.set_state("ˇ123456789", Mode::Normal);
987 cx.simulate_keystrokes("g x");
988 cx.assert_state("1234fooˇ56789", Mode::Normal);
989
990 cx.executor().allow_parking();
991
992 // test command
993 cx.update(|_, cx| {
994 cx.bind_keys([KeyBinding::new(
995 "g w",
996 workspace::SendKeystrokes(": j enter".to_string()),
997 None,
998 )])
999 });
1000 cx.set_state("ˇ1234\n56789", Mode::Normal);
1001 cx.simulate_keystrokes("g w");
1002 cx.assert_state("1234ˇ 56789", Mode::Normal);
1003
1004 // test leaving command
1005 cx.update(|_, cx| {
1006 cx.bind_keys([KeyBinding::new(
1007 "g u",
1008 workspace::SendKeystrokes("g w g z".to_string()),
1009 None,
1010 )])
1011 });
1012 cx.set_state("ˇ1234\n56789", Mode::Normal);
1013 cx.simulate_keystrokes("g u");
1014 cx.assert_state("1234 567ˇ89", Mode::Normal);
1015
1016 // test leaving command
1017 cx.update(|_, cx| {
1018 cx.bind_keys([KeyBinding::new(
1019 "g t",
1020 workspace::SendKeystrokes("i space escape".to_string()),
1021 None,
1022 )])
1023 });
1024 cx.set_state("12ˇ34", Mode::Normal);
1025 cx.simulate_keystrokes("g t");
1026 cx.assert_state("12ˇ 34", Mode::Normal);
1027}
1028
1029#[gpui::test]
1030async fn test_undo(cx: &mut gpui::TestAppContext) {
1031 let mut cx = NeovimBackedTestContext::new(cx).await;
1032
1033 cx.set_shared_state("hello quˇoel world").await;
1034 cx.simulate_shared_keystrokes("v i w s c o escape u").await;
1035 cx.shared_state().await.assert_eq("hello ˇquoel world");
1036 cx.simulate_shared_keystrokes("ctrl-r").await;
1037 cx.shared_state().await.assert_eq("hello ˇco world");
1038 cx.simulate_shared_keystrokes("a o right l escape").await;
1039 cx.shared_state().await.assert_eq("hello cooˇl world");
1040 cx.simulate_shared_keystrokes("u").await;
1041 cx.shared_state().await.assert_eq("hello cooˇ world");
1042 cx.simulate_shared_keystrokes("u").await;
1043 cx.shared_state().await.assert_eq("hello cˇo world");
1044 cx.simulate_shared_keystrokes("u").await;
1045 cx.shared_state().await.assert_eq("hello ˇquoel world");
1046
1047 cx.set_shared_state("hello quˇoel world").await;
1048 cx.simulate_shared_keystrokes("v i w ~ u").await;
1049 cx.shared_state().await.assert_eq("hello ˇquoel world");
1050
1051 cx.set_shared_state("\nhello quˇoel world\n").await;
1052 cx.simulate_shared_keystrokes("shift-v s c escape u").await;
1053 cx.shared_state().await.assert_eq("\nˇhello quoel world\n");
1054
1055 cx.set_shared_state(indoc! {"
1056 ˇ1
1057 2
1058 3"})
1059 .await;
1060
1061 cx.simulate_shared_keystrokes("ctrl-v shift-g ctrl-a").await;
1062 cx.shared_state().await.assert_eq(indoc! {"
1063 ˇ2
1064 3
1065 4"});
1066
1067 cx.simulate_shared_keystrokes("u").await;
1068 cx.shared_state().await.assert_eq(indoc! {"
1069 ˇ1
1070 2
1071 3"});
1072}
1073
1074#[gpui::test]
1075async fn test_mouse_selection(cx: &mut TestAppContext) {
1076 let mut cx = VimTestContext::new(cx, true).await;
1077
1078 cx.set_state("ˇone two three", Mode::Normal);
1079
1080 let start_point = cx.pixel_position("one twˇo three");
1081 let end_point = cx.pixel_position("one ˇtwo three");
1082
1083 cx.simulate_mouse_down(start_point, MouseButton::Left, Modifiers::none());
1084 cx.simulate_mouse_move(end_point, MouseButton::Left, Modifiers::none());
1085 cx.simulate_mouse_up(end_point, MouseButton::Left, Modifiers::none());
1086
1087 cx.assert_state("one «ˇtwo» three", Mode::Visual)
1088}
1089
1090#[gpui::test]
1091async fn test_lowercase_marks(cx: &mut TestAppContext) {
1092 let mut cx = NeovimBackedTestContext::new(cx).await;
1093
1094 cx.set_shared_state("line one\nline ˇtwo\nline three").await;
1095 cx.simulate_shared_keystrokes("m a l ' a").await;
1096 cx.shared_state()
1097 .await
1098 .assert_eq("line one\nˇline two\nline three");
1099 cx.simulate_shared_keystrokes("` a").await;
1100 cx.shared_state()
1101 .await
1102 .assert_eq("line one\nline ˇtwo\nline three");
1103
1104 cx.simulate_shared_keystrokes("^ d ` a").await;
1105 cx.shared_state()
1106 .await
1107 .assert_eq("line one\nˇtwo\nline three");
1108}
1109
1110#[gpui::test]
1111async fn test_lt_gt_marks(cx: &mut TestAppContext) {
1112 let mut cx = NeovimBackedTestContext::new(cx).await;
1113
1114 cx.set_shared_state(indoc!(
1115 "
1116 Line one
1117 Line two
1118 Line ˇthree
1119 Line four
1120 Line five
1121 "
1122 ))
1123 .await;
1124
1125 cx.simulate_shared_keystrokes("v j escape k k").await;
1126
1127 cx.simulate_shared_keystrokes("' <").await;
1128 cx.shared_state().await.assert_eq(indoc! {"
1129 Line one
1130 Line two
1131 ˇLine three
1132 Line four
1133 Line five
1134 "});
1135
1136 cx.simulate_shared_keystrokes("` <").await;
1137 cx.shared_state().await.assert_eq(indoc! {"
1138 Line one
1139 Line two
1140 Line ˇthree
1141 Line four
1142 Line five
1143 "});
1144
1145 cx.simulate_shared_keystrokes("' >").await;
1146 cx.shared_state().await.assert_eq(indoc! {"
1147 Line one
1148 Line two
1149 Line three
1150 ˇLine four
1151 Line five
1152 "
1153 });
1154
1155 cx.simulate_shared_keystrokes("` >").await;
1156 cx.shared_state().await.assert_eq(indoc! {"
1157 Line one
1158 Line two
1159 Line three
1160 Line ˇfour
1161 Line five
1162 "
1163 });
1164
1165 cx.simulate_shared_keystrokes("v i w o escape").await;
1166 cx.simulate_shared_keystrokes("` >").await;
1167 cx.shared_state().await.assert_eq(indoc! {"
1168 Line one
1169 Line two
1170 Line three
1171 Line fouˇr
1172 Line five
1173 "
1174 });
1175 cx.simulate_shared_keystrokes("` <").await;
1176 cx.shared_state().await.assert_eq(indoc! {"
1177 Line one
1178 Line two
1179 Line three
1180 Line ˇfour
1181 Line five
1182 "
1183 });
1184}
1185
1186#[gpui::test]
1187async fn test_caret_mark(cx: &mut TestAppContext) {
1188 let mut cx = NeovimBackedTestContext::new(cx).await;
1189
1190 cx.set_shared_state(indoc!(
1191 "
1192 Line one
1193 Line two
1194 Line three
1195 ˇLine four
1196 Line five
1197 "
1198 ))
1199 .await;
1200
1201 cx.simulate_shared_keystrokes("c w shift-s t r a i g h t space t h i n g escape j j")
1202 .await;
1203
1204 cx.simulate_shared_keystrokes("' ^").await;
1205 cx.shared_state().await.assert_eq(indoc! {"
1206 Line one
1207 Line two
1208 Line three
1209 ˇStraight thing four
1210 Line five
1211 "
1212 });
1213
1214 cx.simulate_shared_keystrokes("` ^").await;
1215 cx.shared_state().await.assert_eq(indoc! {"
1216 Line one
1217 Line two
1218 Line three
1219 Straight thingˇ four
1220 Line five
1221 "
1222 });
1223
1224 cx.simulate_shared_keystrokes("k a ! escape k g i ?").await;
1225 cx.shared_state().await.assert_eq(indoc! {"
1226 Line one
1227 Line two
1228 Line three!?ˇ
1229 Straight thing four
1230 Line five
1231 "
1232 });
1233}
1234
1235#[cfg(target_os = "macos")]
1236#[gpui::test]
1237async fn test_dw_eol(cx: &mut gpui::TestAppContext) {
1238 let mut cx = NeovimBackedTestContext::new(cx).await;
1239
1240 cx.set_shared_wrap(12).await;
1241 cx.set_shared_state("twelve ˇchar twelve char\ntwelve char")
1242 .await;
1243 cx.simulate_shared_keystrokes("d w").await;
1244 cx.shared_state()
1245 .await
1246 .assert_eq("twelve ˇtwelve char\ntwelve char");
1247}
1248
1249#[gpui::test]
1250async fn test_toggle_comments(cx: &mut gpui::TestAppContext) {
1251 let mut cx = VimTestContext::new(cx, true).await;
1252
1253 let language = std::sync::Arc::new(language::Language::new(
1254 language::LanguageConfig {
1255 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
1256 ..Default::default()
1257 },
1258 Some(language::tree_sitter_rust::LANGUAGE.into()),
1259 ));
1260 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
1261
1262 // works in normal model
1263 cx.set_state(
1264 indoc! {"
1265 ˇone
1266 two
1267 three
1268 "},
1269 Mode::Normal,
1270 );
1271 cx.simulate_keystrokes("g c c");
1272 cx.assert_state(
1273 indoc! {"
1274 // ˇone
1275 two
1276 three
1277 "},
1278 Mode::Normal,
1279 );
1280
1281 // works in visual mode
1282 cx.simulate_keystrokes("v j g c");
1283 cx.assert_state(
1284 indoc! {"
1285 // // ˇone
1286 // two
1287 three
1288 "},
1289 Mode::Normal,
1290 );
1291
1292 // works in visual line mode
1293 cx.simulate_keystrokes("shift-v j g c");
1294 cx.assert_state(
1295 indoc! {"
1296 // ˇone
1297 two
1298 three
1299 "},
1300 Mode::Normal,
1301 );
1302
1303 // works with count
1304 cx.simulate_keystrokes("g c 2 j");
1305 cx.assert_state(
1306 indoc! {"
1307 // // ˇone
1308 // two
1309 // three
1310 "},
1311 Mode::Normal,
1312 );
1313
1314 // works with motion object
1315 cx.simulate_keystrokes("shift-g");
1316 cx.simulate_keystrokes("g c g g");
1317 cx.assert_state(
1318 indoc! {"
1319 // one
1320 two
1321 three
1322 ˇ"},
1323 Mode::Normal,
1324 );
1325}
1326
1327#[gpui::test]
1328async fn test_find_multibyte(cx: &mut gpui::TestAppContext) {
1329 let mut cx = NeovimBackedTestContext::new(cx).await;
1330
1331 cx.set_shared_state(r#"<label for="guests">ˇPočet hostů</label>"#)
1332 .await;
1333
1334 cx.simulate_shared_keystrokes("c t < o escape").await;
1335 cx.shared_state()
1336 .await
1337 .assert_eq(r#"<label for="guests">ˇo</label>"#);
1338}
1339
1340#[gpui::test]
1341async fn test_sneak(cx: &mut gpui::TestAppContext) {
1342 let mut cx = VimTestContext::new(cx, true).await;
1343
1344 cx.update(|_window, cx| {
1345 cx.bind_keys([
1346 KeyBinding::new(
1347 "s",
1348 PushSneak { first_char: None },
1349 Some("vim_mode == normal"),
1350 ),
1351 KeyBinding::new(
1352 "shift-s",
1353 PushSneakBackward { first_char: None },
1354 Some("vim_mode == normal"),
1355 ),
1356 KeyBinding::new(
1357 "shift-s",
1358 PushSneakBackward { first_char: None },
1359 Some("vim_mode == visual"),
1360 ),
1361 ])
1362 });
1363
1364 // Sneak forwards multibyte & multiline
1365 cx.set_state(
1366 indoc! {
1367 r#"<labelˇ for="guests">
1368 Počet hostů
1369 </label>"#
1370 },
1371 Mode::Normal,
1372 );
1373 cx.simulate_keystrokes("s t ů");
1374 cx.assert_state(
1375 indoc! {
1376 r#"<label for="guests">
1377 Počet hosˇtů
1378 </label>"#
1379 },
1380 Mode::Normal,
1381 );
1382
1383 // Visual sneak backwards multibyte & multiline
1384 cx.simulate_keystrokes("v S < l");
1385 cx.assert_state(
1386 indoc! {
1387 r#"«ˇ<label for="guests">
1388 Počet host»ů
1389 </label>"#
1390 },
1391 Mode::Visual,
1392 );
1393
1394 // Sneak backwards repeated
1395 cx.set_state(r#"11 12 13 ˇ14"#, Mode::Normal);
1396 cx.simulate_keystrokes("S space 1");
1397 cx.assert_state(r#"11 12ˇ 13 14"#, Mode::Normal);
1398 cx.simulate_keystrokes(";");
1399 cx.assert_state(r#"11ˇ 12 13 14"#, Mode::Normal);
1400}
1401
1402#[gpui::test]
1403async fn test_plus_minus(cx: &mut gpui::TestAppContext) {
1404 let mut cx = NeovimBackedTestContext::new(cx).await;
1405
1406 cx.set_shared_state(indoc! {
1407 "one
1408 two
1409 thrˇee
1410 "})
1411 .await;
1412
1413 cx.simulate_shared_keystrokes("-").await;
1414 cx.shared_state().await.assert_matches();
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}
1420
1421#[gpui::test]
1422async fn test_command_alias(cx: &mut gpui::TestAppContext) {
1423 let mut cx = VimTestContext::new(cx, true).await;
1424 cx.update_global(|store: &mut SettingsStore, cx| {
1425 store.update_user_settings::<WorkspaceSettings>(cx, |s| {
1426 let mut aliases = HashMap::default();
1427 aliases.insert("Q".to_string(), "upper".to_string());
1428 s.command_aliases = Some(aliases)
1429 });
1430 });
1431
1432 cx.set_state("ˇhello world", Mode::Normal);
1433 cx.simulate_keystrokes(": Q");
1434 cx.set_state("ˇHello world", Mode::Normal);
1435}
1436
1437#[gpui::test]
1438async fn test_remap_adjacent_dog_cat(cx: &mut gpui::TestAppContext) {
1439 let mut cx = NeovimBackedTestContext::new(cx).await;
1440 cx.update(|_, cx| {
1441 cx.bind_keys([
1442 KeyBinding::new(
1443 "d o g",
1444 workspace::SendKeystrokes("🐶".to_string()),
1445 Some("vim_mode == insert"),
1446 ),
1447 KeyBinding::new(
1448 "c a t",
1449 workspace::SendKeystrokes("🐱".to_string()),
1450 Some("vim_mode == insert"),
1451 ),
1452 ])
1453 });
1454 cx.neovim.exec("imap dog 🐶").await;
1455 cx.neovim.exec("imap cat 🐱").await;
1456
1457 cx.set_shared_state("ˇ").await;
1458 cx.simulate_shared_keystrokes("i d o g").await;
1459 cx.shared_state().await.assert_eq("🐶ˇ");
1460
1461 cx.set_shared_state("ˇ").await;
1462 cx.simulate_shared_keystrokes("i d o d o g").await;
1463 cx.shared_state().await.assert_eq("do🐶ˇ");
1464
1465 cx.set_shared_state("ˇ").await;
1466 cx.simulate_shared_keystrokes("i d o c a t").await;
1467 cx.shared_state().await.assert_eq("do🐱ˇ");
1468}
1469
1470#[gpui::test]
1471async fn test_remap_nested_pineapple(cx: &mut gpui::TestAppContext) {
1472 let mut cx = NeovimBackedTestContext::new(cx).await;
1473 cx.update(|_, cx| {
1474 cx.bind_keys([
1475 KeyBinding::new(
1476 "p i n",
1477 workspace::SendKeystrokes("📌".to_string()),
1478 Some("vim_mode == insert"),
1479 ),
1480 KeyBinding::new(
1481 "p i n e",
1482 workspace::SendKeystrokes("🌲".to_string()),
1483 Some("vim_mode == insert"),
1484 ),
1485 KeyBinding::new(
1486 "p i n e a p p l e",
1487 workspace::SendKeystrokes("🍍".to_string()),
1488 Some("vim_mode == insert"),
1489 ),
1490 ])
1491 });
1492 cx.neovim.exec("imap pin 📌").await;
1493 cx.neovim.exec("imap pine 🌲").await;
1494 cx.neovim.exec("imap pineapple 🍍").await;
1495
1496 cx.set_shared_state("ˇ").await;
1497 cx.simulate_shared_keystrokes("i p i n").await;
1498 cx.executor().advance_clock(Duration::from_millis(1000));
1499 cx.run_until_parked();
1500 cx.shared_state().await.assert_eq("📌ˇ");
1501
1502 cx.set_shared_state("ˇ").await;
1503 cx.simulate_shared_keystrokes("i p i n e").await;
1504 cx.executor().advance_clock(Duration::from_millis(1000));
1505 cx.run_until_parked();
1506 cx.shared_state().await.assert_eq("🌲ˇ");
1507
1508 cx.set_shared_state("ˇ").await;
1509 cx.simulate_shared_keystrokes("i p i n e a p p l e").await;
1510 cx.shared_state().await.assert_eq("🍍ˇ");
1511}
1512
1513#[gpui::test]
1514async fn test_remap_recursion(cx: &mut gpui::TestAppContext) {
1515 let mut cx = NeovimBackedTestContext::new(cx).await;
1516 cx.update(|_, cx| {
1517 cx.bind_keys([KeyBinding::new(
1518 "x",
1519 workspace::SendKeystrokes("\" _ x".to_string()),
1520 Some("VimControl"),
1521 )]);
1522 cx.bind_keys([KeyBinding::new(
1523 "y",
1524 workspace::SendKeystrokes("2 x".to_string()),
1525 Some("VimControl"),
1526 )])
1527 });
1528 cx.neovim.exec("noremap x \"_x").await;
1529 cx.neovim.exec("map y 2x").await;
1530
1531 cx.set_shared_state("ˇhello").await;
1532 cx.simulate_shared_keystrokes("d l").await;
1533 cx.shared_clipboard().await.assert_eq("h");
1534 cx.simulate_shared_keystrokes("y").await;
1535 cx.shared_clipboard().await.assert_eq("h");
1536 cx.shared_state().await.assert_eq("ˇlo");
1537}
1538
1539#[gpui::test]
1540async fn test_escape_while_waiting(cx: &mut gpui::TestAppContext) {
1541 let mut cx = NeovimBackedTestContext::new(cx).await;
1542 cx.set_shared_state("ˇhi").await;
1543 cx.simulate_shared_keystrokes("\" + escape x").await;
1544 cx.shared_state().await.assert_eq("ˇi");
1545}
1546
1547#[gpui::test]
1548async fn test_ctrl_w_override(cx: &mut gpui::TestAppContext) {
1549 let mut cx = NeovimBackedTestContext::new(cx).await;
1550 cx.update(|_, cx| {
1551 cx.bind_keys([KeyBinding::new("ctrl-w", DeleteLine, None)]);
1552 });
1553 cx.neovim.exec("map <c-w> D").await;
1554 cx.set_shared_state("ˇhi").await;
1555 cx.simulate_shared_keystrokes("ctrl-w").await;
1556 cx.shared_state().await.assert_eq("ˇ");
1557}
1558
1559#[gpui::test]
1560async fn test_visual_indent_count(cx: &mut gpui::TestAppContext) {
1561 let mut cx = VimTestContext::new(cx, true).await;
1562 cx.set_state("ˇhi", Mode::Normal);
1563 cx.simulate_keystrokes("shift-v 3 >");
1564 cx.assert_state(" ˇhi", Mode::Normal);
1565 cx.simulate_keystrokes("shift-v 2 <");
1566 cx.assert_state(" ˇhi", Mode::Normal);
1567}
1568
1569#[gpui::test]
1570async fn test_record_replay_recursion(cx: &mut gpui::TestAppContext) {
1571 let mut cx = NeovimBackedTestContext::new(cx).await;
1572
1573 cx.set_shared_state("ˇhello world").await;
1574 cx.simulate_shared_keystrokes(">").await;
1575 cx.simulate_shared_keystrokes(".").await;
1576 cx.simulate_shared_keystrokes(".").await;
1577 cx.simulate_shared_keystrokes(".").await;
1578 cx.shared_state().await.assert_eq("ˇhello world");
1579}
1580
1581#[gpui::test]
1582async fn test_blackhole_register(cx: &mut gpui::TestAppContext) {
1583 let mut cx = NeovimBackedTestContext::new(cx).await;
1584
1585 cx.set_shared_state("ˇhello world").await;
1586 cx.simulate_shared_keystrokes("d i w \" _ d a w").await;
1587 cx.simulate_shared_keystrokes("p").await;
1588 cx.shared_state().await.assert_eq("hellˇo");
1589}
1590
1591#[gpui::test]
1592async fn test_sentence_backwards(cx: &mut gpui::TestAppContext) {
1593 let mut cx = NeovimBackedTestContext::new(cx).await;
1594
1595 cx.set_shared_state("one\n\ntwo\nthree\nˇ\nfour").await;
1596 cx.simulate_shared_keystrokes("(").await;
1597 cx.shared_state()
1598 .await
1599 .assert_eq("one\n\nˇtwo\nthree\n\nfour");
1600
1601 cx.set_shared_state("hello.\n\n\nworˇld.").await;
1602 cx.simulate_shared_keystrokes("(").await;
1603 cx.shared_state().await.assert_eq("hello.\n\n\nˇworld.");
1604 cx.simulate_shared_keystrokes("(").await;
1605 cx.shared_state().await.assert_eq("hello.\n\nˇ\nworld.");
1606 cx.simulate_shared_keystrokes("(").await;
1607 cx.shared_state().await.assert_eq("ˇhello.\n\n\nworld.");
1608
1609 cx.set_shared_state("hello. worlˇd.").await;
1610 cx.simulate_shared_keystrokes("(").await;
1611 cx.shared_state().await.assert_eq("hello. ˇworld.");
1612 cx.simulate_shared_keystrokes("(").await;
1613 cx.shared_state().await.assert_eq("ˇhello. world.");
1614
1615 cx.set_shared_state(". helˇlo.").await;
1616 cx.simulate_shared_keystrokes("(").await;
1617 cx.shared_state().await.assert_eq(". ˇhello.");
1618 cx.simulate_shared_keystrokes("(").await;
1619 cx.shared_state().await.assert_eq(". ˇhello.");
1620
1621 cx.set_shared_state(indoc! {
1622 "{
1623 hello_world();
1624 ˇ}"
1625 })
1626 .await;
1627 cx.simulate_shared_keystrokes("(").await;
1628 cx.shared_state().await.assert_eq(indoc! {
1629 "ˇ{
1630 hello_world();
1631 }"
1632 });
1633
1634 cx.set_shared_state(indoc! {
1635 "Hello! World..?
1636
1637 \tHello! World... ˇ"
1638 })
1639 .await;
1640 cx.simulate_shared_keystrokes("(").await;
1641 cx.shared_state().await.assert_eq(indoc! {
1642 "Hello! World..?
1643
1644 \tHello! ˇWorld... "
1645 });
1646 cx.simulate_shared_keystrokes("(").await;
1647 cx.shared_state().await.assert_eq(indoc! {
1648 "Hello! World..?
1649
1650 \tˇHello! World... "
1651 });
1652 cx.simulate_shared_keystrokes("(").await;
1653 cx.shared_state().await.assert_eq(indoc! {
1654 "Hello! World..?
1655 ˇ
1656 \tHello! World... "
1657 });
1658 cx.simulate_shared_keystrokes("(").await;
1659 cx.shared_state().await.assert_eq(indoc! {
1660 "Hello! ˇWorld..?
1661
1662 \tHello! World... "
1663 });
1664}
1665
1666#[gpui::test]
1667async fn test_sentence_forwards(cx: &mut gpui::TestAppContext) {
1668 let mut cx = NeovimBackedTestContext::new(cx).await;
1669
1670 cx.set_shared_state("helˇlo.\n\n\nworld.").await;
1671 cx.simulate_shared_keystrokes(")").await;
1672 cx.shared_state().await.assert_eq("hello.\nˇ\n\nworld.");
1673 cx.simulate_shared_keystrokes(")").await;
1674 cx.shared_state().await.assert_eq("hello.\n\n\nˇworld.");
1675 cx.simulate_shared_keystrokes(")").await;
1676 cx.shared_state().await.assert_eq("hello.\n\n\nworldˇ.");
1677
1678 cx.set_shared_state("helˇlo.\n\n\nworld.").await;
1679}
1680
1681#[gpui::test]
1682async fn test_ctrl_o_visual(cx: &mut gpui::TestAppContext) {
1683 let mut cx = NeovimBackedTestContext::new(cx).await;
1684
1685 cx.set_shared_state("helloˇ world.").await;
1686 cx.simulate_shared_keystrokes("i ctrl-o v b r l").await;
1687 cx.shared_state().await.assert_eq("ˇllllllworld.");
1688 cx.simulate_shared_keystrokes("ctrl-o v f w d").await;
1689 cx.shared_state().await.assert_eq("ˇorld.");
1690}
1691
1692#[gpui::test]
1693async fn test_ctrl_o_position(cx: &mut gpui::TestAppContext) {
1694 let mut cx = NeovimBackedTestContext::new(cx).await;
1695
1696 cx.set_shared_state("helˇlo world.").await;
1697 cx.simulate_shared_keystrokes("i ctrl-o d i w").await;
1698 cx.shared_state().await.assert_eq("ˇ world.");
1699 cx.simulate_shared_keystrokes("ctrl-o p").await;
1700 cx.shared_state().await.assert_eq(" helloˇworld.");
1701}
1702
1703#[gpui::test]
1704async fn test_ctrl_o_dot(cx: &mut gpui::TestAppContext) {
1705 let mut cx = NeovimBackedTestContext::new(cx).await;
1706
1707 cx.set_shared_state("heˇllo world.").await;
1708 cx.simulate_shared_keystrokes("x i ctrl-o .").await;
1709 cx.shared_state().await.assert_eq("heˇo world.");
1710 cx.simulate_shared_keystrokes("l l escape .").await;
1711 cx.shared_state().await.assert_eq("hellˇllo world.");
1712}
1713
1714#[gpui::test]
1715async fn test_folded_multibuffer_excerpts(cx: &mut gpui::TestAppContext) {
1716 VimTestContext::init(cx);
1717 cx.update(|cx| {
1718 VimTestContext::init_keybindings(true, cx);
1719 });
1720 let (editor, cx) = cx.add_window_view(|window, cx| {
1721 let multi_buffer = MultiBuffer::build_multi(
1722 [
1723 ("111\n222\n333\n444\n", vec![Point::row_range(0..2)]),
1724 ("aaa\nbbb\nccc\nddd\n", vec![Point::row_range(0..2)]),
1725 ("AAA\nBBB\nCCC\nDDD\n", vec![Point::row_range(0..2)]),
1726 ("one\ntwo\nthr\nfou\n", vec![Point::row_range(0..2)]),
1727 ],
1728 cx,
1729 );
1730 let mut editor = Editor::new(EditorMode::Full, multi_buffer.clone(), None, window, cx);
1731
1732 let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
1733 // fold all but the second buffer, so that we test navigating between two
1734 // adjacent folded buffers, as well as folded buffers at the start and
1735 // end the multibuffer
1736 editor.fold_buffer(buffer_ids[0], cx);
1737 editor.fold_buffer(buffer_ids[2], cx);
1738 editor.fold_buffer(buffer_ids[3], cx);
1739
1740 editor
1741 });
1742 let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
1743
1744 cx.assert_excerpts_with_selections(indoc! {"
1745 [EXCERPT]
1746 ˇ[FOLDED]
1747 [EXCERPT]
1748 aaa
1749 bbb
1750 [EXCERPT]
1751 [FOLDED]
1752 [EXCERPT]
1753 [FOLDED]
1754 "
1755 });
1756 cx.simulate_keystroke("j");
1757 cx.assert_excerpts_with_selections(indoc! {"
1758 [EXCERPT]
1759 [FOLDED]
1760 [EXCERPT]
1761 ˇaaa
1762 bbb
1763 [EXCERPT]
1764 [FOLDED]
1765 [EXCERPT]
1766 [FOLDED]
1767 "
1768 });
1769 cx.simulate_keystroke("j");
1770 cx.simulate_keystroke("j");
1771 cx.assert_excerpts_with_selections(indoc! {"
1772 [EXCERPT]
1773 [FOLDED]
1774 [EXCERPT]
1775 aaa
1776 bbb
1777 ˇ[EXCERPT]
1778 [FOLDED]
1779 [EXCERPT]
1780 [FOLDED]
1781 "
1782 });
1783 cx.simulate_keystroke("j");
1784 cx.assert_excerpts_with_selections(indoc! {"
1785 [EXCERPT]
1786 [FOLDED]
1787 [EXCERPT]
1788 aaa
1789 bbb
1790 [EXCERPT]
1791 ˇ[FOLDED]
1792 [EXCERPT]
1793 [FOLDED]
1794 "
1795 });
1796 cx.simulate_keystroke("j");
1797 cx.assert_excerpts_with_selections(indoc! {"
1798 [EXCERPT]
1799 [FOLDED]
1800 [EXCERPT]
1801 aaa
1802 bbb
1803 [EXCERPT]
1804 [FOLDED]
1805 [EXCERPT]
1806 ˇ[FOLDED]
1807 "
1808 });
1809 cx.simulate_keystroke("k");
1810 cx.assert_excerpts_with_selections(indoc! {"
1811 [EXCERPT]
1812 [FOLDED]
1813 [EXCERPT]
1814 aaa
1815 bbb
1816 [EXCERPT]
1817 ˇ[FOLDED]
1818 [EXCERPT]
1819 [FOLDED]
1820 "
1821 });
1822 cx.simulate_keystroke("k");
1823 cx.simulate_keystroke("k");
1824 cx.simulate_keystroke("k");
1825 cx.assert_excerpts_with_selections(indoc! {"
1826 [EXCERPT]
1827 [FOLDED]
1828 [EXCERPT]
1829 ˇaaa
1830 bbb
1831 [EXCERPT]
1832 [FOLDED]
1833 [EXCERPT]
1834 [FOLDED]
1835 "
1836 });
1837 cx.simulate_keystroke("k");
1838 cx.assert_excerpts_with_selections(indoc! {"
1839 [EXCERPT]
1840 ˇ[FOLDED]
1841 [EXCERPT]
1842 aaa
1843 bbb
1844 [EXCERPT]
1845 [FOLDED]
1846 [EXCERPT]
1847 [FOLDED]
1848 "
1849 });
1850 cx.simulate_keystroke("shift-g");
1851 cx.assert_excerpts_with_selections(indoc! {"
1852 [EXCERPT]
1853 [FOLDED]
1854 [EXCERPT]
1855 aaa
1856 bbb
1857 [EXCERPT]
1858 [FOLDED]
1859 [EXCERPT]
1860 ˇ[FOLDED]
1861 "
1862 });
1863 cx.simulate_keystrokes("g g");
1864 cx.assert_excerpts_with_selections(indoc! {"
1865 [EXCERPT]
1866 ˇ[FOLDED]
1867 [EXCERPT]
1868 aaa
1869 bbb
1870 [EXCERPT]
1871 [FOLDED]
1872 [EXCERPT]
1873 [FOLDED]
1874 "
1875 });
1876 cx.update_editor(|editor, _, cx| {
1877 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
1878 editor.fold_buffer(buffer_ids[1], cx);
1879 });
1880
1881 cx.assert_excerpts_with_selections(indoc! {"
1882 [EXCERPT]
1883 ˇ[FOLDED]
1884 [EXCERPT]
1885 [FOLDED]
1886 [EXCERPT]
1887 [FOLDED]
1888 [EXCERPT]
1889 [FOLDED]
1890 "
1891 });
1892 cx.simulate_keystrokes("2 j");
1893 cx.assert_excerpts_with_selections(indoc! {"
1894 [EXCERPT]
1895 [FOLDED]
1896 [EXCERPT]
1897 [FOLDED]
1898 [EXCERPT]
1899 ˇ[FOLDED]
1900 [EXCERPT]
1901 [FOLDED]
1902 "
1903 });
1904}
1905
1906#[gpui::test]
1907async fn test_delete_paragraph_motion(cx: &mut gpui::TestAppContext) {
1908 let mut cx = NeovimBackedTestContext::new(cx).await;
1909 cx.set_shared_state(indoc! {
1910 "ˇhello world.
1911
1912 hello world.
1913 "
1914 })
1915 .await;
1916 cx.simulate_shared_keystrokes("y }").await;
1917 cx.shared_clipboard().await.assert_eq("hello world.\n");
1918 cx.simulate_shared_keystrokes("d }").await;
1919 cx.shared_state().await.assert_eq("ˇ\nhello world.\n");
1920 cx.shared_clipboard().await.assert_eq("hello world.\n");
1921
1922 cx.set_shared_state(indoc! {
1923 "helˇlo world.
1924
1925 hello world.
1926 "
1927 })
1928 .await;
1929 cx.simulate_shared_keystrokes("y }").await;
1930 cx.shared_clipboard().await.assert_eq("lo world.");
1931 cx.simulate_shared_keystrokes("d }").await;
1932 cx.shared_state().await.assert_eq("heˇl\n\nhello world.\n");
1933 cx.shared_clipboard().await.assert_eq("lo world.");
1934}
1935
1936#[gpui::test]
1937async fn test_delete_unmatched_brace(cx: &mut gpui::TestAppContext) {
1938 let mut cx = NeovimBackedTestContext::new(cx).await;
1939 cx.set_shared_state(indoc! {
1940 "fn o(wow: i32) {
1941 dbgˇ!(wow)
1942 dbg!(wow)
1943 }
1944 "
1945 })
1946 .await;
1947 cx.simulate_shared_keystrokes("d ] }").await;
1948 cx.shared_state().await.assert_eq(indoc! {
1949 "fn o(wow: i32) {
1950 dbˇg
1951 }
1952 "
1953 });
1954 cx.shared_clipboard().await.assert_eq("!(wow)\n dbg!(wow)");
1955 cx.set_shared_state(indoc! {
1956 "fn o(wow: i32) {
1957 ˇdbg!(wow)
1958 dbg!(wow)
1959 }
1960 "
1961 })
1962 .await;
1963 cx.simulate_shared_keystrokes("d ] }").await;
1964 cx.shared_state().await.assert_eq(indoc! {
1965 "fn o(wow: i32) {
1966 ˇ}
1967 "
1968 });
1969 cx.shared_clipboard()
1970 .await
1971 .assert_eq(" dbg!(wow)\n dbg!(wow)\n");
1972}