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