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