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, Modifiers, MouseButton, TestAppContext};
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 cx.set_shared_state(indoc! { "
639 fn boop() {
640 ˇbarp()
641 bazp()
642 }
643 "})
644 .await;
645 cx.simulate_shared_keystrokes(["shift-v", "j", "j", "z", "f"])
646 .await;
647 cx.simulate_shared_keystrokes(["escape"]).await;
648 cx.simulate_shared_keystrokes(["shift-g", "shift-v"]).await;
649 cx.assert_shared_state(indoc! { "
650 fn boop() {
651 barp()
652 bazp()
653 }
654 ˇ"})
655 .await;
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"])
669 .await;
670 cx.assert_shared_state(indoc! {"
671 The quick brown
672 fox juˇ over
673 the lazy dog"})
674 .await;
675}
676
677#[gpui::test]
678async fn test_zero(cx: &mut gpui::TestAppContext) {
679 let mut cx = NeovimBackedTestContext::new(cx).await;
680
681 cx.set_shared_state(indoc! {"
682 The quˇick brown
683 fox jumps over
684 the lazy dog"})
685 .await;
686
687 cx.simulate_shared_keystrokes(["0"]).await;
688 cx.assert_shared_state(indoc! {"
689 ˇThe quick brown
690 fox jumps over
691 the lazy dog"})
692 .await;
693
694 cx.simulate_shared_keystrokes(["1", "0", "l"]).await;
695 cx.assert_shared_state(indoc! {"
696 The quick ˇbrown
697 fox jumps over
698 the lazy dog"})
699 .await;
700}
701
702#[gpui::test]
703async fn test_selection_goal(cx: &mut gpui::TestAppContext) {
704 let mut cx = NeovimBackedTestContext::new(cx).await;
705
706 cx.set_shared_state(indoc! {"
707 ;;ˇ;
708 Lorem Ipsum"})
709 .await;
710
711 cx.simulate_shared_keystrokes(["a", "down", "up", ";", "down", "up"])
712 .await;
713 cx.assert_shared_state(indoc! {"
714 ;;;;ˇ
715 Lorem Ipsum"})
716 .await;
717}
718
719#[gpui::test]
720async fn test_wrapped_motions(cx: &mut gpui::TestAppContext) {
721 let mut cx = NeovimBackedTestContext::new(cx).await;
722
723 cx.set_shared_wrap(12).await;
724
725 cx.set_shared_state(indoc! {"
726 aaˇaa
727 😃😃"
728 })
729 .await;
730 cx.simulate_shared_keystrokes(["j"]).await;
731 cx.assert_shared_state(indoc! {"
732 aaaa
733 😃ˇ😃"
734 })
735 .await;
736
737 cx.set_shared_state(indoc! {"
738 123456789012aaˇaa
739 123456789012😃😃"
740 })
741 .await;
742 cx.simulate_shared_keystrokes(["j"]).await;
743 cx.assert_shared_state(indoc! {"
744 123456789012aaaa
745 123456789012😃ˇ😃"
746 })
747 .await;
748
749 cx.set_shared_state(indoc! {"
750 123456789012aaˇaa
751 123456789012😃😃"
752 })
753 .await;
754 cx.simulate_shared_keystrokes(["j"]).await;
755 cx.assert_shared_state(indoc! {"
756 123456789012aaaa
757 123456789012😃ˇ😃"
758 })
759 .await;
760
761 cx.set_shared_state(indoc! {"
762 123456789012aaaaˇaaaaaaaa123456789012
763 wow
764 123456789012😃😃😃😃😃😃123456789012"
765 })
766 .await;
767 cx.simulate_shared_keystrokes(["j", "j"]).await;
768 cx.assert_shared_state(indoc! {"
769 123456789012aaaaaaaaaaaa123456789012
770 wow
771 123456789012😃😃ˇ😃😃😃😃123456789012"
772 })
773 .await;
774}
775
776#[gpui::test]
777async fn test_paragraphs_dont_wrap(cx: &mut gpui::TestAppContext) {
778 let mut cx = NeovimBackedTestContext::new(cx).await;
779
780 cx.set_shared_state(indoc! {"
781 one
782 ˇ
783 two"})
784 .await;
785
786 cx.simulate_shared_keystrokes(["}", "}"]).await;
787 cx.assert_shared_state(indoc! {"
788 one
789
790 twˇo"})
791 .await;
792
793 cx.simulate_shared_keystrokes(["{", "{", "{"]).await;
794 cx.assert_shared_state(indoc! {"
795 ˇone
796
797 two"})
798 .await;
799}
800
801#[gpui::test]
802async fn test_select_all_issue_2170(cx: &mut gpui::TestAppContext) {
803 let mut cx = VimTestContext::new(cx, true).await;
804
805 cx.set_state(
806 indoc! {"
807 defmodule Test do
808 def test(a, ˇ[_, _] = b), do: IO.puts('hi')
809 end
810 "},
811 Mode::Normal,
812 );
813 cx.simulate_keystrokes(["g", "a"]);
814 cx.assert_state(
815 indoc! {"
816 defmodule Test do
817 def test(a, «[ˇ»_, _] = b), do: IO.puts('hi')
818 end
819 "},
820 Mode::Visual,
821 );
822}
823
824#[gpui::test]
825async fn test_jk(cx: &mut gpui::TestAppContext) {
826 let mut cx = NeovimBackedTestContext::new(cx).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 cx.neovim.exec("imap jk <esc>").await;
836
837 cx.set_shared_state("ˇhello").await;
838 cx.simulate_shared_keystrokes(["i", "j", "o", "j", "k"])
839 .await;
840 cx.assert_shared_state("jˇohello").await;
841}
842
843#[gpui::test]
844async fn test_jk_delay(cx: &mut gpui::TestAppContext) {
845 let mut cx = VimTestContext::new(cx, true).await;
846
847 cx.update(|cx| {
848 cx.bind_keys([KeyBinding::new(
849 "j k",
850 NormalBefore,
851 Some("vim_mode == insert"),
852 )])
853 });
854
855 cx.set_state("ˇhello", Mode::Normal);
856 cx.simulate_keystrokes(["i", "j"]);
857 cx.executor().advance_clock(Duration::from_millis(500));
858 cx.run_until_parked();
859 cx.assert_state("ˇhello", Mode::Insert);
860 cx.executor().advance_clock(Duration::from_millis(500));
861 cx.run_until_parked();
862 cx.assert_state("jˇhello", Mode::Insert);
863 cx.simulate_keystrokes(["k", "j", "k"]);
864 cx.assert_state("jˇkhello", Mode::Normal);
865}
866
867#[gpui::test]
868async fn test_comma_w(cx: &mut gpui::TestAppContext) {
869 let mut cx = NeovimBackedTestContext::new(cx).await;
870
871 cx.update(|cx| {
872 cx.bind_keys([KeyBinding::new(
873 ", w",
874 motion::Down {
875 display_lines: false,
876 },
877 Some("vim_mode == normal"),
878 )])
879 });
880 cx.neovim.exec("map ,w j").await;
881
882 cx.set_shared_state("ˇhello hello\nhello hello").await;
883 cx.simulate_shared_keystrokes(["f", "o", ";", ",", "w"])
884 .await;
885 cx.assert_shared_state("hello hello\nhello hellˇo").await;
886
887 cx.set_shared_state("ˇhello hello\nhello hello").await;
888 cx.simulate_shared_keystrokes(["f", "o", ";", ",", "i"])
889 .await;
890 cx.assert_shared_state("hellˇo hello\nhello hello").await;
891 cx.assert_shared_mode(Mode::Insert).await;
892}
893
894#[gpui::test]
895async fn test_rename(cx: &mut gpui::TestAppContext) {
896 let mut cx = VimTestContext::new_typescript(cx).await;
897
898 cx.set_state("const beˇfore = 2; console.log(before)", Mode::Normal);
899 let def_range = cx.lsp_range("const «beforeˇ» = 2; console.log(before)");
900 let tgt_range = cx.lsp_range("const before = 2; console.log(«beforeˇ»)");
901 let mut prepare_request =
902 cx.handle_request::<lsp::request::PrepareRenameRequest, _, _>(move |_, _, _| async move {
903 Ok(Some(lsp::PrepareRenameResponse::Range(def_range)))
904 });
905 let mut rename_request =
906 cx.handle_request::<lsp::request::Rename, _, _>(move |url, params, _| async move {
907 Ok(Some(lsp::WorkspaceEdit {
908 changes: Some(
909 [(
910 url.clone(),
911 vec![
912 lsp::TextEdit::new(def_range, params.new_name.clone()),
913 lsp::TextEdit::new(tgt_range, params.new_name),
914 ],
915 )]
916 .into(),
917 ),
918 ..Default::default()
919 }))
920 });
921
922 cx.simulate_keystrokes(["c", "d"]);
923 prepare_request.next().await.unwrap();
924 cx.simulate_input("after");
925 cx.simulate_keystrokes(["enter"]);
926 rename_request.next().await.unwrap();
927 cx.assert_state("const afterˇ = 2; console.log(after)", Mode::Normal)
928}
929
930#[gpui::test]
931async fn test_remap(cx: &mut gpui::TestAppContext) {
932 let mut cx = VimTestContext::new(cx, true).await;
933
934 // test moving the cursor
935 cx.update(|cx| {
936 cx.bind_keys([KeyBinding::new(
937 "g z",
938 workspace::SendKeystrokes("l l l l".to_string()),
939 None,
940 )])
941 });
942 cx.set_state("ˇ123456789", Mode::Normal);
943 cx.simulate_keystrokes(["g", "z"]);
944 cx.assert_state("1234ˇ56789", Mode::Normal);
945
946 // test switching modes
947 cx.update(|cx| {
948 cx.bind_keys([KeyBinding::new(
949 "g y",
950 workspace::SendKeystrokes("i f o o escape l".to_string()),
951 None,
952 )])
953 });
954 cx.set_state("ˇ123456789", Mode::Normal);
955 cx.simulate_keystrokes(["g", "y"]);
956 cx.assert_state("fooˇ123456789", Mode::Normal);
957
958 // test recursion
959 cx.update(|cx| {
960 cx.bind_keys([KeyBinding::new(
961 "g x",
962 workspace::SendKeystrokes("g z g y".to_string()),
963 None,
964 )])
965 });
966 cx.set_state("ˇ123456789", Mode::Normal);
967 cx.simulate_keystrokes(["g", "x"]);
968 cx.assert_state("1234fooˇ56789", Mode::Normal);
969
970 cx.executor().allow_parking();
971
972 // test command
973 cx.update(|cx| {
974 cx.bind_keys([KeyBinding::new(
975 "g w",
976 workspace::SendKeystrokes(": j enter".to_string()),
977 None,
978 )])
979 });
980 cx.set_state("ˇ1234\n56789", Mode::Normal);
981 cx.simulate_keystrokes(["g", "w"]);
982 cx.assert_state("1234ˇ 56789", Mode::Normal);
983
984 // test leaving command
985 cx.update(|cx| {
986 cx.bind_keys([KeyBinding::new(
987 "g u",
988 workspace::SendKeystrokes("g w g z".to_string()),
989 None,
990 )])
991 });
992 cx.set_state("ˇ1234\n56789", Mode::Normal);
993 cx.simulate_keystrokes(["g", "u"]);
994 cx.assert_state("1234 567ˇ89", Mode::Normal);
995
996 // test leaving command
997 cx.update(|cx| {
998 cx.bind_keys([KeyBinding::new(
999 "g t",
1000 workspace::SendKeystrokes("i space escape".to_string()),
1001 None,
1002 )])
1003 });
1004 cx.set_state("12ˇ34", Mode::Normal);
1005 cx.simulate_keystrokes(["g", "t"]);
1006 cx.assert_state("12ˇ 34", Mode::Normal);
1007}
1008
1009#[gpui::test]
1010async fn test_undo(cx: &mut gpui::TestAppContext) {
1011 let mut cx = NeovimBackedTestContext::new(cx).await;
1012
1013 cx.set_shared_state("hello quˇoel world").await;
1014 cx.simulate_shared_keystrokes(["v", "i", "w", "s", "c", "o", "escape", "u"])
1015 .await;
1016 cx.assert_shared_state("hello ˇquoel world").await;
1017 cx.simulate_shared_keystrokes(["ctrl-r"]).await;
1018 cx.assert_shared_state("hello ˇco world").await;
1019 cx.simulate_shared_keystrokes(["a", "o", "right", "l", "escape"])
1020 .await;
1021 cx.assert_shared_state("hello cooˇl world").await;
1022 cx.simulate_shared_keystrokes(["u"]).await;
1023 cx.assert_shared_state("hello cooˇ world").await;
1024 cx.simulate_shared_keystrokes(["u"]).await;
1025 cx.assert_shared_state("hello cˇo world").await;
1026 cx.simulate_shared_keystrokes(["u"]).await;
1027 cx.assert_shared_state("hello ˇquoel world").await;
1028
1029 cx.set_shared_state("hello quˇoel world").await;
1030 cx.simulate_shared_keystrokes(["v", "i", "w", "~", "u"])
1031 .await;
1032 cx.assert_shared_state("hello ˇquoel world").await;
1033
1034 cx.set_shared_state("\nhello quˇoel world\n").await;
1035 cx.simulate_shared_keystrokes(["shift-v", "s", "c", "escape", "u"])
1036 .await;
1037 cx.assert_shared_state("\nˇhello quoel world\n").await;
1038
1039 cx.set_shared_state(indoc! {"
1040 ˇ1
1041 2
1042 3"})
1043 .await;
1044
1045 cx.simulate_shared_keystrokes(["ctrl-v", "shift-g", "ctrl-a"])
1046 .await;
1047 cx.assert_shared_state(indoc! {"
1048 ˇ2
1049 3
1050 4"})
1051 .await;
1052
1053 cx.simulate_shared_keystrokes(["u"]).await;
1054 cx.assert_shared_state(indoc! {"
1055 ˇ1
1056 2
1057 3"})
1058 .await;
1059}
1060
1061#[gpui::test]
1062async fn test_mouse_selection(cx: &mut TestAppContext) {
1063 let mut cx = VimTestContext::new(cx, true).await;
1064
1065 cx.set_state("ˇone two three", Mode::Normal);
1066
1067 let start_point = cx.pixel_position("one twˇo three");
1068 let end_point = cx.pixel_position("one ˇtwo three");
1069
1070 cx.simulate_mouse_down(start_point, MouseButton::Left, Modifiers::none());
1071 cx.simulate_mouse_move(end_point, MouseButton::Left, Modifiers::none());
1072 cx.simulate_mouse_up(end_point, MouseButton::Left, Modifiers::none());
1073
1074 cx.assert_state("one «ˇtwo» three", Mode::Visual)
1075}