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