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#[perf]
1307#[gpui::test]
1308async fn test_lowercase_marks(cx: &mut TestAppContext) {
1309 let mut cx = NeovimBackedTestContext::new(cx).await;
1310
1311 cx.set_shared_state("line one\nline ˇtwo\nline three").await;
1312 cx.simulate_shared_keystrokes("m a l ' a").await;
1313 cx.shared_state()
1314 .await
1315 .assert_eq("line one\nˇline two\nline three");
1316 cx.simulate_shared_keystrokes("` a").await;
1317 cx.shared_state()
1318 .await
1319 .assert_eq("line one\nline ˇtwo\nline three");
1320
1321 cx.simulate_shared_keystrokes("^ d ` a").await;
1322 cx.shared_state()
1323 .await
1324 .assert_eq("line one\nˇtwo\nline three");
1325}
1326
1327#[perf]
1328#[gpui::test]
1329async fn test_lt_gt_marks(cx: &mut TestAppContext) {
1330 let mut cx = NeovimBackedTestContext::new(cx).await;
1331
1332 cx.set_shared_state(indoc!(
1333 "
1334 Line one
1335 Line two
1336 Line ˇthree
1337 Line four
1338 Line five
1339 "
1340 ))
1341 .await;
1342
1343 cx.simulate_shared_keystrokes("v j escape k k").await;
1344
1345 cx.simulate_shared_keystrokes("' <").await;
1346 cx.shared_state().await.assert_eq(indoc! {"
1347 Line one
1348 Line two
1349 ˇLine three
1350 Line four
1351 Line five
1352 "});
1353
1354 cx.simulate_shared_keystrokes("` <").await;
1355 cx.shared_state().await.assert_eq(indoc! {"
1356 Line one
1357 Line two
1358 Line ˇthree
1359 Line four
1360 Line five
1361 "});
1362
1363 cx.simulate_shared_keystrokes("' >").await;
1364 cx.shared_state().await.assert_eq(indoc! {"
1365 Line one
1366 Line two
1367 Line three
1368 ˇLine four
1369 Line five
1370 "
1371 });
1372
1373 cx.simulate_shared_keystrokes("` >").await;
1374 cx.shared_state().await.assert_eq(indoc! {"
1375 Line one
1376 Line two
1377 Line three
1378 Line ˇfour
1379 Line five
1380 "
1381 });
1382
1383 cx.simulate_shared_keystrokes("v i w o escape").await;
1384 cx.simulate_shared_keystrokes("` >").await;
1385 cx.shared_state().await.assert_eq(indoc! {"
1386 Line one
1387 Line two
1388 Line three
1389 Line fouˇr
1390 Line five
1391 "
1392 });
1393 cx.simulate_shared_keystrokes("` <").await;
1394 cx.shared_state().await.assert_eq(indoc! {"
1395 Line one
1396 Line two
1397 Line three
1398 Line ˇfour
1399 Line five
1400 "
1401 });
1402}
1403
1404#[perf]
1405#[gpui::test]
1406async fn test_caret_mark(cx: &mut TestAppContext) {
1407 let mut cx = NeovimBackedTestContext::new(cx).await;
1408
1409 cx.set_shared_state(indoc!(
1410 "
1411 Line one
1412 Line two
1413 Line three
1414 ˇLine four
1415 Line five
1416 "
1417 ))
1418 .await;
1419
1420 cx.simulate_shared_keystrokes("c w shift-s t r a i g h t space t h i n g escape j j")
1421 .await;
1422
1423 cx.simulate_shared_keystrokes("' ^").await;
1424 cx.shared_state().await.assert_eq(indoc! {"
1425 Line one
1426 Line two
1427 Line three
1428 ˇStraight thing four
1429 Line five
1430 "
1431 });
1432
1433 cx.simulate_shared_keystrokes("` ^").await;
1434 cx.shared_state().await.assert_eq(indoc! {"
1435 Line one
1436 Line two
1437 Line three
1438 Straight thingˇ four
1439 Line five
1440 "
1441 });
1442
1443 cx.simulate_shared_keystrokes("k a ! escape k g i ?").await;
1444 cx.shared_state().await.assert_eq(indoc! {"
1445 Line one
1446 Line two
1447 Line three!?ˇ
1448 Straight thing four
1449 Line five
1450 "
1451 });
1452}
1453
1454#[cfg(target_os = "macos")]
1455#[perf]
1456#[gpui::test]
1457async fn test_dw_eol(cx: &mut gpui::TestAppContext) {
1458 let mut cx = NeovimBackedTestContext::new(cx).await;
1459
1460 cx.set_shared_wrap(12).await;
1461 cx.set_shared_state("twelve ˇchar twelve char\ntwelve char")
1462 .await;
1463 cx.simulate_shared_keystrokes("d w").await;
1464 cx.shared_state()
1465 .await
1466 .assert_eq("twelve ˇtwelve char\ntwelve char");
1467}
1468
1469#[perf]
1470#[gpui::test]
1471async fn test_toggle_comments(cx: &mut gpui::TestAppContext) {
1472 let mut cx = VimTestContext::new(cx, true).await;
1473
1474 let language = std::sync::Arc::new(language::Language::new(
1475 language::LanguageConfig {
1476 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
1477 ..Default::default()
1478 },
1479 Some(language::tree_sitter_rust::LANGUAGE.into()),
1480 ));
1481 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
1482
1483 // works in normal model
1484 cx.set_state(
1485 indoc! {"
1486 ˇone
1487 two
1488 three
1489 "},
1490 Mode::Normal,
1491 );
1492 cx.simulate_keystrokes("g c c");
1493 cx.assert_state(
1494 indoc! {"
1495 // ˇone
1496 two
1497 three
1498 "},
1499 Mode::Normal,
1500 );
1501
1502 // works in visual mode
1503 cx.simulate_keystrokes("v j g c");
1504 cx.assert_state(
1505 indoc! {"
1506 // // ˇone
1507 // two
1508 three
1509 "},
1510 Mode::Normal,
1511 );
1512
1513 // works in visual line mode
1514 cx.simulate_keystrokes("shift-v j g c");
1515 cx.assert_state(
1516 indoc! {"
1517 // ˇone
1518 two
1519 three
1520 "},
1521 Mode::Normal,
1522 );
1523
1524 // works with count
1525 cx.simulate_keystrokes("g c 2 j");
1526 cx.assert_state(
1527 indoc! {"
1528 // // ˇone
1529 // two
1530 // three
1531 "},
1532 Mode::Normal,
1533 );
1534
1535 // works with motion object
1536 cx.simulate_keystrokes("shift-g");
1537 cx.simulate_keystrokes("g c g g");
1538 cx.assert_state(
1539 indoc! {"
1540 // one
1541 two
1542 three
1543 ˇ"},
1544 Mode::Normal,
1545 );
1546}
1547
1548#[perf]
1549#[gpui::test]
1550async fn test_find_multibyte(cx: &mut gpui::TestAppContext) {
1551 let mut cx = NeovimBackedTestContext::new(cx).await;
1552
1553 cx.set_shared_state(r#"<label for="guests">ˇPočet hostů</label>"#)
1554 .await;
1555
1556 cx.simulate_shared_keystrokes("c t < o escape").await;
1557 cx.shared_state()
1558 .await
1559 .assert_eq(r#"<label for="guests">ˇo</label>"#);
1560}
1561
1562#[perf]
1563#[gpui::test]
1564async fn test_sneak(cx: &mut gpui::TestAppContext) {
1565 let mut cx = VimTestContext::new(cx, true).await;
1566
1567 cx.update(|_window, cx| {
1568 cx.bind_keys([
1569 KeyBinding::new(
1570 "s",
1571 PushSneak { first_char: None },
1572 Some("vim_mode == normal"),
1573 ),
1574 KeyBinding::new(
1575 "shift-s",
1576 PushSneakBackward { first_char: None },
1577 Some("vim_mode == normal"),
1578 ),
1579 KeyBinding::new(
1580 "shift-s",
1581 PushSneakBackward { first_char: None },
1582 Some("vim_mode == visual"),
1583 ),
1584 ])
1585 });
1586
1587 // Sneak forwards multibyte & multiline
1588 cx.set_state(
1589 indoc! {
1590 r#"<labelˇ for="guests">
1591 Počet hostů
1592 </label>"#
1593 },
1594 Mode::Normal,
1595 );
1596 cx.simulate_keystrokes("s t ů");
1597 cx.assert_state(
1598 indoc! {
1599 r#"<label for="guests">
1600 Počet hosˇtů
1601 </label>"#
1602 },
1603 Mode::Normal,
1604 );
1605
1606 // Visual sneak backwards multibyte & multiline
1607 cx.simulate_keystrokes("v S < l");
1608 cx.assert_state(
1609 indoc! {
1610 r#"«ˇ<label for="guests">
1611 Počet host»ů
1612 </label>"#
1613 },
1614 Mode::Visual,
1615 );
1616
1617 // Sneak backwards repeated
1618 cx.set_state(r#"11 12 13 ˇ14"#, Mode::Normal);
1619 cx.simulate_keystrokes("S space 1");
1620 cx.assert_state(r#"11 12ˇ 13 14"#, Mode::Normal);
1621 cx.simulate_keystrokes(";");
1622 cx.assert_state(r#"11ˇ 12 13 14"#, Mode::Normal);
1623}
1624
1625#[perf]
1626#[gpui::test]
1627async fn test_plus_minus(cx: &mut gpui::TestAppContext) {
1628 let mut cx = NeovimBackedTestContext::new(cx).await;
1629
1630 cx.set_shared_state(indoc! {
1631 "one
1632 two
1633 thrˇee
1634 "})
1635 .await;
1636
1637 cx.simulate_shared_keystrokes("-").await;
1638 cx.shared_state().await.assert_matches();
1639 cx.simulate_shared_keystrokes("-").await;
1640 cx.shared_state().await.assert_matches();
1641 cx.simulate_shared_keystrokes("+").await;
1642 cx.shared_state().await.assert_matches();
1643}
1644
1645#[perf]
1646#[gpui::test]
1647async fn test_command_alias(cx: &mut gpui::TestAppContext) {
1648 let mut cx = VimTestContext::new(cx, true).await;
1649 cx.update_global(|store: &mut SettingsStore, cx| {
1650 store.update_user_settings(cx, |s| {
1651 let mut aliases = HashMap::default();
1652 aliases.insert("Q".to_string(), "upper".to_string());
1653 s.workspace.command_aliases = aliases
1654 });
1655 });
1656
1657 cx.set_state("ˇhello world", Mode::Normal);
1658 cx.simulate_keystrokes(": Q");
1659 cx.set_state("ˇHello world", Mode::Normal);
1660}
1661
1662#[perf]
1663#[gpui::test]
1664async fn test_remap_adjacent_dog_cat(cx: &mut gpui::TestAppContext) {
1665 let mut cx = NeovimBackedTestContext::new(cx).await;
1666 cx.update(|_, cx| {
1667 cx.bind_keys([
1668 KeyBinding::new(
1669 "d o g",
1670 workspace::SendKeystrokes("🐶".to_string()),
1671 Some("vim_mode == insert"),
1672 ),
1673 KeyBinding::new(
1674 "c a t",
1675 workspace::SendKeystrokes("🐱".to_string()),
1676 Some("vim_mode == insert"),
1677 ),
1678 ])
1679 });
1680 cx.neovim.exec("imap dog 🐶").await;
1681 cx.neovim.exec("imap cat 🐱").await;
1682
1683 cx.set_shared_state("ˇ").await;
1684 cx.simulate_shared_keystrokes("i d o g").await;
1685 cx.shared_state().await.assert_eq("🐶ˇ");
1686
1687 cx.set_shared_state("ˇ").await;
1688 cx.simulate_shared_keystrokes("i d o d o g").await;
1689 cx.shared_state().await.assert_eq("do🐶ˇ");
1690
1691 cx.set_shared_state("ˇ").await;
1692 cx.simulate_shared_keystrokes("i d o c a t").await;
1693 cx.shared_state().await.assert_eq("do🐱ˇ");
1694}
1695
1696#[perf]
1697#[gpui::test]
1698async fn test_remap_nested_pineapple(cx: &mut gpui::TestAppContext) {
1699 let mut cx = NeovimBackedTestContext::new(cx).await;
1700 cx.update(|_, cx| {
1701 cx.bind_keys([
1702 KeyBinding::new(
1703 "p i n",
1704 workspace::SendKeystrokes("📌".to_string()),
1705 Some("vim_mode == insert"),
1706 ),
1707 KeyBinding::new(
1708 "p i n e",
1709 workspace::SendKeystrokes("🌲".to_string()),
1710 Some("vim_mode == insert"),
1711 ),
1712 KeyBinding::new(
1713 "p i n e a p p l e",
1714 workspace::SendKeystrokes("🍍".to_string()),
1715 Some("vim_mode == insert"),
1716 ),
1717 ])
1718 });
1719 cx.neovim.exec("imap pin 📌").await;
1720 cx.neovim.exec("imap pine 🌲").await;
1721 cx.neovim.exec("imap pineapple 🍍").await;
1722
1723 cx.set_shared_state("ˇ").await;
1724 cx.simulate_shared_keystrokes("i p i n").await;
1725 cx.executor().advance_clock(Duration::from_millis(1000));
1726 cx.run_until_parked();
1727 cx.shared_state().await.assert_eq("📌ˇ");
1728
1729 cx.set_shared_state("ˇ").await;
1730 cx.simulate_shared_keystrokes("i p i n e").await;
1731 cx.executor().advance_clock(Duration::from_millis(1000));
1732 cx.run_until_parked();
1733 cx.shared_state().await.assert_eq("🌲ˇ");
1734
1735 cx.set_shared_state("ˇ").await;
1736 cx.simulate_shared_keystrokes("i p i n e a p p l e").await;
1737 cx.shared_state().await.assert_eq("🍍ˇ");
1738}
1739
1740#[perf]
1741#[gpui::test]
1742async fn test_remap_recursion(cx: &mut gpui::TestAppContext) {
1743 let mut cx = NeovimBackedTestContext::new(cx).await;
1744 cx.update(|_, cx| {
1745 cx.bind_keys([KeyBinding::new(
1746 "x",
1747 workspace::SendKeystrokes("\" _ x".to_string()),
1748 Some("VimControl"),
1749 )]);
1750 cx.bind_keys([KeyBinding::new(
1751 "y",
1752 workspace::SendKeystrokes("2 x".to_string()),
1753 Some("VimControl"),
1754 )])
1755 });
1756 cx.neovim.exec("noremap x \"_x").await;
1757 cx.neovim.exec("map y 2x").await;
1758
1759 cx.set_shared_state("ˇhello").await;
1760 cx.simulate_shared_keystrokes("d l").await;
1761 cx.shared_clipboard().await.assert_eq("h");
1762 cx.simulate_shared_keystrokes("y").await;
1763 cx.shared_clipboard().await.assert_eq("h");
1764 cx.shared_state().await.assert_eq("ˇlo");
1765}
1766
1767#[perf]
1768#[gpui::test]
1769async fn test_escape_while_waiting(cx: &mut gpui::TestAppContext) {
1770 let mut cx = NeovimBackedTestContext::new(cx).await;
1771 cx.set_shared_state("ˇhi").await;
1772 cx.simulate_shared_keystrokes("\" + escape x").await;
1773 cx.shared_state().await.assert_eq("ˇi");
1774}
1775
1776#[perf]
1777#[gpui::test]
1778async fn test_ctrl_w_override(cx: &mut gpui::TestAppContext) {
1779 let mut cx = NeovimBackedTestContext::new(cx).await;
1780 cx.update(|_, cx| {
1781 cx.bind_keys([KeyBinding::new("ctrl-w", DeleteLine, None)]);
1782 });
1783 cx.neovim.exec("map <c-w> D").await;
1784 cx.set_shared_state("ˇhi").await;
1785 cx.simulate_shared_keystrokes("ctrl-w").await;
1786 cx.shared_state().await.assert_eq("ˇ");
1787}
1788
1789#[perf]
1790#[gpui::test]
1791async fn test_visual_indent_count(cx: &mut gpui::TestAppContext) {
1792 let mut cx = VimTestContext::new(cx, true).await;
1793 cx.set_state("ˇhi", Mode::Normal);
1794 cx.simulate_keystrokes("shift-v 3 >");
1795 cx.assert_state(" ˇhi", Mode::Normal);
1796 cx.simulate_keystrokes("shift-v 2 <");
1797 cx.assert_state(" ˇhi", Mode::Normal);
1798}
1799
1800#[perf]
1801#[gpui::test]
1802async fn test_record_replay_recursion(cx: &mut gpui::TestAppContext) {
1803 let mut cx = NeovimBackedTestContext::new(cx).await;
1804
1805 cx.set_shared_state("ˇhello world").await;
1806 cx.simulate_shared_keystrokes(">").await;
1807 cx.simulate_shared_keystrokes(".").await;
1808 cx.simulate_shared_keystrokes(".").await;
1809 cx.simulate_shared_keystrokes(".").await;
1810 cx.shared_state().await.assert_eq("ˇhello world");
1811}
1812
1813#[perf]
1814#[gpui::test]
1815async fn test_blackhole_register(cx: &mut gpui::TestAppContext) {
1816 let mut cx = NeovimBackedTestContext::new(cx).await;
1817
1818 cx.set_shared_state("ˇhello world").await;
1819 cx.simulate_shared_keystrokes("d i w \" _ d a w").await;
1820 cx.simulate_shared_keystrokes("p").await;
1821 cx.shared_state().await.assert_eq("hellˇo");
1822}
1823
1824#[perf]
1825#[gpui::test]
1826async fn test_sentence_backwards(cx: &mut gpui::TestAppContext) {
1827 let mut cx = NeovimBackedTestContext::new(cx).await;
1828
1829 cx.set_shared_state("one\n\ntwo\nthree\nˇ\nfour").await;
1830 cx.simulate_shared_keystrokes("(").await;
1831 cx.shared_state()
1832 .await
1833 .assert_eq("one\n\nˇtwo\nthree\n\nfour");
1834
1835 cx.set_shared_state("hello.\n\n\nworˇld.").await;
1836 cx.simulate_shared_keystrokes("(").await;
1837 cx.shared_state().await.assert_eq("hello.\n\n\nˇworld.");
1838 cx.simulate_shared_keystrokes("(").await;
1839 cx.shared_state().await.assert_eq("hello.\n\nˇ\nworld.");
1840 cx.simulate_shared_keystrokes("(").await;
1841 cx.shared_state().await.assert_eq("ˇhello.\n\n\nworld.");
1842
1843 cx.set_shared_state("hello. worlˇd.").await;
1844 cx.simulate_shared_keystrokes("(").await;
1845 cx.shared_state().await.assert_eq("hello. ˇworld.");
1846 cx.simulate_shared_keystrokes("(").await;
1847 cx.shared_state().await.assert_eq("ˇhello. world.");
1848
1849 cx.set_shared_state(". helˇlo.").await;
1850 cx.simulate_shared_keystrokes("(").await;
1851 cx.shared_state().await.assert_eq(". ˇhello.");
1852 cx.simulate_shared_keystrokes("(").await;
1853 cx.shared_state().await.assert_eq(". ˇhello.");
1854
1855 cx.set_shared_state(indoc! {
1856 "{
1857 hello_world();
1858 ˇ}"
1859 })
1860 .await;
1861 cx.simulate_shared_keystrokes("(").await;
1862 cx.shared_state().await.assert_eq(indoc! {
1863 "ˇ{
1864 hello_world();
1865 }"
1866 });
1867
1868 cx.set_shared_state(indoc! {
1869 "Hello! World..?
1870
1871 \tHello! World... ˇ"
1872 })
1873 .await;
1874 cx.simulate_shared_keystrokes("(").await;
1875 cx.shared_state().await.assert_eq(indoc! {
1876 "Hello! World..?
1877
1878 \tHello! ˇWorld... "
1879 });
1880 cx.simulate_shared_keystrokes("(").await;
1881 cx.shared_state().await.assert_eq(indoc! {
1882 "Hello! World..?
1883
1884 \tˇHello! World... "
1885 });
1886 cx.simulate_shared_keystrokes("(").await;
1887 cx.shared_state().await.assert_eq(indoc! {
1888 "Hello! World..?
1889 ˇ
1890 \tHello! World... "
1891 });
1892 cx.simulate_shared_keystrokes("(").await;
1893 cx.shared_state().await.assert_eq(indoc! {
1894 "Hello! ˇWorld..?
1895
1896 \tHello! World... "
1897 });
1898}
1899
1900#[perf]
1901#[gpui::test]
1902async fn test_sentence_forwards(cx: &mut gpui::TestAppContext) {
1903 let mut cx = NeovimBackedTestContext::new(cx).await;
1904
1905 cx.set_shared_state("helˇlo.\n\n\nworld.").await;
1906 cx.simulate_shared_keystrokes(")").await;
1907 cx.shared_state().await.assert_eq("hello.\nˇ\n\nworld.");
1908 cx.simulate_shared_keystrokes(")").await;
1909 cx.shared_state().await.assert_eq("hello.\n\n\nˇworld.");
1910 cx.simulate_shared_keystrokes(")").await;
1911 cx.shared_state().await.assert_eq("hello.\n\n\nworldˇ.");
1912
1913 cx.set_shared_state("helˇlo.\n\n\nworld.").await;
1914}
1915
1916#[perf]
1917#[gpui::test]
1918async fn test_ctrl_o_visual(cx: &mut gpui::TestAppContext) {
1919 let mut cx = NeovimBackedTestContext::new(cx).await;
1920
1921 cx.set_shared_state("helloˇ world.").await;
1922 cx.simulate_shared_keystrokes("i ctrl-o v b r l").await;
1923 cx.shared_state().await.assert_eq("ˇllllllworld.");
1924 cx.simulate_shared_keystrokes("ctrl-o v f w d").await;
1925 cx.shared_state().await.assert_eq("ˇorld.");
1926}
1927
1928#[perf]
1929#[gpui::test]
1930async fn test_ctrl_o_position(cx: &mut gpui::TestAppContext) {
1931 let mut cx = NeovimBackedTestContext::new(cx).await;
1932
1933 cx.set_shared_state("helˇlo world.").await;
1934 cx.simulate_shared_keystrokes("i ctrl-o d i w").await;
1935 cx.shared_state().await.assert_eq("ˇ world.");
1936 cx.simulate_shared_keystrokes("ctrl-o p").await;
1937 cx.shared_state().await.assert_eq(" helloˇworld.");
1938}
1939
1940#[perf]
1941#[gpui::test]
1942async fn test_ctrl_o_dot(cx: &mut gpui::TestAppContext) {
1943 let mut cx = NeovimBackedTestContext::new(cx).await;
1944
1945 cx.set_shared_state("heˇllo world.").await;
1946 cx.simulate_shared_keystrokes("x i ctrl-o .").await;
1947 cx.shared_state().await.assert_eq("heˇo world.");
1948 cx.simulate_shared_keystrokes("l l escape .").await;
1949 cx.shared_state().await.assert_eq("hellˇllo world.");
1950}
1951
1952#[perf(iterations = 1)]
1953#[gpui::test]
1954async fn test_folded_multibuffer_excerpts(cx: &mut gpui::TestAppContext) {
1955 VimTestContext::init(cx);
1956 cx.update(|cx| {
1957 VimTestContext::init_keybindings(true, cx);
1958 });
1959 let (editor, cx) = cx.add_window_view(|window, cx| {
1960 let multi_buffer = MultiBuffer::build_multi(
1961 [
1962 ("111\n222\n333\n444\n", vec![Point::row_range(0..2)]),
1963 ("aaa\nbbb\nccc\nddd\n", vec![Point::row_range(0..2)]),
1964 ("AAA\nBBB\nCCC\nDDD\n", vec![Point::row_range(0..2)]),
1965 ("one\ntwo\nthr\nfou\n", vec![Point::row_range(0..2)]),
1966 ],
1967 cx,
1968 );
1969 let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
1970
1971 let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
1972 // fold all but the second buffer, so that we test navigating between two
1973 // adjacent folded buffers, as well as folded buffers at the start and
1974 // end the multibuffer
1975 editor.fold_buffer(buffer_ids[0], cx);
1976 editor.fold_buffer(buffer_ids[2], cx);
1977 editor.fold_buffer(buffer_ids[3], cx);
1978
1979 editor
1980 });
1981 let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
1982
1983 cx.assert_excerpts_with_selections(indoc! {"
1984 [EXCERPT]
1985 ˇ[FOLDED]
1986 [EXCERPT]
1987 aaa
1988 bbb
1989 [EXCERPT]
1990 [FOLDED]
1991 [EXCERPT]
1992 [FOLDED]
1993 "
1994 });
1995 cx.simulate_keystroke("j");
1996 cx.assert_excerpts_with_selections(indoc! {"
1997 [EXCERPT]
1998 [FOLDED]
1999 [EXCERPT]
2000 ˇaaa
2001 bbb
2002 [EXCERPT]
2003 [FOLDED]
2004 [EXCERPT]
2005 [FOLDED]
2006 "
2007 });
2008 cx.simulate_keystroke("j");
2009 cx.simulate_keystroke("j");
2010 cx.assert_excerpts_with_selections(indoc! {"
2011 [EXCERPT]
2012 [FOLDED]
2013 [EXCERPT]
2014 aaa
2015 bbb
2016 ˇ[EXCERPT]
2017 [FOLDED]
2018 [EXCERPT]
2019 [FOLDED]
2020 "
2021 });
2022 cx.simulate_keystroke("j");
2023 cx.assert_excerpts_with_selections(indoc! {"
2024 [EXCERPT]
2025 [FOLDED]
2026 [EXCERPT]
2027 aaa
2028 bbb
2029 [EXCERPT]
2030 ˇ[FOLDED]
2031 [EXCERPT]
2032 [FOLDED]
2033 "
2034 });
2035 cx.simulate_keystroke("j");
2036 cx.assert_excerpts_with_selections(indoc! {"
2037 [EXCERPT]
2038 [FOLDED]
2039 [EXCERPT]
2040 aaa
2041 bbb
2042 [EXCERPT]
2043 [FOLDED]
2044 [EXCERPT]
2045 ˇ[FOLDED]
2046 "
2047 });
2048 cx.simulate_keystroke("k");
2049 cx.assert_excerpts_with_selections(indoc! {"
2050 [EXCERPT]
2051 [FOLDED]
2052 [EXCERPT]
2053 aaa
2054 bbb
2055 [EXCERPT]
2056 ˇ[FOLDED]
2057 [EXCERPT]
2058 [FOLDED]
2059 "
2060 });
2061 cx.simulate_keystroke("k");
2062 cx.simulate_keystroke("k");
2063 cx.simulate_keystroke("k");
2064 cx.assert_excerpts_with_selections(indoc! {"
2065 [EXCERPT]
2066 [FOLDED]
2067 [EXCERPT]
2068 ˇaaa
2069 bbb
2070 [EXCERPT]
2071 [FOLDED]
2072 [EXCERPT]
2073 [FOLDED]
2074 "
2075 });
2076 cx.simulate_keystroke("k");
2077 cx.assert_excerpts_with_selections(indoc! {"
2078 [EXCERPT]
2079 ˇ[FOLDED]
2080 [EXCERPT]
2081 aaa
2082 bbb
2083 [EXCERPT]
2084 [FOLDED]
2085 [EXCERPT]
2086 [FOLDED]
2087 "
2088 });
2089 cx.simulate_keystroke("shift-g");
2090 cx.assert_excerpts_with_selections(indoc! {"
2091 [EXCERPT]
2092 [FOLDED]
2093 [EXCERPT]
2094 aaa
2095 bbb
2096 [EXCERPT]
2097 [FOLDED]
2098 [EXCERPT]
2099 ˇ[FOLDED]
2100 "
2101 });
2102 cx.simulate_keystrokes("g g");
2103 cx.assert_excerpts_with_selections(indoc! {"
2104 [EXCERPT]
2105 ˇ[FOLDED]
2106 [EXCERPT]
2107 aaa
2108 bbb
2109 [EXCERPT]
2110 [FOLDED]
2111 [EXCERPT]
2112 [FOLDED]
2113 "
2114 });
2115 cx.update_editor(|editor, _, cx| {
2116 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
2117 editor.fold_buffer(buffer_ids[1], cx);
2118 });
2119
2120 cx.assert_excerpts_with_selections(indoc! {"
2121 [EXCERPT]
2122 ˇ[FOLDED]
2123 [EXCERPT]
2124 [FOLDED]
2125 [EXCERPT]
2126 [FOLDED]
2127 [EXCERPT]
2128 [FOLDED]
2129 "
2130 });
2131 cx.simulate_keystrokes("2 j");
2132 cx.assert_excerpts_with_selections(indoc! {"
2133 [EXCERPT]
2134 [FOLDED]
2135 [EXCERPT]
2136 [FOLDED]
2137 [EXCERPT]
2138 ˇ[FOLDED]
2139 [EXCERPT]
2140 [FOLDED]
2141 "
2142 });
2143}
2144
2145#[perf]
2146#[gpui::test]
2147async fn test_delete_paragraph_motion(cx: &mut gpui::TestAppContext) {
2148 let mut cx = NeovimBackedTestContext::new(cx).await;
2149 cx.set_shared_state(indoc! {
2150 "ˇhello world.
2151
2152 hello world.
2153 "
2154 })
2155 .await;
2156 cx.simulate_shared_keystrokes("y }").await;
2157 cx.shared_clipboard().await.assert_eq("hello world.\n");
2158 cx.simulate_shared_keystrokes("d }").await;
2159 cx.shared_state().await.assert_eq("ˇ\nhello world.\n");
2160 cx.shared_clipboard().await.assert_eq("hello world.\n");
2161
2162 cx.set_shared_state(indoc! {
2163 "helˇlo world.
2164
2165 hello world.
2166 "
2167 })
2168 .await;
2169 cx.simulate_shared_keystrokes("y }").await;
2170 cx.shared_clipboard().await.assert_eq("lo world.");
2171 cx.simulate_shared_keystrokes("d }").await;
2172 cx.shared_state().await.assert_eq("heˇl\n\nhello world.\n");
2173 cx.shared_clipboard().await.assert_eq("lo world.");
2174}
2175
2176#[perf]
2177#[gpui::test]
2178async fn test_delete_unmatched_brace(cx: &mut gpui::TestAppContext) {
2179 let mut cx = NeovimBackedTestContext::new(cx).await;
2180 cx.set_shared_state(indoc! {
2181 "fn o(wow: i32) {
2182 othˇ(wow)
2183 oth(wow)
2184 }
2185 "
2186 })
2187 .await;
2188 cx.simulate_shared_keystrokes("d ] }").await;
2189 cx.shared_state().await.assert_eq(indoc! {
2190 "fn o(wow: i32) {
2191 otˇh
2192 }
2193 "
2194 });
2195 cx.shared_clipboard().await.assert_eq("(wow)\n oth(wow)");
2196 cx.set_shared_state(indoc! {
2197 "fn o(wow: i32) {
2198 ˇoth(wow)
2199 oth(wow)
2200 }
2201 "
2202 })
2203 .await;
2204 cx.simulate_shared_keystrokes("d ] }").await;
2205 cx.shared_state().await.assert_eq(indoc! {
2206 "fn o(wow: i32) {
2207 ˇ}
2208 "
2209 });
2210 cx.shared_clipboard()
2211 .await
2212 .assert_eq(" oth(wow)\n oth(wow)\n");
2213}
2214
2215#[perf]
2216#[gpui::test]
2217async fn test_paragraph_multi_delete(cx: &mut gpui::TestAppContext) {
2218 let mut cx = NeovimBackedTestContext::new(cx).await;
2219 cx.set_shared_state(indoc! {
2220 "
2221 Emacs is
2222 ˇa great
2223
2224 operating system
2225
2226 all it lacks
2227 is a
2228
2229 decent text editor
2230 "
2231 })
2232 .await;
2233
2234 cx.simulate_shared_keystrokes("2 d a p").await;
2235 cx.shared_state().await.assert_eq(indoc! {
2236 "
2237 ˇall it lacks
2238 is a
2239
2240 decent text editor
2241 "
2242 });
2243
2244 cx.simulate_shared_keystrokes("d a p").await;
2245 cx.shared_clipboard()
2246 .await
2247 .assert_eq("all it lacks\nis a\n\n");
2248
2249 //reset to initial state
2250 cx.simulate_shared_keystrokes("2 u").await;
2251
2252 cx.simulate_shared_keystrokes("4 d a p").await;
2253 cx.shared_state().await.assert_eq(indoc! {"ˇ"});
2254}
2255
2256#[perf]
2257#[gpui::test]
2258async fn test_multi_cursor_replay(cx: &mut gpui::TestAppContext) {
2259 let mut cx = VimTestContext::new(cx, true).await;
2260 cx.set_state(
2261 indoc! {
2262 "
2263 oˇne one one
2264
2265 two two two
2266 "
2267 },
2268 Mode::Normal,
2269 );
2270
2271 cx.simulate_keystrokes("3 g l s wow escape escape");
2272 cx.assert_state(
2273 indoc! {
2274 "
2275 woˇw wow wow
2276
2277 two two two
2278 "
2279 },
2280 Mode::Normal,
2281 );
2282
2283 cx.simulate_keystrokes("2 j 3 g l .");
2284 cx.assert_state(
2285 indoc! {
2286 "
2287 wow wow wow
2288
2289 woˇw woˇw woˇw
2290 "
2291 },
2292 Mode::Normal,
2293 );
2294}
2295
2296#[gpui::test]
2297async fn test_clipping_on_mode_change(cx: &mut gpui::TestAppContext) {
2298 let mut cx = VimTestContext::new(cx, true).await;
2299
2300 cx.set_state(
2301 indoc! {
2302 "
2303 ˇverylongline
2304 andsomelinebelow
2305 "
2306 },
2307 Mode::Normal,
2308 );
2309
2310 cx.simulate_keystrokes("v e");
2311 cx.assert_state(
2312 indoc! {
2313 "
2314 «verylonglineˇ»
2315 andsomelinebelow
2316 "
2317 },
2318 Mode::Visual,
2319 );
2320
2321 let mut pixel_position = cx.update_editor(|editor, window, cx| {
2322 let snapshot = editor.snapshot(window, cx);
2323 let current_head = editor
2324 .selections
2325 .newest_display(&snapshot.display_snapshot)
2326 .end;
2327 editor.last_bounds().unwrap().origin
2328 + editor
2329 .display_to_pixel_point(current_head, &snapshot, window)
2330 .unwrap()
2331 });
2332 pixel_position.x += px(100.);
2333 // click beyond end of the line
2334 cx.simulate_click(pixel_position, Modifiers::default());
2335 cx.run_until_parked();
2336
2337 cx.assert_state(
2338 indoc! {
2339 "
2340 verylonglinˇe
2341 andsomelinebelow
2342 "
2343 },
2344 Mode::Normal,
2345 );
2346}
2347
2348#[gpui::test]
2349async fn test_wrap_selections_in_tag_line_mode(cx: &mut gpui::TestAppContext) {
2350 let mut cx = VimTestContext::new(cx, true).await;
2351
2352 let js_language = Arc::new(Language::new(
2353 LanguageConfig {
2354 name: "JavaScript".into(),
2355 wrap_characters: Some(language::WrapCharactersConfig {
2356 start_prefix: "<".into(),
2357 start_suffix: ">".into(),
2358 end_prefix: "</".into(),
2359 end_suffix: ">".into(),
2360 }),
2361 ..LanguageConfig::default()
2362 },
2363 None,
2364 ));
2365
2366 cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
2367
2368 cx.set_state(
2369 indoc! {
2370 "
2371 ˇaaaaa
2372 bbbbb
2373 "
2374 },
2375 Mode::Normal,
2376 );
2377
2378 cx.simulate_keystrokes("shift-v j");
2379 cx.dispatch_action(WrapSelectionsInTag);
2380
2381 cx.assert_state(
2382 indoc! {
2383 "
2384 <ˇ>aaaaa
2385 bbbbb</ˇ>
2386 "
2387 },
2388 Mode::VisualLine,
2389 );
2390}
2391
2392#[gpui::test]
2393async fn test_repeat_grouping_41735(cx: &mut gpui::TestAppContext) {
2394 let mut cx = NeovimBackedTestContext::new(cx).await;
2395
2396 // typically transaction gropuing is disabled in tests, but here we need to test it.
2397 cx.update_buffer(|buffer, _cx| buffer.set_group_interval(Duration::from_millis(300)));
2398
2399 cx.set_shared_state("ˇ").await;
2400
2401 cx.simulate_shared_keystrokes("i a escape").await;
2402 cx.simulate_shared_keystrokes(". . .").await;
2403 cx.shared_state().await.assert_eq("ˇaaaa");
2404 cx.simulate_shared_keystrokes("u").await;
2405 cx.shared_state().await.assert_eq("ˇaaa");
2406}
2407
2408#[gpui::test]
2409async fn test_deactivate(cx: &mut gpui::TestAppContext) {
2410 let mut cx = VimTestContext::new(cx, true).await;
2411
2412 cx.update_global(|store: &mut SettingsStore, cx| {
2413 store.update_user_settings(cx, |settings| {
2414 settings.editor.cursor_shape = Some(settings::CursorShape::Underline);
2415 });
2416 });
2417
2418 // Assert that, while in `Normal` mode, the cursor shape is `Block` but,
2419 // after deactivating vim mode, it should revert to the one specified in the
2420 // user's settings, if set.
2421 cx.update_editor(|editor, _window, _cx| {
2422 assert_eq!(editor.cursor_shape(), CursorShape::Block);
2423 });
2424
2425 cx.disable_vim();
2426
2427 cx.update_editor(|editor, _window, _cx| {
2428 assert_eq!(editor.cursor_shape(), CursorShape::Underline);
2429 });
2430}