1mod neovim_backed_test_context;
2mod neovim_connection;
3mod vim_test_context;
4
5use std::{sync::Arc, time::Duration};
6
7use collections::HashMap;
8use command_palette::CommandPalette;
9use editor::{
10 AnchorRangeExt, DisplayPoint, Editor, EditorMode, MultiBuffer, MultiBufferOffset,
11 actions::{DeleteLine, WrapSelectionsInTag},
12 code_context_menus::CodeContextMenu,
13 display_map::DisplayRow,
14 test::editor_test_context::EditorTestContext,
15};
16use futures::StreamExt;
17use gpui::{KeyBinding, Modifiers, MouseButton, TestAppContext, px};
18use itertools::Itertools;
19use language::{CursorShape, Language, LanguageConfig, Point};
20pub use neovim_backed_test_context::*;
21use settings::SettingsStore;
22use ui::Pixels;
23use util::{path, test::marked_text_ranges};
24pub use vim_test_context::*;
25
26use gpui::VisualTestContext;
27use indoc::indoc;
28use project::FakeFs;
29use search::BufferSearchBar;
30use search::{ProjectSearchView, project_search};
31use serde_json::json;
32use workspace::DeploySearch;
33
34use crate::{PushSneak, PushSneakBackward, VimAddon, insert::NormalBefore, motion, state::Mode};
35
36use util_macros::perf;
37
38#[perf]
39#[gpui::test]
40async fn test_initially_disabled(cx: &mut gpui::TestAppContext) {
41 let mut cx = VimTestContext::new(cx, false).await;
42 cx.simulate_keystrokes("h j k l");
43 cx.assert_editor_state("hjklˇ");
44}
45
46#[perf]
47#[gpui::test]
48async fn test_neovim(cx: &mut gpui::TestAppContext) {
49 let mut cx = NeovimBackedTestContext::new(cx).await;
50
51 cx.simulate_shared_keystrokes("i").await;
52 cx.shared_state().await.assert_matches();
53 cx.simulate_shared_keystrokes("shift-t e s t space t e s t escape 0 d w")
54 .await;
55 cx.shared_state().await.assert_matches();
56 cx.assert_editor_state("ˇtest");
57}
58
59#[perf]
60#[gpui::test]
61async fn test_toggle_through_settings(cx: &mut gpui::TestAppContext) {
62 let mut cx = VimTestContext::new(cx, true).await;
63
64 cx.simulate_keystrokes("i");
65 assert_eq!(cx.mode(), Mode::Insert);
66
67 // Editor acts as though vim is disabled
68 cx.disable_vim();
69 cx.simulate_keystrokes("h j k l");
70 cx.assert_editor_state("hjklˇ");
71
72 // Selections aren't changed if editor is blurred but vim-mode is still disabled.
73 cx.cx.set_state("«hjklˇ»");
74 cx.assert_editor_state("«hjklˇ»");
75 cx.update_editor(|_, window, _cx| window.blur());
76 cx.assert_editor_state("«hjklˇ»");
77 cx.update_editor(|_, window, cx| cx.focus_self(window));
78 cx.assert_editor_state("«hjklˇ»");
79
80 // Enabling dynamically sets vim mode again and restores normal mode
81 cx.enable_vim();
82 assert_eq!(cx.mode(), Mode::Normal);
83 cx.simulate_keystrokes("h h h l");
84 assert_eq!(cx.buffer_text(), "hjkl".to_owned());
85 cx.assert_editor_state("hˇjkl");
86 cx.simulate_keystrokes("i T e s t");
87 cx.assert_editor_state("hTestˇjkl");
88
89 // Disabling and enabling resets to normal mode
90 assert_eq!(cx.mode(), Mode::Insert);
91 cx.disable_vim();
92 cx.enable_vim();
93 assert_eq!(cx.mode(), Mode::Normal);
94}
95
96#[perf]
97#[gpui::test]
98async fn test_cancel_selection(cx: &mut gpui::TestAppContext) {
99 let mut cx = VimTestContext::new(cx, true).await;
100
101 cx.set_state(
102 indoc! {"The quick brown fox juˇmps over the lazy dog"},
103 Mode::Normal,
104 );
105 // jumps
106 cx.simulate_keystrokes("v l l");
107 cx.assert_editor_state("The quick brown fox ju«mpsˇ» over the lazy dog");
108
109 cx.simulate_keystrokes("escape");
110 cx.assert_editor_state("The quick brown fox jumpˇs over the lazy dog");
111
112 // go back to the same selection state
113 cx.simulate_keystrokes("v h h");
114 cx.assert_editor_state("The quick brown fox ju«ˇmps» over the lazy dog");
115
116 // Ctrl-[ should behave like Esc
117 cx.simulate_keystrokes("ctrl-[");
118 cx.assert_editor_state("The quick brown fox juˇmps over the lazy dog");
119}
120
121#[perf]
122#[gpui::test]
123async fn test_buffer_search(cx: &mut gpui::TestAppContext) {
124 let mut cx = VimTestContext::new(cx, true).await;
125
126 cx.set_state(
127 indoc! {"
128 The quick brown
129 fox juˇmps over
130 the lazy dog"},
131 Mode::Normal,
132 );
133 cx.simulate_keystrokes("/");
134
135 let search_bar = cx.workspace(|workspace, _, cx| {
136 workspace
137 .active_pane()
138 .read(cx)
139 .toolbar()
140 .read(cx)
141 .item_of_type::<BufferSearchBar>()
142 .expect("Buffer search bar should be deployed")
143 });
144
145 cx.update_entity(search_bar, |bar, _, cx| {
146 assert_eq!(bar.query(cx), "");
147 })
148}
149
150#[perf]
151#[gpui::test]
152async fn test_count_down(cx: &mut gpui::TestAppContext) {
153 let mut cx = VimTestContext::new(cx, true).await;
154
155 cx.set_state(indoc! {"aˇa\nbb\ncc\ndd\nee"}, Mode::Normal);
156 cx.simulate_keystrokes("2 down");
157 cx.assert_editor_state("aa\nbb\ncˇc\ndd\nee");
158 cx.simulate_keystrokes("9 down");
159 cx.assert_editor_state("aa\nbb\ncc\ndd\neˇe");
160}
161
162#[perf]
163#[gpui::test]
164async fn test_end_of_document_710(cx: &mut gpui::TestAppContext) {
165 let mut cx = VimTestContext::new(cx, true).await;
166
167 // goes to end by default
168 cx.set_state(indoc! {"aˇa\nbb\ncc"}, Mode::Normal);
169 cx.simulate_keystrokes("shift-g");
170 cx.assert_editor_state("aa\nbb\ncˇc");
171
172 // can go to line 1 (https://github.com/zed-industries/zed/issues/5812)
173 cx.simulate_keystrokes("1 shift-g");
174 cx.assert_editor_state("aˇa\nbb\ncc");
175}
176
177#[perf]
178#[gpui::test]
179async fn test_end_of_line_with_times(cx: &mut gpui::TestAppContext) {
180 let mut cx = VimTestContext::new(cx, true).await;
181
182 // goes to current line end
183 cx.set_state(indoc! {"ˇaa\nbb\ncc"}, Mode::Normal);
184 cx.simulate_keystrokes("$");
185 cx.assert_editor_state("aˇa\nbb\ncc");
186
187 // goes to next line end
188 cx.simulate_keystrokes("2 $");
189 cx.assert_editor_state("aa\nbˇb\ncc");
190
191 // try to exceed the final line.
192 cx.simulate_keystrokes("4 $");
193 cx.assert_editor_state("aa\nbb\ncˇc");
194}
195
196#[perf]
197#[gpui::test]
198async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
199 let mut cx = VimTestContext::new(cx, true).await;
200
201 // works in normal mode
202 cx.set_state(indoc! {"aa\nbˇb\ncc"}, Mode::Normal);
203 cx.simulate_keystrokes("> >");
204 cx.assert_editor_state("aa\n bˇb\ncc");
205 cx.simulate_keystrokes("< <");
206 cx.assert_editor_state("aa\nbˇb\ncc");
207
208 // works in visual mode
209 cx.simulate_keystrokes("shift-v down >");
210 cx.assert_editor_state("aa\n bˇb\n cc");
211
212 // works as operator
213 cx.set_state("aa\nbˇb\ncc\n", Mode::Normal);
214 cx.simulate_keystrokes("> j");
215 cx.assert_editor_state("aa\n bˇb\n cc\n");
216 cx.simulate_keystrokes("< k");
217 cx.assert_editor_state("aa\nbˇb\n cc\n");
218 cx.simulate_keystrokes("> i p");
219 cx.assert_editor_state(" aa\n bˇb\n cc\n");
220 cx.simulate_keystrokes("< i p");
221 cx.assert_editor_state("aa\nbˇb\n cc\n");
222 cx.simulate_keystrokes("< i p");
223 cx.assert_editor_state("aa\nbˇb\ncc\n");
224
225 cx.set_state("ˇaa\nbb\ncc\n", Mode::Normal);
226 cx.simulate_keystrokes("> 2 j");
227 cx.assert_editor_state(" ˇaa\n bb\n cc\n");
228
229 cx.set_state("aa\nbb\nˇcc\n", Mode::Normal);
230 cx.simulate_keystrokes("> 2 k");
231 cx.assert_editor_state(" aa\n bb\n ˇcc\n");
232
233 // works with repeat
234 cx.set_state("a\nb\nccˇc\n", Mode::Normal);
235 cx.simulate_keystrokes("> 2 k");
236 cx.assert_editor_state(" a\n b\n ccˇc\n");
237 cx.simulate_keystrokes(".");
238 cx.assert_editor_state(" a\n b\n ccˇc\n");
239 cx.simulate_keystrokes("v k <");
240 cx.assert_editor_state(" a\n bˇ\n ccc\n");
241 cx.simulate_keystrokes(".");
242 cx.assert_editor_state(" a\nbˇ\nccc\n");
243}
244
245#[perf]
246#[gpui::test]
247async fn test_escape_command_palette(cx: &mut gpui::TestAppContext) {
248 let mut cx = VimTestContext::new(cx, true).await;
249
250 cx.set_state("aˇbc\n", Mode::Normal);
251 cx.simulate_keystrokes("i cmd-shift-p");
252
253 assert!(
254 cx.workspace(|workspace, _, cx| workspace.active_modal::<CommandPalette>(cx).is_some())
255 );
256 cx.simulate_keystrokes("escape");
257 cx.run_until_parked();
258 assert!(
259 !cx.workspace(|workspace, _, cx| workspace.active_modal::<CommandPalette>(cx).is_some())
260 );
261 cx.assert_state("aˇbc\n", Mode::Insert);
262}
263
264#[perf]
265#[gpui::test]
266async fn test_escape_cancels(cx: &mut gpui::TestAppContext) {
267 let mut cx = VimTestContext::new(cx, true).await;
268
269 cx.set_state("aˇbˇc", Mode::Normal);
270 cx.simulate_keystrokes("escape");
271
272 cx.assert_state("aˇbc", Mode::Normal);
273}
274
275#[perf]
276#[gpui::test]
277async fn test_selection_on_search(cx: &mut gpui::TestAppContext) {
278 let mut cx = VimTestContext::new(cx, true).await;
279
280 cx.set_state(indoc! {"aa\nbˇb\ncc\ncc\ncc\n"}, Mode::Normal);
281 cx.simulate_keystrokes("/ c c");
282
283 let search_bar = cx.workspace(|workspace, _, cx| {
284 workspace
285 .active_pane()
286 .read(cx)
287 .toolbar()
288 .read(cx)
289 .item_of_type::<BufferSearchBar>()
290 .expect("Buffer search bar should be deployed")
291 });
292
293 cx.update_entity(search_bar, |bar, _, cx| {
294 assert_eq!(bar.query(cx), "cc");
295 });
296
297 cx.update_editor(|editor, window, cx| {
298 let highlights = editor.all_text_background_highlights(window, cx);
299 assert_eq!(3, highlights.len());
300 assert_eq!(
301 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 2),
302 highlights[0].0
303 )
304 });
305 cx.simulate_keystrokes("enter");
306
307 cx.assert_state(indoc! {"aa\nbb\nˇcc\ncc\ncc\n"}, Mode::Normal);
308 cx.simulate_keystrokes("n");
309 cx.assert_state(indoc! {"aa\nbb\ncc\nˇcc\ncc\n"}, Mode::Normal);
310 cx.simulate_keystrokes("shift-n");
311 cx.assert_state(indoc! {"aa\nbb\nˇcc\ncc\ncc\n"}, Mode::Normal);
312}
313
314#[perf]
315#[gpui::test]
316async fn test_word_characters(cx: &mut gpui::TestAppContext) {
317 let mut cx = VimTestContext::new_typescript(cx).await;
318 cx.set_state(
319 indoc! { "
320 class A {
321 #ˇgoop = 99;
322 $ˇgoop () { return this.#gˇoop };
323 };
324 console.log(new A().$gooˇp())
325 "},
326 Mode::Normal,
327 );
328 cx.simulate_keystrokes("v i w");
329 cx.assert_state(
330 indoc! {"
331 class A {
332 «#goopˇ» = 99;
333 «$goopˇ» () { return this.«#goopˇ» };
334 };
335 console.log(new A().«$goopˇ»())
336 "},
337 Mode::Visual,
338 )
339}
340
341#[perf]
342#[gpui::test]
343async fn test_kebab_case(cx: &mut gpui::TestAppContext) {
344 let mut cx = VimTestContext::new_html(cx).await;
345 cx.set_state(
346 indoc! { r#"
347 <div><a class="bg-rˇed"></a></div>
348 "#},
349 Mode::Normal,
350 );
351 cx.simulate_keystrokes("v i w");
352 cx.assert_state(
353 indoc! { r#"
354 <div><a class="bg-«redˇ»"></a></div>
355 "#
356 },
357 Mode::Visual,
358 )
359}
360
361#[perf]
362#[gpui::test]
363async fn test_join_lines(cx: &mut gpui::TestAppContext) {
364 let mut cx = NeovimBackedTestContext::new(cx).await;
365
366 cx.set_shared_state(indoc! {"
367 ˇone
368 two
369 three
370 four
371 five
372 six
373 "})
374 .await;
375 cx.simulate_shared_keystrokes("shift-j").await;
376 cx.shared_state().await.assert_eq(indoc! {"
377 oneˇ two
378 three
379 four
380 five
381 six
382 "});
383 cx.simulate_shared_keystrokes("3 shift-j").await;
384 cx.shared_state().await.assert_eq(indoc! {"
385 one two threeˇ four
386 five
387 six
388 "});
389
390 cx.set_shared_state(indoc! {"
391 ˇone
392 two
393 three
394 four
395 five
396 six
397 "})
398 .await;
399 cx.simulate_shared_keystrokes("j v 3 j shift-j").await;
400 cx.shared_state().await.assert_eq(indoc! {"
401 one
402 two three fourˇ five
403 six
404 "});
405
406 cx.set_shared_state(indoc! {"
407 ˇone
408 two
409 three
410 four
411 five
412 six
413 "})
414 .await;
415 cx.simulate_shared_keystrokes("g shift-j").await;
416 cx.shared_state().await.assert_eq(indoc! {"
417 oneˇtwo
418 three
419 four
420 five
421 six
422 "});
423 cx.simulate_shared_keystrokes("3 g shift-j").await;
424 cx.shared_state().await.assert_eq(indoc! {"
425 onetwothreeˇfour
426 five
427 six
428 "});
429
430 cx.set_shared_state(indoc! {"
431 ˇone
432 two
433 three
434 four
435 five
436 six
437 "})
438 .await;
439 cx.simulate_shared_keystrokes("j v 3 j g shift-j").await;
440 cx.shared_state().await.assert_eq(indoc! {"
441 one
442 twothreefourˇfive
443 six
444 "});
445}
446
447#[cfg(target_os = "macos")]
448#[perf]
449#[gpui::test]
450async fn test_wrapped_lines(cx: &mut gpui::TestAppContext) {
451 let mut cx = NeovimBackedTestContext::new(cx).await;
452
453 cx.set_shared_wrap(12).await;
454 // tests line wrap as follows:
455 // 1: twelve char
456 // twelve char
457 // 2: twelve char
458 cx.set_shared_state(indoc! { "
459 tˇwelve char twelve char
460 twelve char
461 "})
462 .await;
463 cx.simulate_shared_keystrokes("j").await;
464 cx.shared_state().await.assert_eq(indoc! {"
465 twelve char twelve char
466 tˇwelve char
467 "});
468 cx.simulate_shared_keystrokes("k").await;
469 cx.shared_state().await.assert_eq(indoc! {"
470 tˇwelve char twelve char
471 twelve char
472 "});
473 cx.simulate_shared_keystrokes("g j").await;
474 cx.shared_state().await.assert_eq(indoc! {"
475 twelve char tˇwelve char
476 twelve char
477 "});
478 cx.simulate_shared_keystrokes("g j").await;
479 cx.shared_state().await.assert_eq(indoc! {"
480 twelve char twelve char
481 tˇwelve char
482 "});
483
484 cx.simulate_shared_keystrokes("g k").await;
485 cx.shared_state().await.assert_eq(indoc! {"
486 twelve char tˇwelve char
487 twelve char
488 "});
489
490 cx.simulate_shared_keystrokes("g ^").await;
491 cx.shared_state().await.assert_eq(indoc! {"
492 twelve char ˇtwelve char
493 twelve char
494 "});
495
496 cx.simulate_shared_keystrokes("^").await;
497 cx.shared_state().await.assert_eq(indoc! {"
498 ˇtwelve char twelve char
499 twelve char
500 "});
501
502 cx.simulate_shared_keystrokes("g $").await;
503 cx.shared_state().await.assert_eq(indoc! {"
504 twelve charˇ twelve char
505 twelve char
506 "});
507 cx.simulate_shared_keystrokes("$").await;
508 cx.shared_state().await.assert_eq(indoc! {"
509 twelve char twelve chaˇr
510 twelve char
511 "});
512
513 cx.set_shared_state(indoc! { "
514 tˇwelve char twelve char
515 twelve char
516 "})
517 .await;
518 cx.simulate_shared_keystrokes("enter").await;
519 cx.shared_state().await.assert_eq(indoc! {"
520 twelve char twelve char
521 ˇtwelve char
522 "});
523
524 cx.set_shared_state(indoc! { "
525 twelve char
526 tˇwelve char twelve char
527 twelve char
528 "})
529 .await;
530 cx.simulate_shared_keystrokes("o o escape").await;
531 cx.shared_state().await.assert_eq(indoc! {"
532 twelve char
533 twelve char twelve char
534 ˇo
535 twelve char
536 "});
537
538 cx.set_shared_state(indoc! { "
539 twelve char
540 tˇwelve char twelve char
541 twelve char
542 "})
543 .await;
544 cx.simulate_shared_keystrokes("shift-a a escape").await;
545 cx.shared_state().await.assert_eq(indoc! {"
546 twelve char
547 twelve char twelve charˇa
548 twelve char
549 "});
550 cx.simulate_shared_keystrokes("shift-i i escape").await;
551 cx.shared_state().await.assert_eq(indoc! {"
552 twelve char
553 ˇitwelve char twelve chara
554 twelve char
555 "});
556 cx.simulate_shared_keystrokes("shift-d").await;
557 cx.shared_state().await.assert_eq(indoc! {"
558 twelve char
559 ˇ
560 twelve char
561 "});
562
563 cx.set_shared_state(indoc! { "
564 twelve char
565 twelve char tˇwelve char
566 twelve char
567 "})
568 .await;
569 cx.simulate_shared_keystrokes("shift-o o escape").await;
570 cx.shared_state().await.assert_eq(indoc! {"
571 twelve char
572 ˇo
573 twelve char twelve char
574 twelve char
575 "});
576
577 // line wraps as:
578 // fourteen ch
579 // ar
580 // fourteen ch
581 // ar
582 cx.set_shared_state(indoc! { "
583 fourteen chaˇr
584 fourteen char
585 "})
586 .await;
587
588 cx.simulate_shared_keystrokes("d i w").await;
589 cx.shared_state().await.assert_eq(indoc! {"
590 fourteenˇ•
591 fourteen char
592 "});
593 cx.simulate_shared_keystrokes("j shift-f e f r").await;
594 cx.shared_state().await.assert_eq(indoc! {"
595 fourteen•
596 fourteen chaˇr
597 "});
598}
599
600#[perf]
601#[gpui::test]
602async fn test_folds(cx: &mut gpui::TestAppContext) {
603 let mut cx = NeovimBackedTestContext::new(cx).await;
604 cx.set_neovim_option("foldmethod=manual").await;
605
606 cx.set_shared_state(indoc! { "
607 fn boop() {
608 ˇbarp()
609 bazp()
610 }
611 "})
612 .await;
613 cx.simulate_shared_keystrokes("shift-v j z f").await;
614
615 // visual display is now:
616 // fn boop () {
617 // [FOLDED]
618 // }
619
620 // TODO: this should not be needed but currently zf does not
621 // return to normal mode.
622 cx.simulate_shared_keystrokes("escape").await;
623
624 // skip over fold downward
625 cx.simulate_shared_keystrokes("g g").await;
626 cx.shared_state().await.assert_eq(indoc! {"
627 ˇfn boop() {
628 barp()
629 bazp()
630 }
631 "});
632
633 cx.simulate_shared_keystrokes("j j").await;
634 cx.shared_state().await.assert_eq(indoc! {"
635 fn boop() {
636 barp()
637 bazp()
638 ˇ}
639 "});
640
641 // skip over fold upward
642 cx.simulate_shared_keystrokes("2 k").await;
643 cx.shared_state().await.assert_eq(indoc! {"
644 ˇfn boop() {
645 barp()
646 bazp()
647 }
648 "});
649
650 // yank the fold
651 cx.simulate_shared_keystrokes("down y y").await;
652 cx.shared_clipboard()
653 .await
654 .assert_eq(" barp()\n bazp()\n");
655
656 // re-open
657 cx.simulate_shared_keystrokes("z o").await;
658 cx.shared_state().await.assert_eq(indoc! {"
659 fn boop() {
660 ˇ barp()
661 bazp()
662 }
663 "});
664}
665
666#[perf]
667#[gpui::test]
668async fn test_folds_panic(cx: &mut gpui::TestAppContext) {
669 let mut cx = NeovimBackedTestContext::new(cx).await;
670 cx.set_neovim_option("foldmethod=manual").await;
671
672 cx.set_shared_state(indoc! { "
673 fn boop() {
674 ˇbarp()
675 bazp()
676 }
677 "})
678 .await;
679 cx.simulate_shared_keystrokes("shift-v j z f").await;
680 cx.simulate_shared_keystrokes("escape").await;
681 cx.simulate_shared_keystrokes("g g").await;
682 cx.simulate_shared_keystrokes("5 d j").await;
683 cx.shared_state().await.assert_eq("ˇ");
684 cx.set_shared_state(indoc! {"
685 fn boop() {
686 ˇbarp()
687 bazp()
688 }
689 "})
690 .await;
691 cx.simulate_shared_keystrokes("shift-v j j z f").await;
692 cx.simulate_shared_keystrokes("escape").await;
693 cx.simulate_shared_keystrokes("shift-g shift-v").await;
694 cx.shared_state().await.assert_eq(indoc! {"
695 fn boop() {
696 barp()
697 bazp()
698 }
699 ˇ"});
700}
701
702#[perf]
703#[gpui::test]
704async fn test_clear_counts(cx: &mut gpui::TestAppContext) {
705 let mut cx = NeovimBackedTestContext::new(cx).await;
706
707 cx.set_shared_state(indoc! {"
708 The quick brown
709 fox juˇmps over
710 the lazy dog"})
711 .await;
712
713 cx.simulate_shared_keystrokes("4 escape 3 d l").await;
714 cx.shared_state().await.assert_eq(indoc! {"
715 The quick brown
716 fox juˇ over
717 the lazy dog"});
718}
719
720#[perf]
721#[gpui::test]
722async fn test_zero(cx: &mut gpui::TestAppContext) {
723 let mut cx = NeovimBackedTestContext::new(cx).await;
724
725 cx.set_shared_state(indoc! {"
726 The quˇick brown
727 fox jumps over
728 the lazy dog"})
729 .await;
730
731 cx.simulate_shared_keystrokes("0").await;
732 cx.shared_state().await.assert_eq(indoc! {"
733 ˇThe quick brown
734 fox jumps over
735 the lazy dog"});
736
737 cx.simulate_shared_keystrokes("1 0 l").await;
738 cx.shared_state().await.assert_eq(indoc! {"
739 The quick ˇbrown
740 fox jumps over
741 the lazy dog"});
742}
743
744#[perf]
745#[gpui::test]
746async fn test_selection_goal(cx: &mut gpui::TestAppContext) {
747 let mut cx = NeovimBackedTestContext::new(cx).await;
748
749 cx.set_shared_state(indoc! {"
750 ;;ˇ;
751 Lorem Ipsum"})
752 .await;
753
754 cx.simulate_shared_keystrokes("a down up ; down up").await;
755 cx.shared_state().await.assert_eq(indoc! {"
756 ;;;;ˇ
757 Lorem Ipsum"});
758}
759
760#[cfg(target_os = "macos")]
761#[perf]
762#[gpui::test]
763async fn test_wrapped_motions(cx: &mut gpui::TestAppContext) {
764 let mut cx = NeovimBackedTestContext::new(cx).await;
765
766 cx.set_shared_wrap(12).await;
767
768 cx.set_shared_state(indoc! {"
769 aaˇaa
770 😃😃"
771 })
772 .await;
773 cx.simulate_shared_keystrokes("j").await;
774 cx.shared_state().await.assert_eq(indoc! {"
775 aaaa
776 😃ˇ😃"
777 });
778
779 cx.set_shared_state(indoc! {"
780 123456789012aaˇaa
781 123456789012😃😃"
782 })
783 .await;
784 cx.simulate_shared_keystrokes("j").await;
785 cx.shared_state().await.assert_eq(indoc! {"
786 123456789012aaaa
787 123456789012😃ˇ😃"
788 });
789
790 cx.set_shared_state(indoc! {"
791 123456789012aaˇaa
792 123456789012😃😃"
793 })
794 .await;
795 cx.simulate_shared_keystrokes("j").await;
796 cx.shared_state().await.assert_eq(indoc! {"
797 123456789012aaaa
798 123456789012😃ˇ😃"
799 });
800
801 cx.set_shared_state(indoc! {"
802 123456789012aaaaˇaaaaaaaa123456789012
803 wow
804 123456789012😃😃😃😃😃😃123456789012"
805 })
806 .await;
807 cx.simulate_shared_keystrokes("j j").await;
808 cx.shared_state().await.assert_eq(indoc! {"
809 123456789012aaaaaaaaaaaa123456789012
810 wow
811 123456789012😃😃ˇ😃😃😃😃123456789012"
812 });
813}
814
815#[perf]
816#[gpui::test]
817async fn test_wrapped_delete_end_document(cx: &mut gpui::TestAppContext) {
818 let mut cx = NeovimBackedTestContext::new(cx).await;
819
820 cx.set_shared_wrap(12).await;
821
822 cx.set_shared_state(indoc! {"
823 aaˇaaaaaaaaaaaaaaaaaa
824 bbbbbbbbbbbbbbbbbbbb
825 cccccccccccccccccccc"
826 })
827 .await;
828 cx.simulate_shared_keystrokes("d shift-g i z z z").await;
829 cx.shared_state().await.assert_eq(indoc! {"
830 zzzˇ"
831 });
832}
833
834#[perf]
835#[gpui::test]
836async fn test_paragraphs_dont_wrap(cx: &mut gpui::TestAppContext) {
837 let mut cx = NeovimBackedTestContext::new(cx).await;
838
839 cx.set_shared_state(indoc! {"
840 one
841 ˇ
842 two"})
843 .await;
844
845 cx.simulate_shared_keystrokes("} }").await;
846 cx.shared_state().await.assert_eq(indoc! {"
847 one
848
849 twˇo"});
850
851 cx.simulate_shared_keystrokes("{ { {").await;
852 cx.shared_state().await.assert_eq(indoc! {"
853 ˇone
854
855 two"});
856}
857
858#[perf]
859#[gpui::test]
860async fn test_select_all_issue_2170(cx: &mut gpui::TestAppContext) {
861 let mut cx = VimTestContext::new(cx, true).await;
862
863 cx.set_state(
864 indoc! {"
865 defmodule Test do
866 def test(a, ˇ[_, _] = b), do: IO.puts('hi')
867 end
868 "},
869 Mode::Normal,
870 );
871 cx.simulate_keystrokes("g a");
872 cx.assert_state(
873 indoc! {"
874 defmodule Test do
875 def test(a, «[ˇ»_, _] = b), do: IO.puts('hi')
876 end
877 "},
878 Mode::Visual,
879 );
880}
881
882#[perf]
883#[gpui::test]
884async fn test_jk(cx: &mut gpui::TestAppContext) {
885 let mut cx = NeovimBackedTestContext::new(cx).await;
886
887 cx.update(|_, cx| {
888 cx.bind_keys([KeyBinding::new(
889 "j k",
890 NormalBefore,
891 Some("vim_mode == insert"),
892 )])
893 });
894 cx.neovim.exec("imap jk <esc>").await;
895
896 cx.set_shared_state("ˇhello").await;
897 cx.simulate_shared_keystrokes("i j o j k").await;
898 cx.shared_state().await.assert_eq("jˇohello");
899}
900
901fn assert_pending_input(cx: &mut VimTestContext, expected: &str) {
902 cx.update_editor(|editor, window, cx| {
903 let snapshot = editor.snapshot(window, cx);
904 let highlights = editor
905 .text_highlights::<editor::PendingInput>(cx)
906 .unwrap()
907 .1;
908 let (_, ranges) = marked_text_ranges(expected, false);
909
910 assert_eq!(
911 highlights
912 .iter()
913 .map(|highlight| highlight.to_offset(&snapshot.buffer_snapshot()))
914 .collect::<Vec<_>>(),
915 ranges
916 .iter()
917 .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
918 .collect::<Vec<_>>()
919 )
920 });
921}
922
923#[perf]
924#[gpui::test]
925async fn test_jk_multi(cx: &mut gpui::TestAppContext) {
926 let mut cx = VimTestContext::new(cx, true).await;
927
928 cx.update(|_, cx| {
929 cx.bind_keys([KeyBinding::new(
930 "j k l",
931 NormalBefore,
932 Some("vim_mode == insert"),
933 )])
934 });
935
936 cx.set_state("ˇone ˇone ˇone", Mode::Normal);
937 cx.simulate_keystrokes("i j");
938 cx.simulate_keystrokes("k");
939 cx.assert_state("ˇjkone ˇjkone ˇjkone", Mode::Insert);
940 assert_pending_input(&mut cx, "«jk»one «jk»one «jk»one");
941 cx.simulate_keystrokes("o j k");
942 cx.assert_state("jkoˇjkone jkoˇjkone jkoˇjkone", Mode::Insert);
943 assert_pending_input(&mut cx, "jko«jk»one jko«jk»one jko«jk»one");
944 cx.simulate_keystrokes("l");
945 cx.assert_state("jkˇoone jkˇoone jkˇoone", Mode::Normal);
946}
947
948#[perf]
949#[gpui::test]
950async fn test_jk_delay(cx: &mut gpui::TestAppContext) {
951 let mut cx = VimTestContext::new(cx, true).await;
952
953 cx.update(|_, cx| {
954 cx.bind_keys([KeyBinding::new(
955 "j k",
956 NormalBefore,
957 Some("vim_mode == insert"),
958 )])
959 });
960
961 cx.set_state("ˇhello", Mode::Normal);
962 cx.simulate_keystrokes("i j");
963 cx.executor().advance_clock(Duration::from_millis(500));
964 cx.run_until_parked();
965 cx.assert_state("ˇjhello", Mode::Insert);
966 cx.update_editor(|editor, window, cx| {
967 let snapshot = editor.snapshot(window, cx);
968 let highlights = editor
969 .text_highlights::<editor::PendingInput>(cx)
970 .unwrap()
971 .1;
972
973 assert_eq!(
974 highlights
975 .iter()
976 .map(|highlight| highlight.to_offset(&snapshot.buffer_snapshot()))
977 .collect::<Vec<_>>(),
978 vec![MultiBufferOffset(0)..MultiBufferOffset(1)]
979 )
980 });
981 cx.executor().advance_clock(Duration::from_millis(500));
982 cx.run_until_parked();
983 cx.assert_state("jˇhello", Mode::Insert);
984 cx.simulate_keystrokes("k j k");
985 cx.assert_state("jˇkhello", Mode::Normal);
986}
987
988#[perf]
989#[gpui::test]
990async fn test_jk_max_count(cx: &mut gpui::TestAppContext) {
991 let mut cx = NeovimBackedTestContext::new(cx).await;
992
993 cx.set_shared_state("1\nˇ2\n3").await;
994 cx.simulate_shared_keystrokes("9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 j")
995 .await;
996 cx.shared_state().await.assert_eq("1\n2\nˇ3");
997
998 let number: String = usize::MAX.to_string().split("").join(" ");
999 cx.simulate_shared_keystrokes(&format!("{number} k")).await;
1000 cx.shared_state().await.assert_eq("ˇ1\n2\n3");
1001}
1002
1003#[perf]
1004#[gpui::test]
1005async fn test_comma_w(cx: &mut gpui::TestAppContext) {
1006 let mut cx = NeovimBackedTestContext::new(cx).await;
1007
1008 cx.update(|_, cx| {
1009 cx.bind_keys([KeyBinding::new(
1010 ", w",
1011 motion::Down {
1012 display_lines: false,
1013 },
1014 Some("vim_mode == normal"),
1015 )])
1016 });
1017 cx.neovim.exec("map ,w j").await;
1018
1019 cx.set_shared_state("ˇhello hello\nhello hello").await;
1020 cx.simulate_shared_keystrokes("f o ; , w").await;
1021 cx.shared_state()
1022 .await
1023 .assert_eq("hello hello\nhello hellˇo");
1024
1025 cx.set_shared_state("ˇhello hello\nhello hello").await;
1026 cx.simulate_shared_keystrokes("f o ; , i").await;
1027 cx.shared_state()
1028 .await
1029 .assert_eq("hellˇo hello\nhello hello");
1030}
1031
1032#[perf]
1033#[gpui::test]
1034async fn test_completion_menu_scroll_aside(cx: &mut TestAppContext) {
1035 let mut cx = VimTestContext::new_typescript(cx).await;
1036
1037 cx.lsp
1038 .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
1039 Ok(Some(lsp::CompletionResponse::Array(vec![
1040 lsp::CompletionItem {
1041 label: "Test Item".to_string(),
1042 documentation: Some(lsp::Documentation::String(
1043 "This is some very long documentation content that will be displayed in the aside panel for scrolling.\n".repeat(50)
1044 )),
1045 ..Default::default()
1046 },
1047 ])))
1048 });
1049
1050 cx.set_state("variableˇ", Mode::Insert);
1051 cx.simulate_keystroke(".");
1052 cx.executor().run_until_parked();
1053
1054 let mut initial_offset: Pixels = px(0.0);
1055
1056 cx.update_editor(|editor, _, _| {
1057 let binding = editor.context_menu().borrow();
1058 let Some(CodeContextMenu::Completions(menu)) = binding.as_ref() else {
1059 panic!("Should have completions menu open");
1060 };
1061
1062 initial_offset = menu.scroll_handle_aside.offset().y;
1063 });
1064
1065 // The `ctrl-e` shortcut should scroll the completion menu's aside content
1066 // down, so the updated offset should be lower than the initial offset.
1067 cx.simulate_keystroke("ctrl-e");
1068 cx.update_editor(|editor, _, _| {
1069 let binding = editor.context_menu().borrow();
1070 let Some(CodeContextMenu::Completions(menu)) = binding.as_ref() else {
1071 panic!("Should have completions menu open");
1072 };
1073
1074 assert!(menu.scroll_handle_aside.offset().y < initial_offset);
1075 });
1076
1077 // The `ctrl-y` shortcut should do the inverse scrolling as `ctrl-e`, so the
1078 // offset should now be the same as the initial offset.
1079 cx.simulate_keystroke("ctrl-y");
1080 cx.update_editor(|editor, _, _| {
1081 let binding = editor.context_menu().borrow();
1082 let Some(CodeContextMenu::Completions(menu)) = binding.as_ref() else {
1083 panic!("Should have completions menu open");
1084 };
1085
1086 assert_eq!(menu.scroll_handle_aside.offset().y, initial_offset);
1087 });
1088
1089 // The `ctrl-d` shortcut should scroll the completion menu's aside content
1090 // down, so the updated offset should be lower than the initial offset.
1091 cx.simulate_keystroke("ctrl-d");
1092 cx.update_editor(|editor, _, _| {
1093 let binding = editor.context_menu().borrow();
1094 let Some(CodeContextMenu::Completions(menu)) = binding.as_ref() else {
1095 panic!("Should have completions menu open");
1096 };
1097
1098 assert!(menu.scroll_handle_aside.offset().y < initial_offset);
1099 });
1100
1101 // The `ctrl-u` shortcut should do the inverse scrolling as `ctrl-u`, so the
1102 // offset should now be the same as the initial offset.
1103 cx.simulate_keystroke("ctrl-u");
1104 cx.update_editor(|editor, _, _| {
1105 let binding = editor.context_menu().borrow();
1106 let Some(CodeContextMenu::Completions(menu)) = binding.as_ref() else {
1107 panic!("Should have completions menu open");
1108 };
1109
1110 assert_eq!(menu.scroll_handle_aside.offset().y, initial_offset);
1111 });
1112}
1113
1114#[perf]
1115#[gpui::test]
1116async fn test_rename(cx: &mut gpui::TestAppContext) {
1117 let mut cx = VimTestContext::new_typescript(cx).await;
1118
1119 cx.set_state("const beˇfore = 2; console.log(before)", Mode::Normal);
1120 let def_range = cx.lsp_range("const «beforeˇ» = 2; console.log(before)");
1121 let tgt_range = cx.lsp_range("const before = 2; console.log(«beforeˇ»)");
1122 let mut prepare_request = cx.set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
1123 move |_, _, _| async move { Ok(Some(lsp::PrepareRenameResponse::Range(def_range))) },
1124 );
1125 let mut rename_request =
1126 cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, params, _| async move {
1127 Ok(Some(lsp::WorkspaceEdit {
1128 changes: Some(
1129 [(
1130 url.clone(),
1131 vec![
1132 lsp::TextEdit::new(def_range, params.new_name.clone()),
1133 lsp::TextEdit::new(tgt_range, params.new_name),
1134 ],
1135 )]
1136 .into(),
1137 ),
1138 ..Default::default()
1139 }))
1140 });
1141
1142 cx.simulate_keystrokes("c d");
1143 prepare_request.next().await.unwrap();
1144 cx.simulate_input("after");
1145 cx.simulate_keystrokes("enter");
1146 rename_request.next().await.unwrap();
1147 cx.assert_state("const afterˇ = 2; console.log(after)", Mode::Normal)
1148}
1149
1150#[gpui::test]
1151async fn test_go_to_definition(cx: &mut gpui::TestAppContext) {
1152 let mut cx = VimTestContext::new_typescript(cx).await;
1153
1154 cx.set_state("const before = 2; console.log(beforˇe)", Mode::Normal);
1155 let def_range = cx.lsp_range("const «beforeˇ» = 2; console.log(before)");
1156 let mut go_to_request =
1157 cx.set_request_handler::<lsp::request::GotoDefinition, _, _>(move |url, _, _| async move {
1158 Ok(Some(lsp::GotoDefinitionResponse::Scalar(
1159 lsp::Location::new(url.clone(), def_range),
1160 )))
1161 });
1162
1163 cx.simulate_keystrokes("g d");
1164 go_to_request.next().await.unwrap();
1165 cx.run_until_parked();
1166
1167 cx.assert_state("const ˇbefore = 2; console.log(before)", Mode::Normal);
1168}
1169
1170#[perf]
1171#[gpui::test]
1172async fn test_remap(cx: &mut gpui::TestAppContext) {
1173 let mut cx = VimTestContext::new(cx, true).await;
1174
1175 // test moving the cursor
1176 cx.update(|_, cx| {
1177 cx.bind_keys([KeyBinding::new(
1178 "g z",
1179 workspace::SendKeystrokes("l l l l".to_string()),
1180 None,
1181 )])
1182 });
1183 cx.set_state("ˇ123456789", Mode::Normal);
1184 cx.simulate_keystrokes("g z");
1185 cx.assert_state("1234ˇ56789", Mode::Normal);
1186
1187 // test switching modes
1188 cx.update(|_, cx| {
1189 cx.bind_keys([KeyBinding::new(
1190 "g y",
1191 workspace::SendKeystrokes("i f o o escape l".to_string()),
1192 None,
1193 )])
1194 });
1195 cx.set_state("ˇ123456789", Mode::Normal);
1196 cx.simulate_keystrokes("g y");
1197 cx.assert_state("fooˇ123456789", Mode::Normal);
1198
1199 // test recursion
1200 cx.update(|_, cx| {
1201 cx.bind_keys([KeyBinding::new(
1202 "g x",
1203 workspace::SendKeystrokes("g z g y".to_string()),
1204 None,
1205 )])
1206 });
1207 cx.set_state("ˇ123456789", Mode::Normal);
1208 cx.simulate_keystrokes("g x");
1209 cx.assert_state("1234fooˇ56789", Mode::Normal);
1210
1211 // test command
1212 cx.update(|_, cx| {
1213 cx.bind_keys([KeyBinding::new(
1214 "g w",
1215 workspace::SendKeystrokes(": j enter".to_string()),
1216 None,
1217 )])
1218 });
1219 cx.set_state("ˇ1234\n56789", Mode::Normal);
1220 cx.simulate_keystrokes("g w");
1221 cx.assert_state("1234ˇ 56789", Mode::Normal);
1222
1223 // test leaving command
1224 cx.update(|_, cx| {
1225 cx.bind_keys([KeyBinding::new(
1226 "g u",
1227 workspace::SendKeystrokes("g w g z".to_string()),
1228 None,
1229 )])
1230 });
1231 cx.set_state("ˇ1234\n56789", Mode::Normal);
1232 cx.simulate_keystrokes("g u");
1233 cx.assert_state("1234 567ˇ89", Mode::Normal);
1234
1235 // test leaving command
1236 cx.update(|_, cx| {
1237 cx.bind_keys([KeyBinding::new(
1238 "g t",
1239 workspace::SendKeystrokes("i space escape".to_string()),
1240 None,
1241 )])
1242 });
1243 cx.set_state("12ˇ34", Mode::Normal);
1244 cx.simulate_keystrokes("g t");
1245 cx.assert_state("12ˇ 34", Mode::Normal);
1246}
1247
1248#[perf]
1249#[gpui::test]
1250async fn test_undo(cx: &mut gpui::TestAppContext) {
1251 let mut cx = NeovimBackedTestContext::new(cx).await;
1252
1253 cx.set_shared_state("hello quˇoel world").await;
1254 cx.simulate_shared_keystrokes("v i w s c o escape u").await;
1255 cx.shared_state().await.assert_eq("hello ˇquoel world");
1256 cx.simulate_shared_keystrokes("ctrl-r").await;
1257 cx.shared_state().await.assert_eq("hello ˇco world");
1258 cx.simulate_shared_keystrokes("a o right l escape").await;
1259 cx.shared_state().await.assert_eq("hello cooˇl world");
1260 cx.simulate_shared_keystrokes("u").await;
1261 cx.shared_state().await.assert_eq("hello cooˇ world");
1262 cx.simulate_shared_keystrokes("u").await;
1263 cx.shared_state().await.assert_eq("hello cˇo world");
1264 cx.simulate_shared_keystrokes("u").await;
1265 cx.shared_state().await.assert_eq("hello ˇquoel world");
1266
1267 cx.set_shared_state("hello quˇoel world").await;
1268 cx.simulate_shared_keystrokes("v i w ~ u").await;
1269 cx.shared_state().await.assert_eq("hello ˇquoel world");
1270
1271 cx.set_shared_state("\nhello quˇoel world\n").await;
1272 cx.simulate_shared_keystrokes("shift-v s c escape u").await;
1273 cx.shared_state().await.assert_eq("\nˇhello quoel world\n");
1274
1275 cx.set_shared_state(indoc! {"
1276 ˇ1
1277 2
1278 3"})
1279 .await;
1280
1281 cx.simulate_shared_keystrokes("ctrl-v shift-g ctrl-a").await;
1282 cx.shared_state().await.assert_eq(indoc! {"
1283 ˇ2
1284 3
1285 4"});
1286
1287 cx.simulate_shared_keystrokes("u").await;
1288 cx.shared_state().await.assert_eq(indoc! {"
1289 ˇ1
1290 2
1291 3"});
1292}
1293
1294#[perf]
1295#[gpui::test]
1296async fn test_mouse_selection(cx: &mut TestAppContext) {
1297 let mut cx = VimTestContext::new(cx, true).await;
1298
1299 cx.set_state("ˇone two three", Mode::Normal);
1300
1301 let start_point = cx.pixel_position("one twˇo three");
1302 let end_point = cx.pixel_position("one ˇtwo three");
1303
1304 cx.simulate_mouse_down(start_point, MouseButton::Left, Modifiers::none());
1305 cx.simulate_mouse_move(end_point, MouseButton::Left, Modifiers::none());
1306 cx.simulate_mouse_up(end_point, MouseButton::Left, Modifiers::none());
1307
1308 cx.assert_state("one «ˇtwo» three", Mode::Visual)
1309}
1310
1311#[gpui::test]
1312async fn test_mouse_drag_across_anchor_does_not_drift(cx: &mut TestAppContext) {
1313 let mut cx = VimTestContext::new(cx, true).await;
1314
1315 cx.set_state("ˇone two three four", Mode::Normal);
1316
1317 let click_pos = cx.pixel_position("one ˇtwo three four");
1318 let drag_left = cx.pixel_position("ˇone two three four");
1319 let anchor_pos = cx.pixel_position("one tˇwo three four");
1320
1321 cx.simulate_mouse_down(click_pos, MouseButton::Left, Modifiers::none());
1322 cx.run_until_parked();
1323
1324 cx.simulate_mouse_move(drag_left, MouseButton::Left, Modifiers::none());
1325 cx.run_until_parked();
1326 cx.assert_state("«ˇone t»wo three four", Mode::Visual);
1327
1328 cx.simulate_mouse_move(anchor_pos, MouseButton::Left, Modifiers::none());
1329 cx.run_until_parked();
1330
1331 cx.simulate_mouse_move(drag_left, MouseButton::Left, Modifiers::none());
1332 cx.run_until_parked();
1333 cx.assert_state("«ˇone t»wo three four", Mode::Visual);
1334
1335 cx.simulate_mouse_move(anchor_pos, MouseButton::Left, Modifiers::none());
1336 cx.run_until_parked();
1337 cx.simulate_mouse_move(drag_left, MouseButton::Left, Modifiers::none());
1338 cx.run_until_parked();
1339 cx.assert_state("«ˇone t»wo three four", Mode::Visual);
1340
1341 cx.simulate_mouse_up(drag_left, MouseButton::Left, Modifiers::none());
1342}
1343
1344#[perf]
1345#[gpui::test]
1346async fn test_lowercase_marks(cx: &mut TestAppContext) {
1347 let mut cx = NeovimBackedTestContext::new(cx).await;
1348
1349 cx.set_shared_state("line one\nline ˇtwo\nline three").await;
1350 cx.simulate_shared_keystrokes("m a l ' a").await;
1351 cx.shared_state()
1352 .await
1353 .assert_eq("line one\nˇline two\nline three");
1354 cx.simulate_shared_keystrokes("` a").await;
1355 cx.shared_state()
1356 .await
1357 .assert_eq("line one\nline ˇtwo\nline three");
1358
1359 cx.simulate_shared_keystrokes("^ d ` a").await;
1360 cx.shared_state()
1361 .await
1362 .assert_eq("line one\nˇtwo\nline three");
1363}
1364
1365#[perf]
1366#[gpui::test]
1367async fn test_lt_gt_marks(cx: &mut TestAppContext) {
1368 let mut cx = NeovimBackedTestContext::new(cx).await;
1369
1370 cx.set_shared_state(indoc!(
1371 "
1372 Line one
1373 Line two
1374 Line ˇthree
1375 Line four
1376 Line five
1377 "
1378 ))
1379 .await;
1380
1381 cx.simulate_shared_keystrokes("v j escape k k").await;
1382
1383 cx.simulate_shared_keystrokes("' <").await;
1384 cx.shared_state().await.assert_eq(indoc! {"
1385 Line one
1386 Line two
1387 ˇLine three
1388 Line four
1389 Line five
1390 "});
1391
1392 cx.simulate_shared_keystrokes("` <").await;
1393 cx.shared_state().await.assert_eq(indoc! {"
1394 Line one
1395 Line two
1396 Line ˇthree
1397 Line four
1398 Line five
1399 "});
1400
1401 cx.simulate_shared_keystrokes("' >").await;
1402 cx.shared_state().await.assert_eq(indoc! {"
1403 Line one
1404 Line two
1405 Line three
1406 ˇLine four
1407 Line five
1408 "
1409 });
1410
1411 cx.simulate_shared_keystrokes("` >").await;
1412 cx.shared_state().await.assert_eq(indoc! {"
1413 Line one
1414 Line two
1415 Line three
1416 Line ˇfour
1417 Line five
1418 "
1419 });
1420
1421 cx.simulate_shared_keystrokes("v i w o escape").await;
1422 cx.simulate_shared_keystrokes("` >").await;
1423 cx.shared_state().await.assert_eq(indoc! {"
1424 Line one
1425 Line two
1426 Line three
1427 Line fouˇr
1428 Line five
1429 "
1430 });
1431 cx.simulate_shared_keystrokes("` <").await;
1432 cx.shared_state().await.assert_eq(indoc! {"
1433 Line one
1434 Line two
1435 Line three
1436 Line ˇfour
1437 Line five
1438 "
1439 });
1440}
1441
1442#[perf]
1443#[gpui::test]
1444async fn test_caret_mark(cx: &mut TestAppContext) {
1445 let mut cx = NeovimBackedTestContext::new(cx).await;
1446
1447 cx.set_shared_state(indoc!(
1448 "
1449 Line one
1450 Line two
1451 Line three
1452 ˇLine four
1453 Line five
1454 "
1455 ))
1456 .await;
1457
1458 cx.simulate_shared_keystrokes("c w shift-s t r a i g h t space t h i n g escape j j")
1459 .await;
1460
1461 cx.simulate_shared_keystrokes("' ^").await;
1462 cx.shared_state().await.assert_eq(indoc! {"
1463 Line one
1464 Line two
1465 Line three
1466 ˇStraight thing four
1467 Line five
1468 "
1469 });
1470
1471 cx.simulate_shared_keystrokes("` ^").await;
1472 cx.shared_state().await.assert_eq(indoc! {"
1473 Line one
1474 Line two
1475 Line three
1476 Straight thingˇ four
1477 Line five
1478 "
1479 });
1480
1481 cx.simulate_shared_keystrokes("k a ! escape k g i ?").await;
1482 cx.shared_state().await.assert_eq(indoc! {"
1483 Line one
1484 Line two
1485 Line three!?ˇ
1486 Straight thing four
1487 Line five
1488 "
1489 });
1490}
1491
1492#[cfg(target_os = "macos")]
1493#[perf]
1494#[gpui::test]
1495async fn test_dw_eol(cx: &mut gpui::TestAppContext) {
1496 let mut cx = NeovimBackedTestContext::new(cx).await;
1497
1498 cx.set_shared_wrap(12).await;
1499 cx.set_shared_state("twelve ˇchar twelve char\ntwelve char")
1500 .await;
1501 cx.simulate_shared_keystrokes("d w").await;
1502 cx.shared_state()
1503 .await
1504 .assert_eq("twelve ˇtwelve char\ntwelve char");
1505}
1506
1507#[perf]
1508#[gpui::test]
1509async fn test_toggle_comments(cx: &mut gpui::TestAppContext) {
1510 let mut cx = VimTestContext::new(cx, true).await;
1511
1512 let language = std::sync::Arc::new(language::Language::new(
1513 language::LanguageConfig {
1514 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
1515 ..Default::default()
1516 },
1517 Some(language::tree_sitter_rust::LANGUAGE.into()),
1518 ));
1519 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
1520
1521 // works in normal model
1522 cx.set_state(
1523 indoc! {"
1524 ˇone
1525 two
1526 three
1527 "},
1528 Mode::Normal,
1529 );
1530 cx.simulate_keystrokes("g c c");
1531 cx.assert_state(
1532 indoc! {"
1533 // ˇone
1534 two
1535 three
1536 "},
1537 Mode::Normal,
1538 );
1539
1540 // works in visual mode
1541 cx.simulate_keystrokes("v j g c");
1542 cx.assert_state(
1543 indoc! {"
1544 // // ˇone
1545 // two
1546 three
1547 "},
1548 Mode::Normal,
1549 );
1550
1551 // works in visual line mode
1552 cx.simulate_keystrokes("shift-v j g c");
1553 cx.assert_state(
1554 indoc! {"
1555 // ˇone
1556 two
1557 three
1558 "},
1559 Mode::Normal,
1560 );
1561
1562 // works with count
1563 cx.simulate_keystrokes("g c 2 j");
1564 cx.assert_state(
1565 indoc! {"
1566 // // ˇone
1567 // two
1568 // three
1569 "},
1570 Mode::Normal,
1571 );
1572
1573 // works with motion object
1574 cx.simulate_keystrokes("shift-g");
1575 cx.simulate_keystrokes("g c g g");
1576 cx.assert_state(
1577 indoc! {"
1578 // one
1579 two
1580 three
1581 ˇ"},
1582 Mode::Normal,
1583 );
1584}
1585
1586#[perf]
1587#[gpui::test]
1588async fn test_find_multibyte(cx: &mut gpui::TestAppContext) {
1589 let mut cx = NeovimBackedTestContext::new(cx).await;
1590
1591 cx.set_shared_state(r#"<label for="guests">ˇPočet hostů</label>"#)
1592 .await;
1593
1594 cx.simulate_shared_keystrokes("c t < o escape").await;
1595 cx.shared_state()
1596 .await
1597 .assert_eq(r#"<label for="guests">ˇo</label>"#);
1598}
1599
1600#[perf]
1601#[gpui::test]
1602async fn test_sneak(cx: &mut gpui::TestAppContext) {
1603 let mut cx = VimTestContext::new(cx, true).await;
1604
1605 cx.update(|_window, cx| {
1606 cx.bind_keys([
1607 KeyBinding::new(
1608 "s",
1609 PushSneak { first_char: None },
1610 Some("vim_mode == normal"),
1611 ),
1612 KeyBinding::new(
1613 "shift-s",
1614 PushSneakBackward { first_char: None },
1615 Some("vim_mode == normal"),
1616 ),
1617 KeyBinding::new(
1618 "shift-s",
1619 PushSneakBackward { first_char: None },
1620 Some("vim_mode == visual"),
1621 ),
1622 ])
1623 });
1624
1625 // Sneak forwards multibyte & multiline
1626 cx.set_state(
1627 indoc! {
1628 r#"<labelˇ for="guests">
1629 Počet hostů
1630 </label>"#
1631 },
1632 Mode::Normal,
1633 );
1634 cx.simulate_keystrokes("s t ů");
1635 cx.assert_state(
1636 indoc! {
1637 r#"<label for="guests">
1638 Počet hosˇtů
1639 </label>"#
1640 },
1641 Mode::Normal,
1642 );
1643
1644 // Visual sneak backwards multibyte & multiline
1645 cx.simulate_keystrokes("v S < l");
1646 cx.assert_state(
1647 indoc! {
1648 r#"«ˇ<label for="guests">
1649 Počet host»ů
1650 </label>"#
1651 },
1652 Mode::Visual,
1653 );
1654
1655 // Sneak backwards repeated
1656 cx.set_state(r#"11 12 13 ˇ14"#, Mode::Normal);
1657 cx.simulate_keystrokes("S space 1");
1658 cx.assert_state(r#"11 12ˇ 13 14"#, Mode::Normal);
1659 cx.simulate_keystrokes(";");
1660 cx.assert_state(r#"11ˇ 12 13 14"#, Mode::Normal);
1661}
1662
1663#[perf]
1664#[gpui::test]
1665async fn test_plus_minus(cx: &mut gpui::TestAppContext) {
1666 let mut cx = NeovimBackedTestContext::new(cx).await;
1667
1668 cx.set_shared_state(indoc! {
1669 "one
1670 two
1671 thrˇee
1672 "})
1673 .await;
1674
1675 cx.simulate_shared_keystrokes("-").await;
1676 cx.shared_state().await.assert_matches();
1677 cx.simulate_shared_keystrokes("-").await;
1678 cx.shared_state().await.assert_matches();
1679 cx.simulate_shared_keystrokes("+").await;
1680 cx.shared_state().await.assert_matches();
1681}
1682
1683#[perf]
1684#[gpui::test]
1685async fn test_command_alias(cx: &mut gpui::TestAppContext) {
1686 let mut cx = VimTestContext::new(cx, true).await;
1687 cx.update_global(|store: &mut SettingsStore, cx| {
1688 store.update_user_settings(cx, |s| {
1689 let mut aliases = HashMap::default();
1690 aliases.insert("Q".to_string(), "upper".to_string());
1691 s.workspace.command_aliases = aliases
1692 });
1693 });
1694
1695 cx.set_state("ˇhello world", Mode::Normal);
1696 cx.simulate_keystrokes(": Q");
1697 cx.set_state("ˇHello world", Mode::Normal);
1698}
1699
1700#[perf]
1701#[gpui::test]
1702async fn test_remap_adjacent_dog_cat(cx: &mut gpui::TestAppContext) {
1703 let mut cx = NeovimBackedTestContext::new(cx).await;
1704 cx.update(|_, cx| {
1705 cx.bind_keys([
1706 KeyBinding::new(
1707 "d o g",
1708 workspace::SendKeystrokes("🐶".to_string()),
1709 Some("vim_mode == insert"),
1710 ),
1711 KeyBinding::new(
1712 "c a t",
1713 workspace::SendKeystrokes("🐱".to_string()),
1714 Some("vim_mode == insert"),
1715 ),
1716 ])
1717 });
1718 cx.neovim.exec("imap dog 🐶").await;
1719 cx.neovim.exec("imap cat 🐱").await;
1720
1721 cx.set_shared_state("ˇ").await;
1722 cx.simulate_shared_keystrokes("i d o g").await;
1723 cx.shared_state().await.assert_eq("🐶ˇ");
1724
1725 cx.set_shared_state("ˇ").await;
1726 cx.simulate_shared_keystrokes("i d o d o g").await;
1727 cx.shared_state().await.assert_eq("do🐶ˇ");
1728
1729 cx.set_shared_state("ˇ").await;
1730 cx.simulate_shared_keystrokes("i d o c a t").await;
1731 cx.shared_state().await.assert_eq("do🐱ˇ");
1732}
1733
1734#[perf]
1735#[gpui::test]
1736async fn test_remap_nested_pineapple(cx: &mut gpui::TestAppContext) {
1737 let mut cx = NeovimBackedTestContext::new(cx).await;
1738 cx.update(|_, cx| {
1739 cx.bind_keys([
1740 KeyBinding::new(
1741 "p i n",
1742 workspace::SendKeystrokes("📌".to_string()),
1743 Some("vim_mode == insert"),
1744 ),
1745 KeyBinding::new(
1746 "p i n e",
1747 workspace::SendKeystrokes("🌲".to_string()),
1748 Some("vim_mode == insert"),
1749 ),
1750 KeyBinding::new(
1751 "p i n e a p p l e",
1752 workspace::SendKeystrokes("🍍".to_string()),
1753 Some("vim_mode == insert"),
1754 ),
1755 ])
1756 });
1757 cx.neovim.exec("imap pin 📌").await;
1758 cx.neovim.exec("imap pine 🌲").await;
1759 cx.neovim.exec("imap pineapple 🍍").await;
1760
1761 cx.set_shared_state("ˇ").await;
1762 cx.simulate_shared_keystrokes("i p i n").await;
1763 cx.executor().advance_clock(Duration::from_millis(1000));
1764 cx.run_until_parked();
1765 cx.shared_state().await.assert_eq("📌ˇ");
1766
1767 cx.set_shared_state("ˇ").await;
1768 cx.simulate_shared_keystrokes("i p i n e").await;
1769 cx.executor().advance_clock(Duration::from_millis(1000));
1770 cx.run_until_parked();
1771 cx.shared_state().await.assert_eq("🌲ˇ");
1772
1773 cx.set_shared_state("ˇ").await;
1774 cx.simulate_shared_keystrokes("i p i n e a p p l e").await;
1775 cx.shared_state().await.assert_eq("🍍ˇ");
1776}
1777
1778#[perf]
1779#[gpui::test]
1780async fn test_remap_recursion(cx: &mut gpui::TestAppContext) {
1781 let mut cx = NeovimBackedTestContext::new(cx).await;
1782 cx.update(|_, cx| {
1783 cx.bind_keys([KeyBinding::new(
1784 "x",
1785 workspace::SendKeystrokes("\" _ x".to_string()),
1786 Some("VimControl"),
1787 )]);
1788 cx.bind_keys([KeyBinding::new(
1789 "y",
1790 workspace::SendKeystrokes("2 x".to_string()),
1791 Some("VimControl"),
1792 )])
1793 });
1794 cx.neovim.exec("noremap x \"_x").await;
1795 cx.neovim.exec("map y 2x").await;
1796
1797 cx.set_shared_state("ˇhello").await;
1798 cx.simulate_shared_keystrokes("d l").await;
1799 cx.shared_clipboard().await.assert_eq("h");
1800 cx.simulate_shared_keystrokes("y").await;
1801 cx.shared_clipboard().await.assert_eq("h");
1802 cx.shared_state().await.assert_eq("ˇlo");
1803}
1804
1805#[perf]
1806#[gpui::test]
1807async fn test_escape_while_waiting(cx: &mut gpui::TestAppContext) {
1808 let mut cx = NeovimBackedTestContext::new(cx).await;
1809 cx.set_shared_state("ˇhi").await;
1810 cx.simulate_shared_keystrokes("\" + escape x").await;
1811 cx.shared_state().await.assert_eq("ˇi");
1812}
1813
1814#[perf]
1815#[gpui::test]
1816async fn test_ctrl_w_override(cx: &mut gpui::TestAppContext) {
1817 let mut cx = NeovimBackedTestContext::new(cx).await;
1818 cx.update(|_, cx| {
1819 cx.bind_keys([KeyBinding::new("ctrl-w", DeleteLine, None)]);
1820 });
1821 cx.neovim.exec("map <c-w> D").await;
1822 cx.set_shared_state("ˇhi").await;
1823 cx.simulate_shared_keystrokes("ctrl-w").await;
1824 cx.shared_state().await.assert_eq("ˇ");
1825}
1826
1827#[perf]
1828#[gpui::test]
1829async fn test_visual_indent_count(cx: &mut gpui::TestAppContext) {
1830 let mut cx = VimTestContext::new(cx, true).await;
1831 cx.set_state("ˇhi", Mode::Normal);
1832 cx.simulate_keystrokes("shift-v 3 >");
1833 cx.assert_state(" ˇhi", Mode::Normal);
1834 cx.simulate_keystrokes("shift-v 2 <");
1835 cx.assert_state(" ˇhi", Mode::Normal);
1836}
1837
1838#[perf]
1839#[gpui::test]
1840async fn test_record_replay_recursion(cx: &mut gpui::TestAppContext) {
1841 let mut cx = NeovimBackedTestContext::new(cx).await;
1842
1843 cx.set_shared_state("ˇhello world").await;
1844 cx.simulate_shared_keystrokes(">").await;
1845 cx.simulate_shared_keystrokes(".").await;
1846 cx.simulate_shared_keystrokes(".").await;
1847 cx.simulate_shared_keystrokes(".").await;
1848 cx.shared_state().await.assert_eq("ˇhello world");
1849}
1850
1851#[perf]
1852#[gpui::test]
1853async fn test_blackhole_register(cx: &mut gpui::TestAppContext) {
1854 let mut cx = NeovimBackedTestContext::new(cx).await;
1855
1856 cx.set_shared_state("ˇhello world").await;
1857 cx.simulate_shared_keystrokes("d i w \" _ d a w").await;
1858 cx.simulate_shared_keystrokes("p").await;
1859 cx.shared_state().await.assert_eq("hellˇo");
1860}
1861
1862#[perf]
1863#[gpui::test]
1864async fn test_sentence_backwards(cx: &mut gpui::TestAppContext) {
1865 let mut cx = NeovimBackedTestContext::new(cx).await;
1866
1867 cx.set_shared_state("one\n\ntwo\nthree\nˇ\nfour").await;
1868 cx.simulate_shared_keystrokes("(").await;
1869 cx.shared_state()
1870 .await
1871 .assert_eq("one\n\nˇtwo\nthree\n\nfour");
1872
1873 cx.set_shared_state("hello.\n\n\nworˇld.").await;
1874 cx.simulate_shared_keystrokes("(").await;
1875 cx.shared_state().await.assert_eq("hello.\n\n\nˇworld.");
1876 cx.simulate_shared_keystrokes("(").await;
1877 cx.shared_state().await.assert_eq("hello.\n\nˇ\nworld.");
1878 cx.simulate_shared_keystrokes("(").await;
1879 cx.shared_state().await.assert_eq("ˇhello.\n\n\nworld.");
1880
1881 cx.set_shared_state("hello. worlˇd.").await;
1882 cx.simulate_shared_keystrokes("(").await;
1883 cx.shared_state().await.assert_eq("hello. ˇworld.");
1884 cx.simulate_shared_keystrokes("(").await;
1885 cx.shared_state().await.assert_eq("ˇhello. world.");
1886
1887 cx.set_shared_state(". helˇlo.").await;
1888 cx.simulate_shared_keystrokes("(").await;
1889 cx.shared_state().await.assert_eq(". ˇhello.");
1890 cx.simulate_shared_keystrokes("(").await;
1891 cx.shared_state().await.assert_eq(". ˇhello.");
1892
1893 cx.set_shared_state(indoc! {
1894 "{
1895 hello_world();
1896 ˇ}"
1897 })
1898 .await;
1899 cx.simulate_shared_keystrokes("(").await;
1900 cx.shared_state().await.assert_eq(indoc! {
1901 "ˇ{
1902 hello_world();
1903 }"
1904 });
1905
1906 cx.set_shared_state(indoc! {
1907 "Hello! World..?
1908
1909 \tHello! World... ˇ"
1910 })
1911 .await;
1912 cx.simulate_shared_keystrokes("(").await;
1913 cx.shared_state().await.assert_eq(indoc! {
1914 "Hello! World..?
1915
1916 \tHello! ˇWorld... "
1917 });
1918 cx.simulate_shared_keystrokes("(").await;
1919 cx.shared_state().await.assert_eq(indoc! {
1920 "Hello! World..?
1921
1922 \tˇHello! World... "
1923 });
1924 cx.simulate_shared_keystrokes("(").await;
1925 cx.shared_state().await.assert_eq(indoc! {
1926 "Hello! World..?
1927 ˇ
1928 \tHello! World... "
1929 });
1930 cx.simulate_shared_keystrokes("(").await;
1931 cx.shared_state().await.assert_eq(indoc! {
1932 "Hello! ˇWorld..?
1933
1934 \tHello! World... "
1935 });
1936}
1937
1938#[perf]
1939#[gpui::test]
1940async fn test_sentence_forwards(cx: &mut gpui::TestAppContext) {
1941 let mut cx = NeovimBackedTestContext::new(cx).await;
1942
1943 cx.set_shared_state("helˇlo.\n\n\nworld.").await;
1944 cx.simulate_shared_keystrokes(")").await;
1945 cx.shared_state().await.assert_eq("hello.\nˇ\n\nworld.");
1946 cx.simulate_shared_keystrokes(")").await;
1947 cx.shared_state().await.assert_eq("hello.\n\n\nˇworld.");
1948 cx.simulate_shared_keystrokes(")").await;
1949 cx.shared_state().await.assert_eq("hello.\n\n\nworldˇ.");
1950
1951 cx.set_shared_state("helˇlo.\n\n\nworld.").await;
1952}
1953
1954#[perf]
1955#[gpui::test]
1956async fn test_ctrl_o_visual(cx: &mut gpui::TestAppContext) {
1957 let mut cx = NeovimBackedTestContext::new(cx).await;
1958
1959 cx.set_shared_state("helloˇ world.").await;
1960 cx.simulate_shared_keystrokes("i ctrl-o v b r l").await;
1961 cx.shared_state().await.assert_eq("ˇllllllworld.");
1962 cx.simulate_shared_keystrokes("ctrl-o v f w d").await;
1963 cx.shared_state().await.assert_eq("ˇorld.");
1964}
1965
1966#[perf]
1967#[gpui::test]
1968async fn test_ctrl_o_position(cx: &mut gpui::TestAppContext) {
1969 let mut cx = NeovimBackedTestContext::new(cx).await;
1970
1971 cx.set_shared_state("helˇlo world.").await;
1972 cx.simulate_shared_keystrokes("i ctrl-o d i w").await;
1973 cx.shared_state().await.assert_eq("ˇ world.");
1974 cx.simulate_shared_keystrokes("ctrl-o p").await;
1975 cx.shared_state().await.assert_eq(" helloˇworld.");
1976}
1977
1978#[perf]
1979#[gpui::test]
1980async fn test_ctrl_o_dot(cx: &mut gpui::TestAppContext) {
1981 let mut cx = NeovimBackedTestContext::new(cx).await;
1982
1983 cx.set_shared_state("heˇllo world.").await;
1984 cx.simulate_shared_keystrokes("x i ctrl-o .").await;
1985 cx.shared_state().await.assert_eq("heˇo world.");
1986 cx.simulate_shared_keystrokes("l l escape .").await;
1987 cx.shared_state().await.assert_eq("hellˇllo world.");
1988}
1989
1990#[perf(iterations = 1)]
1991#[gpui::test]
1992async fn test_folded_multibuffer_excerpts(cx: &mut gpui::TestAppContext) {
1993 VimTestContext::init(cx);
1994 cx.update(|cx| {
1995 VimTestContext::init_keybindings(true, cx);
1996 });
1997 let (editor, cx) = cx.add_window_view(|window, cx| {
1998 let multi_buffer = MultiBuffer::build_multi(
1999 [
2000 ("111\n222\n333\n444\n", vec![Point::row_range(0..2)]),
2001 ("aaa\nbbb\nccc\nddd\n", vec![Point::row_range(0..2)]),
2002 ("AAA\nBBB\nCCC\nDDD\n", vec![Point::row_range(0..2)]),
2003 ("one\ntwo\nthr\nfou\n", vec![Point::row_range(0..2)]),
2004 ],
2005 cx,
2006 );
2007 let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
2008
2009 let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
2010 // fold all but the second buffer, so that we test navigating between two
2011 // adjacent folded buffers, as well as folded buffers at the start and
2012 // end the multibuffer
2013 editor.fold_buffer(buffer_ids[0], cx);
2014 editor.fold_buffer(buffer_ids[2], cx);
2015 editor.fold_buffer(buffer_ids[3], cx);
2016
2017 editor
2018 });
2019 let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
2020
2021 cx.assert_excerpts_with_selections(indoc! {"
2022 [EXCERPT]
2023 ˇ[FOLDED]
2024 [EXCERPT]
2025 aaa
2026 bbb
2027 [EXCERPT]
2028 [FOLDED]
2029 [EXCERPT]
2030 [FOLDED]
2031 "
2032 });
2033 cx.simulate_keystroke("j");
2034 cx.assert_excerpts_with_selections(indoc! {"
2035 [EXCERPT]
2036 [FOLDED]
2037 [EXCERPT]
2038 ˇaaa
2039 bbb
2040 [EXCERPT]
2041 [FOLDED]
2042 [EXCERPT]
2043 [FOLDED]
2044 "
2045 });
2046 cx.simulate_keystroke("j");
2047 cx.simulate_keystroke("j");
2048 cx.assert_excerpts_with_selections(indoc! {"
2049 [EXCERPT]
2050 [FOLDED]
2051 [EXCERPT]
2052 aaa
2053 bbb
2054 ˇ[EXCERPT]
2055 [FOLDED]
2056 [EXCERPT]
2057 [FOLDED]
2058 "
2059 });
2060 cx.simulate_keystroke("j");
2061 cx.assert_excerpts_with_selections(indoc! {"
2062 [EXCERPT]
2063 [FOLDED]
2064 [EXCERPT]
2065 aaa
2066 bbb
2067 [EXCERPT]
2068 ˇ[FOLDED]
2069 [EXCERPT]
2070 [FOLDED]
2071 "
2072 });
2073 cx.simulate_keystroke("j");
2074 cx.assert_excerpts_with_selections(indoc! {"
2075 [EXCERPT]
2076 [FOLDED]
2077 [EXCERPT]
2078 aaa
2079 bbb
2080 [EXCERPT]
2081 [FOLDED]
2082 [EXCERPT]
2083 ˇ[FOLDED]
2084 "
2085 });
2086 cx.simulate_keystroke("k");
2087 cx.assert_excerpts_with_selections(indoc! {"
2088 [EXCERPT]
2089 [FOLDED]
2090 [EXCERPT]
2091 aaa
2092 bbb
2093 [EXCERPT]
2094 ˇ[FOLDED]
2095 [EXCERPT]
2096 [FOLDED]
2097 "
2098 });
2099 cx.simulate_keystroke("k");
2100 cx.simulate_keystroke("k");
2101 cx.simulate_keystroke("k");
2102 cx.assert_excerpts_with_selections(indoc! {"
2103 [EXCERPT]
2104 [FOLDED]
2105 [EXCERPT]
2106 ˇaaa
2107 bbb
2108 [EXCERPT]
2109 [FOLDED]
2110 [EXCERPT]
2111 [FOLDED]
2112 "
2113 });
2114 cx.simulate_keystroke("k");
2115 cx.assert_excerpts_with_selections(indoc! {"
2116 [EXCERPT]
2117 ˇ[FOLDED]
2118 [EXCERPT]
2119 aaa
2120 bbb
2121 [EXCERPT]
2122 [FOLDED]
2123 [EXCERPT]
2124 [FOLDED]
2125 "
2126 });
2127 cx.simulate_keystroke("shift-g");
2128 cx.assert_excerpts_with_selections(indoc! {"
2129 [EXCERPT]
2130 [FOLDED]
2131 [EXCERPT]
2132 aaa
2133 bbb
2134 [EXCERPT]
2135 [FOLDED]
2136 [EXCERPT]
2137 ˇ[FOLDED]
2138 "
2139 });
2140 cx.simulate_keystrokes("g g");
2141 cx.assert_excerpts_with_selections(indoc! {"
2142 [EXCERPT]
2143 ˇ[FOLDED]
2144 [EXCERPT]
2145 aaa
2146 bbb
2147 [EXCERPT]
2148 [FOLDED]
2149 [EXCERPT]
2150 [FOLDED]
2151 "
2152 });
2153 cx.update_editor(|editor, _, cx| {
2154 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
2155 editor.fold_buffer(buffer_ids[1], cx);
2156 });
2157
2158 cx.assert_excerpts_with_selections(indoc! {"
2159 [EXCERPT]
2160 ˇ[FOLDED]
2161 [EXCERPT]
2162 [FOLDED]
2163 [EXCERPT]
2164 [FOLDED]
2165 [EXCERPT]
2166 [FOLDED]
2167 "
2168 });
2169 cx.simulate_keystrokes("2 j");
2170 cx.assert_excerpts_with_selections(indoc! {"
2171 [EXCERPT]
2172 [FOLDED]
2173 [EXCERPT]
2174 [FOLDED]
2175 [EXCERPT]
2176 ˇ[FOLDED]
2177 [EXCERPT]
2178 [FOLDED]
2179 "
2180 });
2181}
2182
2183#[perf]
2184#[gpui::test]
2185async fn test_delete_paragraph_motion(cx: &mut gpui::TestAppContext) {
2186 let mut cx = NeovimBackedTestContext::new(cx).await;
2187 cx.set_shared_state(indoc! {
2188 "ˇhello world.
2189
2190 hello world.
2191 "
2192 })
2193 .await;
2194 cx.simulate_shared_keystrokes("y }").await;
2195 cx.shared_clipboard().await.assert_eq("hello world.\n");
2196 cx.simulate_shared_keystrokes("d }").await;
2197 cx.shared_state().await.assert_eq("ˇ\nhello world.\n");
2198 cx.shared_clipboard().await.assert_eq("hello world.\n");
2199
2200 cx.set_shared_state(indoc! {
2201 "helˇlo world.
2202
2203 hello world.
2204 "
2205 })
2206 .await;
2207 cx.simulate_shared_keystrokes("y }").await;
2208 cx.shared_clipboard().await.assert_eq("lo world.");
2209 cx.simulate_shared_keystrokes("d }").await;
2210 cx.shared_state().await.assert_eq("heˇl\n\nhello world.\n");
2211 cx.shared_clipboard().await.assert_eq("lo world.");
2212}
2213
2214#[perf]
2215#[gpui::test]
2216async fn test_delete_unmatched_brace(cx: &mut gpui::TestAppContext) {
2217 let mut cx = NeovimBackedTestContext::new(cx).await;
2218 cx.set_shared_state(indoc! {
2219 "fn o(wow: i32) {
2220 othˇ(wow)
2221 oth(wow)
2222 }
2223 "
2224 })
2225 .await;
2226 cx.simulate_shared_keystrokes("d ] }").await;
2227 cx.shared_state().await.assert_eq(indoc! {
2228 "fn o(wow: i32) {
2229 otˇh
2230 }
2231 "
2232 });
2233 cx.shared_clipboard().await.assert_eq("(wow)\n oth(wow)");
2234 cx.set_shared_state(indoc! {
2235 "fn o(wow: i32) {
2236 ˇoth(wow)
2237 oth(wow)
2238 }
2239 "
2240 })
2241 .await;
2242 cx.simulate_shared_keystrokes("d ] }").await;
2243 cx.shared_state().await.assert_eq(indoc! {
2244 "fn o(wow: i32) {
2245 ˇ}
2246 "
2247 });
2248 cx.shared_clipboard()
2249 .await
2250 .assert_eq(" oth(wow)\n oth(wow)\n");
2251}
2252
2253#[perf]
2254#[gpui::test]
2255async fn test_paragraph_multi_delete(cx: &mut gpui::TestAppContext) {
2256 let mut cx = NeovimBackedTestContext::new(cx).await;
2257 cx.set_shared_state(indoc! {
2258 "
2259 Emacs is
2260 ˇa great
2261
2262 operating system
2263
2264 all it lacks
2265 is a
2266
2267 decent text editor
2268 "
2269 })
2270 .await;
2271
2272 cx.simulate_shared_keystrokes("2 d a p").await;
2273 cx.shared_state().await.assert_eq(indoc! {
2274 "
2275 ˇall it lacks
2276 is a
2277
2278 decent text editor
2279 "
2280 });
2281
2282 cx.simulate_shared_keystrokes("d a p").await;
2283 cx.shared_clipboard()
2284 .await
2285 .assert_eq("all it lacks\nis a\n\n");
2286
2287 //reset to initial state
2288 cx.simulate_shared_keystrokes("2 u").await;
2289
2290 cx.simulate_shared_keystrokes("4 d a p").await;
2291 cx.shared_state().await.assert_eq(indoc! {"ˇ"});
2292}
2293
2294#[perf]
2295#[gpui::test]
2296async fn test_yank_paragraph_with_paste(cx: &mut gpui::TestAppContext) {
2297 let mut cx = NeovimBackedTestContext::new(cx).await;
2298 cx.set_shared_state(indoc! {
2299 "
2300 first paragraph
2301 ˇstill first
2302
2303 second paragraph
2304 still second
2305
2306 third paragraph
2307 "
2308 })
2309 .await;
2310
2311 cx.simulate_shared_keystrokes("y a p").await;
2312 cx.shared_clipboard()
2313 .await
2314 .assert_eq("first paragraph\nstill first\n\n");
2315
2316 cx.simulate_shared_keystrokes("j j p").await;
2317 cx.shared_state().await.assert_eq(indoc! {
2318 "
2319 first paragraph
2320 still first
2321
2322 ˇfirst paragraph
2323 still first
2324
2325 second paragraph
2326 still second
2327
2328 third paragraph
2329 "
2330 });
2331}
2332
2333#[perf]
2334#[gpui::test]
2335async fn test_change_paragraph(cx: &mut gpui::TestAppContext) {
2336 let mut cx = NeovimBackedTestContext::new(cx).await;
2337 cx.set_shared_state(indoc! {
2338 "
2339 first paragraph
2340 ˇstill first
2341
2342 second paragraph
2343 still second
2344
2345 third paragraph
2346 "
2347 })
2348 .await;
2349
2350 cx.simulate_shared_keystrokes("c a p").await;
2351 cx.shared_clipboard()
2352 .await
2353 .assert_eq("first paragraph\nstill first\n\n");
2354
2355 cx.simulate_shared_keystrokes("escape").await;
2356 cx.shared_state().await.assert_eq(indoc! {
2357 "
2358 ˇ
2359 second paragraph
2360 still second
2361
2362 third paragraph
2363 "
2364 });
2365}
2366
2367#[perf]
2368#[gpui::test]
2369async fn test_multi_cursor_replay(cx: &mut gpui::TestAppContext) {
2370 let mut cx = VimTestContext::new(cx, true).await;
2371 cx.set_state(
2372 indoc! {
2373 "
2374 oˇne one one
2375
2376 two two two
2377 "
2378 },
2379 Mode::Normal,
2380 );
2381
2382 cx.simulate_keystrokes("3 g l s wow escape escape");
2383 cx.assert_state(
2384 indoc! {
2385 "
2386 woˇw wow wow
2387
2388 two two two
2389 "
2390 },
2391 Mode::Normal,
2392 );
2393
2394 cx.simulate_keystrokes("2 j 3 g l .");
2395 cx.assert_state(
2396 indoc! {
2397 "
2398 wow wow wow
2399
2400 woˇw woˇw woˇw
2401 "
2402 },
2403 Mode::Normal,
2404 );
2405}
2406
2407#[gpui::test]
2408async fn test_clipping_on_mode_change(cx: &mut gpui::TestAppContext) {
2409 let mut cx = VimTestContext::new(cx, true).await;
2410
2411 cx.set_state(
2412 indoc! {
2413 "
2414 ˇverylongline
2415 andsomelinebelow
2416 "
2417 },
2418 Mode::Normal,
2419 );
2420
2421 cx.simulate_keystrokes("v e");
2422 cx.assert_state(
2423 indoc! {
2424 "
2425 «verylonglineˇ»
2426 andsomelinebelow
2427 "
2428 },
2429 Mode::Visual,
2430 );
2431
2432 let mut pixel_position = cx.update_editor(|editor, window, cx| {
2433 let snapshot = editor.snapshot(window, cx);
2434 let current_head = editor
2435 .selections
2436 .newest_display(&snapshot.display_snapshot)
2437 .end;
2438 editor.last_bounds().unwrap().origin
2439 + editor
2440 .display_to_pixel_point(current_head, &snapshot, window, cx)
2441 .unwrap()
2442 });
2443 pixel_position.x += px(100.);
2444 // click beyond end of the line
2445 cx.simulate_click(pixel_position, Modifiers::default());
2446 cx.run_until_parked();
2447
2448 cx.assert_state(
2449 indoc! {
2450 "
2451 verylonglinˇe
2452 andsomelinebelow
2453 "
2454 },
2455 Mode::Normal,
2456 );
2457}
2458
2459#[gpui::test]
2460async fn test_wrap_selections_in_tag_line_mode(cx: &mut gpui::TestAppContext) {
2461 let mut cx = VimTestContext::new(cx, true).await;
2462
2463 let js_language = Arc::new(Language::new(
2464 LanguageConfig {
2465 name: "JavaScript".into(),
2466 wrap_characters: Some(language::WrapCharactersConfig {
2467 start_prefix: "<".into(),
2468 start_suffix: ">".into(),
2469 end_prefix: "</".into(),
2470 end_suffix: ">".into(),
2471 }),
2472 ..LanguageConfig::default()
2473 },
2474 None,
2475 ));
2476
2477 cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
2478
2479 cx.set_state(
2480 indoc! {
2481 "
2482 ˇaaaaa
2483 bbbbb
2484 "
2485 },
2486 Mode::Normal,
2487 );
2488
2489 cx.simulate_keystrokes("shift-v j");
2490 cx.dispatch_action(WrapSelectionsInTag);
2491
2492 cx.assert_state(
2493 indoc! {
2494 "
2495 <ˇ>aaaaa
2496 bbbbb</ˇ>
2497 "
2498 },
2499 Mode::VisualLine,
2500 );
2501}
2502
2503#[gpui::test]
2504async fn test_repeat_grouping_41735(cx: &mut gpui::TestAppContext) {
2505 let mut cx = NeovimBackedTestContext::new(cx).await;
2506
2507 // typically transaction gropuing is disabled in tests, but here we need to test it.
2508 cx.update_buffer(|buffer, _cx| buffer.set_group_interval(Duration::from_millis(300)));
2509
2510 cx.set_shared_state("ˇ").await;
2511
2512 cx.simulate_shared_keystrokes("i a escape").await;
2513 cx.simulate_shared_keystrokes(". . .").await;
2514 cx.shared_state().await.assert_eq("ˇaaaa");
2515 cx.simulate_shared_keystrokes("u").await;
2516 cx.shared_state().await.assert_eq("ˇaaa");
2517}
2518
2519#[gpui::test]
2520async fn test_deactivate(cx: &mut gpui::TestAppContext) {
2521 let mut cx = VimTestContext::new(cx, true).await;
2522
2523 cx.update_global(|store: &mut SettingsStore, cx| {
2524 store.update_user_settings(cx, |settings| {
2525 settings.editor.cursor_shape = Some(settings::CursorShape::Underline);
2526 });
2527 });
2528
2529 // Assert that, while in `Normal` mode, the cursor shape is `Block` but,
2530 // after deactivating vim mode, it should revert to the one specified in the
2531 // user's settings, if set.
2532 cx.update_editor(|editor, _window, _cx| {
2533 assert_eq!(editor.cursor_shape(), CursorShape::Block);
2534 });
2535
2536 cx.disable_vim();
2537
2538 cx.update_editor(|editor, _window, _cx| {
2539 assert_eq!(editor.cursor_shape(), CursorShape::Underline);
2540 });
2541}
2542
2543#[gpui::test]
2544async fn test_project_search_opens_in_normal_mode(cx: &mut gpui::TestAppContext) {
2545 VimTestContext::init(cx);
2546
2547 let fs = FakeFs::new(cx.background_executor.clone());
2548 fs.insert_tree(
2549 path!("/dir"),
2550 json!({
2551 "file_a.rs": "// File A.",
2552 "file_b.rs": "// File B.",
2553 }),
2554 )
2555 .await;
2556
2557 let project = project::Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
2558 let workspace =
2559 cx.add_window(|window, cx| workspace::Workspace::test_new(project.clone(), window, cx));
2560
2561 cx.update(|cx| {
2562 VimTestContext::init_keybindings(true, cx);
2563 });
2564
2565 let cx = &mut VisualTestContext::from_window(*workspace, cx);
2566
2567 workspace
2568 .update(cx, |workspace, window, cx| {
2569 ProjectSearchView::deploy_search(workspace, &DeploySearch::default(), window, cx)
2570 })
2571 .unwrap();
2572
2573 let search_view = workspace
2574 .update(cx, |workspace, _, cx| {
2575 workspace
2576 .active_pane()
2577 .read(cx)
2578 .items()
2579 .find_map(|item| item.downcast::<ProjectSearchView>())
2580 .expect("Project search view should be active")
2581 })
2582 .unwrap();
2583
2584 project_search::perform_project_search(&search_view, "File A", cx);
2585
2586 search_view.update(cx, |search_view, cx| {
2587 let vim_mode = search_view
2588 .results_editor()
2589 .read(cx)
2590 .addon::<VimAddon>()
2591 .map(|addon| addon.entity.read(cx).mode);
2592
2593 assert_eq!(vim_mode, Some(Mode::Normal));
2594 });
2595}