1mod neovim_backed_test_context;
2mod neovim_connection;
3mod vim_test_context;
4
5use std::time::Duration;
6
7use command_palette::CommandPalette;
8use editor::{display_map::DisplayRow, DisplayPoint};
9use futures::StreamExt;
10use gpui::{KeyBinding, Modifiers, MouseButton, TestAppContext};
11pub use neovim_backed_test_context::*;
12pub use vim_test_context::*;
13
14use indoc::indoc;
15use search::BufferSearchBar;
16
17use crate::{insert::NormalBefore, motion, state::Mode, ModeIndicator};
18
19#[gpui::test]
20async fn test_initially_disabled(cx: &mut gpui::TestAppContext) {
21 let mut cx = VimTestContext::new(cx, false).await;
22 cx.simulate_keystrokes("h j k l");
23 cx.assert_editor_state("hjklˇ");
24}
25
26#[gpui::test]
27async fn test_neovim(cx: &mut gpui::TestAppContext) {
28 let mut cx = NeovimBackedTestContext::new(cx).await;
29
30 cx.simulate_shared_keystrokes("i").await;
31 cx.shared_state().await.assert_matches();
32 cx.simulate_shared_keystrokes("shift-t e s t space t e s t escape 0 d w")
33 .await;
34 cx.shared_state().await.assert_matches();
35 cx.assert_editor_state("ˇtest");
36}
37
38#[gpui::test]
39async fn test_toggle_through_settings(cx: &mut gpui::TestAppContext) {
40 let mut cx = VimTestContext::new(cx, true).await;
41
42 cx.simulate_keystrokes("i");
43 assert_eq!(cx.mode(), Mode::Insert);
44
45 // Editor acts as though vim is disabled
46 cx.disable_vim();
47 cx.simulate_keystrokes("h j k l");
48 cx.assert_editor_state("hjklˇ");
49
50 // Selections aren't changed if editor is blurred but vim-mode is still disabled.
51 cx.set_state("«hjklˇ»", Mode::Normal);
52 cx.assert_editor_state("«hjklˇ»");
53 cx.update_editor(|_, cx| cx.blur());
54 cx.assert_editor_state("«hjklˇ»");
55 cx.update_editor(|_, cx| cx.focus_self());
56 cx.assert_editor_state("«hjklˇ»");
57
58 // Enabling dynamically sets vim mode again and restores normal mode
59 cx.enable_vim();
60 assert_eq!(cx.mode(), Mode::Normal);
61 cx.simulate_keystrokes("h h h l");
62 assert_eq!(cx.buffer_text(), "hjkl".to_owned());
63 cx.assert_editor_state("hˇjkl");
64 cx.simulate_keystrokes("i T e s t");
65 cx.assert_editor_state("hTestˇjkl");
66
67 // Disabling and enabling resets to normal mode
68 assert_eq!(cx.mode(), Mode::Insert);
69 cx.disable_vim();
70 cx.enable_vim();
71 assert_eq!(cx.mode(), Mode::Normal);
72}
73
74#[gpui::test]
75async fn test_cancel_selection(cx: &mut gpui::TestAppContext) {
76 let mut cx = VimTestContext::new(cx, true).await;
77
78 cx.set_state(
79 indoc! {"The quick brown fox juˇmps over the lazy dog"},
80 Mode::Normal,
81 );
82 // jumps
83 cx.simulate_keystrokes("v l l");
84 cx.assert_editor_state("The quick brown fox ju«mpsˇ» over the lazy dog");
85
86 cx.simulate_keystrokes("escape");
87 cx.assert_editor_state("The quick brown fox jumpˇs over the lazy dog");
88
89 // go back to the same selection state
90 cx.simulate_keystrokes("v h h");
91 cx.assert_editor_state("The quick brown fox ju«ˇmps» over the lazy dog");
92
93 // Ctrl-[ should behave like Esc
94 cx.simulate_keystrokes("ctrl-[");
95 cx.assert_editor_state("The quick brown fox juˇmps over the lazy dog");
96}
97
98#[gpui::test]
99async fn test_buffer_search(cx: &mut gpui::TestAppContext) {
100 let mut cx = VimTestContext::new(cx, true).await;
101
102 cx.set_state(
103 indoc! {"
104 The quick brown
105 fox juˇmps over
106 the lazy dog"},
107 Mode::Normal,
108 );
109 cx.simulate_keystrokes("/");
110
111 let search_bar = cx.workspace(|workspace, cx| {
112 workspace
113 .active_pane()
114 .read(cx)
115 .toolbar()
116 .read(cx)
117 .item_of_type::<BufferSearchBar>()
118 .expect("Buffer search bar should be deployed")
119 });
120
121 cx.update_view(search_bar, |bar, cx| {
122 assert_eq!(bar.query(cx), "");
123 })
124}
125
126#[gpui::test]
127async fn test_count_down(cx: &mut gpui::TestAppContext) {
128 let mut cx = VimTestContext::new(cx, true).await;
129
130 cx.set_state(indoc! {"aˇa\nbb\ncc\ndd\nee"}, Mode::Normal);
131 cx.simulate_keystrokes("2 down");
132 cx.assert_editor_state("aa\nbb\ncˇc\ndd\nee");
133 cx.simulate_keystrokes("9 down");
134 cx.assert_editor_state("aa\nbb\ncc\ndd\neˇe");
135}
136
137#[gpui::test]
138async fn test_end_of_document_710(cx: &mut gpui::TestAppContext) {
139 let mut cx = VimTestContext::new(cx, true).await;
140
141 // goes to end by default
142 cx.set_state(indoc! {"aˇa\nbb\ncc"}, Mode::Normal);
143 cx.simulate_keystrokes("shift-g");
144 cx.assert_editor_state("aa\nbb\ncˇc");
145
146 // can go to line 1 (https://github.com/zed-industries/zed/issues/5812)
147 cx.simulate_keystrokes("1 shift-g");
148 cx.assert_editor_state("aˇa\nbb\ncc");
149}
150
151#[gpui::test]
152async fn test_end_of_line_with_times(cx: &mut gpui::TestAppContext) {
153 let mut cx = VimTestContext::new(cx, true).await;
154
155 // goes to current line end
156 cx.set_state(indoc! {"ˇaa\nbb\ncc"}, Mode::Normal);
157 cx.simulate_keystrokes("$");
158 cx.assert_editor_state("aˇa\nbb\ncc");
159
160 // goes to next line end
161 cx.simulate_keystrokes("2 $");
162 cx.assert_editor_state("aa\nbˇb\ncc");
163
164 // try to exceed the final line.
165 cx.simulate_keystrokes("4 $");
166 cx.assert_editor_state("aa\nbb\ncˇc");
167}
168
169#[gpui::test]
170async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
171 let mut cx = VimTestContext::new(cx, true).await;
172
173 // works in normal mode
174 cx.set_state(indoc! {"aa\nbˇb\ncc"}, Mode::Normal);
175 cx.simulate_keystrokes("> >");
176 cx.assert_editor_state("aa\n bˇb\ncc");
177 cx.simulate_keystrokes("< <");
178 cx.assert_editor_state("aa\nbˇb\ncc");
179
180 // works in visual mode
181 cx.simulate_keystrokes("shift-v down >");
182 cx.assert_editor_state("aa\n bb\n cˇc");
183}
184
185#[gpui::test]
186async fn test_escape_command_palette(cx: &mut gpui::TestAppContext) {
187 let mut cx = VimTestContext::new(cx, true).await;
188
189 cx.set_state("aˇbc\n", Mode::Normal);
190 cx.simulate_keystrokes("i cmd-shift-p");
191
192 assert!(cx.workspace(|workspace, cx| workspace.active_modal::<CommandPalette>(cx).is_some()));
193 cx.simulate_keystrokes("escape");
194 cx.run_until_parked();
195 assert!(!cx.workspace(|workspace, cx| workspace.active_modal::<CommandPalette>(cx).is_some()));
196 cx.assert_state("aˇbc\n", Mode::Insert);
197}
198
199#[gpui::test]
200async fn test_escape_cancels(cx: &mut gpui::TestAppContext) {
201 let mut cx = VimTestContext::new(cx, true).await;
202
203 cx.set_state("aˇbˇc", Mode::Normal);
204 cx.simulate_keystrokes("escape");
205
206 cx.assert_state("aˇbc", Mode::Normal);
207}
208
209#[gpui::test]
210async fn test_selection_on_search(cx: &mut gpui::TestAppContext) {
211 let mut cx = VimTestContext::new(cx, true).await;
212
213 cx.set_state(indoc! {"aa\nbˇb\ncc\ncc\ncc\n"}, Mode::Normal);
214 cx.simulate_keystrokes("/ c c");
215
216 let search_bar = cx.workspace(|workspace, cx| {
217 workspace
218 .active_pane()
219 .read(cx)
220 .toolbar()
221 .read(cx)
222 .item_of_type::<BufferSearchBar>()
223 .expect("Buffer search bar should be deployed")
224 });
225
226 cx.update_view(search_bar, |bar, cx| {
227 assert_eq!(bar.query(cx), "cc");
228 });
229
230 cx.update_editor(|editor, cx| {
231 let highlights = editor.all_text_background_highlights(cx);
232 assert_eq!(3, highlights.len());
233 assert_eq!(
234 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 2),
235 highlights[0].0
236 )
237 });
238 cx.simulate_keystrokes("enter");
239
240 cx.assert_state(indoc! {"aa\nbb\nˇcc\ncc\ncc\n"}, Mode::Normal);
241 cx.simulate_keystrokes("n");
242 cx.assert_state(indoc! {"aa\nbb\ncc\nˇcc\ncc\n"}, Mode::Normal);
243 cx.simulate_keystrokes("shift-n");
244 cx.assert_state(indoc! {"aa\nbb\nˇcc\ncc\ncc\n"}, Mode::Normal);
245}
246
247#[gpui::test]
248async fn test_status_indicator(cx: &mut gpui::TestAppContext) {
249 let mut cx = VimTestContext::new(cx, true).await;
250
251 let mode_indicator = cx.workspace(|workspace, cx| {
252 let status_bar = workspace.status_bar().read(cx);
253 let mode_indicator = status_bar.item_of_type::<ModeIndicator>();
254 assert!(mode_indicator.is_some());
255 mode_indicator.unwrap()
256 });
257
258 assert_eq!(
259 cx.workspace(|_, cx| mode_indicator.read(cx).mode),
260 Some(Mode::Normal)
261 );
262
263 // shows the correct mode
264 cx.simulate_keystrokes("i");
265 assert_eq!(
266 cx.workspace(|_, cx| mode_indicator.read(cx).mode),
267 Some(Mode::Insert)
268 );
269 cx.simulate_keystrokes("escape shift-r");
270 assert_eq!(
271 cx.workspace(|_, cx| mode_indicator.read(cx).mode),
272 Some(Mode::Replace)
273 );
274
275 // shows even in search
276 cx.simulate_keystrokes("escape v /");
277 assert_eq!(
278 cx.workspace(|_, cx| mode_indicator.read(cx).mode),
279 Some(Mode::Visual)
280 );
281
282 // hides if vim mode is disabled
283 cx.disable_vim();
284 cx.run_until_parked();
285 cx.workspace(|workspace, cx| {
286 let status_bar = workspace.status_bar().read(cx);
287 let mode_indicator = status_bar.item_of_type::<ModeIndicator>().unwrap();
288 assert!(mode_indicator.read(cx).mode.is_none());
289 });
290
291 cx.enable_vim();
292 cx.run_until_parked();
293 cx.workspace(|workspace, cx| {
294 let status_bar = workspace.status_bar().read(cx);
295 let mode_indicator = status_bar.item_of_type::<ModeIndicator>().unwrap();
296 assert!(mode_indicator.read(cx).mode.is_some());
297 });
298}
299
300#[gpui::test]
301async fn test_word_characters(cx: &mut gpui::TestAppContext) {
302 let mut cx = VimTestContext::new_typescript(cx).await;
303 cx.set_state(
304 indoc! { "
305 class A {
306 #ˇgoop = 99;
307 $ˇgoop () { return this.#gˇoop };
308 };
309 console.log(new A().$gooˇp())
310 "},
311 Mode::Normal,
312 );
313 cx.simulate_keystrokes("v i w");
314 cx.assert_state(
315 indoc! {"
316 class A {
317 «#goopˇ» = 99;
318 «$goopˇ» () { return this.«#goopˇ» };
319 };
320 console.log(new A().«$goopˇ»())
321 "},
322 Mode::Visual,
323 )
324}
325
326#[gpui::test]
327async fn test_join_lines(cx: &mut gpui::TestAppContext) {
328 let mut cx = NeovimBackedTestContext::new(cx).await;
329
330 cx.set_shared_state(indoc! {"
331 ˇone
332 two
333 three
334 four
335 five
336 six
337 "})
338 .await;
339 cx.simulate_shared_keystrokes("shift-j").await;
340 cx.shared_state().await.assert_eq(indoc! {"
341 oneˇ two
342 three
343 four
344 five
345 six
346 "});
347 cx.simulate_shared_keystrokes("3 shift-j").await;
348 cx.shared_state().await.assert_eq(indoc! {"
349 one two threeˇ four
350 five
351 six
352 "});
353
354 cx.set_shared_state(indoc! {"
355 ˇone
356 two
357 three
358 four
359 five
360 six
361 "})
362 .await;
363 cx.simulate_shared_keystrokes("j v 3 j shift-j").await;
364 cx.shared_state().await.assert_eq(indoc! {"
365 one
366 two three fourˇ five
367 six
368 "});
369}
370
371#[gpui::test]
372async fn test_wrapped_lines(cx: &mut gpui::TestAppContext) {
373 let mut cx = NeovimBackedTestContext::new(cx).await;
374
375 cx.set_shared_wrap(12).await;
376 // tests line wrap as follows:
377 // 1: twelve char
378 // twelve char
379 // 2: twelve char
380 cx.set_shared_state(indoc! { "
381 tˇwelve char twelve char
382 twelve char
383 "})
384 .await;
385 cx.simulate_shared_keystrokes("j").await;
386 cx.shared_state().await.assert_eq(indoc! {"
387 twelve char twelve char
388 tˇwelve char
389 "});
390 cx.simulate_shared_keystrokes("k").await;
391 cx.shared_state().await.assert_eq(indoc! {"
392 tˇwelve char twelve char
393 twelve char
394 "});
395 cx.simulate_shared_keystrokes("g j").await;
396 cx.shared_state().await.assert_eq(indoc! {"
397 twelve char tˇwelve char
398 twelve char
399 "});
400 cx.simulate_shared_keystrokes("g j").await;
401 cx.shared_state().await.assert_eq(indoc! {"
402 twelve char twelve char
403 tˇwelve char
404 "});
405
406 cx.simulate_shared_keystrokes("g k").await;
407 cx.shared_state().await.assert_eq(indoc! {"
408 twelve char tˇwelve char
409 twelve char
410 "});
411
412 cx.simulate_shared_keystrokes("g ^").await;
413 cx.shared_state().await.assert_eq(indoc! {"
414 twelve char ˇtwelve char
415 twelve char
416 "});
417
418 cx.simulate_shared_keystrokes("^").await;
419 cx.shared_state().await.assert_eq(indoc! {"
420 ˇtwelve char twelve char
421 twelve char
422 "});
423
424 cx.simulate_shared_keystrokes("g $").await;
425 cx.shared_state().await.assert_eq(indoc! {"
426 twelve charˇ twelve char
427 twelve char
428 "});
429 cx.simulate_shared_keystrokes("$").await;
430 cx.shared_state().await.assert_eq(indoc! {"
431 twelve char twelve chaˇr
432 twelve char
433 "});
434
435 cx.set_shared_state(indoc! { "
436 tˇwelve char twelve char
437 twelve char
438 "})
439 .await;
440 cx.simulate_shared_keystrokes("enter").await;
441 cx.shared_state().await.assert_eq(indoc! {"
442 twelve char twelve char
443 ˇtwelve char
444 "});
445
446 cx.set_shared_state(indoc! { "
447 twelve char
448 tˇwelve char twelve char
449 twelve char
450 "})
451 .await;
452 cx.simulate_shared_keystrokes("o o escape").await;
453 cx.shared_state().await.assert_eq(indoc! {"
454 twelve char
455 twelve char twelve char
456 ˇo
457 twelve char
458 "});
459
460 cx.set_shared_state(indoc! { "
461 twelve char
462 tˇwelve char twelve char
463 twelve char
464 "})
465 .await;
466 cx.simulate_shared_keystrokes("shift-a a escape").await;
467 cx.shared_state().await.assert_eq(indoc! {"
468 twelve char
469 twelve char twelve charˇa
470 twelve char
471 "});
472 cx.simulate_shared_keystrokes("shift-i i escape").await;
473 cx.shared_state().await.assert_eq(indoc! {"
474 twelve char
475 ˇitwelve char twelve chara
476 twelve char
477 "});
478 cx.simulate_shared_keystrokes("shift-d").await;
479 cx.shared_state().await.assert_eq(indoc! {"
480 twelve char
481 ˇ
482 twelve char
483 "});
484
485 cx.set_shared_state(indoc! { "
486 twelve char
487 twelve char tˇwelve char
488 twelve char
489 "})
490 .await;
491 cx.simulate_shared_keystrokes("shift-o o escape").await;
492 cx.shared_state().await.assert_eq(indoc! {"
493 twelve char
494 ˇo
495 twelve char twelve char
496 twelve char
497 "});
498
499 // line wraps as:
500 // fourteen ch
501 // ar
502 // fourteen ch
503 // ar
504 cx.set_shared_state(indoc! { "
505 fourteen chaˇr
506 fourteen char
507 "})
508 .await;
509
510 cx.simulate_shared_keystrokes("d i w").await;
511 cx.shared_state().await.assert_eq(indoc! {"
512 fourteenˇ•
513 fourteen char
514 "});
515 cx.simulate_shared_keystrokes("j shift-f e f r").await;
516 cx.shared_state().await.assert_eq(indoc! {"
517 fourteen•
518 fourteen chaˇr
519 "});
520}
521
522#[gpui::test]
523async fn test_folds(cx: &mut gpui::TestAppContext) {
524 let mut cx = NeovimBackedTestContext::new(cx).await;
525 cx.set_neovim_option("foldmethod=manual").await;
526
527 cx.set_shared_state(indoc! { "
528 fn boop() {
529 ˇbarp()
530 bazp()
531 }
532 "})
533 .await;
534 cx.simulate_shared_keystrokes("shift-v j z f").await;
535
536 // visual display is now:
537 // fn boop () {
538 // [FOLDED]
539 // }
540
541 // TODO: this should not be needed but currently zf does not
542 // return to normal mode.
543 cx.simulate_shared_keystrokes("escape").await;
544
545 // skip over fold downward
546 cx.simulate_shared_keystrokes("g g").await;
547 cx.shared_state().await.assert_eq(indoc! {"
548 ˇfn boop() {
549 barp()
550 bazp()
551 }
552 "});
553
554 cx.simulate_shared_keystrokes("j j").await;
555 cx.shared_state().await.assert_eq(indoc! {"
556 fn boop() {
557 barp()
558 bazp()
559 ˇ}
560 "});
561
562 // skip over fold upward
563 cx.simulate_shared_keystrokes("2 k").await;
564 cx.shared_state().await.assert_eq(indoc! {"
565 ˇfn boop() {
566 barp()
567 bazp()
568 }
569 "});
570
571 // yank the fold
572 cx.simulate_shared_keystrokes("down y y").await;
573 cx.shared_clipboard()
574 .await
575 .assert_eq(" barp()\n bazp()\n");
576
577 // re-open
578 cx.simulate_shared_keystrokes("z o").await;
579 cx.shared_state().await.assert_eq(indoc! {"
580 fn boop() {
581 ˇ barp()
582 bazp()
583 }
584 "});
585}
586
587#[gpui::test]
588async fn test_folds_panic(cx: &mut gpui::TestAppContext) {
589 let mut cx = NeovimBackedTestContext::new(cx).await;
590 cx.set_neovim_option("foldmethod=manual").await;
591
592 cx.set_shared_state(indoc! { "
593 fn boop() {
594 ˇbarp()
595 bazp()
596 }
597 "})
598 .await;
599 cx.simulate_shared_keystrokes("shift-v j z f").await;
600 cx.simulate_shared_keystrokes("escape").await;
601 cx.simulate_shared_keystrokes("g g").await;
602 cx.simulate_shared_keystrokes("5 d j").await;
603 cx.shared_state().await.assert_eq("ˇ");
604 cx.set_shared_state(indoc! {"
605 fn boop() {
606 ˇbarp()
607 bazp()
608 }
609 "})
610 .await;
611 cx.simulate_shared_keystrokes("shift-v j j z f").await;
612 cx.simulate_shared_keystrokes("escape").await;
613 cx.simulate_shared_keystrokes("shift-g shift-v").await;
614 cx.shared_state().await.assert_eq(indoc! {"
615 fn boop() {
616 barp()
617 bazp()
618 }
619 ˇ"});
620}
621
622#[gpui::test]
623async fn test_clear_counts(cx: &mut gpui::TestAppContext) {
624 let mut cx = NeovimBackedTestContext::new(cx).await;
625
626 cx.set_shared_state(indoc! {"
627 The quick brown
628 fox juˇmps over
629 the lazy dog"})
630 .await;
631
632 cx.simulate_shared_keystrokes("4 escape 3 d l").await;
633 cx.shared_state().await.assert_eq(indoc! {"
634 The quick brown
635 fox juˇ over
636 the lazy dog"});
637}
638
639#[gpui::test]
640async fn test_zero(cx: &mut gpui::TestAppContext) {
641 let mut cx = NeovimBackedTestContext::new(cx).await;
642
643 cx.set_shared_state(indoc! {"
644 The quˇick brown
645 fox jumps over
646 the lazy dog"})
647 .await;
648
649 cx.simulate_shared_keystrokes("0").await;
650 cx.shared_state().await.assert_eq(indoc! {"
651 ˇThe quick brown
652 fox jumps over
653 the lazy dog"});
654
655 cx.simulate_shared_keystrokes("1 0 l").await;
656 cx.shared_state().await.assert_eq(indoc! {"
657 The quick ˇbrown
658 fox jumps over
659 the lazy dog"});
660}
661
662#[gpui::test]
663async fn test_selection_goal(cx: &mut gpui::TestAppContext) {
664 let mut cx = NeovimBackedTestContext::new(cx).await;
665
666 cx.set_shared_state(indoc! {"
667 ;;ˇ;
668 Lorem Ipsum"})
669 .await;
670
671 cx.simulate_shared_keystrokes("a down up ; down up").await;
672 cx.shared_state().await.assert_eq(indoc! {"
673 ;;;;ˇ
674 Lorem Ipsum"});
675}
676
677#[gpui::test]
678async fn test_wrapped_motions(cx: &mut gpui::TestAppContext) {
679 let mut cx = NeovimBackedTestContext::new(cx).await;
680
681 cx.set_shared_wrap(12).await;
682
683 cx.set_shared_state(indoc! {"
684 aaˇaa
685 😃😃"
686 })
687 .await;
688 cx.simulate_shared_keystrokes("j").await;
689 cx.shared_state().await.assert_eq(indoc! {"
690 aaaa
691 😃ˇ😃"
692 });
693
694 cx.set_shared_state(indoc! {"
695 123456789012aaˇaa
696 123456789012😃😃"
697 })
698 .await;
699 cx.simulate_shared_keystrokes("j").await;
700 cx.shared_state().await.assert_eq(indoc! {"
701 123456789012aaaa
702 123456789012😃ˇ😃"
703 });
704
705 cx.set_shared_state(indoc! {"
706 123456789012aaˇaa
707 123456789012😃😃"
708 })
709 .await;
710 cx.simulate_shared_keystrokes("j").await;
711 cx.shared_state().await.assert_eq(indoc! {"
712 123456789012aaaa
713 123456789012😃ˇ😃"
714 });
715
716 cx.set_shared_state(indoc! {"
717 123456789012aaaaˇaaaaaaaa123456789012
718 wow
719 123456789012😃😃😃😃😃😃123456789012"
720 })
721 .await;
722 cx.simulate_shared_keystrokes("j j").await;
723 cx.shared_state().await.assert_eq(indoc! {"
724 123456789012aaaaaaaaaaaa123456789012
725 wow
726 123456789012😃😃ˇ😃😃😃😃123456789012"
727 });
728}
729
730#[gpui::test]
731async fn test_paragraphs_dont_wrap(cx: &mut gpui::TestAppContext) {
732 let mut cx = NeovimBackedTestContext::new(cx).await;
733
734 cx.set_shared_state(indoc! {"
735 one
736 ˇ
737 two"})
738 .await;
739
740 cx.simulate_shared_keystrokes("} }").await;
741 cx.shared_state().await.assert_eq(indoc! {"
742 one
743
744 twˇo"});
745
746 cx.simulate_shared_keystrokes("{ { {").await;
747 cx.shared_state().await.assert_eq(indoc! {"
748 ˇone
749
750 two"});
751}
752
753#[gpui::test]
754async fn test_select_all_issue_2170(cx: &mut gpui::TestAppContext) {
755 let mut cx = VimTestContext::new(cx, true).await;
756
757 cx.set_state(
758 indoc! {"
759 defmodule Test do
760 def test(a, ˇ[_, _] = b), do: IO.puts('hi')
761 end
762 "},
763 Mode::Normal,
764 );
765 cx.simulate_keystrokes("g a");
766 cx.assert_state(
767 indoc! {"
768 defmodule Test do
769 def test(a, «[ˇ»_, _] = b), do: IO.puts('hi')
770 end
771 "},
772 Mode::Visual,
773 );
774}
775
776#[gpui::test]
777async fn test_jk(cx: &mut gpui::TestAppContext) {
778 let mut cx = NeovimBackedTestContext::new(cx).await;
779
780 cx.update(|cx| {
781 cx.bind_keys([KeyBinding::new(
782 "j k",
783 NormalBefore,
784 Some("vim_mode == insert"),
785 )])
786 });
787 cx.neovim.exec("imap jk <esc>").await;
788
789 cx.set_shared_state("ˇhello").await;
790 cx.simulate_shared_keystrokes("i j o j k").await;
791 cx.shared_state().await.assert_eq("jˇohello");
792}
793
794#[gpui::test]
795async fn test_jk_delay(cx: &mut gpui::TestAppContext) {
796 let mut cx = VimTestContext::new(cx, true).await;
797
798 cx.update(|cx| {
799 cx.bind_keys([KeyBinding::new(
800 "j k",
801 NormalBefore,
802 Some("vim_mode == insert"),
803 )])
804 });
805
806 cx.set_state("ˇhello", Mode::Normal);
807 cx.simulate_keystrokes("i j");
808 cx.executor().advance_clock(Duration::from_millis(500));
809 cx.run_until_parked();
810 cx.assert_state("ˇhello", Mode::Insert);
811 cx.executor().advance_clock(Duration::from_millis(500));
812 cx.run_until_parked();
813 cx.assert_state("jˇhello", Mode::Insert);
814 cx.simulate_keystrokes("k j k");
815 cx.assert_state("jˇkhello", Mode::Normal);
816}
817
818#[gpui::test]
819async fn test_comma_w(cx: &mut gpui::TestAppContext) {
820 let mut cx = NeovimBackedTestContext::new(cx).await;
821
822 cx.update(|cx| {
823 cx.bind_keys([KeyBinding::new(
824 ", w",
825 motion::Down {
826 display_lines: false,
827 },
828 Some("vim_mode == normal"),
829 )])
830 });
831 cx.neovim.exec("map ,w j").await;
832
833 cx.set_shared_state("ˇhello hello\nhello hello").await;
834 cx.simulate_shared_keystrokes("f o ; , w").await;
835 cx.shared_state()
836 .await
837 .assert_eq("hello hello\nhello hellˇo");
838
839 cx.set_shared_state("ˇhello hello\nhello hello").await;
840 cx.simulate_shared_keystrokes("f o ; , i").await;
841 cx.shared_state()
842 .await
843 .assert_eq("hellˇo hello\nhello hello");
844}
845
846#[gpui::test]
847async fn test_rename(cx: &mut gpui::TestAppContext) {
848 let mut cx = VimTestContext::new_typescript(cx).await;
849
850 cx.set_state("const beˇfore = 2; console.log(before)", Mode::Normal);
851 let def_range = cx.lsp_range("const «beforeˇ» = 2; console.log(before)");
852 let tgt_range = cx.lsp_range("const before = 2; console.log(«beforeˇ»)");
853 let mut prepare_request =
854 cx.handle_request::<lsp::request::PrepareRenameRequest, _, _>(move |_, _, _| async move {
855 Ok(Some(lsp::PrepareRenameResponse::Range(def_range)))
856 });
857 let mut rename_request =
858 cx.handle_request::<lsp::request::Rename, _, _>(move |url, params, _| async move {
859 Ok(Some(lsp::WorkspaceEdit {
860 changes: Some(
861 [(
862 url.clone(),
863 vec![
864 lsp::TextEdit::new(def_range, params.new_name.clone()),
865 lsp::TextEdit::new(tgt_range, params.new_name),
866 ],
867 )]
868 .into(),
869 ),
870 ..Default::default()
871 }))
872 });
873
874 cx.simulate_keystrokes("c d");
875 prepare_request.next().await.unwrap();
876 cx.simulate_input("after");
877 cx.simulate_keystrokes("enter");
878 rename_request.next().await.unwrap();
879 cx.assert_state("const afterˇ = 2; console.log(after)", Mode::Normal)
880}
881
882#[gpui::test]
883async fn test_remap(cx: &mut gpui::TestAppContext) {
884 let mut cx = VimTestContext::new(cx, true).await;
885
886 // test moving the cursor
887 cx.update(|cx| {
888 cx.bind_keys([KeyBinding::new(
889 "g z",
890 workspace::SendKeystrokes("l l l l".to_string()),
891 None,
892 )])
893 });
894 cx.set_state("ˇ123456789", Mode::Normal);
895 cx.simulate_keystrokes("g z");
896 cx.assert_state("1234ˇ56789", Mode::Normal);
897
898 // test switching modes
899 cx.update(|cx| {
900 cx.bind_keys([KeyBinding::new(
901 "g y",
902 workspace::SendKeystrokes("i f o o escape l".to_string()),
903 None,
904 )])
905 });
906 cx.set_state("ˇ123456789", Mode::Normal);
907 cx.simulate_keystrokes("g y");
908 cx.assert_state("fooˇ123456789", Mode::Normal);
909
910 // test recursion
911 cx.update(|cx| {
912 cx.bind_keys([KeyBinding::new(
913 "g x",
914 workspace::SendKeystrokes("g z g y".to_string()),
915 None,
916 )])
917 });
918 cx.set_state("ˇ123456789", Mode::Normal);
919 cx.simulate_keystrokes("g x");
920 cx.assert_state("1234fooˇ56789", Mode::Normal);
921
922 cx.executor().allow_parking();
923
924 // test command
925 cx.update(|cx| {
926 cx.bind_keys([KeyBinding::new(
927 "g w",
928 workspace::SendKeystrokes(": j enter".to_string()),
929 None,
930 )])
931 });
932 cx.set_state("ˇ1234\n56789", Mode::Normal);
933 cx.simulate_keystrokes("g w");
934 cx.assert_state("1234ˇ 56789", Mode::Normal);
935
936 // test leaving command
937 cx.update(|cx| {
938 cx.bind_keys([KeyBinding::new(
939 "g u",
940 workspace::SendKeystrokes("g w g z".to_string()),
941 None,
942 )])
943 });
944 cx.set_state("ˇ1234\n56789", Mode::Normal);
945 cx.simulate_keystrokes("g u");
946 cx.assert_state("1234 567ˇ89", Mode::Normal);
947
948 // test leaving command
949 cx.update(|cx| {
950 cx.bind_keys([KeyBinding::new(
951 "g t",
952 workspace::SendKeystrokes("i space escape".to_string()),
953 None,
954 )])
955 });
956 cx.set_state("12ˇ34", Mode::Normal);
957 cx.simulate_keystrokes("g t");
958 cx.assert_state("12ˇ 34", Mode::Normal);
959}
960
961#[gpui::test]
962async fn test_undo(cx: &mut gpui::TestAppContext) {
963 let mut cx = NeovimBackedTestContext::new(cx).await;
964
965 cx.set_shared_state("hello quˇoel world").await;
966 cx.simulate_shared_keystrokes("v i w s c o escape u").await;
967 cx.shared_state().await.assert_eq("hello ˇquoel world");
968 cx.simulate_shared_keystrokes("ctrl-r").await;
969 cx.shared_state().await.assert_eq("hello ˇco world");
970 cx.simulate_shared_keystrokes("a o right l escape").await;
971 cx.shared_state().await.assert_eq("hello cooˇl world");
972 cx.simulate_shared_keystrokes("u").await;
973 cx.shared_state().await.assert_eq("hello cooˇ world");
974 cx.simulate_shared_keystrokes("u").await;
975 cx.shared_state().await.assert_eq("hello cˇo world");
976 cx.simulate_shared_keystrokes("u").await;
977 cx.shared_state().await.assert_eq("hello ˇquoel world");
978
979 cx.set_shared_state("hello quˇoel world").await;
980 cx.simulate_shared_keystrokes("v i w ~ u").await;
981 cx.shared_state().await.assert_eq("hello ˇquoel world");
982
983 cx.set_shared_state("\nhello quˇoel world\n").await;
984 cx.simulate_shared_keystrokes("shift-v s c escape u").await;
985 cx.shared_state().await.assert_eq("\nˇhello quoel world\n");
986
987 cx.set_shared_state(indoc! {"
988 ˇ1
989 2
990 3"})
991 .await;
992
993 cx.simulate_shared_keystrokes("ctrl-v shift-g ctrl-a").await;
994 cx.shared_state().await.assert_eq(indoc! {"
995 ˇ2
996 3
997 4"});
998
999 cx.simulate_shared_keystrokes("u").await;
1000 cx.shared_state().await.assert_eq(indoc! {"
1001 ˇ1
1002 2
1003 3"});
1004}
1005
1006#[gpui::test]
1007async fn test_mouse_selection(cx: &mut TestAppContext) {
1008 let mut cx = VimTestContext::new(cx, true).await;
1009
1010 cx.set_state("ˇone two three", Mode::Normal);
1011
1012 let start_point = cx.pixel_position("one twˇo three");
1013 let end_point = cx.pixel_position("one ˇtwo three");
1014
1015 cx.simulate_mouse_down(start_point, MouseButton::Left, Modifiers::none());
1016 cx.simulate_mouse_move(end_point, MouseButton::Left, Modifiers::none());
1017 cx.simulate_mouse_up(end_point, MouseButton::Left, Modifiers::none());
1018
1019 cx.assert_state("one «ˇtwo» three", Mode::Visual)
1020}
1021
1022#[gpui::test]
1023async fn test_lowercase_marks(cx: &mut TestAppContext) {
1024 let mut cx = NeovimBackedTestContext::new(cx).await;
1025
1026 cx.set_shared_state("line one\nline ˇtwo\nline three").await;
1027 cx.simulate_shared_keystrokes("m a l ' a").await;
1028 cx.shared_state()
1029 .await
1030 .assert_eq("line one\nˇline two\nline three");
1031 cx.simulate_shared_keystrokes("` a").await;
1032 cx.shared_state()
1033 .await
1034 .assert_eq("line one\nline ˇtwo\nline three");
1035
1036 cx.simulate_shared_keystrokes("^ d ` a").await;
1037 cx.shared_state()
1038 .await
1039 .assert_eq("line one\nˇtwo\nline three");
1040}
1041
1042#[gpui::test]
1043async fn test_lt_gt_marks(cx: &mut TestAppContext) {
1044 let mut cx = NeovimBackedTestContext::new(cx).await;
1045
1046 cx.set_shared_state(indoc!(
1047 "
1048 Line one
1049 Line two
1050 Line ˇthree
1051 Line four
1052 Line five
1053 "
1054 ))
1055 .await;
1056
1057 cx.simulate_shared_keystrokes("v j escape k k").await;
1058
1059 cx.simulate_shared_keystrokes("' <").await;
1060 cx.shared_state().await.assert_eq(indoc! {"
1061 Line one
1062 Line two
1063 ˇLine three
1064 Line four
1065 Line five
1066 "});
1067
1068 cx.simulate_shared_keystrokes("` <").await;
1069 cx.shared_state().await.assert_eq(indoc! {"
1070 Line one
1071 Line two
1072 Line ˇthree
1073 Line four
1074 Line five
1075 "});
1076
1077 cx.simulate_shared_keystrokes("' >").await;
1078 cx.shared_state().await.assert_eq(indoc! {"
1079 Line one
1080 Line two
1081 Line three
1082 ˇLine four
1083 Line five
1084 "
1085 });
1086
1087 cx.simulate_shared_keystrokes("` >").await;
1088 cx.shared_state().await.assert_eq(indoc! {"
1089 Line one
1090 Line two
1091 Line three
1092 Line ˇfour
1093 Line five
1094 "
1095 });
1096}
1097
1098#[gpui::test]
1099async fn test_caret_mark(cx: &mut TestAppContext) {
1100 let mut cx = NeovimBackedTestContext::new(cx).await;
1101
1102 cx.set_shared_state(indoc!(
1103 "
1104 Line one
1105 Line two
1106 Line three
1107 ˇLine four
1108 Line five
1109 "
1110 ))
1111 .await;
1112
1113 cx.simulate_shared_keystrokes("c w shift-s t r a i g h t space t h i n g escape j j")
1114 .await;
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 ˇStraight thing 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 Straight thingˇ four
1132 Line five
1133 "
1134 });
1135}