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::{actions::DeleteLine, display_map::DisplayRow, DisplayPoint};
10use futures::StreamExt;
11use gpui::{KeyBinding, Modifiers, MouseButton, TestAppContext};
12pub use neovim_backed_test_context::*;
13use settings::SettingsStore;
14pub use vim_test_context::*;
15
16use indoc::indoc;
17use search::BufferSearchBar;
18use workspace::WorkspaceSettings;
19
20use crate::{insert::NormalBefore, motion, state::Mode, ModeIndicator};
21
22#[gpui::test]
23async fn test_initially_disabled(cx: &mut gpui::TestAppContext) {
24 let mut cx = VimTestContext::new(cx, false).await;
25 cx.simulate_keystrokes("h j k l");
26 cx.assert_editor_state("hjklˇ");
27}
28
29#[gpui::test]
30async fn test_neovim(cx: &mut gpui::TestAppContext) {
31 let mut cx = NeovimBackedTestContext::new(cx).await;
32
33 cx.simulate_shared_keystrokes("i").await;
34 cx.shared_state().await.assert_matches();
35 cx.simulate_shared_keystrokes("shift-t e s t space t e s t escape 0 d w")
36 .await;
37 cx.shared_state().await.assert_matches();
38 cx.assert_editor_state("ˇtest");
39}
40
41#[gpui::test]
42async fn test_toggle_through_settings(cx: &mut gpui::TestAppContext) {
43 let mut cx = VimTestContext::new(cx, true).await;
44
45 cx.simulate_keystrokes("i");
46 assert_eq!(cx.mode(), Mode::Insert);
47
48 // Editor acts as though vim is disabled
49 cx.disable_vim();
50 cx.simulate_keystrokes("h j k l");
51 cx.assert_editor_state("hjklˇ");
52
53 // Selections aren't changed if editor is blurred but vim-mode is still disabled.
54 cx.set_state("«hjklˇ»", Mode::Normal);
55 cx.assert_editor_state("«hjklˇ»");
56 cx.update_editor(|_, cx| cx.blur());
57 cx.assert_editor_state("«hjklˇ»");
58 cx.update_editor(|_, cx| cx.focus_self());
59 cx.assert_editor_state("«hjklˇ»");
60
61 // Enabling dynamically sets vim mode again and restores normal mode
62 cx.enable_vim();
63 assert_eq!(cx.mode(), Mode::Normal);
64 cx.simulate_keystrokes("h h h l");
65 assert_eq!(cx.buffer_text(), "hjkl".to_owned());
66 cx.assert_editor_state("hˇjkl");
67 cx.simulate_keystrokes("i T e s t");
68 cx.assert_editor_state("hTestˇjkl");
69
70 // Disabling and enabling resets to normal mode
71 assert_eq!(cx.mode(), Mode::Insert);
72 cx.disable_vim();
73 cx.enable_vim();
74 assert_eq!(cx.mode(), Mode::Normal);
75}
76
77#[gpui::test]
78async fn test_cancel_selection(cx: &mut gpui::TestAppContext) {
79 let mut cx = VimTestContext::new(cx, true).await;
80
81 cx.set_state(
82 indoc! {"The quick brown fox juˇmps over the lazy dog"},
83 Mode::Normal,
84 );
85 // jumps
86 cx.simulate_keystrokes("v l l");
87 cx.assert_editor_state("The quick brown fox ju«mpsˇ» over the lazy dog");
88
89 cx.simulate_keystrokes("escape");
90 cx.assert_editor_state("The quick brown fox jumpˇs over the lazy dog");
91
92 // go back to the same selection state
93 cx.simulate_keystrokes("v h h");
94 cx.assert_editor_state("The quick brown fox ju«ˇmps» over the lazy dog");
95
96 // Ctrl-[ should behave like Esc
97 cx.simulate_keystrokes("ctrl-[");
98 cx.assert_editor_state("The quick brown fox juˇmps over the lazy dog");
99}
100
101#[gpui::test]
102async fn test_buffer_search(cx: &mut gpui::TestAppContext) {
103 let mut cx = VimTestContext::new(cx, true).await;
104
105 cx.set_state(
106 indoc! {"
107 The quick brown
108 fox juˇmps over
109 the lazy dog"},
110 Mode::Normal,
111 );
112 cx.simulate_keystrokes("/");
113
114 let search_bar = cx.workspace(|workspace, cx| {
115 workspace
116 .active_pane()
117 .read(cx)
118 .toolbar()
119 .read(cx)
120 .item_of_type::<BufferSearchBar>()
121 .expect("Buffer search bar should be deployed")
122 });
123
124 cx.update_view(search_bar, |bar, cx| {
125 assert_eq!(bar.query(cx), "");
126 })
127}
128
129#[gpui::test]
130async fn test_count_down(cx: &mut gpui::TestAppContext) {
131 let mut cx = VimTestContext::new(cx, true).await;
132
133 cx.set_state(indoc! {"aˇa\nbb\ncc\ndd\nee"}, Mode::Normal);
134 cx.simulate_keystrokes("2 down");
135 cx.assert_editor_state("aa\nbb\ncˇc\ndd\nee");
136 cx.simulate_keystrokes("9 down");
137 cx.assert_editor_state("aa\nbb\ncc\ndd\neˇe");
138}
139
140#[gpui::test]
141async fn test_end_of_document_710(cx: &mut gpui::TestAppContext) {
142 let mut cx = VimTestContext::new(cx, true).await;
143
144 // goes to end by default
145 cx.set_state(indoc! {"aˇa\nbb\ncc"}, Mode::Normal);
146 cx.simulate_keystrokes("shift-g");
147 cx.assert_editor_state("aa\nbb\ncˇc");
148
149 // can go to line 1 (https://github.com/zed-industries/zed/issues/5812)
150 cx.simulate_keystrokes("1 shift-g");
151 cx.assert_editor_state("aˇa\nbb\ncc");
152}
153
154#[gpui::test]
155async fn test_end_of_line_with_times(cx: &mut gpui::TestAppContext) {
156 let mut cx = VimTestContext::new(cx, true).await;
157
158 // goes to current line end
159 cx.set_state(indoc! {"ˇaa\nbb\ncc"}, Mode::Normal);
160 cx.simulate_keystrokes("$");
161 cx.assert_editor_state("aˇa\nbb\ncc");
162
163 // goes to next line end
164 cx.simulate_keystrokes("2 $");
165 cx.assert_editor_state("aa\nbˇb\ncc");
166
167 // try to exceed the final line.
168 cx.simulate_keystrokes("4 $");
169 cx.assert_editor_state("aa\nbb\ncˇc");
170}
171
172#[gpui::test]
173async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
174 let mut cx = VimTestContext::new(cx, true).await;
175
176 // works in normal mode
177 cx.set_state(indoc! {"aa\nbˇb\ncc"}, Mode::Normal);
178 cx.simulate_keystrokes("> >");
179 cx.assert_editor_state("aa\n bˇb\ncc");
180 cx.simulate_keystrokes("< <");
181 cx.assert_editor_state("aa\nbˇb\ncc");
182
183 // works in visual mode
184 cx.simulate_keystrokes("shift-v down >");
185 cx.assert_editor_state("aa\n bˇb\n cc");
186
187 // works as operator
188 cx.set_state("aa\nbˇb\ncc\n", Mode::Normal);
189 cx.simulate_keystrokes("> j");
190 cx.assert_editor_state("aa\n bˇb\n cc\n");
191 cx.simulate_keystrokes("< k");
192 cx.assert_editor_state("aa\nbˇb\n cc\n");
193 cx.simulate_keystrokes("> i p");
194 cx.assert_editor_state(" aa\n bˇb\n cc\n");
195 cx.simulate_keystrokes("< i p");
196 cx.assert_editor_state("aa\nbˇb\n cc\n");
197 cx.simulate_keystrokes("< i p");
198 cx.assert_editor_state("aa\nbˇb\ncc\n");
199
200 cx.set_state("ˇaa\nbb\ncc\n", Mode::Normal);
201 cx.simulate_keystrokes("> 2 j");
202 cx.assert_editor_state(" ˇaa\n bb\n cc\n");
203
204 cx.set_state("aa\nbb\nˇcc\n", Mode::Normal);
205 cx.simulate_keystrokes("> 2 k");
206 cx.assert_editor_state(" aa\n bb\n ˇcc\n");
207
208 // works with repeat
209 cx.set_state("a\nb\nccˇc\n", Mode::Normal);
210 cx.simulate_keystrokes("> 2 k");
211 cx.assert_editor_state(" a\n b\n ccˇc\n");
212 cx.simulate_keystrokes(".");
213 cx.assert_editor_state(" a\n b\n ccˇc\n");
214 cx.simulate_keystrokes("v k <");
215 cx.assert_editor_state(" a\n bˇ\n ccc\n");
216 cx.simulate_keystrokes(".");
217 cx.assert_editor_state(" a\nbˇ\nccc\n");
218}
219
220#[gpui::test]
221async fn test_escape_command_palette(cx: &mut gpui::TestAppContext) {
222 let mut cx = VimTestContext::new(cx, true).await;
223
224 cx.set_state("aˇbc\n", Mode::Normal);
225 cx.simulate_keystrokes("i cmd-shift-p");
226
227 assert!(cx.workspace(|workspace, cx| workspace.active_modal::<CommandPalette>(cx).is_some()));
228 cx.simulate_keystrokes("escape");
229 cx.run_until_parked();
230 assert!(!cx.workspace(|workspace, cx| workspace.active_modal::<CommandPalette>(cx).is_some()));
231 cx.assert_state("aˇbc\n", Mode::Insert);
232}
233
234#[gpui::test]
235async fn test_escape_cancels(cx: &mut gpui::TestAppContext) {
236 let mut cx = VimTestContext::new(cx, true).await;
237
238 cx.set_state("aˇbˇc", Mode::Normal);
239 cx.simulate_keystrokes("escape");
240
241 cx.assert_state("aˇbc", Mode::Normal);
242}
243
244#[gpui::test]
245async fn test_selection_on_search(cx: &mut gpui::TestAppContext) {
246 let mut cx = VimTestContext::new(cx, true).await;
247
248 cx.set_state(indoc! {"aa\nbˇb\ncc\ncc\ncc\n"}, Mode::Normal);
249 cx.simulate_keystrokes("/ c c");
250
251 let search_bar = cx.workspace(|workspace, cx| {
252 workspace
253 .active_pane()
254 .read(cx)
255 .toolbar()
256 .read(cx)
257 .item_of_type::<BufferSearchBar>()
258 .expect("Buffer search bar should be deployed")
259 });
260
261 cx.update_view(search_bar, |bar, cx| {
262 assert_eq!(bar.query(cx), "cc");
263 });
264
265 cx.update_editor(|editor, cx| {
266 let highlights = editor.all_text_background_highlights(cx);
267 assert_eq!(3, highlights.len());
268 assert_eq!(
269 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 2),
270 highlights[0].0
271 )
272 });
273 cx.simulate_keystrokes("enter");
274
275 cx.assert_state(indoc! {"aa\nbb\nˇcc\ncc\ncc\n"}, Mode::Normal);
276 cx.simulate_keystrokes("n");
277 cx.assert_state(indoc! {"aa\nbb\ncc\nˇcc\ncc\n"}, Mode::Normal);
278 cx.simulate_keystrokes("shift-n");
279 cx.assert_state(indoc! {"aa\nbb\nˇcc\ncc\ncc\n"}, Mode::Normal);
280}
281
282#[gpui::test]
283async fn test_status_indicator(cx: &mut gpui::TestAppContext) {
284 let mut cx = VimTestContext::new(cx, true).await;
285
286 let mode_indicator = cx.workspace(|workspace, cx| {
287 let status_bar = workspace.status_bar().read(cx);
288 let mode_indicator = status_bar.item_of_type::<ModeIndicator>();
289 assert!(mode_indicator.is_some());
290 mode_indicator.unwrap()
291 });
292
293 assert_eq!(
294 cx.workspace(|_, cx| mode_indicator.read(cx).mode),
295 Some(Mode::Normal)
296 );
297
298 // shows the correct mode
299 cx.simulate_keystrokes("i");
300 assert_eq!(
301 cx.workspace(|_, cx| mode_indicator.read(cx).mode),
302 Some(Mode::Insert)
303 );
304 cx.simulate_keystrokes("escape shift-r");
305 assert_eq!(
306 cx.workspace(|_, cx| mode_indicator.read(cx).mode),
307 Some(Mode::Replace)
308 );
309
310 // shows even in search
311 cx.simulate_keystrokes("escape v /");
312 assert_eq!(
313 cx.workspace(|_, cx| mode_indicator.read(cx).mode),
314 Some(Mode::Visual)
315 );
316
317 // hides if vim mode is disabled
318 cx.disable_vim();
319 cx.run_until_parked();
320 cx.workspace(|workspace, cx| {
321 let status_bar = workspace.status_bar().read(cx);
322 let mode_indicator = status_bar.item_of_type::<ModeIndicator>().unwrap();
323 assert!(mode_indicator.read(cx).mode.is_none());
324 });
325
326 cx.enable_vim();
327 cx.run_until_parked();
328 cx.workspace(|workspace, cx| {
329 let status_bar = workspace.status_bar().read(cx);
330 let mode_indicator = status_bar.item_of_type::<ModeIndicator>().unwrap();
331 assert!(mode_indicator.read(cx).mode.is_some());
332 });
333}
334
335#[gpui::test]
336async fn test_word_characters(cx: &mut gpui::TestAppContext) {
337 let mut cx = VimTestContext::new_typescript(cx).await;
338 cx.set_state(
339 indoc! { "
340 class A {
341 #ˇgoop = 99;
342 $ˇgoop () { return this.#gˇoop };
343 };
344 console.log(new A().$gooˇp())
345 "},
346 Mode::Normal,
347 );
348 cx.simulate_keystrokes("v i w");
349 cx.assert_state(
350 indoc! {"
351 class A {
352 «#goopˇ» = 99;
353 «$goopˇ» () { return this.«#goopˇ» };
354 };
355 console.log(new A().«$goopˇ»())
356 "},
357 Mode::Visual,
358 )
359}
360
361#[gpui::test]
362async fn test_join_lines(cx: &mut gpui::TestAppContext) {
363 let mut cx = NeovimBackedTestContext::new(cx).await;
364
365 cx.set_shared_state(indoc! {"
366 ˇone
367 two
368 three
369 four
370 five
371 six
372 "})
373 .await;
374 cx.simulate_shared_keystrokes("shift-j").await;
375 cx.shared_state().await.assert_eq(indoc! {"
376 oneˇ two
377 three
378 four
379 five
380 six
381 "});
382 cx.simulate_shared_keystrokes("3 shift-j").await;
383 cx.shared_state().await.assert_eq(indoc! {"
384 one two threeˇ four
385 five
386 six
387 "});
388
389 cx.set_shared_state(indoc! {"
390 ˇone
391 two
392 three
393 four
394 five
395 six
396 "})
397 .await;
398 cx.simulate_shared_keystrokes("j v 3 j shift-j").await;
399 cx.shared_state().await.assert_eq(indoc! {"
400 one
401 two three fourˇ five
402 six
403 "});
404}
405
406#[cfg(target_os = "macos")]
407#[gpui::test]
408async fn test_wrapped_lines(cx: &mut gpui::TestAppContext) {
409 let mut cx = NeovimBackedTestContext::new(cx).await;
410
411 cx.set_shared_wrap(12).await;
412 // tests line wrap as follows:
413 // 1: twelve char
414 // twelve char
415 // 2: twelve char
416 cx.set_shared_state(indoc! { "
417 tˇwelve char twelve char
418 twelve char
419 "})
420 .await;
421 cx.simulate_shared_keystrokes("j").await;
422 cx.shared_state().await.assert_eq(indoc! {"
423 twelve char twelve char
424 tˇwelve char
425 "});
426 cx.simulate_shared_keystrokes("k").await;
427 cx.shared_state().await.assert_eq(indoc! {"
428 tˇwelve char twelve char
429 twelve char
430 "});
431 cx.simulate_shared_keystrokes("g j").await;
432 cx.shared_state().await.assert_eq(indoc! {"
433 twelve char tˇwelve char
434 twelve char
435 "});
436 cx.simulate_shared_keystrokes("g j").await;
437 cx.shared_state().await.assert_eq(indoc! {"
438 twelve char twelve char
439 tˇwelve char
440 "});
441
442 cx.simulate_shared_keystrokes("g k").await;
443 cx.shared_state().await.assert_eq(indoc! {"
444 twelve char tˇwelve char
445 twelve char
446 "});
447
448 cx.simulate_shared_keystrokes("g ^").await;
449 cx.shared_state().await.assert_eq(indoc! {"
450 twelve char ˇtwelve char
451 twelve char
452 "});
453
454 cx.simulate_shared_keystrokes("^").await;
455 cx.shared_state().await.assert_eq(indoc! {"
456 ˇtwelve char twelve char
457 twelve char
458 "});
459
460 cx.simulate_shared_keystrokes("g $").await;
461 cx.shared_state().await.assert_eq(indoc! {"
462 twelve charˇ twelve char
463 twelve char
464 "});
465 cx.simulate_shared_keystrokes("$").await;
466 cx.shared_state().await.assert_eq(indoc! {"
467 twelve char twelve chaˇr
468 twelve char
469 "});
470
471 cx.set_shared_state(indoc! { "
472 tˇwelve char twelve char
473 twelve char
474 "})
475 .await;
476 cx.simulate_shared_keystrokes("enter").await;
477 cx.shared_state().await.assert_eq(indoc! {"
478 twelve char twelve char
479 ˇtwelve char
480 "});
481
482 cx.set_shared_state(indoc! { "
483 twelve char
484 tˇwelve char twelve char
485 twelve char
486 "})
487 .await;
488 cx.simulate_shared_keystrokes("o o escape").await;
489 cx.shared_state().await.assert_eq(indoc! {"
490 twelve char
491 twelve char twelve char
492 ˇo
493 twelve char
494 "});
495
496 cx.set_shared_state(indoc! { "
497 twelve char
498 tˇwelve char twelve char
499 twelve char
500 "})
501 .await;
502 cx.simulate_shared_keystrokes("shift-a a escape").await;
503 cx.shared_state().await.assert_eq(indoc! {"
504 twelve char
505 twelve char twelve charˇa
506 twelve char
507 "});
508 cx.simulate_shared_keystrokes("shift-i i escape").await;
509 cx.shared_state().await.assert_eq(indoc! {"
510 twelve char
511 ˇitwelve char twelve chara
512 twelve char
513 "});
514 cx.simulate_shared_keystrokes("shift-d").await;
515 cx.shared_state().await.assert_eq(indoc! {"
516 twelve char
517 ˇ
518 twelve char
519 "});
520
521 cx.set_shared_state(indoc! { "
522 twelve char
523 twelve char tˇwelve char
524 twelve char
525 "})
526 .await;
527 cx.simulate_shared_keystrokes("shift-o o escape").await;
528 cx.shared_state().await.assert_eq(indoc! {"
529 twelve char
530 ˇo
531 twelve char twelve char
532 twelve char
533 "});
534
535 // line wraps as:
536 // fourteen ch
537 // ar
538 // fourteen ch
539 // ar
540 cx.set_shared_state(indoc! { "
541 fourteen chaˇr
542 fourteen char
543 "})
544 .await;
545
546 cx.simulate_shared_keystrokes("d i w").await;
547 cx.shared_state().await.assert_eq(indoc! {"
548 fourteenˇ•
549 fourteen char
550 "});
551 cx.simulate_shared_keystrokes("j shift-f e f r").await;
552 cx.shared_state().await.assert_eq(indoc! {"
553 fourteen•
554 fourteen chaˇr
555 "});
556}
557
558#[gpui::test]
559async fn test_folds(cx: &mut gpui::TestAppContext) {
560 let mut cx = NeovimBackedTestContext::new(cx).await;
561 cx.set_neovim_option("foldmethod=manual").await;
562
563 cx.set_shared_state(indoc! { "
564 fn boop() {
565 ˇbarp()
566 bazp()
567 }
568 "})
569 .await;
570 cx.simulate_shared_keystrokes("shift-v j z f").await;
571
572 // visual display is now:
573 // fn boop () {
574 // [FOLDED]
575 // }
576
577 // TODO: this should not be needed but currently zf does not
578 // return to normal mode.
579 cx.simulate_shared_keystrokes("escape").await;
580
581 // skip over fold downward
582 cx.simulate_shared_keystrokes("g g").await;
583 cx.shared_state().await.assert_eq(indoc! {"
584 ˇfn boop() {
585 barp()
586 bazp()
587 }
588 "});
589
590 cx.simulate_shared_keystrokes("j j").await;
591 cx.shared_state().await.assert_eq(indoc! {"
592 fn boop() {
593 barp()
594 bazp()
595 ˇ}
596 "});
597
598 // skip over fold upward
599 cx.simulate_shared_keystrokes("2 k").await;
600 cx.shared_state().await.assert_eq(indoc! {"
601 ˇfn boop() {
602 barp()
603 bazp()
604 }
605 "});
606
607 // yank the fold
608 cx.simulate_shared_keystrokes("down y y").await;
609 cx.shared_clipboard()
610 .await
611 .assert_eq(" barp()\n bazp()\n");
612
613 // re-open
614 cx.simulate_shared_keystrokes("z o").await;
615 cx.shared_state().await.assert_eq(indoc! {"
616 fn boop() {
617 ˇ barp()
618 bazp()
619 }
620 "});
621}
622
623#[gpui::test]
624async fn test_folds_panic(cx: &mut gpui::TestAppContext) {
625 let mut cx = NeovimBackedTestContext::new(cx).await;
626 cx.set_neovim_option("foldmethod=manual").await;
627
628 cx.set_shared_state(indoc! { "
629 fn boop() {
630 ˇbarp()
631 bazp()
632 }
633 "})
634 .await;
635 cx.simulate_shared_keystrokes("shift-v j z f").await;
636 cx.simulate_shared_keystrokes("escape").await;
637 cx.simulate_shared_keystrokes("g g").await;
638 cx.simulate_shared_keystrokes("5 d j").await;
639 cx.shared_state().await.assert_eq("ˇ");
640 cx.set_shared_state(indoc! {"
641 fn boop() {
642 ˇbarp()
643 bazp()
644 }
645 "})
646 .await;
647 cx.simulate_shared_keystrokes("shift-v j j z f").await;
648 cx.simulate_shared_keystrokes("escape").await;
649 cx.simulate_shared_keystrokes("shift-g shift-v").await;
650 cx.shared_state().await.assert_eq(indoc! {"
651 fn boop() {
652 barp()
653 bazp()
654 }
655 ˇ"});
656}
657
658#[gpui::test]
659async fn test_clear_counts(cx: &mut gpui::TestAppContext) {
660 let mut cx = NeovimBackedTestContext::new(cx).await;
661
662 cx.set_shared_state(indoc! {"
663 The quick brown
664 fox juˇmps over
665 the lazy dog"})
666 .await;
667
668 cx.simulate_shared_keystrokes("4 escape 3 d l").await;
669 cx.shared_state().await.assert_eq(indoc! {"
670 The quick brown
671 fox juˇ over
672 the lazy dog"});
673}
674
675#[gpui::test]
676async fn test_zero(cx: &mut gpui::TestAppContext) {
677 let mut cx = NeovimBackedTestContext::new(cx).await;
678
679 cx.set_shared_state(indoc! {"
680 The quˇick brown
681 fox jumps over
682 the lazy dog"})
683 .await;
684
685 cx.simulate_shared_keystrokes("0").await;
686 cx.shared_state().await.assert_eq(indoc! {"
687 ˇThe quick brown
688 fox jumps over
689 the lazy dog"});
690
691 cx.simulate_shared_keystrokes("1 0 l").await;
692 cx.shared_state().await.assert_eq(indoc! {"
693 The quick ˇbrown
694 fox jumps over
695 the lazy dog"});
696}
697
698#[gpui::test]
699async fn test_selection_goal(cx: &mut gpui::TestAppContext) {
700 let mut cx = NeovimBackedTestContext::new(cx).await;
701
702 cx.set_shared_state(indoc! {"
703 ;;ˇ;
704 Lorem Ipsum"})
705 .await;
706
707 cx.simulate_shared_keystrokes("a down up ; down up").await;
708 cx.shared_state().await.assert_eq(indoc! {"
709 ;;;;ˇ
710 Lorem Ipsum"});
711}
712
713#[cfg(target_os = "macos")]
714#[gpui::test]
715async fn test_wrapped_motions(cx: &mut gpui::TestAppContext) {
716 let mut cx = NeovimBackedTestContext::new(cx).await;
717
718 cx.set_shared_wrap(12).await;
719
720 cx.set_shared_state(indoc! {"
721 aaˇaa
722 😃😃"
723 })
724 .await;
725 cx.simulate_shared_keystrokes("j").await;
726 cx.shared_state().await.assert_eq(indoc! {"
727 aaaa
728 😃ˇ😃"
729 });
730
731 cx.set_shared_state(indoc! {"
732 123456789012aaˇaa
733 123456789012😃😃"
734 })
735 .await;
736 cx.simulate_shared_keystrokes("j").await;
737 cx.shared_state().await.assert_eq(indoc! {"
738 123456789012aaaa
739 123456789012😃ˇ😃"
740 });
741
742 cx.set_shared_state(indoc! {"
743 123456789012aaˇaa
744 123456789012😃😃"
745 })
746 .await;
747 cx.simulate_shared_keystrokes("j").await;
748 cx.shared_state().await.assert_eq(indoc! {"
749 123456789012aaaa
750 123456789012😃ˇ😃"
751 });
752
753 cx.set_shared_state(indoc! {"
754 123456789012aaaaˇaaaaaaaa123456789012
755 wow
756 123456789012😃😃😃😃😃😃123456789012"
757 })
758 .await;
759 cx.simulate_shared_keystrokes("j j").await;
760 cx.shared_state().await.assert_eq(indoc! {"
761 123456789012aaaaaaaaaaaa123456789012
762 wow
763 123456789012😃😃ˇ😃😃😃😃123456789012"
764 });
765}
766
767#[gpui::test]
768async fn test_paragraphs_dont_wrap(cx: &mut gpui::TestAppContext) {
769 let mut cx = NeovimBackedTestContext::new(cx).await;
770
771 cx.set_shared_state(indoc! {"
772 one
773 ˇ
774 two"})
775 .await;
776
777 cx.simulate_shared_keystrokes("} }").await;
778 cx.shared_state().await.assert_eq(indoc! {"
779 one
780
781 twˇo"});
782
783 cx.simulate_shared_keystrokes("{ { {").await;
784 cx.shared_state().await.assert_eq(indoc! {"
785 ˇone
786
787 two"});
788}
789
790#[gpui::test]
791async fn test_select_all_issue_2170(cx: &mut gpui::TestAppContext) {
792 let mut cx = VimTestContext::new(cx, true).await;
793
794 cx.set_state(
795 indoc! {"
796 defmodule Test do
797 def test(a, ˇ[_, _] = b), do: IO.puts('hi')
798 end
799 "},
800 Mode::Normal,
801 );
802 cx.simulate_keystrokes("g a");
803 cx.assert_state(
804 indoc! {"
805 defmodule Test do
806 def test(a, «[ˇ»_, _] = b), do: IO.puts('hi')
807 end
808 "},
809 Mode::Visual,
810 );
811}
812
813#[gpui::test]
814async fn test_jk(cx: &mut gpui::TestAppContext) {
815 let mut cx = NeovimBackedTestContext::new(cx).await;
816
817 cx.update(|cx| {
818 cx.bind_keys([KeyBinding::new(
819 "j k",
820 NormalBefore,
821 Some("vim_mode == insert"),
822 )])
823 });
824 cx.neovim.exec("imap jk <esc>").await;
825
826 cx.set_shared_state("ˇhello").await;
827 cx.simulate_shared_keystrokes("i j o j k").await;
828 cx.shared_state().await.assert_eq("jˇohello");
829}
830
831#[gpui::test]
832async fn test_jk_delay(cx: &mut gpui::TestAppContext) {
833 let mut cx = VimTestContext::new(cx, true).await;
834
835 cx.update(|cx| {
836 cx.bind_keys([KeyBinding::new(
837 "j k",
838 NormalBefore,
839 Some("vim_mode == insert"),
840 )])
841 });
842
843 cx.set_state("ˇhello", Mode::Normal);
844 cx.simulate_keystrokes("i j");
845 cx.executor().advance_clock(Duration::from_millis(500));
846 cx.run_until_parked();
847 cx.assert_state("ˇhello", Mode::Insert);
848 cx.executor().advance_clock(Duration::from_millis(500));
849 cx.run_until_parked();
850 cx.assert_state("jˇhello", Mode::Insert);
851 cx.simulate_keystrokes("k j k");
852 cx.assert_state("jˇkhello", Mode::Normal);
853}
854
855#[gpui::test]
856async fn test_comma_w(cx: &mut gpui::TestAppContext) {
857 let mut cx = NeovimBackedTestContext::new(cx).await;
858
859 cx.update(|cx| {
860 cx.bind_keys([KeyBinding::new(
861 ", w",
862 motion::Down {
863 display_lines: false,
864 },
865 Some("vim_mode == normal"),
866 )])
867 });
868 cx.neovim.exec("map ,w j").await;
869
870 cx.set_shared_state("ˇhello hello\nhello hello").await;
871 cx.simulate_shared_keystrokes("f o ; , w").await;
872 cx.shared_state()
873 .await
874 .assert_eq("hello hello\nhello hellˇo");
875
876 cx.set_shared_state("ˇhello hello\nhello hello").await;
877 cx.simulate_shared_keystrokes("f o ; , i").await;
878 cx.shared_state()
879 .await
880 .assert_eq("hellˇo hello\nhello hello");
881}
882
883#[gpui::test]
884async fn test_rename(cx: &mut gpui::TestAppContext) {
885 let mut cx = VimTestContext::new_typescript(cx).await;
886
887 cx.set_state("const beˇfore = 2; console.log(before)", Mode::Normal);
888 let def_range = cx.lsp_range("const «beforeˇ» = 2; console.log(before)");
889 let tgt_range = cx.lsp_range("const before = 2; console.log(«beforeˇ»)");
890 let mut prepare_request =
891 cx.handle_request::<lsp::request::PrepareRenameRequest, _, _>(move |_, _, _| async move {
892 Ok(Some(lsp::PrepareRenameResponse::Range(def_range)))
893 });
894 let mut rename_request =
895 cx.handle_request::<lsp::request::Rename, _, _>(move |url, params, _| async move {
896 Ok(Some(lsp::WorkspaceEdit {
897 changes: Some(
898 [(
899 url.clone(),
900 vec![
901 lsp::TextEdit::new(def_range, params.new_name.clone()),
902 lsp::TextEdit::new(tgt_range, params.new_name),
903 ],
904 )]
905 .into(),
906 ),
907 ..Default::default()
908 }))
909 });
910
911 cx.simulate_keystrokes("c d");
912 prepare_request.next().await.unwrap();
913 cx.simulate_input("after");
914 cx.simulate_keystrokes("enter");
915 rename_request.next().await.unwrap();
916 cx.assert_state("const afterˇ = 2; console.log(after)", Mode::Normal)
917}
918
919// TODO: this test is flaky on our linux CI machines
920#[cfg(target_os = "macos")]
921#[gpui::test]
922async fn test_remap(cx: &mut gpui::TestAppContext) {
923 let mut cx = VimTestContext::new(cx, true).await;
924
925 // test moving the cursor
926 cx.update(|cx| {
927 cx.bind_keys([KeyBinding::new(
928 "g z",
929 workspace::SendKeystrokes("l l l l".to_string()),
930 None,
931 )])
932 });
933 cx.set_state("ˇ123456789", Mode::Normal);
934 cx.simulate_keystrokes("g z");
935 cx.assert_state("1234ˇ56789", Mode::Normal);
936
937 // test switching modes
938 cx.update(|cx| {
939 cx.bind_keys([KeyBinding::new(
940 "g y",
941 workspace::SendKeystrokes("i f o o escape l".to_string()),
942 None,
943 )])
944 });
945 cx.set_state("ˇ123456789", Mode::Normal);
946 cx.simulate_keystrokes("g y");
947 cx.assert_state("fooˇ123456789", Mode::Normal);
948
949 // test recursion
950 cx.update(|cx| {
951 cx.bind_keys([KeyBinding::new(
952 "g x",
953 workspace::SendKeystrokes("g z g y".to_string()),
954 None,
955 )])
956 });
957 cx.set_state("ˇ123456789", Mode::Normal);
958 cx.simulate_keystrokes("g x");
959 cx.assert_state("1234fooˇ56789", Mode::Normal);
960
961 cx.executor().allow_parking();
962
963 // test command
964 cx.update(|cx| {
965 cx.bind_keys([KeyBinding::new(
966 "g w",
967 workspace::SendKeystrokes(": j enter".to_string()),
968 None,
969 )])
970 });
971 cx.set_state("ˇ1234\n56789", Mode::Normal);
972 cx.simulate_keystrokes("g w");
973 cx.assert_state("1234ˇ 56789", Mode::Normal);
974
975 // test leaving command
976 cx.update(|cx| {
977 cx.bind_keys([KeyBinding::new(
978 "g u",
979 workspace::SendKeystrokes("g w g z".to_string()),
980 None,
981 )])
982 });
983 cx.set_state("ˇ1234\n56789", Mode::Normal);
984 cx.simulate_keystrokes("g u");
985 cx.assert_state("1234 567ˇ89", Mode::Normal);
986
987 // test leaving command
988 cx.update(|cx| {
989 cx.bind_keys([KeyBinding::new(
990 "g t",
991 workspace::SendKeystrokes("i space escape".to_string()),
992 None,
993 )])
994 });
995 cx.set_state("12ˇ34", Mode::Normal);
996 cx.simulate_keystrokes("g t");
997 cx.assert_state("12ˇ 34", Mode::Normal);
998}
999
1000#[gpui::test]
1001async fn test_undo(cx: &mut gpui::TestAppContext) {
1002 let mut cx = NeovimBackedTestContext::new(cx).await;
1003
1004 cx.set_shared_state("hello quˇoel world").await;
1005 cx.simulate_shared_keystrokes("v i w s c o escape u").await;
1006 cx.shared_state().await.assert_eq("hello ˇquoel world");
1007 cx.simulate_shared_keystrokes("ctrl-r").await;
1008 cx.shared_state().await.assert_eq("hello ˇco world");
1009 cx.simulate_shared_keystrokes("a o right l escape").await;
1010 cx.shared_state().await.assert_eq("hello cooˇl world");
1011 cx.simulate_shared_keystrokes("u").await;
1012 cx.shared_state().await.assert_eq("hello cooˇ world");
1013 cx.simulate_shared_keystrokes("u").await;
1014 cx.shared_state().await.assert_eq("hello cˇo world");
1015 cx.simulate_shared_keystrokes("u").await;
1016 cx.shared_state().await.assert_eq("hello ˇquoel world");
1017
1018 cx.set_shared_state("hello quˇoel world").await;
1019 cx.simulate_shared_keystrokes("v i w ~ u").await;
1020 cx.shared_state().await.assert_eq("hello ˇquoel world");
1021
1022 cx.set_shared_state("\nhello quˇoel world\n").await;
1023 cx.simulate_shared_keystrokes("shift-v s c escape u").await;
1024 cx.shared_state().await.assert_eq("\nˇhello quoel world\n");
1025
1026 cx.set_shared_state(indoc! {"
1027 ˇ1
1028 2
1029 3"})
1030 .await;
1031
1032 cx.simulate_shared_keystrokes("ctrl-v shift-g ctrl-a").await;
1033 cx.shared_state().await.assert_eq(indoc! {"
1034 ˇ2
1035 3
1036 4"});
1037
1038 cx.simulate_shared_keystrokes("u").await;
1039 cx.shared_state().await.assert_eq(indoc! {"
1040 ˇ1
1041 2
1042 3"});
1043}
1044
1045#[gpui::test]
1046async fn test_mouse_selection(cx: &mut TestAppContext) {
1047 let mut cx = VimTestContext::new(cx, true).await;
1048
1049 cx.set_state("ˇone two three", Mode::Normal);
1050
1051 let start_point = cx.pixel_position("one twˇo three");
1052 let end_point = cx.pixel_position("one ˇtwo three");
1053
1054 cx.simulate_mouse_down(start_point, MouseButton::Left, Modifiers::none());
1055 cx.simulate_mouse_move(end_point, MouseButton::Left, Modifiers::none());
1056 cx.simulate_mouse_up(end_point, MouseButton::Left, Modifiers::none());
1057
1058 cx.assert_state("one «ˇtwo» three", Mode::Visual)
1059}
1060
1061#[gpui::test]
1062async fn test_lowercase_marks(cx: &mut TestAppContext) {
1063 let mut cx = NeovimBackedTestContext::new(cx).await;
1064
1065 cx.set_shared_state("line one\nline ˇtwo\nline three").await;
1066 cx.simulate_shared_keystrokes("m a l ' a").await;
1067 cx.shared_state()
1068 .await
1069 .assert_eq("line one\nˇline two\nline three");
1070 cx.simulate_shared_keystrokes("` a").await;
1071 cx.shared_state()
1072 .await
1073 .assert_eq("line one\nline ˇtwo\nline three");
1074
1075 cx.simulate_shared_keystrokes("^ d ` a").await;
1076 cx.shared_state()
1077 .await
1078 .assert_eq("line one\nˇtwo\nline three");
1079}
1080
1081#[gpui::test]
1082async fn test_lt_gt_marks(cx: &mut TestAppContext) {
1083 let mut cx = NeovimBackedTestContext::new(cx).await;
1084
1085 cx.set_shared_state(indoc!(
1086 "
1087 Line one
1088 Line two
1089 Line ˇthree
1090 Line four
1091 Line five
1092 "
1093 ))
1094 .await;
1095
1096 cx.simulate_shared_keystrokes("v j escape k k").await;
1097
1098 cx.simulate_shared_keystrokes("' <").await;
1099 cx.shared_state().await.assert_eq(indoc! {"
1100 Line one
1101 Line two
1102 ˇLine three
1103 Line four
1104 Line five
1105 "});
1106
1107 cx.simulate_shared_keystrokes("` <").await;
1108 cx.shared_state().await.assert_eq(indoc! {"
1109 Line one
1110 Line two
1111 Line ˇthree
1112 Line four
1113 Line five
1114 "});
1115
1116 cx.simulate_shared_keystrokes("' >").await;
1117 cx.shared_state().await.assert_eq(indoc! {"
1118 Line one
1119 Line two
1120 Line three
1121 ˇLine four
1122 Line five
1123 "
1124 });
1125
1126 cx.simulate_shared_keystrokes("` >").await;
1127 cx.shared_state().await.assert_eq(indoc! {"
1128 Line one
1129 Line two
1130 Line three
1131 Line ˇfour
1132 Line five
1133 "
1134 });
1135
1136 cx.simulate_shared_keystrokes("v i w o escape").await;
1137 cx.simulate_shared_keystrokes("` >").await;
1138 cx.shared_state().await.assert_eq(indoc! {"
1139 Line one
1140 Line two
1141 Line three
1142 Line fouˇr
1143 Line five
1144 "
1145 });
1146 cx.simulate_shared_keystrokes("` <").await;
1147 cx.shared_state().await.assert_eq(indoc! {"
1148 Line one
1149 Line two
1150 Line three
1151 Line ˇfour
1152 Line five
1153 "
1154 });
1155}
1156
1157#[gpui::test]
1158async fn test_caret_mark(cx: &mut TestAppContext) {
1159 let mut cx = NeovimBackedTestContext::new(cx).await;
1160
1161 cx.set_shared_state(indoc!(
1162 "
1163 Line one
1164 Line two
1165 Line three
1166 ˇLine four
1167 Line five
1168 "
1169 ))
1170 .await;
1171
1172 cx.simulate_shared_keystrokes("c w shift-s t r a i g h t space t h i n g escape j j")
1173 .await;
1174
1175 cx.simulate_shared_keystrokes("' ^").await;
1176 cx.shared_state().await.assert_eq(indoc! {"
1177 Line one
1178 Line two
1179 Line three
1180 ˇStraight thing four
1181 Line five
1182 "
1183 });
1184
1185 cx.simulate_shared_keystrokes("` ^").await;
1186 cx.shared_state().await.assert_eq(indoc! {"
1187 Line one
1188 Line two
1189 Line three
1190 Straight thingˇ four
1191 Line five
1192 "
1193 });
1194
1195 cx.simulate_shared_keystrokes("k a ! escape k g i ?").await;
1196 cx.shared_state().await.assert_eq(indoc! {"
1197 Line one
1198 Line two
1199 Line three!?ˇ
1200 Straight thing four
1201 Line five
1202 "
1203 });
1204}
1205
1206#[cfg(target_os = "macos")]
1207#[gpui::test]
1208async fn test_dw_eol(cx: &mut gpui::TestAppContext) {
1209 let mut cx = NeovimBackedTestContext::new(cx).await;
1210
1211 cx.set_shared_wrap(12).await;
1212 cx.set_shared_state("twelve ˇchar twelve char\ntwelve char")
1213 .await;
1214 cx.simulate_shared_keystrokes("d w").await;
1215 cx.shared_state()
1216 .await
1217 .assert_eq("twelve ˇtwelve char\ntwelve char");
1218}
1219
1220#[gpui::test]
1221async fn test_toggle_comments(cx: &mut gpui::TestAppContext) {
1222 let mut cx = VimTestContext::new(cx, true).await;
1223
1224 let language = std::sync::Arc::new(language::Language::new(
1225 language::LanguageConfig {
1226 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
1227 ..Default::default()
1228 },
1229 Some(language::tree_sitter_rust::language()),
1230 ));
1231 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
1232
1233 // works in normal model
1234 cx.set_state(
1235 indoc! {"
1236 ˇone
1237 two
1238 three
1239 "},
1240 Mode::Normal,
1241 );
1242 cx.simulate_keystrokes("g c c");
1243 cx.assert_state(
1244 indoc! {"
1245 // ˇone
1246 two
1247 three
1248 "},
1249 Mode::Normal,
1250 );
1251
1252 // works in visual mode
1253 cx.simulate_keystrokes("v j g c");
1254 cx.assert_state(
1255 indoc! {"
1256 // // ˇone
1257 // two
1258 three
1259 "},
1260 Mode::Normal,
1261 );
1262
1263 // works in visual line mode
1264 cx.simulate_keystrokes("shift-v j g c");
1265 cx.assert_state(
1266 indoc! {"
1267 // ˇone
1268 two
1269 three
1270 "},
1271 Mode::Normal,
1272 );
1273
1274 // works with count
1275 cx.simulate_keystrokes("g c 2 j");
1276 cx.assert_state(
1277 indoc! {"
1278 // // ˇone
1279 // two
1280 // three
1281 "},
1282 Mode::Normal,
1283 );
1284
1285 // works with motion object
1286 cx.simulate_keystrokes("shift-g");
1287 cx.simulate_keystrokes("g c g g");
1288 cx.assert_state(
1289 indoc! {"
1290 // one
1291 two
1292 three
1293 ˇ"},
1294 Mode::Normal,
1295 );
1296}
1297
1298#[gpui::test]
1299async fn test_find_multibyte(cx: &mut gpui::TestAppContext) {
1300 let mut cx = NeovimBackedTestContext::new(cx).await;
1301
1302 cx.set_shared_state(r#"<label for="guests">ˇPočet hostů</label>"#)
1303 .await;
1304
1305 cx.simulate_shared_keystrokes("c t < o escape").await;
1306 cx.shared_state()
1307 .await
1308 .assert_eq(r#"<label for="guests">ˇo</label>"#);
1309}
1310
1311#[gpui::test]
1312async fn test_plus_minus(cx: &mut gpui::TestAppContext) {
1313 let mut cx = NeovimBackedTestContext::new(cx).await;
1314
1315 cx.set_shared_state(indoc! {
1316 "one
1317 two
1318 thrˇee
1319 "})
1320 .await;
1321
1322 cx.simulate_shared_keystrokes("-").await;
1323 cx.shared_state().await.assert_matches();
1324 cx.simulate_shared_keystrokes("-").await;
1325 cx.shared_state().await.assert_matches();
1326 cx.simulate_shared_keystrokes("+").await;
1327 cx.shared_state().await.assert_matches();
1328}
1329
1330#[gpui::test]
1331async fn test_command_alias(cx: &mut gpui::TestAppContext) {
1332 let mut cx = VimTestContext::new(cx, true).await;
1333 cx.update_global(|store: &mut SettingsStore, cx| {
1334 store.update_user_settings::<WorkspaceSettings>(cx, |s| {
1335 let mut aliases = HashMap::default();
1336 aliases.insert("Q".to_string(), "upper".to_string());
1337 s.command_aliases = Some(aliases)
1338 });
1339 });
1340
1341 cx.set_state("ˇhello world", Mode::Normal);
1342 cx.simulate_keystrokes(": Q");
1343 cx.set_state("ˇHello world", Mode::Normal);
1344}
1345
1346#[gpui::test]
1347async fn test_remap_adjacent_dog_cat(cx: &mut gpui::TestAppContext) {
1348 let mut cx = NeovimBackedTestContext::new(cx).await;
1349 cx.update(|cx| {
1350 cx.bind_keys([
1351 KeyBinding::new(
1352 "d o g",
1353 workspace::SendKeystrokes("🐶".to_string()),
1354 Some("vim_mode == insert"),
1355 ),
1356 KeyBinding::new(
1357 "c a t",
1358 workspace::SendKeystrokes("🐱".to_string()),
1359 Some("vim_mode == insert"),
1360 ),
1361 ])
1362 });
1363 cx.neovim.exec("imap dog 🐶").await;
1364 cx.neovim.exec("imap cat 🐱").await;
1365
1366 cx.set_shared_state("ˇ").await;
1367 cx.simulate_shared_keystrokes("i d o g").await;
1368 cx.shared_state().await.assert_eq("🐶ˇ");
1369
1370 cx.set_shared_state("ˇ").await;
1371 cx.simulate_shared_keystrokes("i d o d o g").await;
1372 cx.shared_state().await.assert_eq("do🐶ˇ");
1373
1374 cx.set_shared_state("ˇ").await;
1375 cx.simulate_shared_keystrokes("i d o c a t").await;
1376 cx.shared_state().await.assert_eq("do🐱ˇ");
1377}
1378
1379#[gpui::test]
1380async fn test_remap_nested_pineapple(cx: &mut gpui::TestAppContext) {
1381 let mut cx = NeovimBackedTestContext::new(cx).await;
1382 cx.update(|cx| {
1383 cx.bind_keys([
1384 KeyBinding::new(
1385 "p i n",
1386 workspace::SendKeystrokes("📌".to_string()),
1387 Some("vim_mode == insert"),
1388 ),
1389 KeyBinding::new(
1390 "p i n e",
1391 workspace::SendKeystrokes("🌲".to_string()),
1392 Some("vim_mode == insert"),
1393 ),
1394 KeyBinding::new(
1395 "p i n e a p p l e",
1396 workspace::SendKeystrokes("🍍".to_string()),
1397 Some("vim_mode == insert"),
1398 ),
1399 ])
1400 });
1401 cx.neovim.exec("imap pin 📌").await;
1402 cx.neovim.exec("imap pine 🌲").await;
1403 cx.neovim.exec("imap pineapple 🍍").await;
1404
1405 cx.set_shared_state("ˇ").await;
1406 cx.simulate_shared_keystrokes("i p i n").await;
1407 cx.executor().advance_clock(Duration::from_millis(1000));
1408 cx.run_until_parked();
1409 cx.shared_state().await.assert_eq("📌ˇ");
1410
1411 cx.set_shared_state("ˇ").await;
1412 cx.simulate_shared_keystrokes("i p i n e").await;
1413 cx.executor().advance_clock(Duration::from_millis(1000));
1414 cx.run_until_parked();
1415 cx.shared_state().await.assert_eq("🌲ˇ");
1416
1417 cx.set_shared_state("ˇ").await;
1418 cx.simulate_shared_keystrokes("i p i n e a p p l e").await;
1419 cx.shared_state().await.assert_eq("🍍ˇ");
1420}
1421
1422#[gpui::test]
1423async fn test_escape_while_waiting(cx: &mut gpui::TestAppContext) {
1424 let mut cx = NeovimBackedTestContext::new(cx).await;
1425 cx.set_shared_state("ˇhi").await;
1426 cx.simulate_shared_keystrokes("\" + escape x").await;
1427 cx.shared_state().await.assert_eq("ˇi");
1428}
1429
1430#[gpui::test]
1431async fn test_ctrl_w_override(cx: &mut gpui::TestAppContext) {
1432 let mut cx = NeovimBackedTestContext::new(cx).await;
1433 cx.update(|cx| {
1434 cx.bind_keys([KeyBinding::new("ctrl-w", DeleteLine, None)]);
1435 });
1436 cx.neovim.exec("map <c-w> D").await;
1437 cx.set_shared_state("ˇhi").await;
1438 cx.simulate_shared_keystrokes("ctrl-w").await;
1439 cx.shared_state().await.assert_eq("ˇ");
1440}
1441
1442#[gpui::test]
1443async fn test_visual_indent_count(cx: &mut gpui::TestAppContext) {
1444 let mut cx = VimTestContext::new(cx, true).await;
1445 cx.set_state("ˇhi", Mode::Normal);
1446 cx.simulate_keystrokes("shift-v 3 >");
1447 cx.assert_state(" ˇhi", Mode::Normal);
1448 cx.simulate_keystrokes("shift-v 2 <");
1449 cx.assert_state(" ˇhi", Mode::Normal);
1450}
1451
1452#[gpui::test]
1453async fn test_record_replay_recursion(cx: &mut gpui::TestAppContext) {
1454 let mut cx = NeovimBackedTestContext::new(cx).await;
1455
1456 cx.set_shared_state("ˇhello world").await;
1457 cx.simulate_shared_keystrokes(">").await;
1458 cx.simulate_shared_keystrokes(".").await;
1459 cx.simulate_shared_keystrokes(".").await;
1460 cx.simulate_shared_keystrokes(".").await;
1461 cx.shared_state().await.assert_eq("ˇhello world"); // takes a _long_ time
1462}