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#[cfg(target_os = "macos")]
404#[gpui::test]
405async fn test_wrapped_lines(cx: &mut gpui::TestAppContext) {
406 let mut cx = NeovimBackedTestContext::new(cx).await;
407
408 cx.set_shared_wrap(12).await;
409 // tests line wrap as follows:
410 // 1: twelve char
411 // twelve char
412 // 2: twelve char
413 cx.set_shared_state(indoc! { "
414 tˇwelve char twelve char
415 twelve char
416 "})
417 .await;
418 cx.simulate_shared_keystrokes("j").await;
419 cx.shared_state().await.assert_eq(indoc! {"
420 twelve char twelve char
421 tˇwelve char
422 "});
423 cx.simulate_shared_keystrokes("k").await;
424 cx.shared_state().await.assert_eq(indoc! {"
425 tˇwelve char twelve char
426 twelve char
427 "});
428 cx.simulate_shared_keystrokes("g j").await;
429 cx.shared_state().await.assert_eq(indoc! {"
430 twelve char tˇwelve char
431 twelve char
432 "});
433 cx.simulate_shared_keystrokes("g j").await;
434 cx.shared_state().await.assert_eq(indoc! {"
435 twelve char twelve char
436 tˇwelve char
437 "});
438
439 cx.simulate_shared_keystrokes("g k").await;
440 cx.shared_state().await.assert_eq(indoc! {"
441 twelve char tˇwelve char
442 twelve char
443 "});
444
445 cx.simulate_shared_keystrokes("g ^").await;
446 cx.shared_state().await.assert_eq(indoc! {"
447 twelve char ˇtwelve char
448 twelve char
449 "});
450
451 cx.simulate_shared_keystrokes("^").await;
452 cx.shared_state().await.assert_eq(indoc! {"
453 ˇtwelve char twelve char
454 twelve char
455 "});
456
457 cx.simulate_shared_keystrokes("g $").await;
458 cx.shared_state().await.assert_eq(indoc! {"
459 twelve charˇ twelve char
460 twelve char
461 "});
462 cx.simulate_shared_keystrokes("$").await;
463 cx.shared_state().await.assert_eq(indoc! {"
464 twelve char twelve chaˇr
465 twelve char
466 "});
467
468 cx.set_shared_state(indoc! { "
469 tˇwelve char twelve char
470 twelve char
471 "})
472 .await;
473 cx.simulate_shared_keystrokes("enter").await;
474 cx.shared_state().await.assert_eq(indoc! {"
475 twelve char twelve char
476 ˇtwelve char
477 "});
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("o o escape").await;
486 cx.shared_state().await.assert_eq(indoc! {"
487 twelve char
488 twelve char twelve char
489 ˇo
490 twelve char
491 "});
492
493 cx.set_shared_state(indoc! { "
494 twelve char
495 tˇwelve char twelve char
496 twelve char
497 "})
498 .await;
499 cx.simulate_shared_keystrokes("shift-a a escape").await;
500 cx.shared_state().await.assert_eq(indoc! {"
501 twelve char
502 twelve char twelve charˇa
503 twelve char
504 "});
505 cx.simulate_shared_keystrokes("shift-i i escape").await;
506 cx.shared_state().await.assert_eq(indoc! {"
507 twelve char
508 ˇitwelve char twelve chara
509 twelve char
510 "});
511 cx.simulate_shared_keystrokes("shift-d").await;
512 cx.shared_state().await.assert_eq(indoc! {"
513 twelve char
514 ˇ
515 twelve char
516 "});
517
518 cx.set_shared_state(indoc! { "
519 twelve char
520 twelve char tˇwelve char
521 twelve char
522 "})
523 .await;
524 cx.simulate_shared_keystrokes("shift-o o escape").await;
525 cx.shared_state().await.assert_eq(indoc! {"
526 twelve char
527 ˇo
528 twelve char twelve char
529 twelve char
530 "});
531
532 // line wraps as:
533 // fourteen ch
534 // ar
535 // fourteen ch
536 // ar
537 cx.set_shared_state(indoc! { "
538 fourteen chaˇr
539 fourteen char
540 "})
541 .await;
542
543 cx.simulate_shared_keystrokes("d i w").await;
544 cx.shared_state().await.assert_eq(indoc! {"
545 fourteenˇ•
546 fourteen char
547 "});
548 cx.simulate_shared_keystrokes("j shift-f e f r").await;
549 cx.shared_state().await.assert_eq(indoc! {"
550 fourteen•
551 fourteen chaˇr
552 "});
553}
554
555#[gpui::test]
556async fn test_folds(cx: &mut gpui::TestAppContext) {
557 let mut cx = NeovimBackedTestContext::new(cx).await;
558 cx.set_neovim_option("foldmethod=manual").await;
559
560 cx.set_shared_state(indoc! { "
561 fn boop() {
562 ˇbarp()
563 bazp()
564 }
565 "})
566 .await;
567 cx.simulate_shared_keystrokes("shift-v j z f").await;
568
569 // visual display is now:
570 // fn boop () {
571 // [FOLDED]
572 // }
573
574 // TODO: this should not be needed but currently zf does not
575 // return to normal mode.
576 cx.simulate_shared_keystrokes("escape").await;
577
578 // skip over fold downward
579 cx.simulate_shared_keystrokes("g g").await;
580 cx.shared_state().await.assert_eq(indoc! {"
581 ˇfn boop() {
582 barp()
583 bazp()
584 }
585 "});
586
587 cx.simulate_shared_keystrokes("j j").await;
588 cx.shared_state().await.assert_eq(indoc! {"
589 fn boop() {
590 barp()
591 bazp()
592 ˇ}
593 "});
594
595 // skip over fold upward
596 cx.simulate_shared_keystrokes("2 k").await;
597 cx.shared_state().await.assert_eq(indoc! {"
598 ˇfn boop() {
599 barp()
600 bazp()
601 }
602 "});
603
604 // yank the fold
605 cx.simulate_shared_keystrokes("down y y").await;
606 cx.shared_clipboard()
607 .await
608 .assert_eq(" barp()\n bazp()\n");
609
610 // re-open
611 cx.simulate_shared_keystrokes("z o").await;
612 cx.shared_state().await.assert_eq(indoc! {"
613 fn boop() {
614 ˇ barp()
615 bazp()
616 }
617 "});
618}
619
620#[gpui::test]
621async fn test_folds_panic(cx: &mut gpui::TestAppContext) {
622 let mut cx = NeovimBackedTestContext::new(cx).await;
623 cx.set_neovim_option("foldmethod=manual").await;
624
625 cx.set_shared_state(indoc! { "
626 fn boop() {
627 ˇbarp()
628 bazp()
629 }
630 "})
631 .await;
632 cx.simulate_shared_keystrokes("shift-v j z f").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.shared_state().await.assert_eq("ˇ");
637 cx.set_shared_state(indoc! {"
638 fn boop() {
639 ˇbarp()
640 bazp()
641 }
642 "})
643 .await;
644 cx.simulate_shared_keystrokes("shift-v j j z f").await;
645 cx.simulate_shared_keystrokes("escape").await;
646 cx.simulate_shared_keystrokes("shift-g shift-v").await;
647 cx.shared_state().await.assert_eq(indoc! {"
648 fn boop() {
649 barp()
650 bazp()
651 }
652 ˇ"});
653}
654
655#[gpui::test]
656async fn test_clear_counts(cx: &mut gpui::TestAppContext) {
657 let mut cx = NeovimBackedTestContext::new(cx).await;
658
659 cx.set_shared_state(indoc! {"
660 The quick brown
661 fox juˇmps over
662 the lazy dog"})
663 .await;
664
665 cx.simulate_shared_keystrokes("4 escape 3 d l").await;
666 cx.shared_state().await.assert_eq(indoc! {"
667 The quick brown
668 fox juˇ over
669 the lazy dog"});
670}
671
672#[gpui::test]
673async fn test_zero(cx: &mut gpui::TestAppContext) {
674 let mut cx = NeovimBackedTestContext::new(cx).await;
675
676 cx.set_shared_state(indoc! {"
677 The quˇick brown
678 fox jumps over
679 the lazy dog"})
680 .await;
681
682 cx.simulate_shared_keystrokes("0").await;
683 cx.shared_state().await.assert_eq(indoc! {"
684 ˇThe quick brown
685 fox jumps over
686 the lazy dog"});
687
688 cx.simulate_shared_keystrokes("1 0 l").await;
689 cx.shared_state().await.assert_eq(indoc! {"
690 The quick ˇbrown
691 fox jumps over
692 the lazy dog"});
693}
694
695#[gpui::test]
696async fn test_selection_goal(cx: &mut gpui::TestAppContext) {
697 let mut cx = NeovimBackedTestContext::new(cx).await;
698
699 cx.set_shared_state(indoc! {"
700 ;;ˇ;
701 Lorem Ipsum"})
702 .await;
703
704 cx.simulate_shared_keystrokes("a down up ; down up").await;
705 cx.shared_state().await.assert_eq(indoc! {"
706 ;;;;ˇ
707 Lorem Ipsum"});
708}
709
710#[cfg(target_os = "macos")]
711#[gpui::test]
712async fn test_wrapped_motions(cx: &mut gpui::TestAppContext) {
713 let mut cx = NeovimBackedTestContext::new(cx).await;
714
715 cx.set_shared_wrap(12).await;
716
717 cx.set_shared_state(indoc! {"
718 aaˇaa
719 😃😃"
720 })
721 .await;
722 cx.simulate_shared_keystrokes("j").await;
723 cx.shared_state().await.assert_eq(indoc! {"
724 aaaa
725 😃ˇ😃"
726 });
727
728 cx.set_shared_state(indoc! {"
729 123456789012aaˇaa
730 123456789012😃😃"
731 })
732 .await;
733 cx.simulate_shared_keystrokes("j").await;
734 cx.shared_state().await.assert_eq(indoc! {"
735 123456789012aaaa
736 123456789012😃ˇ😃"
737 });
738
739 cx.set_shared_state(indoc! {"
740 123456789012aaˇaa
741 123456789012😃😃"
742 })
743 .await;
744 cx.simulate_shared_keystrokes("j").await;
745 cx.shared_state().await.assert_eq(indoc! {"
746 123456789012aaaa
747 123456789012😃ˇ😃"
748 });
749
750 cx.set_shared_state(indoc! {"
751 123456789012aaaaˇaaaaaaaa123456789012
752 wow
753 123456789012😃😃😃😃😃😃123456789012"
754 })
755 .await;
756 cx.simulate_shared_keystrokes("j j").await;
757 cx.shared_state().await.assert_eq(indoc! {"
758 123456789012aaaaaaaaaaaa123456789012
759 wow
760 123456789012😃😃ˇ😃😃😃😃123456789012"
761 });
762}
763
764#[gpui::test]
765async fn test_paragraphs_dont_wrap(cx: &mut gpui::TestAppContext) {
766 let mut cx = NeovimBackedTestContext::new(cx).await;
767
768 cx.set_shared_state(indoc! {"
769 one
770 ˇ
771 two"})
772 .await;
773
774 cx.simulate_shared_keystrokes("} }").await;
775 cx.shared_state().await.assert_eq(indoc! {"
776 one
777
778 twˇo"});
779
780 cx.simulate_shared_keystrokes("{ { {").await;
781 cx.shared_state().await.assert_eq(indoc! {"
782 ˇone
783
784 two"});
785}
786
787#[gpui::test]
788async fn test_select_all_issue_2170(cx: &mut gpui::TestAppContext) {
789 let mut cx = VimTestContext::new(cx, true).await;
790
791 cx.set_state(
792 indoc! {"
793 defmodule Test do
794 def test(a, ˇ[_, _] = b), do: IO.puts('hi')
795 end
796 "},
797 Mode::Normal,
798 );
799 cx.simulate_keystrokes("g a");
800 cx.assert_state(
801 indoc! {"
802 defmodule Test do
803 def test(a, «[ˇ»_, _] = b), do: IO.puts('hi')
804 end
805 "},
806 Mode::Visual,
807 );
808}
809
810#[gpui::test]
811async fn test_jk(cx: &mut gpui::TestAppContext) {
812 let mut cx = NeovimBackedTestContext::new(cx).await;
813
814 cx.update(|cx| {
815 cx.bind_keys([KeyBinding::new(
816 "j k",
817 NormalBefore,
818 Some("vim_mode == insert"),
819 )])
820 });
821 cx.neovim.exec("imap jk <esc>").await;
822
823 cx.set_shared_state("ˇhello").await;
824 cx.simulate_shared_keystrokes("i j o j k").await;
825 cx.shared_state().await.assert_eq("jˇohello");
826}
827
828#[gpui::test]
829async fn test_jk_delay(cx: &mut gpui::TestAppContext) {
830 let mut cx = VimTestContext::new(cx, true).await;
831
832 cx.update(|cx| {
833 cx.bind_keys([KeyBinding::new(
834 "j k",
835 NormalBefore,
836 Some("vim_mode == insert"),
837 )])
838 });
839
840 cx.set_state("ˇhello", Mode::Normal);
841 cx.simulate_keystrokes("i j");
842 cx.executor().advance_clock(Duration::from_millis(500));
843 cx.run_until_parked();
844 cx.assert_state("ˇhello", Mode::Insert);
845 cx.executor().advance_clock(Duration::from_millis(500));
846 cx.run_until_parked();
847 cx.assert_state("jˇhello", Mode::Insert);
848 cx.simulate_keystrokes("k j k");
849 cx.assert_state("jˇkhello", Mode::Normal);
850}
851
852#[gpui::test]
853async fn test_comma_w(cx: &mut gpui::TestAppContext) {
854 let mut cx = NeovimBackedTestContext::new(cx).await;
855
856 cx.update(|cx| {
857 cx.bind_keys([KeyBinding::new(
858 ", w",
859 motion::Down {
860 display_lines: false,
861 },
862 Some("vim_mode == normal"),
863 )])
864 });
865 cx.neovim.exec("map ,w j").await;
866
867 cx.set_shared_state("ˇhello hello\nhello hello").await;
868 cx.simulate_shared_keystrokes("f o ; , w").await;
869 cx.shared_state()
870 .await
871 .assert_eq("hello hello\nhello hellˇo");
872
873 cx.set_shared_state("ˇhello hello\nhello hello").await;
874 cx.simulate_shared_keystrokes("f o ; , i").await;
875 cx.shared_state()
876 .await
877 .assert_eq("hellˇo hello\nhello hello");
878}
879
880#[gpui::test]
881async fn test_rename(cx: &mut gpui::TestAppContext) {
882 let mut cx = VimTestContext::new_typescript(cx).await;
883
884 cx.set_state("const beˇfore = 2; console.log(before)", Mode::Normal);
885 let def_range = cx.lsp_range("const «beforeˇ» = 2; console.log(before)");
886 let tgt_range = cx.lsp_range("const before = 2; console.log(«beforeˇ»)");
887 let mut prepare_request =
888 cx.handle_request::<lsp::request::PrepareRenameRequest, _, _>(move |_, _, _| async move {
889 Ok(Some(lsp::PrepareRenameResponse::Range(def_range)))
890 });
891 let mut rename_request =
892 cx.handle_request::<lsp::request::Rename, _, _>(move |url, params, _| async move {
893 Ok(Some(lsp::WorkspaceEdit {
894 changes: Some(
895 [(
896 url.clone(),
897 vec![
898 lsp::TextEdit::new(def_range, params.new_name.clone()),
899 lsp::TextEdit::new(tgt_range, params.new_name),
900 ],
901 )]
902 .into(),
903 ),
904 ..Default::default()
905 }))
906 });
907
908 cx.simulate_keystrokes("c d");
909 prepare_request.next().await.unwrap();
910 cx.simulate_input("after");
911 cx.simulate_keystrokes("enter");
912 rename_request.next().await.unwrap();
913 cx.assert_state("const afterˇ = 2; console.log(after)", Mode::Normal)
914}
915
916#[gpui::test]
917async fn test_remap(cx: &mut gpui::TestAppContext) {
918 let mut cx = VimTestContext::new(cx, true).await;
919
920 // test moving the cursor
921 cx.update(|cx| {
922 cx.bind_keys([KeyBinding::new(
923 "g z",
924 workspace::SendKeystrokes("l l l l".to_string()),
925 None,
926 )])
927 });
928 cx.set_state("ˇ123456789", Mode::Normal);
929 cx.simulate_keystrokes("g z");
930 cx.assert_state("1234ˇ56789", Mode::Normal);
931
932 // test switching modes
933 cx.update(|cx| {
934 cx.bind_keys([KeyBinding::new(
935 "g y",
936 workspace::SendKeystrokes("i f o o escape l".to_string()),
937 None,
938 )])
939 });
940 cx.set_state("ˇ123456789", Mode::Normal);
941 cx.simulate_keystrokes("g y");
942 cx.assert_state("fooˇ123456789", Mode::Normal);
943
944 // test recursion
945 cx.update(|cx| {
946 cx.bind_keys([KeyBinding::new(
947 "g x",
948 workspace::SendKeystrokes("g z g y".to_string()),
949 None,
950 )])
951 });
952 cx.set_state("ˇ123456789", Mode::Normal);
953 cx.simulate_keystrokes("g x");
954 cx.assert_state("1234fooˇ56789", Mode::Normal);
955
956 cx.executor().allow_parking();
957
958 // test command
959 cx.update(|cx| {
960 cx.bind_keys([KeyBinding::new(
961 "g w",
962 workspace::SendKeystrokes(": j enter".to_string()),
963 None,
964 )])
965 });
966 cx.set_state("ˇ1234\n56789", Mode::Normal);
967 cx.simulate_keystrokes("g w");
968 cx.assert_state("1234ˇ 56789", Mode::Normal);
969
970 // test leaving command
971 cx.update(|cx| {
972 cx.bind_keys([KeyBinding::new(
973 "g u",
974 workspace::SendKeystrokes("g w g z".to_string()),
975 None,
976 )])
977 });
978 cx.set_state("ˇ1234\n56789", Mode::Normal);
979 cx.simulate_keystrokes("g u");
980 cx.assert_state("1234 567ˇ89", Mode::Normal);
981
982 // test leaving command
983 cx.update(|cx| {
984 cx.bind_keys([KeyBinding::new(
985 "g t",
986 workspace::SendKeystrokes("i space escape".to_string()),
987 None,
988 )])
989 });
990 cx.set_state("12ˇ34", Mode::Normal);
991 cx.simulate_keystrokes("g t");
992 cx.assert_state("12ˇ 34", Mode::Normal);
993}
994
995#[gpui::test]
996async fn test_undo(cx: &mut gpui::TestAppContext) {
997 let mut cx = NeovimBackedTestContext::new(cx).await;
998
999 cx.set_shared_state("hello quˇoel world").await;
1000 cx.simulate_shared_keystrokes("v i w s c o escape u").await;
1001 cx.shared_state().await.assert_eq("hello ˇquoel world");
1002 cx.simulate_shared_keystrokes("ctrl-r").await;
1003 cx.shared_state().await.assert_eq("hello ˇco world");
1004 cx.simulate_shared_keystrokes("a o right l escape").await;
1005 cx.shared_state().await.assert_eq("hello cooˇl world");
1006 cx.simulate_shared_keystrokes("u").await;
1007 cx.shared_state().await.assert_eq("hello cooˇ world");
1008 cx.simulate_shared_keystrokes("u").await;
1009 cx.shared_state().await.assert_eq("hello cˇo world");
1010 cx.simulate_shared_keystrokes("u").await;
1011 cx.shared_state().await.assert_eq("hello ˇquoel world");
1012
1013 cx.set_shared_state("hello quˇoel world").await;
1014 cx.simulate_shared_keystrokes("v i w ~ u").await;
1015 cx.shared_state().await.assert_eq("hello ˇquoel world");
1016
1017 cx.set_shared_state("\nhello quˇoel world\n").await;
1018 cx.simulate_shared_keystrokes("shift-v s c escape u").await;
1019 cx.shared_state().await.assert_eq("\nˇhello quoel world\n");
1020
1021 cx.set_shared_state(indoc! {"
1022 ˇ1
1023 2
1024 3"})
1025 .await;
1026
1027 cx.simulate_shared_keystrokes("ctrl-v shift-g ctrl-a").await;
1028 cx.shared_state().await.assert_eq(indoc! {"
1029 ˇ2
1030 3
1031 4"});
1032
1033 cx.simulate_shared_keystrokes("u").await;
1034 cx.shared_state().await.assert_eq(indoc! {"
1035 ˇ1
1036 2
1037 3"});
1038}
1039
1040#[gpui::test]
1041async fn test_mouse_selection(cx: &mut TestAppContext) {
1042 let mut cx = VimTestContext::new(cx, true).await;
1043
1044 cx.set_state("ˇone two three", Mode::Normal);
1045
1046 let start_point = cx.pixel_position("one twˇo three");
1047 let end_point = cx.pixel_position("one ˇtwo three");
1048
1049 cx.simulate_mouse_down(start_point, MouseButton::Left, Modifiers::none());
1050 cx.simulate_mouse_move(end_point, MouseButton::Left, Modifiers::none());
1051 cx.simulate_mouse_up(end_point, MouseButton::Left, Modifiers::none());
1052
1053 cx.assert_state("one «ˇtwo» three", Mode::Visual)
1054}
1055
1056#[gpui::test]
1057async fn test_lowercase_marks(cx: &mut TestAppContext) {
1058 let mut cx = NeovimBackedTestContext::new(cx).await;
1059
1060 cx.set_shared_state("line one\nline ˇtwo\nline three").await;
1061 cx.simulate_shared_keystrokes("m a l ' a").await;
1062 cx.shared_state()
1063 .await
1064 .assert_eq("line one\nˇline two\nline three");
1065 cx.simulate_shared_keystrokes("` a").await;
1066 cx.shared_state()
1067 .await
1068 .assert_eq("line one\nline ˇtwo\nline three");
1069
1070 cx.simulate_shared_keystrokes("^ d ` a").await;
1071 cx.shared_state()
1072 .await
1073 .assert_eq("line one\nˇtwo\nline three");
1074}
1075
1076#[gpui::test]
1077async fn test_lt_gt_marks(cx: &mut TestAppContext) {
1078 let mut cx = NeovimBackedTestContext::new(cx).await;
1079
1080 cx.set_shared_state(indoc!(
1081 "
1082 Line one
1083 Line two
1084 Line ˇthree
1085 Line four
1086 Line five
1087 "
1088 ))
1089 .await;
1090
1091 cx.simulate_shared_keystrokes("v j escape k k").await;
1092
1093 cx.simulate_shared_keystrokes("' <").await;
1094 cx.shared_state().await.assert_eq(indoc! {"
1095 Line one
1096 Line two
1097 ˇLine three
1098 Line four
1099 Line five
1100 "});
1101
1102 cx.simulate_shared_keystrokes("` <").await;
1103 cx.shared_state().await.assert_eq(indoc! {"
1104 Line one
1105 Line two
1106 Line ˇthree
1107 Line four
1108 Line five
1109 "});
1110
1111 cx.simulate_shared_keystrokes("' >").await;
1112 cx.shared_state().await.assert_eq(indoc! {"
1113 Line one
1114 Line two
1115 Line three
1116 ˇLine four
1117 Line five
1118 "
1119 });
1120
1121 cx.simulate_shared_keystrokes("` >").await;
1122 cx.shared_state().await.assert_eq(indoc! {"
1123 Line one
1124 Line two
1125 Line three
1126 Line ˇfour
1127 Line five
1128 "
1129 });
1130
1131 cx.simulate_shared_keystrokes("v i w o escape").await;
1132 cx.simulate_shared_keystrokes("` >").await;
1133 cx.shared_state().await.assert_eq(indoc! {"
1134 Line one
1135 Line two
1136 Line three
1137 Line fouˇr
1138 Line five
1139 "
1140 });
1141 cx.simulate_shared_keystrokes("` <").await;
1142 cx.shared_state().await.assert_eq(indoc! {"
1143 Line one
1144 Line two
1145 Line three
1146 Line ˇfour
1147 Line five
1148 "
1149 });
1150}
1151
1152#[gpui::test]
1153async fn test_caret_mark(cx: &mut TestAppContext) {
1154 let mut cx = NeovimBackedTestContext::new(cx).await;
1155
1156 cx.set_shared_state(indoc!(
1157 "
1158 Line one
1159 Line two
1160 Line three
1161 ˇLine four
1162 Line five
1163 "
1164 ))
1165 .await;
1166
1167 cx.simulate_shared_keystrokes("c w shift-s t r a i g h t space t h i n g escape j j")
1168 .await;
1169
1170 cx.simulate_shared_keystrokes("' ^").await;
1171 cx.shared_state().await.assert_eq(indoc! {"
1172 Line one
1173 Line two
1174 Line three
1175 ˇStraight thing four
1176 Line five
1177 "
1178 });
1179
1180 cx.simulate_shared_keystrokes("` ^").await;
1181 cx.shared_state().await.assert_eq(indoc! {"
1182 Line one
1183 Line two
1184 Line three
1185 Straight thingˇ four
1186 Line five
1187 "
1188 });
1189
1190 cx.simulate_shared_keystrokes("k a ! escape k g i ?").await;
1191 cx.shared_state().await.assert_eq(indoc! {"
1192 Line one
1193 Line two
1194 Line three!?ˇ
1195 Straight thing four
1196 Line five
1197 "
1198 });
1199}
1200
1201#[cfg(target_os = "macos")]
1202#[gpui::test]
1203async fn test_dw_eol(cx: &mut gpui::TestAppContext) {
1204 let mut cx = NeovimBackedTestContext::new(cx).await;
1205
1206 cx.set_shared_wrap(12).await;
1207 cx.set_shared_state("twelve ˇchar twelve char\ntwelve char")
1208 .await;
1209 cx.simulate_shared_keystrokes("d w").await;
1210 cx.shared_state()
1211 .await
1212 .assert_eq("twelve ˇtwelve char\ntwelve char");
1213}
1214
1215#[gpui::test]
1216async fn test_toggle_comments(cx: &mut gpui::TestAppContext) {
1217 let mut cx = VimTestContext::new(cx, true).await;
1218
1219 let language = std::sync::Arc::new(language::Language::new(
1220 language::LanguageConfig {
1221 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
1222 ..Default::default()
1223 },
1224 Some(language::tree_sitter_rust::language()),
1225 ));
1226 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
1227
1228 // works in normal model
1229 cx.set_state(
1230 indoc! {"
1231 ˇone
1232 two
1233 three
1234 "},
1235 Mode::Normal,
1236 );
1237 cx.simulate_keystrokes("g c c");
1238 cx.assert_state(
1239 indoc! {"
1240 // ˇone
1241 two
1242 three
1243 "},
1244 Mode::Normal,
1245 );
1246
1247 // works in visual mode
1248 cx.simulate_keystrokes("v j g c");
1249 cx.assert_state(
1250 indoc! {"
1251 // // ˇone
1252 // two
1253 three
1254 "},
1255 Mode::Normal,
1256 );
1257
1258 // works in visual line mode
1259 cx.simulate_keystrokes("shift-v j g c");
1260 cx.assert_state(
1261 indoc! {"
1262 // ˇone
1263 two
1264 three
1265 "},
1266 Mode::Normal,
1267 );
1268}