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