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