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