Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T05:35:32.957Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

test.rs

3205 lines · 90.2 KB · rust
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\n    ccc\n");
352    cx.simulate_keystrokes(".");
353    cx.assert_editor_state("        a\n\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
Served at tenant.openagents/omega Member data and write actions are omitted.