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