1mod neovim_backed_binding_test_context;
2mod neovim_backed_test_context;
3mod neovim_connection;
4mod vim_test_context;
5
6use command_palette::CommandPalette;
7use editor::DisplayPoint;
8pub use neovim_backed_binding_test_context::*;
9pub use neovim_backed_test_context::*;
10pub use vim_test_context::*;
11
12use indoc::indoc;
13use search::BufferSearchBar;
14
15use crate::{state::Mode, ModeIndicator};
16
17#[gpui::test]
18async fn test_initially_disabled(cx: &mut gpui::TestAppContext) {
19 let mut cx = VimTestContext::new(cx, false).await;
20 cx.simulate_keystrokes(["h", "j", "k", "l"]);
21 cx.assert_editor_state("hjklˇ");
22}
23
24#[gpui::test]
25async fn test_neovim(cx: &mut gpui::TestAppContext) {
26 let mut cx = NeovimBackedTestContext::new(cx).await;
27
28 cx.simulate_shared_keystroke("i").await;
29 cx.assert_state_matches().await;
30 cx.simulate_shared_keystrokes([
31 "shift-T", "e", "s", "t", " ", "t", "e", "s", "t", "escape", "0", "d", "w",
32 ])
33 .await;
34 cx.assert_state_matches().await;
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_keystroke("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_keystroke("/");
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/community/issues/710)
147 cx.simulate_keystrokes(["1", "shift-g"]);
148 cx.assert_editor_state("aˇa\nbb\ncc");
149}
150
151#[gpui::test]
152async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
153 let mut cx = VimTestContext::new(cx, true).await;
154
155 // works in normal mode
156 cx.set_state(indoc! {"aa\nbˇb\ncc"}, Mode::Normal);
157 cx.simulate_keystrokes([">", ">"]);
158 cx.assert_editor_state("aa\n bˇb\ncc");
159 cx.simulate_keystrokes(["<", "<"]);
160 cx.assert_editor_state("aa\nbˇb\ncc");
161
162 // works in visuial mode
163 cx.simulate_keystrokes(["shift-v", "down", ">"]);
164 cx.assert_editor_state("aa\n b«b\n ccˇ»");
165}
166
167#[gpui::test]
168async fn test_escape_command_palette(cx: &mut gpui::TestAppContext) {
169 let mut cx = VimTestContext::new(cx, true).await;
170
171 cx.set_state("aˇbc\n", Mode::Normal);
172 cx.simulate_keystrokes(["i", "cmd-shift-p"]);
173
174 assert!(cx.workspace(|workspace, cx| workspace.active_modal::<CommandPalette>(cx).is_some()));
175 cx.simulate_keystroke("escape");
176 cx.run_until_parked();
177 assert!(!cx.workspace(|workspace, cx| workspace.active_modal::<CommandPalette>(cx).is_some()));
178 cx.assert_state("aˇbc\n", Mode::Insert);
179}
180
181#[gpui::test]
182async fn test_escape_cancels(cx: &mut gpui::TestAppContext) {
183 let mut cx = VimTestContext::new(cx, true).await;
184
185 cx.set_state("aˇbˇc", Mode::Normal);
186 cx.simulate_keystrokes(["escape"]);
187
188 cx.assert_state("aˇbc", Mode::Normal);
189}
190
191#[gpui::test]
192async fn test_selection_on_search(cx: &mut gpui::TestAppContext) {
193 let mut cx = VimTestContext::new(cx, true).await;
194
195 cx.set_state(indoc! {"aa\nbˇb\ncc\ncc\ncc\n"}, Mode::Normal);
196 cx.simulate_keystrokes(["/", "c", "c"]);
197
198 let search_bar = cx.workspace(|workspace, cx| {
199 workspace
200 .active_pane()
201 .read(cx)
202 .toolbar()
203 .read(cx)
204 .item_of_type::<BufferSearchBar>()
205 .expect("Buffer search bar should be deployed")
206 });
207
208 cx.update_view(search_bar, |bar, cx| {
209 assert_eq!(bar.query(cx), "cc");
210 });
211
212 cx.update_editor(|editor, cx| {
213 let highlights = editor.all_text_background_highlights(cx);
214 assert_eq!(3, highlights.len());
215 assert_eq!(
216 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 2),
217 highlights[0].0
218 )
219 });
220 cx.simulate_keystrokes(["enter"]);
221
222 cx.assert_state(indoc! {"aa\nbb\nˇcc\ncc\ncc\n"}, Mode::Normal);
223 cx.simulate_keystrokes(["n"]);
224 cx.assert_state(indoc! {"aa\nbb\ncc\nˇcc\ncc\n"}, Mode::Normal);
225 cx.simulate_keystrokes(["shift-n"]);
226 cx.assert_state(indoc! {"aa\nbb\nˇcc\ncc\ncc\n"}, Mode::Normal);
227}
228
229#[gpui::test]
230async fn test_status_indicator(cx: &mut gpui::TestAppContext) {
231 let mut cx = VimTestContext::new(cx, true).await;
232
233 let mode_indicator = cx.workspace(|workspace, cx| {
234 let status_bar = workspace.status_bar().read(cx);
235 let mode_indicator = status_bar.item_of_type::<ModeIndicator>();
236 assert!(mode_indicator.is_some());
237 mode_indicator.unwrap()
238 });
239
240 assert_eq!(
241 cx.workspace(|_, cx| mode_indicator.read(cx).mode),
242 Some(Mode::Normal)
243 );
244
245 // shows the correct mode
246 cx.simulate_keystrokes(["i"]);
247 assert_eq!(
248 cx.workspace(|_, cx| mode_indicator.read(cx).mode),
249 Some(Mode::Insert)
250 );
251
252 // shows even in search
253 cx.simulate_keystrokes(["escape", "v", "/"]);
254 assert_eq!(
255 cx.workspace(|_, cx| mode_indicator.read(cx).mode),
256 Some(Mode::Visual)
257 );
258
259 // hides if vim mode is disabled
260 cx.disable_vim();
261 cx.run_until_parked();
262 cx.workspace(|workspace, cx| {
263 let status_bar = workspace.status_bar().read(cx);
264 let mode_indicator = status_bar.item_of_type::<ModeIndicator>().unwrap();
265 assert!(mode_indicator.read(cx).mode.is_none());
266 });
267
268 cx.enable_vim();
269 cx.run_until_parked();
270 cx.workspace(|workspace, cx| {
271 let status_bar = workspace.status_bar().read(cx);
272 let mode_indicator = status_bar.item_of_type::<ModeIndicator>().unwrap();
273 assert!(mode_indicator.read(cx).mode.is_some());
274 });
275}
276
277#[gpui::test]
278async fn test_word_characters(cx: &mut gpui::TestAppContext) {
279 let mut cx = VimTestContext::new_typescript(cx).await;
280 cx.set_state(
281 indoc! { "
282 class A {
283 #ˇgoop = 99;
284 $ˇgoop () { return this.#gˇoop };
285 };
286 console.log(new A().$gooˇp())
287 "},
288 Mode::Normal,
289 );
290 cx.simulate_keystrokes(["v", "i", "w"]);
291 cx.assert_state(
292 indoc! {"
293 class A {
294 «#goopˇ» = 99;
295 «$goopˇ» () { return this.«#goopˇ» };
296 };
297 console.log(new A().«$goopˇ»())
298 "},
299 Mode::Visual,
300 )
301}
302
303#[gpui::test]
304async fn test_join_lines(cx: &mut gpui::TestAppContext) {
305 let mut cx = NeovimBackedTestContext::new(cx).await;
306
307 cx.set_shared_state(indoc! {"
308 ˇone
309 two
310 three
311 four
312 five
313 six
314 "})
315 .await;
316 cx.simulate_shared_keystrokes(["shift-j"]).await;
317 cx.assert_shared_state(indoc! {"
318 oneˇ two
319 three
320 four
321 five
322 six
323 "})
324 .await;
325 cx.simulate_shared_keystrokes(["3", "shift-j"]).await;
326 cx.assert_shared_state(indoc! {"
327 one two threeˇ four
328 five
329 six
330 "})
331 .await;
332
333 cx.set_shared_state(indoc! {"
334 ˇone
335 two
336 three
337 four
338 five
339 six
340 "})
341 .await;
342 cx.simulate_shared_keystrokes(["j", "v", "3", "j", "shift-j"])
343 .await;
344 cx.assert_shared_state(indoc! {"
345 one
346 two three fourˇ five
347 six
348 "})
349 .await;
350}
351
352#[gpui::test]
353async fn test_wrapped_lines(cx: &mut gpui::TestAppContext) {
354 let mut cx = NeovimBackedTestContext::new(cx).await;
355
356 cx.set_shared_wrap(12).await;
357 // tests line wrap as follows:
358 // 1: twelve char
359 // twelve char
360 // 2: twelve char
361 cx.set_shared_state(indoc! { "
362 tˇwelve char twelve char
363 twelve char
364 "})
365 .await;
366 cx.simulate_shared_keystrokes(["j"]).await;
367 cx.assert_shared_state(indoc! { "
368 twelve char twelve char
369 tˇwelve char
370 "})
371 .await;
372 cx.simulate_shared_keystrokes(["k"]).await;
373 cx.assert_shared_state(indoc! { "
374 tˇwelve char twelve char
375 twelve char
376 "})
377 .await;
378 cx.simulate_shared_keystrokes(["g", "j"]).await;
379 cx.assert_shared_state(indoc! { "
380 twelve char tˇwelve char
381 twelve char
382 "})
383 .await;
384 cx.simulate_shared_keystrokes(["g", "j"]).await;
385 cx.assert_shared_state(indoc! { "
386 twelve char twelve char
387 tˇwelve char
388 "})
389 .await;
390
391 cx.simulate_shared_keystrokes(["g", "k"]).await;
392 cx.assert_shared_state(indoc! { "
393 twelve char tˇwelve char
394 twelve char
395 "})
396 .await;
397
398 cx.simulate_shared_keystrokes(["g", "^"]).await;
399 cx.assert_shared_state(indoc! { "
400 twelve char ˇtwelve char
401 twelve char
402 "})
403 .await;
404
405 cx.simulate_shared_keystrokes(["^"]).await;
406 cx.assert_shared_state(indoc! { "
407 ˇtwelve char twelve char
408 twelve char
409 "})
410 .await;
411
412 cx.simulate_shared_keystrokes(["g", "$"]).await;
413 cx.assert_shared_state(indoc! { "
414 twelve charˇ twelve char
415 twelve char
416 "})
417 .await;
418 cx.simulate_shared_keystrokes(["$"]).await;
419 cx.assert_shared_state(indoc! { "
420 twelve char twelve chaˇr
421 twelve char
422 "})
423 .await;
424
425 cx.set_shared_state(indoc! { "
426 tˇwelve char twelve char
427 twelve char
428 "})
429 .await;
430 cx.simulate_shared_keystrokes(["enter"]).await;
431 cx.assert_shared_state(indoc! { "
432 twelve char twelve char
433 ˇtwelve char
434 "})
435 .await;
436
437 cx.set_shared_state(indoc! { "
438 twelve char
439 tˇwelve char twelve char
440 twelve char
441 "})
442 .await;
443 cx.simulate_shared_keystrokes(["o", "o", "escape"]).await;
444 cx.assert_shared_state(indoc! { "
445 twelve char
446 twelve char twelve char
447 ˇo
448 twelve char
449 "})
450 .await;
451
452 cx.set_shared_state(indoc! { "
453 twelve char
454 tˇwelve char twelve char
455 twelve char
456 "})
457 .await;
458 cx.simulate_shared_keystrokes(["shift-a", "a", "escape"])
459 .await;
460 cx.assert_shared_state(indoc! { "
461 twelve char
462 twelve char twelve charˇa
463 twelve char
464 "})
465 .await;
466 cx.simulate_shared_keystrokes(["shift-i", "i", "escape"])
467 .await;
468 cx.assert_shared_state(indoc! { "
469 twelve char
470 ˇitwelve char twelve chara
471 twelve char
472 "})
473 .await;
474 cx.simulate_shared_keystrokes(["shift-d"]).await;
475 cx.assert_shared_state(indoc! { "
476 twelve char
477 ˇ
478 twelve char
479 "})
480 .await;
481
482 cx.set_shared_state(indoc! { "
483 twelve char
484 twelve char tˇwelve char
485 twelve char
486 "})
487 .await;
488 cx.simulate_shared_keystrokes(["shift-o", "o", "escape"])
489 .await;
490 cx.assert_shared_state(indoc! { "
491 twelve char
492 ˇo
493 twelve char twelve char
494 twelve char
495 "})
496 .await;
497
498 // line wraps as:
499 // fourteen ch
500 // ar
501 // fourteen ch
502 // ar
503 cx.set_shared_state(indoc! { "
504 fourteen chaˇr
505 fourteen char
506 "})
507 .await;
508
509 cx.simulate_shared_keystrokes(["d", "i", "w"]).await;
510 cx.assert_shared_state(indoc! {"
511 fourteenˇ•
512 fourteen char
513 "})
514 .await;
515 cx.simulate_shared_keystrokes(["j", "shift-f", "e", "f", "r"])
516 .await;
517 cx.assert_shared_state(indoc! {"
518 fourteen•
519 fourteen chaˇr
520 "})
521 .await;
522}
523
524#[gpui::test]
525async fn test_folds(cx: &mut gpui::TestAppContext) {
526 let mut cx = NeovimBackedTestContext::new(cx).await;
527 cx.set_neovim_option("foldmethod=manual").await;
528
529 cx.set_shared_state(indoc! { "
530 fn boop() {
531 ˇbarp()
532 bazp()
533 }
534 "})
535 .await;
536 cx.simulate_shared_keystrokes(["shift-v", "j", "z", "f"])
537 .await;
538
539 // visual display is now:
540 // fn boop () {
541 // [FOLDED]
542 // }
543
544 // TODO: this should not be needed but currently zf does not
545 // return to normal mode.
546 cx.simulate_shared_keystrokes(["escape"]).await;
547
548 // skip over fold downward
549 cx.simulate_shared_keystrokes(["g", "g"]).await;
550 cx.assert_shared_state(indoc! { "
551 ˇfn boop() {
552 barp()
553 bazp()
554 }
555 "})
556 .await;
557
558 cx.simulate_shared_keystrokes(["j", "j"]).await;
559 cx.assert_shared_state(indoc! { "
560 fn boop() {
561 barp()
562 bazp()
563 ˇ}
564 "})
565 .await;
566
567 // skip over fold upward
568 cx.simulate_shared_keystrokes(["2", "k"]).await;
569 cx.assert_shared_state(indoc! { "
570 ˇfn boop() {
571 barp()
572 bazp()
573 }
574 "})
575 .await;
576
577 // yank the fold
578 cx.simulate_shared_keystrokes(["down", "y", "y"]).await;
579 cx.assert_shared_clipboard(" barp()\n bazp()\n").await;
580
581 // re-open
582 cx.simulate_shared_keystrokes(["z", "o"]).await;
583 cx.assert_shared_state(indoc! { "
584 fn boop() {
585 ˇ barp()
586 bazp()
587 }
588 "})
589 .await;
590}
591
592#[gpui::test]
593async fn test_folds_panic(cx: &mut gpui::TestAppContext) {
594 let mut cx = NeovimBackedTestContext::new(cx).await;
595 cx.set_neovim_option("foldmethod=manual").await;
596
597 cx.set_shared_state(indoc! { "
598 fn boop() {
599 ˇbarp()
600 bazp()
601 }
602 "})
603 .await;
604 cx.simulate_shared_keystrokes(["shift-v", "j", "z", "f"])
605 .await;
606 cx.simulate_shared_keystrokes(["escape"]).await;
607 cx.simulate_shared_keystrokes(["g", "g"]).await;
608 cx.simulate_shared_keystrokes(["5", "d", "j"]).await;
609 cx.assert_shared_state(indoc! { "ˇ"}).await;
610}
611
612#[gpui::test]
613async fn test_clear_counts(cx: &mut gpui::TestAppContext) {
614 let mut cx = NeovimBackedTestContext::new(cx).await;
615
616 cx.set_shared_state(indoc! {"
617 The quick brown
618 fox juˇmps over
619 the lazy dog"})
620 .await;
621
622 cx.simulate_shared_keystrokes(["4", "escape", "3", "d", "l"])
623 .await;
624 cx.assert_shared_state(indoc! {"
625 The quick brown
626 fox juˇ over
627 the lazy dog"})
628 .await;
629}
630
631#[gpui::test]
632async fn test_zero(cx: &mut gpui::TestAppContext) {
633 let mut cx = NeovimBackedTestContext::new(cx).await;
634
635 cx.set_shared_state(indoc! {"
636 The quˇick brown
637 fox jumps over
638 the lazy dog"})
639 .await;
640
641 cx.simulate_shared_keystrokes(["0"]).await;
642 cx.assert_shared_state(indoc! {"
643 ˇThe quick brown
644 fox jumps over
645 the lazy dog"})
646 .await;
647
648 cx.simulate_shared_keystrokes(["1", "0", "l"]).await;
649 cx.assert_shared_state(indoc! {"
650 The quick ˇbrown
651 fox jumps over
652 the lazy dog"})
653 .await;
654}
655
656#[gpui::test]
657async fn test_selection_goal(cx: &mut gpui::TestAppContext) {
658 let mut cx = NeovimBackedTestContext::new(cx).await;
659
660 cx.set_shared_state(indoc! {"
661 ;;ˇ;
662 Lorem Ipsum"})
663 .await;
664
665 cx.simulate_shared_keystrokes(["a", "down", "up", ";", "down", "up"])
666 .await;
667 cx.assert_shared_state(indoc! {"
668 ;;;;ˇ
669 Lorem Ipsum"})
670 .await;
671}
672
673#[gpui::test]
674async fn test_wrapped_motions(cx: &mut gpui::TestAppContext) {
675 let mut cx = NeovimBackedTestContext::new(cx).await;
676
677 cx.set_shared_wrap(12).await;
678
679 cx.set_shared_state(indoc! {"
680 aaˇaa
681 😃😃"
682 })
683 .await;
684 cx.simulate_shared_keystrokes(["j"]).await;
685 cx.assert_shared_state(indoc! {"
686 aaaa
687 😃ˇ😃"
688 })
689 .await;
690
691 cx.set_shared_state(indoc! {"
692 123456789012aaˇaa
693 123456789012😃😃"
694 })
695 .await;
696 cx.simulate_shared_keystrokes(["j"]).await;
697 cx.assert_shared_state(indoc! {"
698 123456789012aaaa
699 123456789012😃ˇ😃"
700 })
701 .await;
702
703 cx.set_shared_state(indoc! {"
704 123456789012aaˇaa
705 123456789012😃😃"
706 })
707 .await;
708 cx.simulate_shared_keystrokes(["j"]).await;
709 cx.assert_shared_state(indoc! {"
710 123456789012aaaa
711 123456789012😃ˇ😃"
712 })
713 .await;
714
715 cx.set_shared_state(indoc! {"
716 123456789012aaaaˇaaaaaaaa123456789012
717 wow
718 123456789012😃😃😃😃😃😃123456789012"
719 })
720 .await;
721 cx.simulate_shared_keystrokes(["j", "j"]).await;
722 cx.assert_shared_state(indoc! {"
723 123456789012aaaaaaaaaaaa123456789012
724 wow
725 123456789012😃😃ˇ😃😃😃😃123456789012"
726 })
727 .await;
728}
729
730#[gpui::test]
731async fn test_paragraphs_dont_wrap(cx: &mut gpui::TestAppContext) {
732 let mut cx = NeovimBackedTestContext::new(cx).await;
733
734 cx.set_shared_state(indoc! {"
735 one
736 ˇ
737 two"})
738 .await;
739
740 cx.simulate_shared_keystrokes(["}", "}"]).await;
741 cx.assert_shared_state(indoc! {"
742 one
743
744 twˇo"})
745 .await;
746
747 cx.simulate_shared_keystrokes(["{", "{", "{"]).await;
748 cx.assert_shared_state(indoc! {"
749 ˇone
750
751 two"})
752 .await;
753}
754
755#[gpui::test]
756async fn test_select_all_issue_2170(cx: &mut gpui::TestAppContext) {
757 let mut cx = VimTestContext::new(cx, true).await;
758
759 cx.set_state(
760 indoc! {"
761 defmodule Test do
762 def test(a, ˇ[_, _] = b), do: IO.puts('hi')
763 end
764 "},
765 Mode::Normal,
766 );
767 cx.simulate_keystrokes(["g", "a"]);
768 cx.assert_state(
769 indoc! {"
770 defmodule Test do
771 def test(a, «[ˇ»_, _] = b), do: IO.puts('hi')
772 end
773 "},
774 Mode::Visual,
775 );
776}