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