Skip to repository content3205 lines · 90.2 KB · rust
tenant.openagents/omega
No repository description is available.
OpenAgents Git authority 2026-07-28T04:31:53.532Z Public web read
NIP-34 coordinate
30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omegaMaintainersHidden in public view
References2 branches · 1 tag
Read-only clone
git clone https://openagents.com/git/tenant.openagents/omega.gitBrowse files
test.rs
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::{CommandAliasTarget, 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, MultiWorkspace};
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_vim_linked_edits_delete_x(app_cx: &mut gpui::TestAppContext) {
99 let mut cx = VimTestContext::new_html(app_cx).await;
100
101 cx.set_state("<diˇv></div>", Mode::Normal);
102 cx.update_editor(|editor, _window, cx| {
103 editor
104 .set_linked_edit_ranges_for_testing(
105 vec![(
106 Point::new(0, 1)..Point::new(0, 4),
107 vec![Point::new(0, 7)..Point::new(0, 10)],
108 )],
109 cx,
110 )
111 .expect("linked edit ranges should be set");
112 });
113
114 cx.simulate_keystrokes("x");
115 cx.assert_editor_state("<diˇ></di>");
116}
117
118#[perf]
119#[gpui::test]
120async fn test_vim_linked_edits_change_iw(app_cx: &mut gpui::TestAppContext) {
121 let mut cx = VimTestContext::new_html(app_cx).await;
122
123 cx.set_state("<diˇv></div>", Mode::Normal);
124 cx.update_editor(|editor, _window, cx| {
125 editor
126 .set_linked_edit_ranges_for_testing(
127 vec![(
128 Point::new(0, 1)..Point::new(0, 4),
129 vec![Point::new(0, 7)..Point::new(0, 10)],
130 )],
131 cx,
132 )
133 .expect("linked edit ranges should be set");
134 });
135
136 cx.simulate_keystrokes("c i w s p a n escape");
137 cx.assert_editor_state("<spaˇn></span>");
138}
139
140#[perf]
141#[gpui::test]
142async fn test_vim_linked_edits_substitute_s(app_cx: &mut gpui::TestAppContext) {
143 let mut cx = VimTestContext::new_html(app_cx).await;
144
145 cx.set_state("<diˇv></div>", Mode::Normal);
146 cx.update_editor(|editor, _window, cx| {
147 editor
148 .set_linked_edit_ranges_for_testing(
149 vec![(
150 Point::new(0, 1)..Point::new(0, 4),
151 vec![Point::new(0, 7)..Point::new(0, 10)],
152 )],
153 cx,
154 )
155 .expect("linked edit ranges should be set");
156 });
157
158 cx.simulate_keystrokes("s s p a n escape");
159 cx.assert_editor_state("<dispaˇn></dispan>");
160}
161
162#[perf]
163#[gpui::test]
164async fn test_vim_linked_edits_visual_change(app_cx: &mut gpui::TestAppContext) {
165 let mut cx = VimTestContext::new_html(app_cx).await;
166
167 cx.set_state("<diˇv></div>", Mode::Normal);
168 cx.update_editor(|editor, _window, cx| {
169 editor
170 .set_linked_edit_ranges_for_testing(
171 vec![(
172 Point::new(0, 1)..Point::new(0, 4),
173 vec![Point::new(0, 7)..Point::new(0, 10)],
174 )],
175 cx,
176 )
177 .expect("linked edit ranges should be set");
178 });
179
180 // Visual change routes through substitute; visual `s` shares this path.
181 cx.simulate_keystrokes("v i w c s p a n escape");
182 cx.assert_editor_state("<spaˇn></span>");
183}
184
185#[perf]
186#[gpui::test]
187async fn test_vim_linked_edits_visual_substitute_s(app_cx: &mut gpui::TestAppContext) {
188 let mut cx = VimTestContext::new_html(app_cx).await;
189
190 cx.set_state("<diˇv></div>", Mode::Normal);
191 cx.update_editor(|editor, _window, cx| {
192 editor
193 .set_linked_edit_ranges_for_testing(
194 vec![(
195 Point::new(0, 1)..Point::new(0, 4),
196 vec![Point::new(0, 7)..Point::new(0, 10)],
197 )],
198 cx,
199 )
200 .expect("linked edit ranges should be set");
201 });
202
203 cx.simulate_keystrokes("v i w s s p a n escape");
204 cx.assert_editor_state("<spaˇn></span>");
205}
206
207#[perf]
208#[gpui::test]
209async fn test_cancel_selection(cx: &mut gpui::TestAppContext) {
210 let mut cx = VimTestContext::new(cx, true).await;
211
212 cx.set_state(
213 indoc! {"The quick brown fox juˇmps over the lazy dog"},
214 Mode::Normal,
215 );
216 // jumps
217 cx.simulate_keystrokes("v l l");
218 cx.assert_editor_state("The quick brown fox ju«mpsˇ» over the lazy dog");
219
220 cx.simulate_keystrokes("escape");
221 cx.assert_editor_state("The quick brown fox jumpˇs over the lazy dog");
222
223 // go back to the same selection state
224 cx.simulate_keystrokes("v h h");
225 cx.assert_editor_state("The quick brown fox ju«ˇmps» over the lazy dog");
226
227 // Ctrl-[ should behave like Esc
228 cx.simulate_keystrokes("ctrl-[");
229 cx.assert_editor_state("The quick brown fox juˇmps over the lazy dog");
230}
231
232#[perf]
233#[gpui::test]
234async fn test_buffer_search(cx: &mut gpui::TestAppContext) {
235 let mut cx = VimTestContext::new(cx, true).await;
236
237 cx.set_state(
238 indoc! {"
239 The quick brown
240 fox juˇmps over
241 the lazy dog"},
242 Mode::Normal,
243 );
244 cx.simulate_keystrokes("/");
245
246 let search_bar = cx.workspace(|workspace, _, cx| {
247 workspace
248 .active_pane()
249 .read(cx)
250 .toolbar()
251 .read(cx)
252 .item_of_type::<BufferSearchBar>()
253 .expect("Buffer search bar should be deployed")
254 });
255
256 cx.update_entity(search_bar, |bar, _, cx| {
257 assert_eq!(bar.query(cx), "");
258 })
259}
260
261#[perf]
262#[gpui::test]
263async fn test_count_down(cx: &mut gpui::TestAppContext) {
264 let mut cx = VimTestContext::new(cx, true).await;
265
266 cx.set_state(indoc! {"aˇa\nbb\ncc\ndd\nee"}, Mode::Normal);
267 cx.simulate_keystrokes("2 down");
268 cx.assert_editor_state("aa\nbb\ncˇc\ndd\nee");
269 cx.simulate_keystrokes("9 down");
270 cx.assert_editor_state("aa\nbb\ncc\ndd\neˇe");
271}
272
273#[perf]
274#[gpui::test]
275async fn test_end_of_document_710(cx: &mut gpui::TestAppContext) {
276 let mut cx = VimTestContext::new(cx, true).await;
277
278 // goes to end by default
279 cx.set_state(indoc! {"aˇa\nbb\ncc"}, Mode::Normal);
280 cx.simulate_keystrokes("shift-g");
281 cx.assert_editor_state("aa\nbb\ncˇc");
282
283 // can go to line 1 (https://github.com/zed-industries/zed/issues/5812)
284 cx.simulate_keystrokes("1 shift-g");
285 cx.assert_editor_state("aˇa\nbb\ncc");
286}
287
288#[perf]
289#[gpui::test]
290async fn test_end_of_line_with_times(cx: &mut gpui::TestAppContext) {
291 let mut cx = VimTestContext::new(cx, true).await;
292
293 // goes to current line end
294 cx.set_state(indoc! {"ˇaa\nbb\ncc"}, Mode::Normal);
295 cx.simulate_keystrokes("$");
296 cx.assert_editor_state("aˇa\nbb\ncc");
297
298 // goes to next line end
299 cx.simulate_keystrokes("2 $");
300 cx.assert_editor_state("aa\nbˇb\ncc");
301
302 // try to exceed the final line.
303 cx.simulate_keystrokes("4 $");
304 cx.assert_editor_state("aa\nbb\ncˇc");
305}
306
307#[perf]
308#[gpui::test]
309async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
310 let mut cx = VimTestContext::new(cx, true).await;
311
312 // works in normal mode
313 cx.set_state(indoc! {"aa\nbˇb\ncc"}, Mode::Normal);
314 cx.simulate_keystrokes("> >");
315 cx.assert_editor_state("aa\n bˇb\ncc");
316 cx.simulate_keystrokes("< <");
317 cx.assert_editor_state("aa\nbˇb\ncc");
318
319 // works in visual mode
320 cx.simulate_keystrokes("shift-v down >");
321 cx.assert_editor_state("aa\n bˇb\n cc");
322
323 // works as operator
324 cx.set_state("aa\nbˇb\ncc\n", Mode::Normal);
325 cx.simulate_keystrokes("> j");
326 cx.assert_editor_state("aa\n bˇb\n cc\n");
327 cx.simulate_keystrokes("< k");
328 cx.assert_editor_state("aa\nbˇb\n cc\n");
329 cx.simulate_keystrokes("> i p");
330 cx.assert_editor_state(" aa\n bˇb\n cc\n");
331 cx.simulate_keystrokes("< i p");
332 cx.assert_editor_state("aa\nbˇb\n cc\n");
333 cx.simulate_keystrokes("< i p");
334 cx.assert_editor_state("aa\nbˇb\ncc\n");
335
336 cx.set_state("ˇaa\nbb\ncc\n", Mode::Normal);
337 cx.simulate_keystrokes("> 2 j");
338 cx.assert_editor_state(" ˇaa\n bb\n cc\n");
339
340 cx.set_state("aa\nbb\nˇcc\n", Mode::Normal);
341 cx.simulate_keystrokes("> 2 k");
342 cx.assert_editor_state(" aa\n bb\n ˇcc\n");
343
344 // works with repeat
345 cx.set_state("a\nb\nccˇc\n", Mode::Normal);
346 cx.simulate_keystrokes("> 2 k");
347 cx.assert_editor_state(" a\n b\n ccˇc\n");
348 cx.simulate_keystrokes(".");
349 cx.assert_editor_state(" a\n b\n ccˇc\n");
350 cx.simulate_keystrokes("v k <");
351 cx.assert_editor_state(" a\n bˇ\n ccc\n");
352 cx.simulate_keystrokes(".");
353 cx.assert_editor_state(" a\nbˇ\nccc\n");
354}
355
356#[perf]
357#[gpui::test]
358async fn test_escape_command_palette(cx: &mut gpui::TestAppContext) {
359 let mut cx = VimTestContext::new(cx, true).await;
360
361 cx.set_state("aˇbc\n", Mode::Normal);
362 cx.simulate_keystrokes("i cmd-shift-p");
363
364 assert!(
365 cx.workspace(|workspace, _, cx| workspace.active_modal::<CommandPalette>(cx).is_some())
366 );
367 cx.simulate_keystrokes("escape");
368 cx.run_until_parked();
369 assert!(
370 !cx.workspace(|workspace, _, cx| workspace.active_modal::<CommandPalette>(cx).is_some())
371 );
372 cx.assert_state("aˇbc\n", Mode::Insert);
373}
374
375#[perf]
376#[gpui::test]
377async fn test_escape_cancels(cx: &mut gpui::TestAppContext) {
378 let mut cx = VimTestContext::new(cx, true).await;
379
380 cx.set_state("aˇbˇc", Mode::Normal);
381 cx.simulate_keystrokes("escape");
382
383 cx.assert_state("aˇbc", Mode::Normal);
384}
385
386#[perf]
387#[gpui::test]
388async fn test_selection_on_search(cx: &mut gpui::TestAppContext) {
389 let mut cx = VimTestContext::new(cx, true).await;
390
391 cx.set_state(indoc! {"aa\nbˇb\ncc\ncc\ncc\n"}, Mode::Normal);
392 cx.simulate_keystrokes("/ c c");
393
394 let search_bar = cx.workspace(|workspace, _, cx| {
395 workspace
396 .active_pane()
397 .read(cx)
398 .toolbar()
399 .read(cx)
400 .item_of_type::<BufferSearchBar>()
401 .expect("Buffer search bar should be deployed")
402 });
403
404 cx.update_entity(search_bar, |bar, _, cx| {
405 assert_eq!(bar.query(cx), "cc");
406 });
407
408 cx.update_editor(|editor, window, cx| {
409 let highlights = editor.all_text_background_highlights(window, cx);
410 assert_eq!(3, highlights.len());
411 assert_eq!(
412 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 2),
413 highlights[0].0
414 )
415 });
416 cx.simulate_keystrokes("enter");
417
418 cx.assert_state(indoc! {"aa\nbb\nˇcc\ncc\ncc\n"}, Mode::Normal);
419 cx.simulate_keystrokes("n");
420 cx.assert_state(indoc! {"aa\nbb\ncc\nˇcc\ncc\n"}, Mode::Normal);
421 cx.simulate_keystrokes("shift-n");
422 cx.assert_state(indoc! {"aa\nbb\nˇcc\ncc\ncc\n"}, Mode::Normal);
423}
424
425#[perf]
426#[gpui::test]
427async fn test_word_characters(cx: &mut gpui::TestAppContext) {
428 let mut cx = VimTestContext::new_typescript(cx).await;
429 cx.set_state(
430 indoc! { "
431 class A {
432 #ˇgoop = 99;
433 $ˇgoop () { return this.#gˇoop };
434 };
435 console.log(new A().$gooˇp())
436 "},
437 Mode::Normal,
438 );
439 cx.simulate_keystrokes("v i w");
440 cx.assert_state(
441 indoc! {"
442 class A {
443 «#goopˇ» = 99;
444 «$goopˇ» () { return this.«#goopˇ» };
445 };
446 console.log(new A().«$goopˇ»())
447 "},
448 Mode::Visual,
449 )
450}
451
452#[perf]
453#[gpui::test]
454async fn test_kebab_case(cx: &mut gpui::TestAppContext) {
455 let mut cx = VimTestContext::new_html(cx).await;
456 cx.set_state(
457 indoc! { r#"
458 <div><a class="bg-rˇed"></a></div>
459 "#},
460 Mode::Normal,
461 );
462 cx.simulate_keystrokes("v i w");
463 cx.assert_state(
464 indoc! { r#"
465 <div><a class="bg-«redˇ»"></a></div>
466 "#
467 },
468 Mode::Visual,
469 )
470}
471
472#[perf]
473#[gpui::test]
474async fn test_join_lines(cx: &mut gpui::TestAppContext) {
475 let mut cx = NeovimBackedTestContext::new(cx).await;
476
477 cx.set_shared_state(indoc! {"
478 ˇone
479 two
480 three
481 four
482 five
483 six
484 "})
485 .await;
486 cx.simulate_shared_keystrokes("shift-j").await;
487 cx.shared_state().await.assert_eq(indoc! {"
488 oneˇ two
489 three
490 four
491 five
492 six
493 "});
494 cx.simulate_shared_keystrokes("3 shift-j").await;
495 cx.shared_state().await.assert_eq(indoc! {"
496 one two threeˇ four
497 five
498 six
499 "});
500
501 cx.set_shared_state(indoc! {"
502 ˇone
503 two
504 three
505 four
506 five
507 six
508 "})
509 .await;
510 cx.simulate_shared_keystrokes("j v 3 j shift-j").await;
511 cx.shared_state().await.assert_eq(indoc! {"
512 one
513 two three fourˇ five
514 six
515 "});
516
517 cx.set_shared_state(indoc! {"
518 ˇone
519 two
520 three
521 four
522 five
523 six
524 "})
525 .await;
526 cx.simulate_shared_keystrokes("g shift-j").await;
527 cx.shared_state().await.assert_eq(indoc! {"
528 oneˇtwo
529 three
530 four
531 five
532 six
533 "});
534 cx.simulate_shared_keystrokes("3 g shift-j").await;
535 cx.shared_state().await.assert_eq(indoc! {"
536 onetwothreeˇfour
537 five
538 six
539 "});
540
541 cx.set_shared_state(indoc! {"
542 ˇone
543 two
544 three
545 four
546 five
547 six
548 "})
549 .await;
550 cx.simulate_shared_keystrokes("j v 3 j g shift-j").await;
551 cx.shared_state().await.assert_eq(indoc! {"
552 one
553 twothreefourˇfive
554 six
555 "});
556}
557
558#[cfg(target_os = "macos")]
559#[perf]
560#[gpui::test]
561async fn test_wrapped_lines(cx: &mut gpui::TestAppContext) {
562 let mut cx = NeovimBackedTestContext::new(cx).await;
563
564 cx.set_shared_wrap(12).await;
565 // tests line wrap as follows:
566 // 1: twelve char
567 // twelve char
568 // 2: twelve char
569 cx.set_shared_state(indoc! { "
570 tˇwelve char twelve char
571 twelve char
572 "})
573 .await;
574 cx.simulate_shared_keystrokes("j").await;
575 cx.shared_state().await.assert_eq(indoc! {"
576 twelve char twelve char
577 tˇwelve char
578 "});
579 cx.simulate_shared_keystrokes("k").await;
580 cx.shared_state().await.assert_eq(indoc! {"
581 tˇwelve char twelve char
582 twelve char
583 "});
584 cx.simulate_shared_keystrokes("g j").await;
585 cx.shared_state().await.assert_eq(indoc! {"
586 twelve char tˇwelve char
587 twelve char
588 "});
589 cx.simulate_shared_keystrokes("g j").await;
590 cx.shared_state().await.assert_eq(indoc! {"
591 twelve char twelve char
592 tˇwelve char
593 "});
594
595 cx.simulate_shared_keystrokes("g k").await;
596 cx.shared_state().await.assert_eq(indoc! {"
597 twelve char tˇwelve char
598 twelve char
599 "});
600
601 cx.simulate_shared_keystrokes("g ^").await;
602 cx.shared_state().await.assert_eq(indoc! {"
603 twelve char ˇtwelve char
604 twelve char
605 "});
606
607 cx.simulate_shared_keystrokes("^").await;
608 cx.shared_state().await.assert_eq(indoc! {"
609 ˇtwelve char twelve char
610 twelve char
611 "});
612
613 cx.simulate_shared_keystrokes("g $").await;
614 cx.shared_state().await.assert_eq(indoc! {"
615 twelve charˇ twelve char
616 twelve char
617 "});
618 cx.simulate_shared_keystrokes("$").await;
619 cx.shared_state().await.assert_eq(indoc! {"
620 twelve char twelve chaˇr
621 twelve char
622 "});
623
624 cx.set_shared_state(indoc! { "
625 tˇwelve char twelve char
626 twelve char
627 "})
628 .await;
629 cx.simulate_shared_keystrokes("enter").await;
630 cx.shared_state().await.assert_eq(indoc! {"
631 twelve char twelve char
632 ˇtwelve char
633 "});
634
635 cx.set_shared_state(indoc! { "
636 twelve char
637 tˇwelve char twelve char
638 twelve char
639 "})
640 .await;
641 cx.simulate_shared_keystrokes("o o escape").await;
642 cx.shared_state().await.assert_eq(indoc! {"
643 twelve char
644 twelve char twelve char
645 ˇo
646 twelve char
647 "});
648
649 cx.set_shared_state(indoc! { "
650 twelve char
651 tˇwelve char twelve char
652 twelve char
653 "})
654 .await;
655 cx.simulate_shared_keystrokes("shift-a a escape").await;
656 cx.shared_state().await.assert_eq(indoc! {"
657 twelve char
658 twelve char twelve charˇa
659 twelve char
660 "});
661 cx.simulate_shared_keystrokes("shift-i i escape").await;
662 cx.shared_state().await.assert_eq(indoc! {"
663 twelve char
664 ˇitwelve char twelve chara
665 twelve char
666 "});
667 cx.simulate_shared_keystrokes("shift-d").await;
668 cx.shared_state().await.assert_eq(indoc! {"
669 twelve char
670 ˇ
671 twelve char
672 "});
673
674 cx.set_shared_state(indoc! { "
675 twelve char
676 twelve char tˇwelve char
677 twelve char
678 "})
679 .await;
680 cx.simulate_shared_keystrokes("shift-o o escape").await;
681 cx.shared_state().await.assert_eq(indoc! {"
682 twelve char
683 ˇo
684 twelve char twelve char
685 twelve char
686 "});
687
688 // line wraps as:
689 // fourteen ch
690 // ar
691 // fourteen ch
692 // ar
693 cx.set_shared_state(indoc! { "
694 fourteen chaˇr
695 fourteen char
696 "})
697 .await;
698
699 cx.simulate_shared_keystrokes("d i w").await;
700 cx.shared_state().await.assert_eq(indoc! {"
701 fourteenˇ•
702 fourteen char
703 "});
704 cx.simulate_shared_keystrokes("j shift-f e f r").await;
705 cx.shared_state().await.assert_eq(indoc! {"
706 fourteen•
707 fourteen chaˇr
708 "});
709}
710
711#[perf]
712#[gpui::test]
713async fn test_folds(cx: &mut gpui::TestAppContext) {
714 let mut cx = NeovimBackedTestContext::new(cx).await;
715 cx.set_neovim_option("foldmethod=manual").await;
716
717 cx.set_shared_state(indoc! { "
718 fn boop() {
719 ˇbarp()
720 bazp()
721 }
722 "})
723 .await;
724 cx.simulate_shared_keystrokes("shift-v j z f").await;
725
726 // visual display is now:
727 // fn boop () {
728 // [FOLDED]
729 // }
730
731 // TODO: this should not be needed but currently zf does not
732 // return to normal mode.
733 cx.simulate_shared_keystrokes("escape").await;
734
735 // skip over fold downward
736 cx.simulate_shared_keystrokes("g g").await;
737 cx.shared_state().await.assert_eq(indoc! {"
738 ˇfn boop() {
739 barp()
740 bazp()
741 }
742 "});
743
744 cx.simulate_shared_keystrokes("j j").await;
745 cx.shared_state().await.assert_eq(indoc! {"
746 fn boop() {
747 barp()
748 bazp()
749 ˇ}
750 "});
751
752 // skip over fold upward
753 cx.simulate_shared_keystrokes("2 k").await;
754 cx.shared_state().await.assert_eq(indoc! {"
755 ˇfn boop() {
756 barp()
757 bazp()
758 }
759 "});
760
761 // yank the fold
762 cx.simulate_shared_keystrokes("down y y").await;
763 cx.shared_clipboard()
764 .await
765 .assert_eq(" barp()\n bazp()\n");
766
767 // re-open
768 cx.simulate_shared_keystrokes("z o").await;
769 cx.shared_state().await.assert_eq(indoc! {"
770 fn boop() {
771 ˇ barp()
772 bazp()
773 }
774 "});
775}
776
777#[perf]
778#[gpui::test]
779async fn test_folds_panic(cx: &mut gpui::TestAppContext) {
780 let mut cx = NeovimBackedTestContext::new(cx).await;
781 cx.set_neovim_option("foldmethod=manual").await;
782
783 cx.set_shared_state(indoc! { "
784 fn boop() {
785 ˇbarp()
786 bazp()
787 }
788 "})
789 .await;
790 cx.simulate_shared_keystrokes("shift-v j z f").await;
791 cx.simulate_shared_keystrokes("escape").await;
792 cx.simulate_shared_keystrokes("g g").await;
793 cx.simulate_shared_keystrokes("5 d j").await;
794 cx.shared_state().await.assert_eq("ˇ");
795 cx.set_shared_state(indoc! {"
796 fn boop() {
797 ˇbarp()
798 bazp()
799 }
800 "})
801 .await;
802 cx.simulate_shared_keystrokes("shift-v j j z f").await;
803 cx.simulate_shared_keystrokes("escape").await;
804 cx.simulate_shared_keystrokes("shift-g shift-v").await;
805 cx.shared_state().await.assert_eq(indoc! {"
806 fn boop() {
807 barp()
808 bazp()
809 }
810 ˇ"});
811}
812
813#[perf]
814#[gpui::test]
815async fn test_clear_counts(cx: &mut gpui::TestAppContext) {
816 let mut cx = NeovimBackedTestContext::new(cx).await;
817
818 cx.set_shared_state(indoc! {"
819 The quick brown
820 fox juˇmps over
821 the lazy dog"})
822 .await;
823
824 cx.simulate_shared_keystrokes("4 escape 3 d l").await;
825 cx.shared_state().await.assert_eq(indoc! {"
826 The quick brown
827 fox juˇ over
828 the lazy dog"});
829}
830
831#[perf]
832#[gpui::test]
833async fn test_zero(cx: &mut gpui::TestAppContext) {
834 let mut cx = NeovimBackedTestContext::new(cx).await;
835
836 cx.set_shared_state(indoc! {"
837 The quˇick brown
838 fox jumps over
839 the lazy dog"})
840 .await;
841
842 cx.simulate_shared_keystrokes("0").await;
843 cx.shared_state().await.assert_eq(indoc! {"
844 ˇThe quick brown
845 fox jumps over
846 the lazy dog"});
847
848 cx.simulate_shared_keystrokes("1 0 l").await;
849 cx.shared_state().await.assert_eq(indoc! {"
850 The quick ˇbrown
851 fox jumps over
852 the lazy dog"});
853}
854
855#[perf]
856#[gpui::test]
857async fn test_selection_goal(cx: &mut gpui::TestAppContext) {
858 let mut cx = NeovimBackedTestContext::new(cx).await;
859
860 cx.set_shared_state(indoc! {"
861 ;;ˇ;
862 Lorem Ipsum"})
863 .await;
864
865 cx.simulate_shared_keystrokes("a down up ; down up").await;
866 cx.shared_state().await.assert_eq(indoc! {"
867 ;;;;ˇ
868 Lorem Ipsum"});
869}
870
871#[cfg(target_os = "macos")]
872#[perf]
873#[gpui::test]
874async fn test_wrapped_motions(cx: &mut gpui::TestAppContext) {
875 let mut cx = NeovimBackedTestContext::new(cx).await;
876
877 cx.set_shared_wrap(12).await;
878
879 cx.set_shared_state(indoc! {"
880 aaˇaa
881 😃😃"
882 })
883 .await;
884 cx.simulate_shared_keystrokes("j").await;
885 cx.shared_state().await.assert_eq(indoc! {"
886 aaaa
887 😃ˇ😃"
888 });
889
890 cx.set_shared_state(indoc! {"
891 123456789012aaˇaa
892 123456789012😃😃"
893 })
894 .await;
895 cx.simulate_shared_keystrokes("j").await;
896 cx.shared_state().await.assert_eq(indoc! {"
897 123456789012aaaa
898 123456789012😃ˇ😃"
899 });
900
901 cx.set_shared_state(indoc! {"
902 123456789012aaˇaa
903 123456789012😃😃"
904 })
905 .await;
906 cx.simulate_shared_keystrokes("j").await;
907 cx.shared_state().await.assert_eq(indoc! {"
908 123456789012aaaa
909 123456789012😃ˇ😃"
910 });
911
912 cx.set_shared_state(indoc! {"
913 123456789012aaaaˇaaaaaaaa123456789012
914 wow
915 123456789012😃😃😃😃😃😃123456789012"
916 })
917 .await;
918 cx.simulate_shared_keystrokes("j j").await;
919 cx.shared_state().await.assert_eq(indoc! {"
920 123456789012aaaaaaaaaaaa123456789012
921 wow
922 123456789012😃😃ˇ😃😃😃😃123456789012"
923 });
924}
925
926#[perf]
927#[gpui::test]
928async fn test_wrapped_delete_end_document(cx: &mut gpui::TestAppContext) {
929 let mut cx = NeovimBackedTestContext::new(cx).await;
930
931 cx.set_shared_wrap(12).await;
932
933 cx.set_shared_state(indoc! {"
934 aaˇaaaaaaaaaaaaaaaaaa
935 bbbbbbbbbbbbbbbbbbbb
936 cccccccccccccccccccc"
937 })
938 .await;
939 cx.simulate_shared_keystrokes("d shift-g i z z z").await;
940 cx.shared_state().await.assert_eq(indoc! {"
941 zzzˇ"
942 });
943}
944
945#[perf]
946#[gpui::test]
947async fn test_paragraphs_dont_wrap(cx: &mut gpui::TestAppContext) {
948 let mut cx = NeovimBackedTestContext::new(cx).await;
949
950 cx.set_shared_state(indoc! {"
951 one
952 ˇ
953 two"})
954 .await;
955
956 cx.simulate_shared_keystrokes("} }").await;
957 cx.shared_state().await.assert_eq(indoc! {"
958 one
959
960 twˇo"});
961
962 cx.simulate_shared_keystrokes("{ { {").await;
963 cx.shared_state().await.assert_eq(indoc! {"
964 ˇone
965
966 two"});
967}
968
969#[perf]
970#[gpui::test]
971async fn test_select_all_issue_2170(cx: &mut gpui::TestAppContext) {
972 let mut cx = VimTestContext::new(cx, true).await;
973
974 cx.set_state(
975 indoc! {"
976 defmodule Test do
977 def test(a, ˇ[_, _] = b), do: IO.puts('hi')
978 end
979 "},
980 Mode::Normal,
981 );
982 cx.simulate_keystrokes("g a");
983 cx.assert_state(
984 indoc! {"
985 defmodule Test do
986 def test(a, «[ˇ»_, _] = b), do: IO.puts('hi')
987 end
988 "},
989 Mode::Visual,
990 );
991}
992
993#[perf]
994#[gpui::test]
995async fn test_jk(cx: &mut gpui::TestAppContext) {
996 let mut cx = NeovimBackedTestContext::new(cx).await;
997
998 cx.update(|_, cx| {
999 cx.bind_keys([KeyBinding::new(
1000 "j k",
1001 NormalBefore,
1002 Some("vim_mode == insert"),
1003 )])
1004 });
1005 cx.neovim.exec("imap jk <esc>").await;
1006
1007 cx.set_shared_state("ˇhello").await;
1008 cx.simulate_shared_keystrokes("i j o j k").await;
1009 cx.shared_state().await.assert_eq("jˇohello");
1010}
1011
1012fn assert_pending_input(cx: &mut VimTestContext, expected: &str) {
1013 cx.update_editor(|editor, window, cx| {
1014 let snapshot = editor.snapshot(window, cx);
1015 let highlights = editor
1016 .text_highlights(editor::HighlightKey::PendingInput, cx)
1017 .unwrap()
1018 .1;
1019 let (_, ranges) = marked_text_ranges(expected, false);
1020
1021 assert_eq!(
1022 highlights
1023 .iter()
1024 .map(|highlight| highlight.to_offset(&snapshot.buffer_snapshot()))
1025 .collect::<Vec<_>>(),
1026 ranges
1027 .iter()
1028 .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
1029 .collect::<Vec<_>>()
1030 )
1031 });
1032}
1033
1034#[perf]
1035#[gpui::test]
1036async fn test_jk_multi(cx: &mut gpui::TestAppContext) {
1037 let mut cx = VimTestContext::new(cx, true).await;
1038
1039 cx.update(|_, cx| {
1040 cx.bind_keys([KeyBinding::new(
1041 "j k l",
1042 NormalBefore,
1043 Some("vim_mode == insert"),
1044 )])
1045 });
1046
1047 cx.set_state("ˇone ˇone ˇone", Mode::Normal);
1048 cx.simulate_keystrokes("i j");
1049 cx.simulate_keystrokes("k");
1050 cx.assert_state("ˇjkone ˇjkone ˇjkone", Mode::Insert);
1051 assert_pending_input(&mut cx, "«jk»one «jk»one «jk»one");
1052 cx.simulate_keystrokes("o j k");
1053 cx.assert_state("jkoˇjkone jkoˇjkone jkoˇjkone", Mode::Insert);
1054 assert_pending_input(&mut cx, "jko«jk»one jko«jk»one jko«jk»one");
1055 cx.simulate_keystrokes("l");
1056 cx.assert_state("jkˇoone jkˇoone jkˇoone", Mode::Normal);
1057}
1058
1059#[perf]
1060#[gpui::test]
1061async fn test_jk_delay(cx: &mut gpui::TestAppContext) {
1062 let mut cx = VimTestContext::new(cx, true).await;
1063
1064 cx.update(|_, cx| {
1065 cx.bind_keys([KeyBinding::new(
1066 "j k",
1067 NormalBefore,
1068 Some("vim_mode == insert"),
1069 )])
1070 });
1071
1072 cx.set_state("ˇhello", Mode::Normal);
1073 cx.simulate_keystrokes("i j");
1074 cx.executor().advance_clock(Duration::from_millis(500));
1075 cx.run_until_parked();
1076 cx.assert_state("ˇjhello", Mode::Insert);
1077 cx.update_editor(|editor, window, cx| {
1078 let snapshot = editor.snapshot(window, cx);
1079 let highlights = editor
1080 .text_highlights(editor::HighlightKey::PendingInput, cx)
1081 .unwrap()
1082 .1;
1083
1084 assert_eq!(
1085 highlights
1086 .iter()
1087 .map(|highlight| highlight.to_offset(&snapshot.buffer_snapshot()))
1088 .collect::<Vec<_>>(),
1089 vec![MultiBufferOffset(0)..MultiBufferOffset(1)]
1090 )
1091 });
1092 cx.executor().advance_clock(Duration::from_millis(500));
1093 cx.run_until_parked();
1094 cx.assert_state("jˇhello", Mode::Insert);
1095 cx.simulate_keystrokes("k j k");
1096 cx.assert_state("jˇkhello", Mode::Normal);
1097}
1098
1099#[perf]
1100#[gpui::test]
1101async fn test_jk_max_count(cx: &mut gpui::TestAppContext) {
1102 let mut cx = NeovimBackedTestContext::new(cx).await;
1103
1104 cx.set_shared_state("1\nˇ2\n3").await;
1105 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")
1106 .await;
1107 cx.shared_state().await.assert_eq("1\n2\nˇ3");
1108
1109 let number: String = usize::MAX.to_string().split("").join(" ");
1110 cx.simulate_shared_keystrokes(&format!("{number} k")).await;
1111 cx.shared_state().await.assert_eq("ˇ1\n2\n3");
1112}
1113
1114#[perf]
1115#[gpui::test]
1116async fn test_comma_w(cx: &mut gpui::TestAppContext) {
1117 let mut cx = NeovimBackedTestContext::new(cx).await;
1118
1119 cx.update(|_, cx| {
1120 cx.bind_keys([KeyBinding::new(
1121 ", w",
1122 motion::Down {
1123 display_lines: false,
1124 },
1125 Some("vim_mode == normal"),
1126 )])
1127 });
1128 cx.neovim.exec("map ,w j").await;
1129
1130 cx.set_shared_state("ˇhello hello\nhello hello").await;
1131 cx.simulate_shared_keystrokes("f o ; , w").await;
1132 cx.shared_state()
1133 .await
1134 .assert_eq("hello hello\nhello hellˇo");
1135
1136 cx.set_shared_state("ˇhello hello\nhello hello").await;
1137 cx.simulate_shared_keystrokes("f o ; , i").await;
1138 cx.shared_state()
1139 .await
1140 .assert_eq("hellˇo hello\nhello hello");
1141}
1142
1143#[perf]
1144#[gpui::test]
1145async fn test_completion_menu_scroll_aside(cx: &mut TestAppContext) {
1146 let mut cx = VimTestContext::new_typescript(cx).await;
1147
1148 cx.lsp
1149 .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
1150 Ok(Some(lsp::CompletionResponse::Array(vec![
1151 lsp::CompletionItem {
1152 label: "Test Item".to_string(),
1153 documentation: Some(lsp::Documentation::String(
1154 "This is some very long documentation content that will be displayed in the aside panel for scrolling.\n".repeat(50)
1155 )),
1156 ..Default::default()
1157 },
1158 ])))
1159 });
1160
1161 cx.set_state("variableˇ", Mode::Insert);
1162 cx.simulate_keystroke(".");
1163 cx.executor().run_until_parked();
1164
1165 let mut initial_offset: Pixels = px(0.0);
1166
1167 cx.update_editor(|editor, _, _| {
1168 let binding = editor.context_menu().borrow();
1169 let Some(CodeContextMenu::Completions(menu)) = binding.as_ref() else {
1170 panic!("Should have completions menu open");
1171 };
1172
1173 initial_offset = menu.scroll_handle_aside.offset().y;
1174 });
1175
1176 // The `ctrl-e` shortcut should scroll the completion menu's aside content
1177 // down, so the updated offset should be lower than the initial offset.
1178 cx.simulate_keystroke("ctrl-e");
1179 cx.update_editor(|editor, _, _| {
1180 let binding = editor.context_menu().borrow();
1181 let Some(CodeContextMenu::Completions(menu)) = binding.as_ref() else {
1182 panic!("Should have completions menu open");
1183 };
1184
1185 assert!(menu.scroll_handle_aside.offset().y < initial_offset);
1186 });
1187
1188 // The `ctrl-y` shortcut should do the inverse scrolling as `ctrl-e`, so the
1189 // offset should now be the same as the initial offset.
1190 cx.simulate_keystroke("ctrl-y");
1191 cx.update_editor(|editor, _, _| {
1192 let binding = editor.context_menu().borrow();
1193 let Some(CodeContextMenu::Completions(menu)) = binding.as_ref() else {
1194 panic!("Should have completions menu open");
1195 };
1196
1197 assert_eq!(menu.scroll_handle_aside.offset().y, initial_offset);
1198 });
1199
1200 // The `ctrl-d` shortcut should scroll the completion menu's aside content
1201 // down, so the updated offset should be lower than the initial offset.
1202 cx.simulate_keystroke("ctrl-d");
1203 cx.update_editor(|editor, _, _| {
1204 let binding = editor.context_menu().borrow();
1205 let Some(CodeContextMenu::Completions(menu)) = binding.as_ref() else {
1206 panic!("Should have completions menu open");
1207 };
1208
1209 assert!(menu.scroll_handle_aside.offset().y < initial_offset);
1210 });
1211
1212 // The `ctrl-u` shortcut should do the inverse scrolling as `ctrl-u`, so the
1213 // offset should now be the same as the initial offset.
1214 cx.simulate_keystroke("ctrl-u");
1215 cx.update_editor(|editor, _, _| {
1216 let binding = editor.context_menu().borrow();
1217 let Some(CodeContextMenu::Completions(menu)) = binding.as_ref() else {
1218 panic!("Should have completions menu open");
1219 };
1220
1221 assert_eq!(menu.scroll_handle_aside.offset().y, initial_offset);
1222 });
1223}
1224
1225#[perf]
1226#[gpui::test]
1227async fn test_rename(cx: &mut gpui::TestAppContext) {
1228 let mut cx = VimTestContext::new_typescript(cx).await;
1229
1230 cx.set_state("const beˇfore = 2; console.log(before)", Mode::Normal);
1231 let def_range = cx.lsp_range("const «beforeˇ» = 2; console.log(before)");
1232 let tgt_range = cx.lsp_range("const before = 2; console.log(«beforeˇ»)");
1233 let mut prepare_request = cx.set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
1234 move |_, _, _| async move { Ok(Some(lsp::PrepareRenameResponse::Range(def_range))) },
1235 );
1236 let mut rename_request =
1237 cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, params, _| async move {
1238 Ok(Some(lsp::WorkspaceEdit {
1239 changes: Some(
1240 [(
1241 url.clone(),
1242 vec![
1243 lsp::TextEdit::new(def_range, params.new_name.clone()),
1244 lsp::TextEdit::new(tgt_range, params.new_name),
1245 ],
1246 )]
1247 .into(),
1248 ),
1249 ..Default::default()
1250 }))
1251 });
1252
1253 cx.simulate_keystrokes("c d");
1254 prepare_request.next().await.unwrap();
1255 cx.simulate_input("after");
1256 cx.simulate_keystrokes("enter");
1257 rename_request.next().await.unwrap();
1258 cx.assert_state("const afterˇ = 2; console.log(after)", Mode::Normal)
1259}
1260
1261#[gpui::test]
1262async fn test_visual_rename_uses_visible_cursor_position(cx: &mut gpui::TestAppContext) {
1263 let mut cx = VimTestContext::new_typescript(cx).await;
1264
1265 cx.set_state("const before = 2; console.log(«beforeˇ»)", Mode::Visual);
1266
1267 let expected_position = cx.to_lsp(MultiBufferOffset(
1268 "const before = 2; console.log(befor".len(),
1269 ));
1270 let def_range = cx.lsp_range("const «beforeˇ» = 2; console.log(before)");
1271 let tgt_range = cx.lsp_range("const before = 2; console.log(«beforeˇ»)");
1272 let mut prepare_request = cx.set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
1273 move |_, params, _| async move {
1274 assert_eq!(params.position, expected_position);
1275 Ok(Some(lsp::PrepareRenameResponse::Range(tgt_range)))
1276 },
1277 );
1278 let mut rename_request =
1279 cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, params, _| async move {
1280 Ok(Some(lsp::WorkspaceEdit {
1281 changes: Some(
1282 [(
1283 url.clone(),
1284 vec![
1285 lsp::TextEdit::new(def_range, params.new_name.clone()),
1286 lsp::TextEdit::new(tgt_range, params.new_name),
1287 ],
1288 )]
1289 .into(),
1290 ),
1291 ..Default::default()
1292 }))
1293 });
1294
1295 cx.simulate_keystrokes("g r n");
1296 prepare_request.next().await.unwrap();
1297 cx.simulate_input("after");
1298 cx.simulate_keystrokes("enter");
1299 rename_request.next().await.unwrap();
1300
1301 cx.assert_state("const after = 2; console.log(afterˇ)", Mode::Visual);
1302}
1303
1304#[gpui::test]
1305async fn test_go_to_definition(cx: &mut gpui::TestAppContext) {
1306 let mut cx = VimTestContext::new_typescript(cx).await;
1307
1308 cx.set_state("const before = 2; console.log(beforˇe)", Mode::Normal);
1309 let def_range = cx.lsp_range("const «beforeˇ» = 2; console.log(before)");
1310 let mut go_to_request =
1311 cx.set_request_handler::<lsp::request::GotoDefinition, _, _>(move |url, _, _| async move {
1312 Ok(Some(lsp::GotoDefinitionResponse::Scalar(
1313 lsp::Location::new(url.clone(), def_range),
1314 )))
1315 });
1316
1317 cx.simulate_keystrokes("g d");
1318 go_to_request.next().await.unwrap();
1319 cx.run_until_parked();
1320
1321 cx.assert_state("const ˇbefore = 2; console.log(before)", Mode::Normal);
1322}
1323
1324#[perf]
1325#[gpui::test]
1326async fn test_remap(cx: &mut gpui::TestAppContext) {
1327 let mut cx = VimTestContext::new(cx, true).await;
1328
1329 // test moving the cursor
1330 cx.update(|_, cx| {
1331 cx.bind_keys([KeyBinding::new(
1332 "g z",
1333 workspace::SendKeystrokes("l l l l".to_string()),
1334 None,
1335 )])
1336 });
1337 cx.set_state("ˇ123456789", Mode::Normal);
1338 cx.simulate_keystrokes("g z");
1339 cx.assert_state("1234ˇ56789", Mode::Normal);
1340
1341 // test switching modes
1342 cx.update(|_, cx| {
1343 cx.bind_keys([KeyBinding::new(
1344 "g y",
1345 workspace::SendKeystrokes("i f o o escape l".to_string()),
1346 None,
1347 )])
1348 });
1349 cx.set_state("ˇ123456789", Mode::Normal);
1350 cx.simulate_keystrokes("g y");
1351 cx.assert_state("fooˇ123456789", Mode::Normal);
1352
1353 // test recursion
1354 cx.update(|_, cx| {
1355 cx.bind_keys([KeyBinding::new(
1356 "g x",
1357 workspace::SendKeystrokes("g z g y".to_string()),
1358 None,
1359 )])
1360 });
1361 cx.set_state("ˇ123456789", Mode::Normal);
1362 cx.simulate_keystrokes("g x");
1363 cx.assert_state("1234fooˇ56789", Mode::Normal);
1364
1365 // test command
1366 cx.update(|_, cx| {
1367 cx.bind_keys([KeyBinding::new(
1368 "g w",
1369 workspace::SendKeystrokes(": j enter".to_string()),
1370 None,
1371 )])
1372 });
1373 cx.set_state("ˇ1234\n56789", Mode::Normal);
1374 cx.simulate_keystrokes("g w");
1375 cx.assert_state("1234ˇ 56789", Mode::Normal);
1376
1377 // test leaving command
1378 cx.update(|_, cx| {
1379 cx.bind_keys([KeyBinding::new(
1380 "g u",
1381 workspace::SendKeystrokes("g w g z".to_string()),
1382 None,
1383 )])
1384 });
1385 cx.set_state("ˇ1234\n56789", Mode::Normal);
1386 cx.simulate_keystrokes("g u");
1387 cx.assert_state("1234 567ˇ89", Mode::Normal);
1388
1389 // test leaving command
1390 cx.update(|_, cx| {
1391 cx.bind_keys([KeyBinding::new(
1392 "g t",
1393 workspace::SendKeystrokes("i space escape".to_string()),
1394 None,
1395 )])
1396 });
1397 cx.set_state("12ˇ34", Mode::Normal);
1398 cx.simulate_keystrokes("g t");
1399 cx.assert_state("12ˇ 34", Mode::Normal);
1400}
1401
1402#[perf]
1403#[gpui::test]
1404async fn test_undo(cx: &mut gpui::TestAppContext) {
1405 let mut cx = NeovimBackedTestContext::new(cx).await;
1406
1407 cx.set_shared_state("hello quˇoel world").await;
1408 cx.simulate_shared_keystrokes("v i w s c o escape u").await;
1409 cx.shared_state().await.assert_eq("hello ˇquoel world");
1410 cx.simulate_shared_keystrokes("ctrl-r").await;
1411 cx.shared_state().await.assert_eq("hello ˇco world");
1412 cx.simulate_shared_keystrokes("a o right l escape").await;
1413 cx.shared_state().await.assert_eq("hello cooˇl world");
1414 cx.simulate_shared_keystrokes("u").await;
1415 cx.shared_state().await.assert_eq("hello cooˇ world");
1416 cx.simulate_shared_keystrokes("u").await;
1417 cx.shared_state().await.assert_eq("hello cˇo world");
1418 cx.simulate_shared_keystrokes("u").await;
1419 cx.shared_state().await.assert_eq("hello ˇquoel world");
1420
1421 cx.set_shared_state("hello quˇoel world").await;
1422 cx.simulate_shared_keystrokes("v i w ~ u").await;
1423 cx.shared_state().await.assert_eq("hello ˇquoel world");
1424
1425 cx.set_shared_state("\nhello quˇoel world\n").await;
1426 cx.simulate_shared_keystrokes("shift-v s c escape u").await;
1427 cx.shared_state().await.assert_eq("\nˇhello quoel world\n");
1428
1429 cx.set_shared_state(indoc! {"
1430 ˇ1
1431 2
1432 3"})
1433 .await;
1434
1435 cx.simulate_shared_keystrokes("ctrl-v shift-g ctrl-a").await;
1436 cx.shared_state().await.assert_eq(indoc! {"
1437 ˇ2
1438 3
1439 4"});
1440
1441 cx.simulate_shared_keystrokes("u").await;
1442 cx.shared_state().await.assert_eq(indoc! {"
1443 ˇ1
1444 2
1445 3"});
1446}
1447
1448#[perf]
1449#[gpui::test]
1450async fn test_lsp_completions_undo(cx: &mut gpui::TestAppContext) {
1451 use editor::test::editor_lsp_test_context::EditorLspTestContext;
1452 VimTestContext::init(cx);
1453 let mut cx = VimTestContext::new_with_lsp(
1454 EditorLspTestContext::new_rust(
1455 lsp::ServerCapabilities {
1456 completion_provider: Some(lsp::CompletionOptions {
1457 trigger_characters: Some(vec![".".to_string()]),
1458 resolve_provider: Some(true),
1459 ..Default::default()
1460 }),
1461 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
1462 ..Default::default()
1463 },
1464 cx,
1465 )
1466 .await,
1467 true,
1468 );
1469
1470 cx.set_state("fn main() { let a = ˇ }", Mode::Normal);
1471 cx.simulate_keystroke("i");
1472 cx.set_state("fn main() { let a = ˇ }", Mode::Insert);
1473
1474 cx.simulate_keystroke(";");
1475 cx.assert_state("fn main() { let a = ;ˇ }", Mode::Insert);
1476
1477 cx.simulate_keystroke("escape");
1478 cx.assert_state("fn main() { let a = ˇ; }", Mode::Normal);
1479
1480 cx.simulate_keystroke("i");
1481 cx.simulate_keystroke("2");
1482 cx.assert_state("fn main() { let a = 2ˇ; }", Mode::Insert);
1483 cx.simulate_keystroke(".");
1484
1485 let completion_item = lsp::CompletionItem {
1486 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
1487 range: lsp::Range {
1488 start: lsp::Position {
1489 line: 0,
1490 character: 22,
1491 },
1492 end: lsp::Position {
1493 line: 0,
1494 character: 22,
1495 },
1496 },
1497 new_text: "completion".to_string(),
1498 })),
1499 additional_text_edits: None,
1500 ..Default::default()
1501 };
1502
1503 let closure_completion_item = completion_item.clone();
1504 let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
1505 let task_completion_item = closure_completion_item.clone();
1506 async move {
1507 Ok(Some(lsp::CompletionResponse::Array(vec![
1508 task_completion_item,
1509 ])))
1510 }
1511 });
1512
1513 request.next().await;
1514
1515 cx.condition(|editor, _| editor.context_menu_visible())
1516 .await;
1517
1518 let _ = cx.update_editor(|editor, window, cx| {
1519 editor
1520 .confirm_completion(&editor::actions::ConfirmCompletion::default(), window, cx)
1521 .unwrap()
1522 });
1523
1524 cx.assert_editor_state("fn main() { let a = 2.completionˇ; }");
1525
1526 cx.simulate_keystrokes("escape u");
1527
1528 cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
1529}
1530
1531#[perf]
1532#[gpui::test]
1533async fn test_lsp_completions_with_additional_edits_undo(cx: &mut gpui::TestAppContext) {
1534 use editor::test::editor_lsp_test_context::EditorLspTestContext;
1535 VimTestContext::init(cx);
1536 let mut cx = VimTestContext::new_with_lsp(
1537 EditorLspTestContext::new_rust(
1538 lsp::ServerCapabilities {
1539 completion_provider: Some(lsp::CompletionOptions {
1540 trigger_characters: Some(vec![".".to_string()]),
1541 resolve_provider: Some(true),
1542 ..Default::default()
1543 }),
1544 ..Default::default()
1545 },
1546 cx,
1547 )
1548 .await,
1549 true,
1550 );
1551
1552 cx.set_state("fn main() { let a = ˇ }", Mode::Normal);
1553 cx.simulate_keystroke("i");
1554 cx.set_state("fn main() { let a = ˇ }", Mode::Insert);
1555
1556 cx.simulate_keystroke("2");
1557 cx.simulate_keystroke(";");
1558 cx.assert_state("fn main() { let a = 2;ˇ }", Mode::Insert);
1559
1560 cx.simulate_keystroke("escape");
1561 cx.assert_state("fn main() { let a = 2ˇ; }", Mode::Normal);
1562
1563 cx.simulate_keystroke("i");
1564 cx.assert_state("fn main() { let a = 2ˇ; }", Mode::Insert);
1565
1566 cx.simulate_keystroke(".");
1567 let completion_item = lsp::CompletionItem {
1568 label: "some".into(),
1569 kind: Some(lsp::CompletionItemKind::SNIPPET),
1570 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
1571 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
1572 kind: lsp::MarkupKind::Markdown,
1573 value: "```rust\nSome(2)\n```".to_string(),
1574 })),
1575 deprecated: Some(false),
1576 sort_text: Some("fffffff2".to_string()),
1577 filter_text: Some("some".to_string()),
1578 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
1579 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
1580 range: lsp::Range {
1581 start: lsp::Position {
1582 line: 0,
1583 character: 22,
1584 },
1585 end: lsp::Position {
1586 line: 0,
1587 character: 22,
1588 },
1589 },
1590 new_text: "Some(2)".to_string(),
1591 })),
1592 additional_text_edits: Some(vec![lsp::TextEdit {
1593 range: lsp::Range {
1594 start: lsp::Position {
1595 line: 0,
1596 character: 20,
1597 },
1598 end: lsp::Position {
1599 line: 0,
1600 character: 22,
1601 },
1602 },
1603 new_text: "".to_string(),
1604 }]),
1605 ..Default::default()
1606 };
1607
1608 let closure_completion_item = completion_item.clone();
1609 let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
1610 let task_completion_item = closure_completion_item.clone();
1611 async move {
1612 Ok(Some(lsp::CompletionResponse::Array(vec![
1613 task_completion_item,
1614 ])))
1615 }
1616 });
1617
1618 request.next().await;
1619
1620 cx.condition(|editor, _| editor.context_menu_visible())
1621 .await;
1622
1623 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
1624 editor
1625 .confirm_completion(&editor::actions::ConfirmCompletion::default(), window, cx)
1626 .unwrap()
1627 });
1628 cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
1629
1630 cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
1631 let task_completion_item = completion_item.clone();
1632 async move { Ok(task_completion_item) }
1633 })
1634 .next()
1635 .await
1636 .unwrap();
1637
1638 apply_additional_edits.await.unwrap();
1639 cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
1640
1641 cx.simulate_keystrokes("escape u");
1642
1643 cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
1644}
1645
1646#[perf]
1647#[gpui::test]
1648async fn test_mouse_selection(cx: &mut TestAppContext) {
1649 let mut cx = VimTestContext::new(cx, true).await;
1650
1651 cx.set_state("ˇone two three", Mode::Normal);
1652
1653 let start_point = cx.pixel_position("one twˇo three");
1654 let end_point = cx.pixel_position("one ˇtwo three");
1655
1656 cx.simulate_mouse_down(start_point, MouseButton::Left, Modifiers::none());
1657 cx.simulate_mouse_move(end_point, MouseButton::Left, Modifiers::none());
1658 cx.simulate_mouse_up(end_point, MouseButton::Left, Modifiers::none());
1659
1660 cx.assert_state("one «ˇtwo» three", Mode::Visual)
1661}
1662
1663#[gpui::test]
1664async fn test_mouse_drag_across_anchor_does_not_drift(cx: &mut TestAppContext) {
1665 let mut cx = VimTestContext::new(cx, true).await;
1666
1667 cx.set_state("ˇone two three four", Mode::Normal);
1668
1669 let click_pos = cx.pixel_position("one ˇtwo three four");
1670 let drag_left = cx.pixel_position("ˇone two three four");
1671 let anchor_pos = cx.pixel_position("one tˇwo three four");
1672
1673 cx.simulate_mouse_down(click_pos, MouseButton::Left, Modifiers::none());
1674 cx.run_until_parked();
1675
1676 cx.simulate_mouse_move(drag_left, MouseButton::Left, Modifiers::none());
1677 cx.run_until_parked();
1678 cx.assert_state("«ˇone t»wo three four", Mode::Visual);
1679
1680 cx.simulate_mouse_move(anchor_pos, MouseButton::Left, Modifiers::none());
1681 cx.run_until_parked();
1682
1683 cx.simulate_mouse_move(drag_left, MouseButton::Left, Modifiers::none());
1684 cx.run_until_parked();
1685 cx.assert_state("«ˇone t»wo three four", Mode::Visual);
1686
1687 cx.simulate_mouse_move(anchor_pos, MouseButton::Left, Modifiers::none());
1688 cx.run_until_parked();
1689 cx.simulate_mouse_move(drag_left, MouseButton::Left, Modifiers::none());
1690 cx.run_until_parked();
1691 cx.assert_state("«ˇone t»wo three four", Mode::Visual);
1692
1693 cx.simulate_mouse_up(drag_left, MouseButton::Left, Modifiers::none());
1694}
1695
1696#[perf]
1697#[gpui::test]
1698async fn test_lowercase_marks(cx: &mut TestAppContext) {
1699 let mut cx = NeovimBackedTestContext::new(cx).await;
1700
1701 cx.set_shared_state("line one\nline ˇtwo\nline three").await;
1702 cx.simulate_shared_keystrokes("m a l ' a").await;
1703 cx.shared_state()
1704 .await
1705 .assert_eq("line one\nˇline two\nline three");
1706 cx.simulate_shared_keystrokes("` a").await;
1707 cx.shared_state()
1708 .await
1709 .assert_eq("line one\nline ˇtwo\nline three");
1710
1711 cx.simulate_shared_keystrokes("^ d ` a").await;
1712 cx.shared_state()
1713 .await
1714 .assert_eq("line one\nˇtwo\nline three");
1715}
1716
1717#[perf]
1718#[gpui::test]
1719async fn test_lt_gt_marks(cx: &mut TestAppContext) {
1720 let mut cx = NeovimBackedTestContext::new(cx).await;
1721
1722 cx.set_shared_state(indoc!(
1723 "
1724 Line one
1725 Line two
1726 Line ˇthree
1727 Line four
1728 Line five
1729 "
1730 ))
1731 .await;
1732
1733 cx.simulate_shared_keystrokes("v j escape k k").await;
1734
1735 cx.simulate_shared_keystrokes("' <").await;
1736 cx.shared_state().await.assert_eq(indoc! {"
1737 Line one
1738 Line two
1739 ˇLine three
1740 Line four
1741 Line five
1742 "});
1743
1744 cx.simulate_shared_keystrokes("` <").await;
1745 cx.shared_state().await.assert_eq(indoc! {"
1746 Line one
1747 Line two
1748 Line ˇthree
1749 Line four
1750 Line five
1751 "});
1752
1753 cx.simulate_shared_keystrokes("' >").await;
1754 cx.shared_state().await.assert_eq(indoc! {"
1755 Line one
1756 Line two
1757 Line three
1758 ˇLine four
1759 Line five
1760 "
1761 });
1762
1763 cx.simulate_shared_keystrokes("` >").await;
1764 cx.shared_state().await.assert_eq(indoc! {"
1765 Line one
1766 Line two
1767 Line three
1768 Line ˇfour
1769 Line five
1770 "
1771 });
1772
1773 cx.simulate_shared_keystrokes("v i w o escape").await;
1774 cx.simulate_shared_keystrokes("` >").await;
1775 cx.shared_state().await.assert_eq(indoc! {"
1776 Line one
1777 Line two
1778 Line three
1779 Line fouˇr
1780 Line five
1781 "
1782 });
1783 cx.simulate_shared_keystrokes("` <").await;
1784 cx.shared_state().await.assert_eq(indoc! {"
1785 Line one
1786 Line two
1787 Line three
1788 Line ˇfour
1789 Line five
1790 "
1791 });
1792}
1793
1794#[perf]
1795#[gpui::test]
1796async fn test_caret_mark(cx: &mut TestAppContext) {
1797 let mut cx = NeovimBackedTestContext::new(cx).await;
1798
1799 cx.set_shared_state(indoc!(
1800 "
1801 Line one
1802 Line two
1803 Line three
1804 ˇLine four
1805 Line five
1806 "
1807 ))
1808 .await;
1809
1810 cx.simulate_shared_keystrokes("c w shift-s t r a i g h t space t h i n g escape j j")
1811 .await;
1812
1813 cx.simulate_shared_keystrokes("' ^").await;
1814 cx.shared_state().await.assert_eq(indoc! {"
1815 Line one
1816 Line two
1817 Line three
1818 ˇStraight thing four
1819 Line five
1820 "
1821 });
1822
1823 cx.simulate_shared_keystrokes("` ^").await;
1824 cx.shared_state().await.assert_eq(indoc! {"
1825 Line one
1826 Line two
1827 Line three
1828 Straight thingˇ four
1829 Line five
1830 "
1831 });
1832
1833 cx.simulate_shared_keystrokes("k a ! escape k g i ?").await;
1834 cx.shared_state().await.assert_eq(indoc! {"
1835 Line one
1836 Line two
1837 Line three!?ˇ
1838 Straight thing four
1839 Line five
1840 "
1841 });
1842}
1843
1844#[cfg(target_os = "macos")]
1845#[perf]
1846#[gpui::test]
1847async fn test_dw_eol(cx: &mut gpui::TestAppContext) {
1848 let mut cx = NeovimBackedTestContext::new(cx).await;
1849
1850 cx.set_shared_wrap(12).await;
1851 cx.set_shared_state("twelve ˇchar twelve char\ntwelve char")
1852 .await;
1853 cx.simulate_shared_keystrokes("d w").await;
1854 cx.shared_state()
1855 .await
1856 .assert_eq("twelve ˇtwelve char\ntwelve char");
1857}
1858
1859#[perf]
1860#[gpui::test]
1861async fn test_toggle_comments(cx: &mut gpui::TestAppContext) {
1862 let mut cx = VimTestContext::new(cx, true).await;
1863
1864 let language = std::sync::Arc::new(language::Language::new(
1865 language::LanguageConfig {
1866 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
1867 ..Default::default()
1868 },
1869 Some(language::tree_sitter_rust::LANGUAGE.into()),
1870 ));
1871 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
1872
1873 // works in normal model
1874 cx.set_state(
1875 indoc! {"
1876 ˇone
1877 two
1878 three
1879 "},
1880 Mode::Normal,
1881 );
1882 cx.simulate_keystrokes("g c c");
1883 cx.assert_state(
1884 indoc! {"
1885 // ˇone
1886 two
1887 three
1888 "},
1889 Mode::Normal,
1890 );
1891
1892 // works in visual mode
1893 cx.simulate_keystrokes("v j g c");
1894 cx.assert_state(
1895 indoc! {"
1896 // // ˇone
1897 // two
1898 three
1899 "},
1900 Mode::Normal,
1901 );
1902
1903 // works in visual line mode
1904 cx.simulate_keystrokes("shift-v j g c");
1905 cx.assert_state(
1906 indoc! {"
1907 // ˇone
1908 two
1909 three
1910 "},
1911 Mode::Normal,
1912 );
1913
1914 // works with count
1915 cx.simulate_keystrokes("g c 2 j");
1916 cx.assert_state(
1917 indoc! {"
1918 // // ˇone
1919 // two
1920 // three
1921 "},
1922 Mode::Normal,
1923 );
1924
1925 // works with motion object
1926 cx.simulate_keystrokes("shift-g");
1927 cx.simulate_keystrokes("g c g g");
1928 cx.assert_state(
1929 indoc! {"
1930 // one
1931 two
1932 three
1933 ˇ"},
1934 Mode::Normal,
1935 );
1936}
1937
1938#[perf]
1939#[gpui::test]
1940async fn test_toggle_block_comments(cx: &mut gpui::TestAppContext) {
1941 let mut cx = VimTestContext::new(cx, true).await;
1942
1943 let language = std::sync::Arc::new(language::Language::new(
1944 language::LanguageConfig {
1945 block_comment: Some(language::BlockCommentConfig {
1946 start: "/* ".into(),
1947 prefix: "".into(),
1948 end: " */".into(),
1949 tab_size: 1,
1950 }),
1951 ..Default::default()
1952 },
1953 Some(language::tree_sitter_rust::LANGUAGE.into()),
1954 ));
1955 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
1956
1957 // works in normal mode with current-line shorthand
1958 cx.set_state(
1959 indoc! {"
1960 ˇone
1961 two
1962 three
1963 "},
1964 Mode::Normal,
1965 );
1966 cx.simulate_keystrokes("g b c");
1967 cx.assert_state(
1968 indoc! {"
1969 /* ˇone */
1970 two
1971 three
1972 "},
1973 Mode::Normal,
1974 );
1975
1976 // toggle off with cursor inside the comment
1977 cx.simulate_keystrokes("g b c");
1978 cx.assert_state(
1979 indoc! {"
1980 ˇone
1981 two
1982 three
1983 "},
1984 Mode::Normal,
1985 );
1986
1987 // works in visual line mode (wraps full lines)
1988 cx.simulate_keystrokes("shift-v j g b");
1989 cx.assert_state(
1990 indoc! {"
1991 /* ˇone
1992 two */
1993 three
1994 "},
1995 Mode::Normal,
1996 );
1997
1998 // works in visual mode and restores the cursor to the selection start
1999 cx.set_state(
2000 indoc! {"
2001 «oneˇ»
2002 two
2003 three
2004 "},
2005 Mode::Visual,
2006 );
2007 cx.simulate_keystrokes("g b");
2008 cx.assert_state(
2009 indoc! {"
2010 /* ˇone */
2011 two
2012 three
2013 "},
2014 Mode::Normal,
2015 );
2016
2017 // works with multiple visual selections and restores each cursor
2018 cx.set_state(
2019 indoc! {"
2020 «oneˇ» «twoˇ»
2021 three
2022 "},
2023 Mode::Visual,
2024 );
2025 cx.simulate_keystrokes("g b");
2026 cx.assert_state(
2027 indoc! {"
2028 /* ˇone */ /* ˇtwo */
2029 three
2030 "},
2031 Mode::Normal,
2032 );
2033
2034 // works with count
2035 cx.set_state(
2036 indoc! {"
2037 ˇone
2038 two
2039 three
2040 "},
2041 Mode::Normal,
2042 );
2043 cx.simulate_keystrokes("g b 2 j");
2044 cx.assert_state(
2045 indoc! {"
2046 /* ˇone
2047 two
2048 three */
2049 "},
2050 Mode::Normal,
2051 );
2052
2053 // works with motion object
2054 cx.simulate_keystrokes("shift-g");
2055 cx.simulate_keystrokes("g b g g");
2056 cx.assert_state(
2057 indoc! {"
2058 one
2059 two
2060 three
2061 ˇ"},
2062 Mode::Normal,
2063 );
2064}
2065
2066#[perf]
2067#[gpui::test]
2068async fn test_find_multibyte(cx: &mut gpui::TestAppContext) {
2069 let mut cx = NeovimBackedTestContext::new(cx).await;
2070
2071 cx.set_shared_state(r#"<label for="guests">ˇPočet hostů</label>"#)
2072 .await;
2073
2074 cx.simulate_shared_keystrokes("c t < o escape").await;
2075 cx.shared_state()
2076 .await
2077 .assert_eq(r#"<label for="guests">ˇo</label>"#);
2078}
2079
2080#[perf]
2081#[gpui::test]
2082async fn test_sneak(cx: &mut gpui::TestAppContext) {
2083 let mut cx = VimTestContext::new(cx, true).await;
2084
2085 cx.update(|_window, cx| {
2086 cx.bind_keys([
2087 KeyBinding::new(
2088 "s",
2089 PushSneak { first_char: None },
2090 Some("vim_mode == normal"),
2091 ),
2092 KeyBinding::new(
2093 "shift-s",
2094 PushSneakBackward { first_char: None },
2095 Some("vim_mode == normal"),
2096 ),
2097 KeyBinding::new(
2098 "shift-s",
2099 PushSneakBackward { first_char: None },
2100 Some("vim_mode == visual"),
2101 ),
2102 ])
2103 });
2104
2105 // Sneak forwards multibyte & multiline
2106 cx.set_state(
2107 indoc! {
2108 r#"<labelˇ for="guests">
2109 Počet hostů
2110 </label>"#
2111 },
2112 Mode::Normal,
2113 );
2114 cx.simulate_keystrokes("s t ů");
2115 cx.assert_state(
2116 indoc! {
2117 r#"<label for="guests">
2118 Počet hosˇtů
2119 </label>"#
2120 },
2121 Mode::Normal,
2122 );
2123
2124 // Visual sneak backwards multibyte & multiline
2125 cx.simulate_keystrokes("v S < l");
2126 cx.assert_state(
2127 indoc! {
2128 r#"«ˇ<label for="guests">
2129 Počet host»ů
2130 </label>"#
2131 },
2132 Mode::Visual,
2133 );
2134
2135 // Sneak backwards repeated
2136 cx.set_state(r#"11 12 13 ˇ14"#, Mode::Normal);
2137 cx.simulate_keystrokes("S space 1");
2138 cx.assert_state(r#"11 12ˇ 13 14"#, Mode::Normal);
2139 cx.simulate_keystrokes(";");
2140 cx.assert_state(r#"11ˇ 12 13 14"#, Mode::Normal);
2141}
2142
2143#[perf]
2144#[gpui::test]
2145async fn test_plus_minus(cx: &mut gpui::TestAppContext) {
2146 let mut cx = NeovimBackedTestContext::new(cx).await;
2147
2148 cx.set_shared_state(indoc! {
2149 "one
2150 two
2151 thrˇee
2152 "})
2153 .await;
2154
2155 cx.simulate_shared_keystrokes("-").await;
2156 cx.shared_state().await.assert_matches();
2157 cx.simulate_shared_keystrokes("-").await;
2158 cx.shared_state().await.assert_matches();
2159 cx.simulate_shared_keystrokes("+").await;
2160 cx.shared_state().await.assert_matches();
2161}
2162
2163#[perf]
2164#[gpui::test]
2165async fn test_command_alias(cx: &mut gpui::TestAppContext) {
2166 let mut cx = VimTestContext::new(cx, true).await;
2167 cx.update_global(|store: &mut SettingsStore, cx| {
2168 store.update_user_settings(cx, |s| {
2169 let mut aliases = HashMap::default();
2170 aliases.insert("Q".to_string(), CommandAliasTarget::new("upper"));
2171 s.workspace.command_aliases = aliases
2172 });
2173 });
2174
2175 cx.set_state("ˇhello world", Mode::Normal);
2176 cx.simulate_keystrokes(": Q");
2177 cx.set_state("ˇHello world", Mode::Normal);
2178}
2179
2180#[perf]
2181#[gpui::test]
2182async fn test_remap_adjacent_dog_cat(cx: &mut gpui::TestAppContext) {
2183 let mut cx = NeovimBackedTestContext::new(cx).await;
2184 cx.update(|_, cx| {
2185 cx.bind_keys([
2186 KeyBinding::new(
2187 "d o g",
2188 workspace::SendKeystrokes("🐶".to_string()),
2189 Some("vim_mode == insert"),
2190 ),
2191 KeyBinding::new(
2192 "c a t",
2193 workspace::SendKeystrokes("🐱".to_string()),
2194 Some("vim_mode == insert"),
2195 ),
2196 ])
2197 });
2198 cx.neovim.exec("imap dog 🐶").await;
2199 cx.neovim.exec("imap cat 🐱").await;
2200
2201 cx.set_shared_state("ˇ").await;
2202 cx.simulate_shared_keystrokes("i d o g").await;
2203 cx.shared_state().await.assert_eq("🐶ˇ");
2204
2205 cx.set_shared_state("ˇ").await;
2206 cx.simulate_shared_keystrokes("i d o d o g").await;
2207 cx.shared_state().await.assert_eq("do🐶ˇ");
2208
2209 cx.set_shared_state("ˇ").await;
2210 cx.simulate_shared_keystrokes("i d o c a t").await;
2211 cx.shared_state().await.assert_eq("do🐱ˇ");
2212}
2213
2214#[perf]
2215#[gpui::test]
2216async fn test_remap_nested_pineapple(cx: &mut gpui::TestAppContext) {
2217 let mut cx = NeovimBackedTestContext::new(cx).await;
2218 cx.update(|_, cx| {
2219 cx.bind_keys([
2220 KeyBinding::new(
2221 "p i n",
2222 workspace::SendKeystrokes("📌".to_string()),
2223 Some("vim_mode == insert"),
2224 ),
2225 KeyBinding::new(
2226 "p i n e",
2227 workspace::SendKeystrokes("🌲".to_string()),
2228 Some("vim_mode == insert"),
2229 ),
2230 KeyBinding::new(
2231 "p i n e a p p l e",
2232 workspace::SendKeystrokes("🍍".to_string()),
2233 Some("vim_mode == insert"),
2234 ),
2235 ])
2236 });
2237 cx.neovim.exec("imap pin 📌").await;
2238 cx.neovim.exec("imap pine 🌲").await;
2239 cx.neovim.exec("imap pineapple 🍍").await;
2240
2241 cx.set_shared_state("ˇ").await;
2242 cx.simulate_shared_keystrokes("i p i n").await;
2243 cx.executor().advance_clock(Duration::from_millis(1000));
2244 cx.run_until_parked();
2245 cx.shared_state().await.assert_eq("📌ˇ");
2246
2247 cx.set_shared_state("ˇ").await;
2248 cx.simulate_shared_keystrokes("i p i n e").await;
2249 cx.executor().advance_clock(Duration::from_millis(1000));
2250 cx.run_until_parked();
2251 cx.shared_state().await.assert_eq("🌲ˇ");
2252
2253 cx.set_shared_state("ˇ").await;
2254 cx.simulate_shared_keystrokes("i p i n e a p p l e").await;
2255 cx.shared_state().await.assert_eq("🍍ˇ");
2256}
2257
2258#[perf]
2259#[gpui::test]
2260async fn test_remap_recursion(cx: &mut gpui::TestAppContext) {
2261 let mut cx = NeovimBackedTestContext::new(cx).await;
2262 cx.update(|_, cx| {
2263 cx.bind_keys([KeyBinding::new(
2264 "x",
2265 workspace::SendKeystrokes("\" _ x".to_string()),
2266 Some("VimControl"),
2267 )]);
2268 cx.bind_keys([KeyBinding::new(
2269 "y",
2270 workspace::SendKeystrokes("2 x".to_string()),
2271 Some("VimControl"),
2272 )])
2273 });
2274 cx.neovim.exec("noremap x \"_x").await;
2275 cx.neovim.exec("map y 2x").await;
2276
2277 cx.set_shared_state("ˇhello").await;
2278 cx.simulate_shared_keystrokes("d l").await;
2279 cx.shared_clipboard().await.assert_eq("h");
2280 cx.simulate_shared_keystrokes("y").await;
2281 cx.shared_clipboard().await.assert_eq("h");
2282 cx.shared_state().await.assert_eq("ˇlo");
2283}
2284
2285#[perf]
2286#[gpui::test]
2287async fn test_escape_while_waiting(cx: &mut gpui::TestAppContext) {
2288 let mut cx = NeovimBackedTestContext::new(cx).await;
2289 cx.set_shared_state("ˇhi").await;
2290 cx.simulate_shared_keystrokes("\" + escape x").await;
2291 cx.shared_state().await.assert_eq("ˇi");
2292}
2293
2294#[perf]
2295#[gpui::test]
2296async fn test_ctrl_w_override(cx: &mut gpui::TestAppContext) {
2297 let mut cx = NeovimBackedTestContext::new(cx).await;
2298 cx.update(|_, cx| {
2299 cx.bind_keys([KeyBinding::new("ctrl-w", DeleteLine, None)]);
2300 });
2301 cx.neovim.exec("map <c-w> D").await;
2302 cx.set_shared_state("ˇhi").await;
2303 cx.simulate_shared_keystrokes("ctrl-w").await;
2304 cx.shared_state().await.assert_eq("ˇ");
2305}
2306
2307#[perf]
2308#[gpui::test]
2309async fn test_visual_indent_count(cx: &mut gpui::TestAppContext) {
2310 let mut cx = VimTestContext::new(cx, true).await;
2311 cx.set_state("ˇhi", Mode::Normal);
2312 cx.simulate_keystrokes("shift-v 3 >");
2313 cx.assert_state(" ˇhi", Mode::Normal);
2314 cx.simulate_keystrokes("shift-v 2 <");
2315 cx.assert_state(" ˇhi", Mode::Normal);
2316}
2317
2318#[perf]
2319#[gpui::test]
2320async fn test_record_replay_recursion(cx: &mut gpui::TestAppContext) {
2321 let mut cx = NeovimBackedTestContext::new(cx).await;
2322
2323 cx.set_shared_state("ˇhello world").await;
2324 cx.simulate_shared_keystrokes(">").await;
2325 cx.simulate_shared_keystrokes(".").await;
2326 cx.simulate_shared_keystrokes(".").await;
2327 cx.simulate_shared_keystrokes(".").await;
2328 cx.shared_state().await.assert_eq("ˇhello world");
2329}
2330
2331#[perf]
2332#[gpui::test]
2333async fn test_blackhole_register(cx: &mut gpui::TestAppContext) {
2334 let mut cx = NeovimBackedTestContext::new(cx).await;
2335
2336 cx.set_shared_state("ˇhello world").await;
2337 cx.simulate_shared_keystrokes("d i w \" _ d a w").await;
2338 cx.simulate_shared_keystrokes("p").await;
2339 cx.shared_state().await.assert_eq("hellˇo");
2340}
2341
2342#[perf]
2343#[gpui::test]
2344async fn test_sentence_backwards(cx: &mut gpui::TestAppContext) {
2345 let mut cx = NeovimBackedTestContext::new(cx).await;
2346
2347 cx.set_shared_state("one\n\ntwo\nthree\nˇ\nfour").await;
2348 cx.simulate_shared_keystrokes("(").await;
2349 cx.shared_state()
2350 .await
2351 .assert_eq("one\n\nˇtwo\nthree\n\nfour");
2352
2353 cx.set_shared_state("hello.\n\n\nworˇld.").await;
2354 cx.simulate_shared_keystrokes("(").await;
2355 cx.shared_state().await.assert_eq("hello.\n\n\nˇworld.");
2356 cx.simulate_shared_keystrokes("(").await;
2357 cx.shared_state().await.assert_eq("hello.\n\nˇ\nworld.");
2358 cx.simulate_shared_keystrokes("(").await;
2359 cx.shared_state().await.assert_eq("ˇhello.\n\n\nworld.");
2360
2361 cx.set_shared_state("hello. worlˇd.").await;
2362 cx.simulate_shared_keystrokes("(").await;
2363 cx.shared_state().await.assert_eq("hello. ˇworld.");
2364 cx.simulate_shared_keystrokes("(").await;
2365 cx.shared_state().await.assert_eq("ˇhello. world.");
2366
2367 cx.set_shared_state(". helˇlo.").await;
2368 cx.simulate_shared_keystrokes("(").await;
2369 cx.shared_state().await.assert_eq(". ˇhello.");
2370 cx.simulate_shared_keystrokes("(").await;
2371 cx.shared_state().await.assert_eq(". ˇhello.");
2372
2373 cx.set_shared_state(indoc! {
2374 "{
2375 hello_world();
2376 ˇ}"
2377 })
2378 .await;
2379 cx.simulate_shared_keystrokes("(").await;
2380 cx.shared_state().await.assert_eq(indoc! {
2381 "ˇ{
2382 hello_world();
2383 }"
2384 });
2385
2386 cx.set_shared_state(indoc! {
2387 "Hello! World..?
2388
2389 \tHello! World... ˇ"
2390 })
2391 .await;
2392 cx.simulate_shared_keystrokes("(").await;
2393 cx.shared_state().await.assert_eq(indoc! {
2394 "Hello! World..?
2395
2396 \tHello! ˇWorld... "
2397 });
2398 cx.simulate_shared_keystrokes("(").await;
2399 cx.shared_state().await.assert_eq(indoc! {
2400 "Hello! World..?
2401
2402 \tˇHello! World... "
2403 });
2404 cx.simulate_shared_keystrokes("(").await;
2405 cx.shared_state().await.assert_eq(indoc! {
2406 "Hello! World..?
2407 ˇ
2408 \tHello! World... "
2409 });
2410 cx.simulate_shared_keystrokes("(").await;
2411 cx.shared_state().await.assert_eq(indoc! {
2412 "Hello! ˇWorld..?
2413
2414 \tHello! World... "
2415 });
2416}
2417
2418#[perf]
2419#[gpui::test]
2420async fn test_sentence_forwards(cx: &mut gpui::TestAppContext) {
2421 let mut cx = NeovimBackedTestContext::new(cx).await;
2422
2423 cx.set_shared_state("helˇlo.\n\n\nworld.").await;
2424 cx.simulate_shared_keystrokes(")").await;
2425 cx.shared_state().await.assert_eq("hello.\nˇ\n\nworld.");
2426 cx.simulate_shared_keystrokes(")").await;
2427 cx.shared_state().await.assert_eq("hello.\n\n\nˇworld.");
2428 cx.simulate_shared_keystrokes(")").await;
2429 cx.shared_state().await.assert_eq("hello.\n\n\nworldˇ.");
2430
2431 cx.set_shared_state("helˇlo.\n\n\nworld.").await;
2432}
2433
2434#[perf]
2435#[gpui::test]
2436async fn test_ctrl_o_visual(cx: &mut gpui::TestAppContext) {
2437 let mut cx = NeovimBackedTestContext::new(cx).await;
2438
2439 cx.set_shared_state("helloˇ world.").await;
2440 cx.simulate_shared_keystrokes("i ctrl-o v b r l").await;
2441 cx.shared_state().await.assert_eq("ˇllllllworld.");
2442 cx.simulate_shared_keystrokes("ctrl-o v f w d").await;
2443 cx.shared_state().await.assert_eq("ˇorld.");
2444}
2445
2446#[perf]
2447#[gpui::test]
2448async fn test_ctrl_o_position(cx: &mut gpui::TestAppContext) {
2449 let mut cx = NeovimBackedTestContext::new(cx).await;
2450
2451 cx.set_shared_state("helˇlo world.").await;
2452 cx.simulate_shared_keystrokes("i ctrl-o d i w").await;
2453 cx.shared_state().await.assert_eq("ˇ world.");
2454 cx.simulate_shared_keystrokes("ctrl-o p").await;
2455 cx.shared_state().await.assert_eq(" helloˇworld.");
2456}
2457
2458#[perf]
2459#[gpui::test]
2460async fn test_ctrl_o_dot(cx: &mut gpui::TestAppContext) {
2461 let mut cx = NeovimBackedTestContext::new(cx).await;
2462
2463 cx.set_shared_state("heˇllo world.").await;
2464 cx.simulate_shared_keystrokes("x i ctrl-o .").await;
2465 cx.shared_state().await.assert_eq("heˇo world.");
2466 cx.simulate_shared_keystrokes("l l escape .").await;
2467 cx.shared_state().await.assert_eq("hellˇllo world.");
2468}
2469
2470#[perf(iterations = 1)]
2471#[gpui::test]
2472async fn test_folded_multibuffer_excerpts(cx: &mut gpui::TestAppContext) {
2473 VimTestContext::init(cx);
2474 cx.update(|cx| {
2475 VimTestContext::init_keybindings(true, cx);
2476 });
2477 let (editor, cx) = cx.add_window_view(|window, cx| {
2478 let multi_buffer = MultiBuffer::build_multi(
2479 [
2480 ("111\n222\n333\n444\n", vec![Point::row_range(0..2)]),
2481 ("aaa\nbbb\nccc\nddd\n", vec![Point::row_range(0..2)]),
2482 ("AAA\nBBB\nCCC\nDDD\n", vec![Point::row_range(0..2)]),
2483 ("one\ntwo\nthr\nfou\n", vec![Point::row_range(0..2)]),
2484 ],
2485 cx,
2486 );
2487 let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
2488
2489 let buffer_ids = multi_buffer
2490 .read(cx)
2491 .snapshot(cx)
2492 .excerpts()
2493 .map(|excerpt| excerpt.context.start.buffer_id)
2494 .collect::<Vec<_>>();
2495 // fold all but the second buffer, so that we test navigating between two
2496 // adjacent folded buffers, as well as folded buffers at the start and
2497 // end the multibuffer
2498 editor.fold_buffer(buffer_ids[0], cx);
2499 editor.fold_buffer(buffer_ids[2], cx);
2500 editor.fold_buffer(buffer_ids[3], cx);
2501
2502 editor
2503 });
2504 let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
2505
2506 cx.assert_excerpts_with_selections(indoc! {"
2507 [EXCERPT]
2508 ˇ[FOLDED]
2509 [EXCERPT]
2510 aaa
2511 bbb
2512 [EXCERPT]
2513 [FOLDED]
2514 [EXCERPT]
2515 [FOLDED]
2516 "
2517 });
2518 cx.simulate_keystroke("j");
2519 cx.assert_excerpts_with_selections(indoc! {"
2520 [EXCERPT]
2521 [FOLDED]
2522 [EXCERPT]
2523 ˇaaa
2524 bbb
2525 [EXCERPT]
2526 [FOLDED]
2527 [EXCERPT]
2528 [FOLDED]
2529 "
2530 });
2531 cx.simulate_keystroke("j");
2532 cx.simulate_keystroke("j");
2533 cx.assert_excerpts_with_selections(indoc! {"
2534 [EXCERPT]
2535 [FOLDED]
2536 [EXCERPT]
2537 aaa
2538 bbb
2539 ˇ[EXCERPT]
2540 [FOLDED]
2541 [EXCERPT]
2542 [FOLDED]
2543 "
2544 });
2545 cx.simulate_keystroke("j");
2546 cx.assert_excerpts_with_selections(indoc! {"
2547 [EXCERPT]
2548 [FOLDED]
2549 [EXCERPT]
2550 aaa
2551 bbb
2552 [EXCERPT]
2553 ˇ[FOLDED]
2554 [EXCERPT]
2555 [FOLDED]
2556 "
2557 });
2558 cx.simulate_keystroke("j");
2559 cx.assert_excerpts_with_selections(indoc! {"
2560 [EXCERPT]
2561 [FOLDED]
2562 [EXCERPT]
2563 aaa
2564 bbb
2565 [EXCERPT]
2566 [FOLDED]
2567 [EXCERPT]
2568 ˇ[FOLDED]
2569 "
2570 });
2571 cx.simulate_keystroke("k");
2572 cx.assert_excerpts_with_selections(indoc! {"
2573 [EXCERPT]
2574 [FOLDED]
2575 [EXCERPT]
2576 aaa
2577 bbb
2578 [EXCERPT]
2579 ˇ[FOLDED]
2580 [EXCERPT]
2581 [FOLDED]
2582 "
2583 });
2584 cx.simulate_keystroke("k");
2585 cx.simulate_keystroke("k");
2586 cx.simulate_keystroke("k");
2587 cx.assert_excerpts_with_selections(indoc! {"
2588 [EXCERPT]
2589 [FOLDED]
2590 [EXCERPT]
2591 ˇaaa
2592 bbb
2593 [EXCERPT]
2594 [FOLDED]
2595 [EXCERPT]
2596 [FOLDED]
2597 "
2598 });
2599 cx.simulate_keystroke("k");
2600 cx.assert_excerpts_with_selections(indoc! {"
2601 [EXCERPT]
2602 ˇ[FOLDED]
2603 [EXCERPT]
2604 aaa
2605 bbb
2606 [EXCERPT]
2607 [FOLDED]
2608 [EXCERPT]
2609 [FOLDED]
2610 "
2611 });
2612 cx.simulate_keystroke("shift-g");
2613 cx.assert_excerpts_with_selections(indoc! {"
2614 [EXCERPT]
2615 [FOLDED]
2616 [EXCERPT]
2617 aaa
2618 bbb
2619 [EXCERPT]
2620 [FOLDED]
2621 [EXCERPT]
2622 ˇ[FOLDED]
2623 "
2624 });
2625 cx.simulate_keystrokes("g g");
2626 cx.assert_excerpts_with_selections(indoc! {"
2627 [EXCERPT]
2628 ˇ[FOLDED]
2629 [EXCERPT]
2630 aaa
2631 bbb
2632 [EXCERPT]
2633 [FOLDED]
2634 [EXCERPT]
2635 [FOLDED]
2636 "
2637 });
2638 cx.update_editor(|editor, _, cx| {
2639 let buffer_ids = editor
2640 .buffer()
2641 .read(cx)
2642 .snapshot(cx)
2643 .excerpts()
2644 .map(|excerpt| excerpt.context.start.buffer_id)
2645 .collect::<Vec<_>>();
2646 editor.fold_buffer(buffer_ids[1], cx);
2647 });
2648
2649 cx.assert_excerpts_with_selections(indoc! {"
2650 [EXCERPT]
2651 ˇ[FOLDED]
2652 [EXCERPT]
2653 [FOLDED]
2654 [EXCERPT]
2655 [FOLDED]
2656 [EXCERPT]
2657 [FOLDED]
2658 "
2659 });
2660 cx.simulate_keystrokes("2 j");
2661 cx.assert_excerpts_with_selections(indoc! {"
2662 [EXCERPT]
2663 [FOLDED]
2664 [EXCERPT]
2665 [FOLDED]
2666 [EXCERPT]
2667 ˇ[FOLDED]
2668 [EXCERPT]
2669 [FOLDED]
2670 "
2671 });
2672}
2673
2674#[perf]
2675#[gpui::test]
2676async fn test_delete_paragraph_motion(cx: &mut gpui::TestAppContext) {
2677 let mut cx = NeovimBackedTestContext::new(cx).await;
2678 cx.set_shared_state(indoc! {
2679 "ˇhello world.
2680
2681 hello world.
2682 "
2683 })
2684 .await;
2685 cx.simulate_shared_keystrokes("y }").await;
2686 cx.shared_clipboard().await.assert_eq("hello world.\n");
2687 cx.simulate_shared_keystrokes("d }").await;
2688 cx.shared_state().await.assert_eq("ˇ\nhello world.\n");
2689 cx.shared_clipboard().await.assert_eq("hello world.\n");
2690
2691 cx.set_shared_state(indoc! {
2692 "helˇlo world.
2693
2694 hello world.
2695 "
2696 })
2697 .await;
2698 cx.simulate_shared_keystrokes("y }").await;
2699 cx.shared_clipboard().await.assert_eq("lo world.");
2700 cx.simulate_shared_keystrokes("d }").await;
2701 cx.shared_state().await.assert_eq("heˇl\n\nhello world.\n");
2702 cx.shared_clipboard().await.assert_eq("lo world.");
2703}
2704
2705#[perf]
2706#[gpui::test]
2707async fn test_delete_unmatched_brace(cx: &mut gpui::TestAppContext) {
2708 let mut cx = NeovimBackedTestContext::new(cx).await;
2709 cx.set_shared_state(indoc! {
2710 "fn o(wow: i32) {
2711 othˇ(wow)
2712 oth(wow)
2713 }
2714 "
2715 })
2716 .await;
2717 cx.simulate_shared_keystrokes("d ] }").await;
2718 cx.shared_state().await.assert_eq(indoc! {
2719 "fn o(wow: i32) {
2720 otˇh
2721 }
2722 "
2723 });
2724 cx.shared_clipboard().await.assert_eq("(wow)\n oth(wow)");
2725 cx.set_shared_state(indoc! {
2726 "fn o(wow: i32) {
2727 ˇoth(wow)
2728 oth(wow)
2729 }
2730 "
2731 })
2732 .await;
2733 cx.simulate_shared_keystrokes("d ] }").await;
2734 cx.shared_state().await.assert_eq(indoc! {
2735 "fn o(wow: i32) {
2736 ˇ}
2737 "
2738 });
2739 cx.shared_clipboard()
2740 .await
2741 .assert_eq(" oth(wow)\n oth(wow)\n");
2742}
2743
2744#[perf]
2745#[gpui::test]
2746async fn test_paragraph_multi_delete(cx: &mut gpui::TestAppContext) {
2747 let mut cx = NeovimBackedTestContext::new(cx).await;
2748 cx.set_shared_state(indoc! {
2749 "
2750 Emacs is
2751 ˇa great
2752
2753 operating system
2754
2755 all it lacks
2756 is a
2757
2758 decent text editor
2759 "
2760 })
2761 .await;
2762
2763 cx.simulate_shared_keystrokes("2 d a p").await;
2764 cx.shared_state().await.assert_eq(indoc! {
2765 "
2766 ˇall it lacks
2767 is a
2768
2769 decent text editor
2770 "
2771 });
2772
2773 cx.simulate_shared_keystrokes("d a p").await;
2774 cx.shared_clipboard()
2775 .await
2776 .assert_eq("all it lacks\nis a\n\n");
2777
2778 //reset to initial state
2779 cx.simulate_shared_keystrokes("2 u").await;
2780
2781 cx.simulate_shared_keystrokes("4 d a p").await;
2782 cx.shared_state().await.assert_eq(indoc! {"ˇ"});
2783}
2784
2785#[perf]
2786#[gpui::test]
2787async fn test_yank_paragraph_with_paste(cx: &mut gpui::TestAppContext) {
2788 let mut cx = NeovimBackedTestContext::new(cx).await;
2789 cx.set_shared_state(indoc! {
2790 "
2791 first paragraph
2792 ˇstill first
2793
2794 second paragraph
2795 still second
2796
2797 third paragraph
2798 "
2799 })
2800 .await;
2801
2802 cx.simulate_shared_keystrokes("y a p").await;
2803 cx.shared_clipboard()
2804 .await
2805 .assert_eq("first paragraph\nstill first\n\n");
2806
2807 cx.simulate_shared_keystrokes("j j p").await;
2808 cx.shared_state().await.assert_eq(indoc! {
2809 "
2810 first paragraph
2811 still first
2812
2813 ˇfirst paragraph
2814 still first
2815
2816 second paragraph
2817 still second
2818
2819 third paragraph
2820 "
2821 });
2822}
2823
2824#[perf]
2825#[gpui::test]
2826async fn test_change_paragraph(cx: &mut gpui::TestAppContext) {
2827 let mut cx = NeovimBackedTestContext::new(cx).await;
2828 cx.set_shared_state(indoc! {
2829 "
2830 first paragraph
2831 ˇstill first
2832
2833 second paragraph
2834 still second
2835
2836 third paragraph
2837 "
2838 })
2839 .await;
2840
2841 cx.simulate_shared_keystrokes("c a p").await;
2842 cx.shared_clipboard()
2843 .await
2844 .assert_eq("first paragraph\nstill first\n\n");
2845
2846 cx.simulate_shared_keystrokes("escape").await;
2847 cx.shared_state().await.assert_eq(indoc! {
2848 "
2849 ˇ
2850 second paragraph
2851 still second
2852
2853 third paragraph
2854 "
2855 });
2856}
2857
2858#[perf]
2859#[gpui::test]
2860async fn test_multi_cursor_replay(cx: &mut gpui::TestAppContext) {
2861 let mut cx = VimTestContext::new(cx, true).await;
2862 cx.set_state(
2863 indoc! {
2864 "
2865 oˇne one one
2866
2867 two two two
2868 "
2869 },
2870 Mode::Normal,
2871 );
2872
2873 cx.simulate_keystrokes("3 g l s wow escape escape");
2874 cx.assert_state(
2875 indoc! {
2876 "
2877 woˇw wow wow
2878
2879 two two two
2880 "
2881 },
2882 Mode::Normal,
2883 );
2884
2885 cx.simulate_keystrokes("2 j 3 g l .");
2886 cx.assert_state(
2887 indoc! {
2888 "
2889 wow wow wow
2890
2891 woˇw woˇw woˇw
2892 "
2893 },
2894 Mode::Normal,
2895 );
2896}
2897
2898#[gpui::test]
2899async fn test_clipping_on_mode_change(cx: &mut gpui::TestAppContext) {
2900 let mut cx = VimTestContext::new(cx, true).await;
2901
2902 cx.set_state(
2903 indoc! {
2904 "
2905 ˇverylongline
2906 andsomelinebelow
2907 "
2908 },
2909 Mode::Normal,
2910 );
2911
2912 cx.simulate_keystrokes("v e");
2913 cx.assert_state(
2914 indoc! {
2915 "
2916 «verylonglineˇ»
2917 andsomelinebelow
2918 "
2919 },
2920 Mode::Visual,
2921 );
2922
2923 let mut pixel_position = cx.update_editor(|editor, window, cx| {
2924 let snapshot = editor.snapshot(window, cx);
2925 let current_head = editor
2926 .selections
2927 .newest_display(&snapshot.display_snapshot)
2928 .end;
2929 editor.last_bounds().unwrap().origin
2930 + editor
2931 .display_to_pixel_point(current_head, &snapshot, window, cx)
2932 .unwrap()
2933 });
2934 pixel_position.x += px(100.);
2935 // click beyond end of the line
2936 cx.simulate_click(pixel_position, Modifiers::default());
2937 cx.run_until_parked();
2938
2939 cx.assert_state(
2940 indoc! {
2941 "
2942 verylonglinˇe
2943 andsomelinebelow
2944 "
2945 },
2946 Mode::Normal,
2947 );
2948}
2949
2950#[gpui::test]
2951async fn test_wrap_selections_in_tag_line_mode(cx: &mut gpui::TestAppContext) {
2952 let mut cx = VimTestContext::new(cx, true).await;
2953
2954 let js_language = Arc::new(Language::new(
2955 LanguageConfig {
2956 name: "JavaScript".into(),
2957 wrap_characters: Some(language::WrapCharactersConfig {
2958 start_prefix: "<".into(),
2959 start_suffix: ">".into(),
2960 end_prefix: "</".into(),
2961 end_suffix: ">".into(),
2962 }),
2963 ..LanguageConfig::default()
2964 },
2965 None,
2966 ));
2967
2968 cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
2969
2970 cx.set_state(
2971 indoc! {
2972 "
2973 ˇaaaaa
2974 bbbbb
2975 "
2976 },
2977 Mode::Normal,
2978 );
2979
2980 cx.simulate_keystrokes("shift-v j");
2981 cx.dispatch_action(WrapSelectionsInTag);
2982
2983 cx.assert_state(
2984 indoc! {
2985 "
2986 <ˇ>aaaaa
2987 bbbbb</ˇ>
2988 "
2989 },
2990 Mode::VisualLine,
2991 );
2992}
2993
2994#[gpui::test]
2995async fn test_repeat_grouping_41735(cx: &mut gpui::TestAppContext) {
2996 let mut cx = NeovimBackedTestContext::new(cx).await;
2997
2998 // typically transaction gropuing is disabled in tests, but here we need to test it.
2999 cx.update_buffer(|buffer, _cx| buffer.set_group_interval(Duration::from_millis(300)));
3000
3001 cx.set_shared_state("ˇ").await;
3002
3003 cx.simulate_shared_keystrokes("i a escape").await;
3004 cx.simulate_shared_keystrokes(". . .").await;
3005 cx.shared_state().await.assert_eq("ˇaaaa");
3006 cx.simulate_shared_keystrokes("u").await;
3007 cx.shared_state().await.assert_eq("ˇaaa");
3008}
3009
3010#[gpui::test]
3011async fn test_deactivate(cx: &mut gpui::TestAppContext) {
3012 let mut cx = VimTestContext::new(cx, true).await;
3013
3014 cx.update_global(|store: &mut SettingsStore, cx| {
3015 store.update_user_settings(cx, |settings| {
3016 settings.editor.cursor_shape = Some(settings::CursorShape::Underline);
3017 });
3018 });
3019
3020 // Assert that, while in `Normal` mode, the cursor shape is `Block` but,
3021 // after deactivating vim mode, it should revert to the one specified in the
3022 // user's settings, if set.
3023 cx.update_editor(|editor, _window, _cx| {
3024 assert_eq!(editor.cursor_shape(), CursorShape::Block);
3025 });
3026
3027 cx.disable_vim();
3028
3029 cx.update_editor(|editor, _window, _cx| {
3030 assert_eq!(editor.cursor_shape(), CursorShape::Underline);
3031 });
3032}
3033
3034// workspace::SendKeystrokes should pass literal keystrokes without triggering vim motions.
3035// When sending `" _ x`, the `_` should select the blackhole register, not trigger
3036// vim::StartOfLineDownward.
3037#[gpui::test]
3038async fn test_send_keystrokes_underscore_is_literal_46509(cx: &mut gpui::TestAppContext) {
3039 let mut cx = VimTestContext::new(cx, true).await;
3040
3041 // Bind a key to send `" _ x` which should:
3042 // `"` - start register selection
3043 // `_` - select blackhole register (NOT vim::StartOfLineDownward)
3044 // `x` - delete character into blackhole register
3045 cx.update(|_, cx| {
3046 cx.bind_keys([KeyBinding::new(
3047 "g x",
3048 workspace::SendKeystrokes("\" _ x".to_string()),
3049 Some("VimControl"),
3050 )])
3051 });
3052
3053 cx.set_state("helˇlo", Mode::Normal);
3054
3055 cx.simulate_keystrokes("g x");
3056 cx.run_until_parked();
3057
3058 cx.assert_state("helˇo", Mode::Normal);
3059}
3060
3061#[gpui::test]
3062async fn test_send_keystrokes_no_key_equivalent_mapping_46509(cx: &mut gpui::TestAppContext) {
3063 use collections::HashMap;
3064 use gpui::{KeybindingKeystroke, Keystroke, PlatformKeyboardMapper};
3065
3066 // create a mock Danish keyboard mapper
3067 // on Danish keyboards, the macOS key equivalents mapping includes: '{' -> 'Æ' and '}' -> 'Ø'
3068 // this means the `{` character is produced by the key labeled `Æ` (with shift modifier)
3069 struct DanishKeyboardMapper;
3070 impl PlatformKeyboardMapper for DanishKeyboardMapper {
3071 fn map_key_equivalent(
3072 &self,
3073 mut keystroke: Keystroke,
3074 use_key_equivalents: bool,
3075 ) -> KeybindingKeystroke {
3076 if use_key_equivalents {
3077 if keystroke.key == "{" {
3078 keystroke.key = "Æ".to_string();
3079 }
3080 if keystroke.key == "}" {
3081 keystroke.key = "Ø".to_string();
3082 }
3083 }
3084 KeybindingKeystroke::from_keystroke(keystroke)
3085 }
3086
3087 fn get_key_equivalents(&self) -> Option<&HashMap<char, char>> {
3088 None
3089 }
3090 }
3091
3092 let mapper = DanishKeyboardMapper;
3093
3094 let keystroke_brace = Keystroke::parse("{").unwrap();
3095 let mapped_with_bug = mapper.map_key_equivalent(keystroke_brace.clone(), true);
3096 assert_eq!(
3097 mapped_with_bug.key(),
3098 "Æ",
3099 "BUG: With use_key_equivalents=true, {{ is mapped to Æ on Danish keyboard"
3100 );
3101
3102 // Fixed behavior, where the literal `{` character is preserved
3103 let mapped_fixed = mapper.map_key_equivalent(keystroke_brace.clone(), false);
3104 assert_eq!(
3105 mapped_fixed.key(),
3106 "{",
3107 "FIX: With use_key_equivalents=false, {{ stays as {{"
3108 );
3109
3110 // Same applies to }
3111 let keystroke_close = Keystroke::parse("}").unwrap();
3112 let mapped_close_bug = mapper.map_key_equivalent(keystroke_close.clone(), true);
3113 assert_eq!(mapped_close_bug.key(), "Ø");
3114 let mapped_close_fixed = mapper.map_key_equivalent(keystroke_close.clone(), false);
3115 assert_eq!(mapped_close_fixed.key(), "}");
3116
3117 let mut cx = VimTestContext::new(cx, true).await;
3118
3119 cx.update(|_, cx| {
3120 cx.bind_keys([KeyBinding::new(
3121 "g p",
3122 workspace::SendKeystrokes("{".to_string()),
3123 Some("vim_mode == normal"),
3124 )])
3125 });
3126
3127 cx.set_state(
3128 indoc! {"
3129 first paragraph
3130
3131 second paragraphˇ
3132
3133 third paragraph
3134 "},
3135 Mode::Normal,
3136 );
3137
3138 cx.simulate_keystrokes("g p");
3139 cx.run_until_parked();
3140
3141 cx.assert_state(
3142 indoc! {"
3143 first paragraph
3144 ˇ
3145 second paragraph
3146
3147 third paragraph
3148 "},
3149 Mode::Normal,
3150 );
3151}
3152
3153#[gpui::test]
3154async fn test_project_search_opens_in_normal_mode(cx: &mut gpui::TestAppContext) {
3155 VimTestContext::init(cx);
3156
3157 let fs = FakeFs::new(cx.background_executor.clone());
3158 fs.insert_tree(
3159 path!("/dir"),
3160 json!({
3161 "file_a.rs": "// File A.",
3162 "file_b.rs": "// File B.",
3163 }),
3164 )
3165 .await;
3166
3167 let project = project::Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
3168 let window_handle =
3169 cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
3170 let workspace = window_handle
3171 .read_with(cx, |mw, _| mw.workspace().clone())
3172 .unwrap();
3173
3174 cx.update(|cx| {
3175 VimTestContext::init_keybindings(true, cx);
3176 });
3177
3178 let cx = &mut VisualTestContext::from_window(window_handle.into(), cx);
3179
3180 workspace.update_in(cx, |workspace, window, cx| {
3181 ProjectSearchView::deploy_search(workspace, &DeploySearch::default(), window, cx)
3182 });
3183
3184 let search_view = workspace.update_in(cx, |workspace, _, cx| {
3185 workspace
3186 .active_pane()
3187 .read(cx)
3188 .items()
3189 .find_map(|item| item.downcast::<ProjectSearchView>())
3190 .expect("Project search view should be active")
3191 });
3192
3193 project_search::perform_project_search(&search_view, "File A", cx);
3194
3195 search_view.update(cx, |search_view, cx| {
3196 let vim_mode = search_view
3197 .results_editor()
3198 .read(cx)
3199 .addon::<VimAddon>()
3200 .map(|addon| addon.entity.read(cx).mode);
3201
3202 assert_eq!(vim_mode, Some(Mode::Normal));
3203 });
3204}
3205