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