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