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