test.rs

   1mod neovim_backed_test_context;
   2mod neovim_connection;
   3mod vim_test_context;
   4
   5use std::{sync::Arc, time::Duration};
   6
   7use collections::HashMap;
   8use command_palette::CommandPalette;
   9use editor::{
  10    AnchorRangeExt, DisplayPoint, Editor, EditorMode, MultiBuffer, MultiBufferOffset,
  11    actions::{DeleteLine, WrapSelectionsInTag},
  12    code_context_menus::CodeContextMenu,
  13    display_map::DisplayRow,
  14    test::editor_test_context::EditorTestContext,
  15};
  16use futures::StreamExt;
  17use gpui::{KeyBinding, Modifiers, MouseButton, TestAppContext, px};
  18use itertools::Itertools;
  19use language::{CursorShape, Language, LanguageConfig, Point};
  20pub use neovim_backed_test_context::*;
  21use settings::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_go_to_definition(cx: &mut gpui::TestAppContext) {
1263    let mut cx = VimTestContext::new_typescript(cx).await;
1264
1265    cx.set_state("const before = 2; console.log(beforˇe)", Mode::Normal);
1266    let def_range = cx.lsp_range("const «beforeˇ» = 2; console.log(before)");
1267    let mut go_to_request =
1268        cx.set_request_handler::<lsp::request::GotoDefinition, _, _>(move |url, _, _| async move {
1269            Ok(Some(lsp::GotoDefinitionResponse::Scalar(
1270                lsp::Location::new(url.clone(), def_range),
1271            )))
1272        });
1273
1274    cx.simulate_keystrokes("g d");
1275    go_to_request.next().await.unwrap();
1276    cx.run_until_parked();
1277
1278    cx.assert_state("const ˇbefore = 2; console.log(before)", Mode::Normal);
1279}
1280
1281#[perf]
1282#[gpui::test]
1283async fn test_remap(cx: &mut gpui::TestAppContext) {
1284    let mut cx = VimTestContext::new(cx, true).await;
1285
1286    // test moving the cursor
1287    cx.update(|_, cx| {
1288        cx.bind_keys([KeyBinding::new(
1289            "g z",
1290            workspace::SendKeystrokes("l l l l".to_string()),
1291            None,
1292        )])
1293    });
1294    cx.set_state("ˇ123456789", Mode::Normal);
1295    cx.simulate_keystrokes("g z");
1296    cx.assert_state("1234ˇ56789", Mode::Normal);
1297
1298    // test switching modes
1299    cx.update(|_, cx| {
1300        cx.bind_keys([KeyBinding::new(
1301            "g y",
1302            workspace::SendKeystrokes("i f o o escape l".to_string()),
1303            None,
1304        )])
1305    });
1306    cx.set_state("ˇ123456789", Mode::Normal);
1307    cx.simulate_keystrokes("g y");
1308    cx.assert_state("fooˇ123456789", Mode::Normal);
1309
1310    // test recursion
1311    cx.update(|_, cx| {
1312        cx.bind_keys([KeyBinding::new(
1313            "g x",
1314            workspace::SendKeystrokes("g z g y".to_string()),
1315            None,
1316        )])
1317    });
1318    cx.set_state("ˇ123456789", Mode::Normal);
1319    cx.simulate_keystrokes("g x");
1320    cx.assert_state("1234fooˇ56789", Mode::Normal);
1321
1322    // test command
1323    cx.update(|_, cx| {
1324        cx.bind_keys([KeyBinding::new(
1325            "g w",
1326            workspace::SendKeystrokes(": j enter".to_string()),
1327            None,
1328        )])
1329    });
1330    cx.set_state("ˇ1234\n56789", Mode::Normal);
1331    cx.simulate_keystrokes("g w");
1332    cx.assert_state("1234ˇ 56789", Mode::Normal);
1333
1334    // test leaving command
1335    cx.update(|_, cx| {
1336        cx.bind_keys([KeyBinding::new(
1337            "g u",
1338            workspace::SendKeystrokes("g w g z".to_string()),
1339            None,
1340        )])
1341    });
1342    cx.set_state("ˇ1234\n56789", Mode::Normal);
1343    cx.simulate_keystrokes("g u");
1344    cx.assert_state("1234 567ˇ89", Mode::Normal);
1345
1346    // test leaving command
1347    cx.update(|_, cx| {
1348        cx.bind_keys([KeyBinding::new(
1349            "g t",
1350            workspace::SendKeystrokes("i space escape".to_string()),
1351            None,
1352        )])
1353    });
1354    cx.set_state("12ˇ34", Mode::Normal);
1355    cx.simulate_keystrokes("g t");
1356    cx.assert_state("12ˇ 34", Mode::Normal);
1357}
1358
1359#[perf]
1360#[gpui::test]
1361async fn test_undo(cx: &mut gpui::TestAppContext) {
1362    let mut cx = NeovimBackedTestContext::new(cx).await;
1363
1364    cx.set_shared_state("hello quˇoel world").await;
1365    cx.simulate_shared_keystrokes("v i w s c o escape u").await;
1366    cx.shared_state().await.assert_eq("hello ˇquoel world");
1367    cx.simulate_shared_keystrokes("ctrl-r").await;
1368    cx.shared_state().await.assert_eq("hello ˇco world");
1369    cx.simulate_shared_keystrokes("a o right l escape").await;
1370    cx.shared_state().await.assert_eq("hello cooˇl world");
1371    cx.simulate_shared_keystrokes("u").await;
1372    cx.shared_state().await.assert_eq("hello cooˇ world");
1373    cx.simulate_shared_keystrokes("u").await;
1374    cx.shared_state().await.assert_eq("hello cˇo world");
1375    cx.simulate_shared_keystrokes("u").await;
1376    cx.shared_state().await.assert_eq("hello ˇquoel world");
1377
1378    cx.set_shared_state("hello quˇoel world").await;
1379    cx.simulate_shared_keystrokes("v i w ~ u").await;
1380    cx.shared_state().await.assert_eq("hello ˇquoel world");
1381
1382    cx.set_shared_state("\nhello quˇoel world\n").await;
1383    cx.simulate_shared_keystrokes("shift-v s c escape u").await;
1384    cx.shared_state().await.assert_eq("\nˇhello quoel world\n");
1385
1386    cx.set_shared_state(indoc! {"
1387        ˇ1
1388        2
1389        3"})
1390        .await;
1391
1392    cx.simulate_shared_keystrokes("ctrl-v shift-g ctrl-a").await;
1393    cx.shared_state().await.assert_eq(indoc! {"
1394        ˇ2
1395        3
1396        4"});
1397
1398    cx.simulate_shared_keystrokes("u").await;
1399    cx.shared_state().await.assert_eq(indoc! {"
1400        ˇ1
1401        2
1402        3"});
1403}
1404
1405#[perf]
1406#[gpui::test]
1407async fn test_mouse_selection(cx: &mut TestAppContext) {
1408    let mut cx = VimTestContext::new(cx, true).await;
1409
1410    cx.set_state("ˇone two three", Mode::Normal);
1411
1412    let start_point = cx.pixel_position("one twˇo three");
1413    let end_point = cx.pixel_position("one ˇtwo three");
1414
1415    cx.simulate_mouse_down(start_point, MouseButton::Left, Modifiers::none());
1416    cx.simulate_mouse_move(end_point, MouseButton::Left, Modifiers::none());
1417    cx.simulate_mouse_up(end_point, MouseButton::Left, Modifiers::none());
1418
1419    cx.assert_state("one «ˇtwo» three", Mode::Visual)
1420}
1421
1422#[gpui::test]
1423async fn test_mouse_drag_across_anchor_does_not_drift(cx: &mut TestAppContext) {
1424    let mut cx = VimTestContext::new(cx, true).await;
1425
1426    cx.set_state("ˇone two three four", Mode::Normal);
1427
1428    let click_pos = cx.pixel_position("one ˇtwo three four");
1429    let drag_left = cx.pixel_position("ˇone two three four");
1430    let anchor_pos = cx.pixel_position("one tˇwo three four");
1431
1432    cx.simulate_mouse_down(click_pos, MouseButton::Left, Modifiers::none());
1433    cx.run_until_parked();
1434
1435    cx.simulate_mouse_move(drag_left, MouseButton::Left, Modifiers::none());
1436    cx.run_until_parked();
1437    cx.assert_state("«ˇone t»wo three four", Mode::Visual);
1438
1439    cx.simulate_mouse_move(anchor_pos, MouseButton::Left, Modifiers::none());
1440    cx.run_until_parked();
1441
1442    cx.simulate_mouse_move(drag_left, MouseButton::Left, Modifiers::none());
1443    cx.run_until_parked();
1444    cx.assert_state("«ˇone t»wo three four", Mode::Visual);
1445
1446    cx.simulate_mouse_move(anchor_pos, MouseButton::Left, Modifiers::none());
1447    cx.run_until_parked();
1448    cx.simulate_mouse_move(drag_left, MouseButton::Left, Modifiers::none());
1449    cx.run_until_parked();
1450    cx.assert_state("«ˇone t»wo three four", Mode::Visual);
1451
1452    cx.simulate_mouse_up(drag_left, MouseButton::Left, Modifiers::none());
1453}
1454
1455#[perf]
1456#[gpui::test]
1457async fn test_lowercase_marks(cx: &mut TestAppContext) {
1458    let mut cx = NeovimBackedTestContext::new(cx).await;
1459
1460    cx.set_shared_state("line one\nline ˇtwo\nline three").await;
1461    cx.simulate_shared_keystrokes("m a l ' a").await;
1462    cx.shared_state()
1463        .await
1464        .assert_eq("line one\nˇline two\nline three");
1465    cx.simulate_shared_keystrokes("` a").await;
1466    cx.shared_state()
1467        .await
1468        .assert_eq("line one\nline ˇtwo\nline three");
1469
1470    cx.simulate_shared_keystrokes("^ d ` a").await;
1471    cx.shared_state()
1472        .await
1473        .assert_eq("line one\nˇtwo\nline three");
1474}
1475
1476#[perf]
1477#[gpui::test]
1478async fn test_lt_gt_marks(cx: &mut TestAppContext) {
1479    let mut cx = NeovimBackedTestContext::new(cx).await;
1480
1481    cx.set_shared_state(indoc!(
1482        "
1483        Line one
1484        Line two
1485        Line ˇthree
1486        Line four
1487        Line five
1488    "
1489    ))
1490    .await;
1491
1492    cx.simulate_shared_keystrokes("v j escape k k").await;
1493
1494    cx.simulate_shared_keystrokes("' <").await;
1495    cx.shared_state().await.assert_eq(indoc! {"
1496        Line one
1497        Line two
1498        ˇLine three
1499        Line four
1500        Line five
1501    "});
1502
1503    cx.simulate_shared_keystrokes("` <").await;
1504    cx.shared_state().await.assert_eq(indoc! {"
1505        Line one
1506        Line two
1507        Line ˇthree
1508        Line four
1509        Line five
1510    "});
1511
1512    cx.simulate_shared_keystrokes("' >").await;
1513    cx.shared_state().await.assert_eq(indoc! {"
1514        Line one
1515        Line two
1516        Line three
1517        ˇLine four
1518        Line five
1519    "
1520    });
1521
1522    cx.simulate_shared_keystrokes("` >").await;
1523    cx.shared_state().await.assert_eq(indoc! {"
1524        Line one
1525        Line two
1526        Line three
1527        Line ˇfour
1528        Line five
1529    "
1530    });
1531
1532    cx.simulate_shared_keystrokes("v i w o escape").await;
1533    cx.simulate_shared_keystrokes("` >").await;
1534    cx.shared_state().await.assert_eq(indoc! {"
1535        Line one
1536        Line two
1537        Line three
1538        Line fouˇr
1539        Line five
1540    "
1541    });
1542    cx.simulate_shared_keystrokes("` <").await;
1543    cx.shared_state().await.assert_eq(indoc! {"
1544        Line one
1545        Line two
1546        Line three
1547        Line ˇfour
1548        Line five
1549    "
1550    });
1551}
1552
1553#[perf]
1554#[gpui::test]
1555async fn test_caret_mark(cx: &mut TestAppContext) {
1556    let mut cx = NeovimBackedTestContext::new(cx).await;
1557
1558    cx.set_shared_state(indoc!(
1559        "
1560        Line one
1561        Line two
1562        Line three
1563        ˇLine four
1564        Line five
1565    "
1566    ))
1567    .await;
1568
1569    cx.simulate_shared_keystrokes("c w shift-s t r a i g h t space t h i n g escape j j")
1570        .await;
1571
1572    cx.simulate_shared_keystrokes("' ^").await;
1573    cx.shared_state().await.assert_eq(indoc! {"
1574        Line one
1575        Line two
1576        Line three
1577        ˇStraight thing four
1578        Line five
1579    "
1580    });
1581
1582    cx.simulate_shared_keystrokes("` ^").await;
1583    cx.shared_state().await.assert_eq(indoc! {"
1584        Line one
1585        Line two
1586        Line three
1587        Straight thingˇ four
1588        Line five
1589    "
1590    });
1591
1592    cx.simulate_shared_keystrokes("k a ! escape k g i ?").await;
1593    cx.shared_state().await.assert_eq(indoc! {"
1594        Line one
1595        Line two
1596        Line three!?ˇ
1597        Straight thing four
1598        Line five
1599    "
1600    });
1601}
1602
1603#[cfg(target_os = "macos")]
1604#[perf]
1605#[gpui::test]
1606async fn test_dw_eol(cx: &mut gpui::TestAppContext) {
1607    let mut cx = NeovimBackedTestContext::new(cx).await;
1608
1609    cx.set_shared_wrap(12).await;
1610    cx.set_shared_state("twelve ˇchar twelve char\ntwelve char")
1611        .await;
1612    cx.simulate_shared_keystrokes("d w").await;
1613    cx.shared_state()
1614        .await
1615        .assert_eq("twelve ˇtwelve char\ntwelve char");
1616}
1617
1618#[perf]
1619#[gpui::test]
1620async fn test_toggle_comments(cx: &mut gpui::TestAppContext) {
1621    let mut cx = VimTestContext::new(cx, true).await;
1622
1623    let language = std::sync::Arc::new(language::Language::new(
1624        language::LanguageConfig {
1625            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
1626            ..Default::default()
1627        },
1628        Some(language::tree_sitter_rust::LANGUAGE.into()),
1629    ));
1630    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
1631
1632    // works in normal model
1633    cx.set_state(
1634        indoc! {"
1635      ˇone
1636      two
1637      three
1638      "},
1639        Mode::Normal,
1640    );
1641    cx.simulate_keystrokes("g c c");
1642    cx.assert_state(
1643        indoc! {"
1644          // ˇone
1645          two
1646          three
1647          "},
1648        Mode::Normal,
1649    );
1650
1651    // works in visual mode
1652    cx.simulate_keystrokes("v j g c");
1653    cx.assert_state(
1654        indoc! {"
1655          // // ˇone
1656          // two
1657          three
1658          "},
1659        Mode::Normal,
1660    );
1661
1662    // works in visual line mode
1663    cx.simulate_keystrokes("shift-v j g c");
1664    cx.assert_state(
1665        indoc! {"
1666          // ˇone
1667          two
1668          three
1669          "},
1670        Mode::Normal,
1671    );
1672
1673    // works with count
1674    cx.simulate_keystrokes("g c 2 j");
1675    cx.assert_state(
1676        indoc! {"
1677            // // ˇone
1678            // two
1679            // three
1680            "},
1681        Mode::Normal,
1682    );
1683
1684    // works with motion object
1685    cx.simulate_keystrokes("shift-g");
1686    cx.simulate_keystrokes("g c g g");
1687    cx.assert_state(
1688        indoc! {"
1689            // one
1690            two
1691            three
1692            ˇ"},
1693        Mode::Normal,
1694    );
1695}
1696
1697#[perf]
1698#[gpui::test]
1699async fn test_toggle_block_comments(cx: &mut gpui::TestAppContext) {
1700    let mut cx = VimTestContext::new(cx, true).await;
1701
1702    let language = std::sync::Arc::new(language::Language::new(
1703        language::LanguageConfig {
1704            block_comment: Some(language::BlockCommentConfig {
1705                start: "/* ".into(),
1706                prefix: "".into(),
1707                end: " */".into(),
1708                tab_size: 1,
1709            }),
1710            ..Default::default()
1711        },
1712        Some(language::tree_sitter_rust::LANGUAGE.into()),
1713    ));
1714    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
1715
1716    // works in normal mode with current-line shorthand
1717    cx.set_state(
1718        indoc! {"
1719        ˇone
1720        two
1721        three
1722        "},
1723        Mode::Normal,
1724    );
1725    cx.simulate_keystrokes("g b c");
1726    cx.assert_state(
1727        indoc! {"
1728        /* ˇone */
1729        two
1730        three
1731        "},
1732        Mode::Normal,
1733    );
1734
1735    // toggle off with cursor inside the comment
1736    cx.simulate_keystrokes("g b c");
1737    cx.assert_state(
1738        indoc! {"
1739        ˇone
1740        two
1741        three
1742        "},
1743        Mode::Normal,
1744    );
1745
1746    // works in visual line mode (wraps full lines)
1747    cx.simulate_keystrokes("shift-v j g b");
1748    cx.assert_state(
1749        indoc! {"
1750        /* ˇone
1751        two */
1752        three
1753        "},
1754        Mode::Normal,
1755    );
1756
1757    // works in visual mode and restores the cursor to the selection start
1758    cx.set_state(
1759        indoc! {"
1760        «oneˇ»
1761        two
1762        three
1763        "},
1764        Mode::Visual,
1765    );
1766    cx.simulate_keystrokes("g b");
1767    cx.assert_state(
1768        indoc! {"
1769        /* ˇone */
1770        two
1771        three
1772        "},
1773        Mode::Normal,
1774    );
1775
1776    // works with multiple visual selections and restores each cursor
1777    cx.set_state(
1778        indoc! {"
1779        «oneˇ» «twoˇ»
1780        three
1781        "},
1782        Mode::Visual,
1783    );
1784    cx.simulate_keystrokes("g b");
1785    cx.assert_state(
1786        indoc! {"
1787        /* ˇone */ /* ˇtwo */
1788        three
1789        "},
1790        Mode::Normal,
1791    );
1792
1793    // works with count
1794    cx.set_state(
1795        indoc! {"
1796        ˇone
1797        two
1798        three
1799        "},
1800        Mode::Normal,
1801    );
1802    cx.simulate_keystrokes("g b 2 j");
1803    cx.assert_state(
1804        indoc! {"
1805        /* ˇone
1806        two
1807        three */
1808        "},
1809        Mode::Normal,
1810    );
1811
1812    // works with motion object
1813    cx.simulate_keystrokes("shift-g");
1814    cx.simulate_keystrokes("g b g g");
1815    cx.assert_state(
1816        indoc! {"
1817        one
1818        two
1819        three
1820        ˇ"},
1821        Mode::Normal,
1822    );
1823}
1824
1825#[perf]
1826#[gpui::test]
1827async fn test_find_multibyte(cx: &mut gpui::TestAppContext) {
1828    let mut cx = NeovimBackedTestContext::new(cx).await;
1829
1830    cx.set_shared_state(r#"<label for="guests">ˇPočet hostů</label>"#)
1831        .await;
1832
1833    cx.simulate_shared_keystrokes("c t < o escape").await;
1834    cx.shared_state()
1835        .await
1836        .assert_eq(r#"<label for="guests">ˇo</label>"#);
1837}
1838
1839#[perf]
1840#[gpui::test]
1841async fn test_sneak(cx: &mut gpui::TestAppContext) {
1842    let mut cx = VimTestContext::new(cx, true).await;
1843
1844    cx.update(|_window, cx| {
1845        cx.bind_keys([
1846            KeyBinding::new(
1847                "s",
1848                PushSneak { first_char: None },
1849                Some("vim_mode == normal"),
1850            ),
1851            KeyBinding::new(
1852                "shift-s",
1853                PushSneakBackward { first_char: None },
1854                Some("vim_mode == normal"),
1855            ),
1856            KeyBinding::new(
1857                "shift-s",
1858                PushSneakBackward { first_char: None },
1859                Some("vim_mode == visual"),
1860            ),
1861        ])
1862    });
1863
1864    // Sneak forwards multibyte & multiline
1865    cx.set_state(
1866        indoc! {
1867            r#"<labelˇ for="guests">
1868                    Počet hostů
1869                </label>"#
1870        },
1871        Mode::Normal,
1872    );
1873    cx.simulate_keystrokes("s t ů");
1874    cx.assert_state(
1875        indoc! {
1876            r#"<label for="guests">
1877                Počet hosˇtů
1878            </label>"#
1879        },
1880        Mode::Normal,
1881    );
1882
1883    // Visual sneak backwards multibyte & multiline
1884    cx.simulate_keystrokes("v S < l");
1885    cx.assert_state(
1886        indoc! {
1887            r#"«ˇ<label for="guests">
1888                Počet host»ů
1889            </label>"#
1890        },
1891        Mode::Visual,
1892    );
1893
1894    // Sneak backwards repeated
1895    cx.set_state(r#"11 12 13 ˇ14"#, Mode::Normal);
1896    cx.simulate_keystrokes("S space 1");
1897    cx.assert_state(r#"11 12ˇ 13 14"#, Mode::Normal);
1898    cx.simulate_keystrokes(";");
1899    cx.assert_state(r#"11ˇ 12 13 14"#, Mode::Normal);
1900}
1901
1902#[perf]
1903#[gpui::test]
1904async fn test_plus_minus(cx: &mut gpui::TestAppContext) {
1905    let mut cx = NeovimBackedTestContext::new(cx).await;
1906
1907    cx.set_shared_state(indoc! {
1908        "one
1909           two
1910        thrˇee
1911    "})
1912        .await;
1913
1914    cx.simulate_shared_keystrokes("-").await;
1915    cx.shared_state().await.assert_matches();
1916    cx.simulate_shared_keystrokes("-").await;
1917    cx.shared_state().await.assert_matches();
1918    cx.simulate_shared_keystrokes("+").await;
1919    cx.shared_state().await.assert_matches();
1920}
1921
1922#[perf]
1923#[gpui::test]
1924async fn test_command_alias(cx: &mut gpui::TestAppContext) {
1925    let mut cx = VimTestContext::new(cx, true).await;
1926    cx.update_global(|store: &mut SettingsStore, cx| {
1927        store.update_user_settings(cx, |s| {
1928            let mut aliases = HashMap::default();
1929            aliases.insert("Q".to_string(), "upper".to_string());
1930            s.workspace.command_aliases = aliases
1931        });
1932    });
1933
1934    cx.set_state("ˇhello world", Mode::Normal);
1935    cx.simulate_keystrokes(": Q");
1936    cx.set_state("ˇHello world", Mode::Normal);
1937}
1938
1939#[perf]
1940#[gpui::test]
1941async fn test_remap_adjacent_dog_cat(cx: &mut gpui::TestAppContext) {
1942    let mut cx = NeovimBackedTestContext::new(cx).await;
1943    cx.update(|_, cx| {
1944        cx.bind_keys([
1945            KeyBinding::new(
1946                "d o g",
1947                workspace::SendKeystrokes("🐶".to_string()),
1948                Some("vim_mode == insert"),
1949            ),
1950            KeyBinding::new(
1951                "c a t",
1952                workspace::SendKeystrokes("🐱".to_string()),
1953                Some("vim_mode == insert"),
1954            ),
1955        ])
1956    });
1957    cx.neovim.exec("imap dog 🐶").await;
1958    cx.neovim.exec("imap cat 🐱").await;
1959
1960    cx.set_shared_state("ˇ").await;
1961    cx.simulate_shared_keystrokes("i d o g").await;
1962    cx.shared_state().await.assert_eq("🐶ˇ");
1963
1964    cx.set_shared_state("ˇ").await;
1965    cx.simulate_shared_keystrokes("i d o d o g").await;
1966    cx.shared_state().await.assert_eq("do🐶ˇ");
1967
1968    cx.set_shared_state("ˇ").await;
1969    cx.simulate_shared_keystrokes("i d o c a t").await;
1970    cx.shared_state().await.assert_eq("do🐱ˇ");
1971}
1972
1973#[perf]
1974#[gpui::test]
1975async fn test_remap_nested_pineapple(cx: &mut gpui::TestAppContext) {
1976    let mut cx = NeovimBackedTestContext::new(cx).await;
1977    cx.update(|_, cx| {
1978        cx.bind_keys([
1979            KeyBinding::new(
1980                "p i n",
1981                workspace::SendKeystrokes("📌".to_string()),
1982                Some("vim_mode == insert"),
1983            ),
1984            KeyBinding::new(
1985                "p i n e",
1986                workspace::SendKeystrokes("🌲".to_string()),
1987                Some("vim_mode == insert"),
1988            ),
1989            KeyBinding::new(
1990                "p i n e a p p l e",
1991                workspace::SendKeystrokes("🍍".to_string()),
1992                Some("vim_mode == insert"),
1993            ),
1994        ])
1995    });
1996    cx.neovim.exec("imap pin 📌").await;
1997    cx.neovim.exec("imap pine 🌲").await;
1998    cx.neovim.exec("imap pineapple 🍍").await;
1999
2000    cx.set_shared_state("ˇ").await;
2001    cx.simulate_shared_keystrokes("i p i n").await;
2002    cx.executor().advance_clock(Duration::from_millis(1000));
2003    cx.run_until_parked();
2004    cx.shared_state().await.assert_eq("📌ˇ");
2005
2006    cx.set_shared_state("ˇ").await;
2007    cx.simulate_shared_keystrokes("i p i n e").await;
2008    cx.executor().advance_clock(Duration::from_millis(1000));
2009    cx.run_until_parked();
2010    cx.shared_state().await.assert_eq("🌲ˇ");
2011
2012    cx.set_shared_state("ˇ").await;
2013    cx.simulate_shared_keystrokes("i p i n e a p p l e").await;
2014    cx.shared_state().await.assert_eq("🍍ˇ");
2015}
2016
2017#[perf]
2018#[gpui::test]
2019async fn test_remap_recursion(cx: &mut gpui::TestAppContext) {
2020    let mut cx = NeovimBackedTestContext::new(cx).await;
2021    cx.update(|_, cx| {
2022        cx.bind_keys([KeyBinding::new(
2023            "x",
2024            workspace::SendKeystrokes("\" _ x".to_string()),
2025            Some("VimControl"),
2026        )]);
2027        cx.bind_keys([KeyBinding::new(
2028            "y",
2029            workspace::SendKeystrokes("2 x".to_string()),
2030            Some("VimControl"),
2031        )])
2032    });
2033    cx.neovim.exec("noremap x \"_x").await;
2034    cx.neovim.exec("map y 2x").await;
2035
2036    cx.set_shared_state("ˇhello").await;
2037    cx.simulate_shared_keystrokes("d l").await;
2038    cx.shared_clipboard().await.assert_eq("h");
2039    cx.simulate_shared_keystrokes("y").await;
2040    cx.shared_clipboard().await.assert_eq("h");
2041    cx.shared_state().await.assert_eq("ˇlo");
2042}
2043
2044#[perf]
2045#[gpui::test]
2046async fn test_escape_while_waiting(cx: &mut gpui::TestAppContext) {
2047    let mut cx = NeovimBackedTestContext::new(cx).await;
2048    cx.set_shared_state("ˇhi").await;
2049    cx.simulate_shared_keystrokes("\" + escape x").await;
2050    cx.shared_state().await.assert_eq("ˇi");
2051}
2052
2053#[perf]
2054#[gpui::test]
2055async fn test_ctrl_w_override(cx: &mut gpui::TestAppContext) {
2056    let mut cx = NeovimBackedTestContext::new(cx).await;
2057    cx.update(|_, cx| {
2058        cx.bind_keys([KeyBinding::new("ctrl-w", DeleteLine, None)]);
2059    });
2060    cx.neovim.exec("map <c-w> D").await;
2061    cx.set_shared_state("ˇhi").await;
2062    cx.simulate_shared_keystrokes("ctrl-w").await;
2063    cx.shared_state().await.assert_eq("ˇ");
2064}
2065
2066#[perf]
2067#[gpui::test]
2068async fn test_visual_indent_count(cx: &mut gpui::TestAppContext) {
2069    let mut cx = VimTestContext::new(cx, true).await;
2070    cx.set_state("ˇhi", Mode::Normal);
2071    cx.simulate_keystrokes("shift-v 3 >");
2072    cx.assert_state("            ˇhi", Mode::Normal);
2073    cx.simulate_keystrokes("shift-v 2 <");
2074    cx.assert_state("    ˇhi", Mode::Normal);
2075}
2076
2077#[perf]
2078#[gpui::test]
2079async fn test_record_replay_recursion(cx: &mut gpui::TestAppContext) {
2080    let mut cx = NeovimBackedTestContext::new(cx).await;
2081
2082    cx.set_shared_state("ˇhello world").await;
2083    cx.simulate_shared_keystrokes(">").await;
2084    cx.simulate_shared_keystrokes(".").await;
2085    cx.simulate_shared_keystrokes(".").await;
2086    cx.simulate_shared_keystrokes(".").await;
2087    cx.shared_state().await.assert_eq("ˇhello world");
2088}
2089
2090#[perf]
2091#[gpui::test]
2092async fn test_blackhole_register(cx: &mut gpui::TestAppContext) {
2093    let mut cx = NeovimBackedTestContext::new(cx).await;
2094
2095    cx.set_shared_state("ˇhello world").await;
2096    cx.simulate_shared_keystrokes("d i w \" _ d a w").await;
2097    cx.simulate_shared_keystrokes("p").await;
2098    cx.shared_state().await.assert_eq("hellˇo");
2099}
2100
2101#[perf]
2102#[gpui::test]
2103async fn test_sentence_backwards(cx: &mut gpui::TestAppContext) {
2104    let mut cx = NeovimBackedTestContext::new(cx).await;
2105
2106    cx.set_shared_state("one\n\ntwo\nthree\nˇ\nfour").await;
2107    cx.simulate_shared_keystrokes("(").await;
2108    cx.shared_state()
2109        .await
2110        .assert_eq("one\n\nˇtwo\nthree\n\nfour");
2111
2112    cx.set_shared_state("hello.\n\n\nworˇld.").await;
2113    cx.simulate_shared_keystrokes("(").await;
2114    cx.shared_state().await.assert_eq("hello.\n\n\nˇworld.");
2115    cx.simulate_shared_keystrokes("(").await;
2116    cx.shared_state().await.assert_eq("hello.\n\nˇ\nworld.");
2117    cx.simulate_shared_keystrokes("(").await;
2118    cx.shared_state().await.assert_eq("ˇhello.\n\n\nworld.");
2119
2120    cx.set_shared_state("hello. worlˇd.").await;
2121    cx.simulate_shared_keystrokes("(").await;
2122    cx.shared_state().await.assert_eq("hello. ˇworld.");
2123    cx.simulate_shared_keystrokes("(").await;
2124    cx.shared_state().await.assert_eq("ˇhello. world.");
2125
2126    cx.set_shared_state(". helˇlo.").await;
2127    cx.simulate_shared_keystrokes("(").await;
2128    cx.shared_state().await.assert_eq(". ˇhello.");
2129    cx.simulate_shared_keystrokes("(").await;
2130    cx.shared_state().await.assert_eq(". ˇhello.");
2131
2132    cx.set_shared_state(indoc! {
2133        "{
2134            hello_world();
2135        ˇ}"
2136    })
2137    .await;
2138    cx.simulate_shared_keystrokes("(").await;
2139    cx.shared_state().await.assert_eq(indoc! {
2140        "ˇ{
2141            hello_world();
2142        }"
2143    });
2144
2145    cx.set_shared_state(indoc! {
2146        "Hello! World..?
2147
2148        \tHello! World... ˇ"
2149    })
2150    .await;
2151    cx.simulate_shared_keystrokes("(").await;
2152    cx.shared_state().await.assert_eq(indoc! {
2153        "Hello! World..?
2154
2155        \tHello! ˇWorld... "
2156    });
2157    cx.simulate_shared_keystrokes("(").await;
2158    cx.shared_state().await.assert_eq(indoc! {
2159        "Hello! World..?
2160
2161        \tˇHello! World... "
2162    });
2163    cx.simulate_shared_keystrokes("(").await;
2164    cx.shared_state().await.assert_eq(indoc! {
2165        "Hello! World..?
2166        ˇ
2167        \tHello! World... "
2168    });
2169    cx.simulate_shared_keystrokes("(").await;
2170    cx.shared_state().await.assert_eq(indoc! {
2171        "Hello! ˇWorld..?
2172
2173        \tHello! World... "
2174    });
2175}
2176
2177#[perf]
2178#[gpui::test]
2179async fn test_sentence_forwards(cx: &mut gpui::TestAppContext) {
2180    let mut cx = NeovimBackedTestContext::new(cx).await;
2181
2182    cx.set_shared_state("helˇlo.\n\n\nworld.").await;
2183    cx.simulate_shared_keystrokes(")").await;
2184    cx.shared_state().await.assert_eq("hello.\nˇ\n\nworld.");
2185    cx.simulate_shared_keystrokes(")").await;
2186    cx.shared_state().await.assert_eq("hello.\n\n\nˇworld.");
2187    cx.simulate_shared_keystrokes(")").await;
2188    cx.shared_state().await.assert_eq("hello.\n\n\nworldˇ.");
2189
2190    cx.set_shared_state("helˇlo.\n\n\nworld.").await;
2191}
2192
2193#[perf]
2194#[gpui::test]
2195async fn test_ctrl_o_visual(cx: &mut gpui::TestAppContext) {
2196    let mut cx = NeovimBackedTestContext::new(cx).await;
2197
2198    cx.set_shared_state("helloˇ world.").await;
2199    cx.simulate_shared_keystrokes("i ctrl-o v b r l").await;
2200    cx.shared_state().await.assert_eq("ˇllllllworld.");
2201    cx.simulate_shared_keystrokes("ctrl-o v f w d").await;
2202    cx.shared_state().await.assert_eq("ˇorld.");
2203}
2204
2205#[perf]
2206#[gpui::test]
2207async fn test_ctrl_o_position(cx: &mut gpui::TestAppContext) {
2208    let mut cx = NeovimBackedTestContext::new(cx).await;
2209
2210    cx.set_shared_state("helˇlo world.").await;
2211    cx.simulate_shared_keystrokes("i ctrl-o d i w").await;
2212    cx.shared_state().await.assert_eq("ˇ world.");
2213    cx.simulate_shared_keystrokes("ctrl-o p").await;
2214    cx.shared_state().await.assert_eq(" helloˇworld.");
2215}
2216
2217#[perf]
2218#[gpui::test]
2219async fn test_ctrl_o_dot(cx: &mut gpui::TestAppContext) {
2220    let mut cx = NeovimBackedTestContext::new(cx).await;
2221
2222    cx.set_shared_state("heˇllo world.").await;
2223    cx.simulate_shared_keystrokes("x i ctrl-o .").await;
2224    cx.shared_state().await.assert_eq("heˇo world.");
2225    cx.simulate_shared_keystrokes("l l escape .").await;
2226    cx.shared_state().await.assert_eq("hellˇllo world.");
2227}
2228
2229#[perf(iterations = 1)]
2230#[gpui::test]
2231async fn test_folded_multibuffer_excerpts(cx: &mut gpui::TestAppContext) {
2232    VimTestContext::init(cx);
2233    cx.update(|cx| {
2234        VimTestContext::init_keybindings(true, cx);
2235    });
2236    let (editor, cx) = cx.add_window_view(|window, cx| {
2237        let multi_buffer = MultiBuffer::build_multi(
2238            [
2239                ("111\n222\n333\n444\n", vec![Point::row_range(0..2)]),
2240                ("aaa\nbbb\nccc\nddd\n", vec![Point::row_range(0..2)]),
2241                ("AAA\nBBB\nCCC\nDDD\n", vec![Point::row_range(0..2)]),
2242                ("one\ntwo\nthr\nfou\n", vec![Point::row_range(0..2)]),
2243            ],
2244            cx,
2245        );
2246        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
2247
2248        let buffer_ids = multi_buffer
2249            .read(cx)
2250            .snapshot(cx)
2251            .excerpts()
2252            .map(|excerpt| excerpt.context.start.buffer_id)
2253            .collect::<Vec<_>>();
2254        // fold all but the second buffer, so that we test navigating between two
2255        // adjacent folded buffers, as well as folded buffers at the start and
2256        // end the multibuffer
2257        editor.fold_buffer(buffer_ids[0], cx);
2258        editor.fold_buffer(buffer_ids[2], cx);
2259        editor.fold_buffer(buffer_ids[3], cx);
2260
2261        editor
2262    });
2263    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
2264
2265    cx.assert_excerpts_with_selections(indoc! {"
2266        [EXCERPT]
2267        ˇ[FOLDED]
2268        [EXCERPT]
2269        aaa
2270        bbb
2271        [EXCERPT]
2272        [FOLDED]
2273        [EXCERPT]
2274        [FOLDED]
2275        "
2276    });
2277    cx.simulate_keystroke("j");
2278    cx.assert_excerpts_with_selections(indoc! {"
2279        [EXCERPT]
2280        [FOLDED]
2281        [EXCERPT]
2282        ˇaaa
2283        bbb
2284        [EXCERPT]
2285        [FOLDED]
2286        [EXCERPT]
2287        [FOLDED]
2288        "
2289    });
2290    cx.simulate_keystroke("j");
2291    cx.simulate_keystroke("j");
2292    cx.assert_excerpts_with_selections(indoc! {"
2293        [EXCERPT]
2294        [FOLDED]
2295        [EXCERPT]
2296        aaa
2297        bbb
2298        ˇ[EXCERPT]
2299        [FOLDED]
2300        [EXCERPT]
2301        [FOLDED]
2302        "
2303    });
2304    cx.simulate_keystroke("j");
2305    cx.assert_excerpts_with_selections(indoc! {"
2306        [EXCERPT]
2307        [FOLDED]
2308        [EXCERPT]
2309        aaa
2310        bbb
2311        [EXCERPT]
2312        ˇ[FOLDED]
2313        [EXCERPT]
2314        [FOLDED]
2315        "
2316    });
2317    cx.simulate_keystroke("j");
2318    cx.assert_excerpts_with_selections(indoc! {"
2319        [EXCERPT]
2320        [FOLDED]
2321        [EXCERPT]
2322        aaa
2323        bbb
2324        [EXCERPT]
2325        [FOLDED]
2326        [EXCERPT]
2327        ˇ[FOLDED]
2328        "
2329    });
2330    cx.simulate_keystroke("k");
2331    cx.assert_excerpts_with_selections(indoc! {"
2332        [EXCERPT]
2333        [FOLDED]
2334        [EXCERPT]
2335        aaa
2336        bbb
2337        [EXCERPT]
2338        ˇ[FOLDED]
2339        [EXCERPT]
2340        [FOLDED]
2341        "
2342    });
2343    cx.simulate_keystroke("k");
2344    cx.simulate_keystroke("k");
2345    cx.simulate_keystroke("k");
2346    cx.assert_excerpts_with_selections(indoc! {"
2347        [EXCERPT]
2348        [FOLDED]
2349        [EXCERPT]
2350        ˇaaa
2351        bbb
2352        [EXCERPT]
2353        [FOLDED]
2354        [EXCERPT]
2355        [FOLDED]
2356        "
2357    });
2358    cx.simulate_keystroke("k");
2359    cx.assert_excerpts_with_selections(indoc! {"
2360        [EXCERPT]
2361        ˇ[FOLDED]
2362        [EXCERPT]
2363        aaa
2364        bbb
2365        [EXCERPT]
2366        [FOLDED]
2367        [EXCERPT]
2368        [FOLDED]
2369        "
2370    });
2371    cx.simulate_keystroke("shift-g");
2372    cx.assert_excerpts_with_selections(indoc! {"
2373        [EXCERPT]
2374        [FOLDED]
2375        [EXCERPT]
2376        aaa
2377        bbb
2378        [EXCERPT]
2379        [FOLDED]
2380        [EXCERPT]
2381        ˇ[FOLDED]
2382        "
2383    });
2384    cx.simulate_keystrokes("g g");
2385    cx.assert_excerpts_with_selections(indoc! {"
2386        [EXCERPT]
2387        ˇ[FOLDED]
2388        [EXCERPT]
2389        aaa
2390        bbb
2391        [EXCERPT]
2392        [FOLDED]
2393        [EXCERPT]
2394        [FOLDED]
2395        "
2396    });
2397    cx.update_editor(|editor, _, cx| {
2398        let buffer_ids = editor
2399            .buffer()
2400            .read(cx)
2401            .snapshot(cx)
2402            .excerpts()
2403            .map(|excerpt| excerpt.context.start.buffer_id)
2404            .collect::<Vec<_>>();
2405        editor.fold_buffer(buffer_ids[1], cx);
2406    });
2407
2408    cx.assert_excerpts_with_selections(indoc! {"
2409        [EXCERPT]
2410        ˇ[FOLDED]
2411        [EXCERPT]
2412        [FOLDED]
2413        [EXCERPT]
2414        [FOLDED]
2415        [EXCERPT]
2416        [FOLDED]
2417        "
2418    });
2419    cx.simulate_keystrokes("2 j");
2420    cx.assert_excerpts_with_selections(indoc! {"
2421        [EXCERPT]
2422        [FOLDED]
2423        [EXCERPT]
2424        [FOLDED]
2425        [EXCERPT]
2426        ˇ[FOLDED]
2427        [EXCERPT]
2428        [FOLDED]
2429        "
2430    });
2431}
2432
2433#[perf]
2434#[gpui::test]
2435async fn test_delete_paragraph_motion(cx: &mut gpui::TestAppContext) {
2436    let mut cx = NeovimBackedTestContext::new(cx).await;
2437    cx.set_shared_state(indoc! {
2438        "ˇhello world.
2439
2440        hello world.
2441        "
2442    })
2443    .await;
2444    cx.simulate_shared_keystrokes("y }").await;
2445    cx.shared_clipboard().await.assert_eq("hello world.\n");
2446    cx.simulate_shared_keystrokes("d }").await;
2447    cx.shared_state().await.assert_eq("ˇ\nhello world.\n");
2448    cx.shared_clipboard().await.assert_eq("hello world.\n");
2449
2450    cx.set_shared_state(indoc! {
2451        "helˇlo world.
2452
2453            hello world.
2454            "
2455    })
2456    .await;
2457    cx.simulate_shared_keystrokes("y }").await;
2458    cx.shared_clipboard().await.assert_eq("lo world.");
2459    cx.simulate_shared_keystrokes("d }").await;
2460    cx.shared_state().await.assert_eq("heˇl\n\nhello world.\n");
2461    cx.shared_clipboard().await.assert_eq("lo world.");
2462}
2463
2464#[perf]
2465#[gpui::test]
2466async fn test_delete_unmatched_brace(cx: &mut gpui::TestAppContext) {
2467    let mut cx = NeovimBackedTestContext::new(cx).await;
2468    cx.set_shared_state(indoc! {
2469        "fn o(wow: i32) {
2470          othˇ(wow)
2471          oth(wow)
2472        }
2473        "
2474    })
2475    .await;
2476    cx.simulate_shared_keystrokes("d ] }").await;
2477    cx.shared_state().await.assert_eq(indoc! {
2478        "fn o(wow: i32) {
2479          otˇh
2480        }
2481        "
2482    });
2483    cx.shared_clipboard().await.assert_eq("(wow)\n  oth(wow)");
2484    cx.set_shared_state(indoc! {
2485        "fn o(wow: i32) {
2486          ˇoth(wow)
2487          oth(wow)
2488        }
2489        "
2490    })
2491    .await;
2492    cx.simulate_shared_keystrokes("d ] }").await;
2493    cx.shared_state().await.assert_eq(indoc! {
2494        "fn o(wow: i32) {
2495         ˇ}
2496        "
2497    });
2498    cx.shared_clipboard()
2499        .await
2500        .assert_eq("  oth(wow)\n  oth(wow)\n");
2501}
2502
2503#[perf]
2504#[gpui::test]
2505async fn test_paragraph_multi_delete(cx: &mut gpui::TestAppContext) {
2506    let mut cx = NeovimBackedTestContext::new(cx).await;
2507    cx.set_shared_state(indoc! {
2508        "
2509        Emacs is
2510        ˇa great
2511
2512        operating system
2513
2514        all it lacks
2515        is a
2516
2517        decent text editor
2518        "
2519    })
2520    .await;
2521
2522    cx.simulate_shared_keystrokes("2 d a p").await;
2523    cx.shared_state().await.assert_eq(indoc! {
2524        "
2525        ˇall it lacks
2526        is a
2527
2528        decent text editor
2529        "
2530    });
2531
2532    cx.simulate_shared_keystrokes("d a p").await;
2533    cx.shared_clipboard()
2534        .await
2535        .assert_eq("all it lacks\nis a\n\n");
2536
2537    //reset to initial state
2538    cx.simulate_shared_keystrokes("2 u").await;
2539
2540    cx.simulate_shared_keystrokes("4 d a p").await;
2541    cx.shared_state().await.assert_eq(indoc! {"ˇ"});
2542}
2543
2544#[perf]
2545#[gpui::test]
2546async fn test_yank_paragraph_with_paste(cx: &mut gpui::TestAppContext) {
2547    let mut cx = NeovimBackedTestContext::new(cx).await;
2548    cx.set_shared_state(indoc! {
2549        "
2550        first paragraph
2551        ˇstill first
2552
2553        second paragraph
2554        still second
2555
2556        third paragraph
2557        "
2558    })
2559    .await;
2560
2561    cx.simulate_shared_keystrokes("y a p").await;
2562    cx.shared_clipboard()
2563        .await
2564        .assert_eq("first paragraph\nstill first\n\n");
2565
2566    cx.simulate_shared_keystrokes("j j p").await;
2567    cx.shared_state().await.assert_eq(indoc! {
2568        "
2569        first paragraph
2570        still first
2571
2572        ˇfirst paragraph
2573        still first
2574
2575        second paragraph
2576        still second
2577
2578        third paragraph
2579        "
2580    });
2581}
2582
2583#[perf]
2584#[gpui::test]
2585async fn test_change_paragraph(cx: &mut gpui::TestAppContext) {
2586    let mut cx = NeovimBackedTestContext::new(cx).await;
2587    cx.set_shared_state(indoc! {
2588        "
2589        first paragraph
2590        ˇstill first
2591
2592        second paragraph
2593        still second
2594
2595        third paragraph
2596        "
2597    })
2598    .await;
2599
2600    cx.simulate_shared_keystrokes("c a p").await;
2601    cx.shared_clipboard()
2602        .await
2603        .assert_eq("first paragraph\nstill first\n\n");
2604
2605    cx.simulate_shared_keystrokes("escape").await;
2606    cx.shared_state().await.assert_eq(indoc! {
2607        "
2608        ˇ
2609        second paragraph
2610        still second
2611
2612        third paragraph
2613        "
2614    });
2615}
2616
2617#[perf]
2618#[gpui::test]
2619async fn test_multi_cursor_replay(cx: &mut gpui::TestAppContext) {
2620    let mut cx = VimTestContext::new(cx, true).await;
2621    cx.set_state(
2622        indoc! {
2623            "
2624        oˇne one one
2625
2626        two two two
2627        "
2628        },
2629        Mode::Normal,
2630    );
2631
2632    cx.simulate_keystrokes("3 g l s wow escape escape");
2633    cx.assert_state(
2634        indoc! {
2635            "
2636        woˇw wow wow
2637
2638        two two two
2639        "
2640        },
2641        Mode::Normal,
2642    );
2643
2644    cx.simulate_keystrokes("2 j 3 g l .");
2645    cx.assert_state(
2646        indoc! {
2647            "
2648        wow wow wow
2649
2650        woˇw woˇw woˇw
2651        "
2652        },
2653        Mode::Normal,
2654    );
2655}
2656
2657#[gpui::test]
2658async fn test_clipping_on_mode_change(cx: &mut gpui::TestAppContext) {
2659    let mut cx = VimTestContext::new(cx, true).await;
2660
2661    cx.set_state(
2662        indoc! {
2663        "
2664        ˇverylongline
2665        andsomelinebelow
2666        "
2667        },
2668        Mode::Normal,
2669    );
2670
2671    cx.simulate_keystrokes("v e");
2672    cx.assert_state(
2673        indoc! {
2674        "
2675        «verylonglineˇ»
2676        andsomelinebelow
2677        "
2678        },
2679        Mode::Visual,
2680    );
2681
2682    let mut pixel_position = cx.update_editor(|editor, window, cx| {
2683        let snapshot = editor.snapshot(window, cx);
2684        let current_head = editor
2685            .selections
2686            .newest_display(&snapshot.display_snapshot)
2687            .end;
2688        editor.last_bounds().unwrap().origin
2689            + editor
2690                .display_to_pixel_point(current_head, &snapshot, window, cx)
2691                .unwrap()
2692    });
2693    pixel_position.x += px(100.);
2694    // click beyond end of the line
2695    cx.simulate_click(pixel_position, Modifiers::default());
2696    cx.run_until_parked();
2697
2698    cx.assert_state(
2699        indoc! {
2700        "
2701        verylonglinˇe
2702        andsomelinebelow
2703        "
2704        },
2705        Mode::Normal,
2706    );
2707}
2708
2709#[gpui::test]
2710async fn test_wrap_selections_in_tag_line_mode(cx: &mut gpui::TestAppContext) {
2711    let mut cx = VimTestContext::new(cx, true).await;
2712
2713    let js_language = Arc::new(Language::new(
2714        LanguageConfig {
2715            name: "JavaScript".into(),
2716            wrap_characters: Some(language::WrapCharactersConfig {
2717                start_prefix: "<".into(),
2718                start_suffix: ">".into(),
2719                end_prefix: "</".into(),
2720                end_suffix: ">".into(),
2721            }),
2722            ..LanguageConfig::default()
2723        },
2724        None,
2725    ));
2726
2727    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
2728
2729    cx.set_state(
2730        indoc! {
2731        "
2732        ˇaaaaa
2733        bbbbb
2734        "
2735        },
2736        Mode::Normal,
2737    );
2738
2739    cx.simulate_keystrokes("shift-v j");
2740    cx.dispatch_action(WrapSelectionsInTag);
2741
2742    cx.assert_state(
2743        indoc! {
2744            "
2745            <ˇ>aaaaa
2746            bbbbb</ˇ>
2747            "
2748        },
2749        Mode::VisualLine,
2750    );
2751}
2752
2753#[gpui::test]
2754async fn test_repeat_grouping_41735(cx: &mut gpui::TestAppContext) {
2755    let mut cx = NeovimBackedTestContext::new(cx).await;
2756
2757    // typically transaction gropuing is disabled in tests, but here we need to test it.
2758    cx.update_buffer(|buffer, _cx| buffer.set_group_interval(Duration::from_millis(300)));
2759
2760    cx.set_shared_state("ˇ").await;
2761
2762    cx.simulate_shared_keystrokes("i a escape").await;
2763    cx.simulate_shared_keystrokes(". . .").await;
2764    cx.shared_state().await.assert_eq("ˇaaaa");
2765    cx.simulate_shared_keystrokes("u").await;
2766    cx.shared_state().await.assert_eq("ˇaaa");
2767}
2768
2769#[gpui::test]
2770async fn test_deactivate(cx: &mut gpui::TestAppContext) {
2771    let mut cx = VimTestContext::new(cx, true).await;
2772
2773    cx.update_global(|store: &mut SettingsStore, cx| {
2774        store.update_user_settings(cx, |settings| {
2775            settings.editor.cursor_shape = Some(settings::CursorShape::Underline);
2776        });
2777    });
2778
2779    // Assert that, while in `Normal` mode, the cursor shape is `Block` but,
2780    // after deactivating vim mode, it should revert to the one specified in the
2781    // user's settings, if set.
2782    cx.update_editor(|editor, _window, _cx| {
2783        assert_eq!(editor.cursor_shape(), CursorShape::Block);
2784    });
2785
2786    cx.disable_vim();
2787
2788    cx.update_editor(|editor, _window, _cx| {
2789        assert_eq!(editor.cursor_shape(), CursorShape::Underline);
2790    });
2791}
2792
2793// workspace::SendKeystrokes should pass literal keystrokes without triggering vim motions.
2794// When sending `" _ x`, the `_` should select the blackhole register, not trigger
2795// vim::StartOfLineDownward.
2796#[gpui::test]
2797async fn test_send_keystrokes_underscore_is_literal_46509(cx: &mut gpui::TestAppContext) {
2798    let mut cx = VimTestContext::new(cx, true).await;
2799
2800    // Bind a key to send `" _ x` which should:
2801    // `"` - start register selection
2802    // `_` - select blackhole register (NOT vim::StartOfLineDownward)
2803    // `x` - delete character into blackhole register
2804    cx.update(|_, cx| {
2805        cx.bind_keys([KeyBinding::new(
2806            "g x",
2807            workspace::SendKeystrokes("\" _ x".to_string()),
2808            Some("VimControl"),
2809        )])
2810    });
2811
2812    cx.set_state("helˇlo", Mode::Normal);
2813
2814    cx.simulate_keystrokes("g x");
2815    cx.run_until_parked();
2816
2817    cx.assert_state("helˇo", Mode::Normal);
2818}
2819
2820#[gpui::test]
2821async fn test_send_keystrokes_no_key_equivalent_mapping_46509(cx: &mut gpui::TestAppContext) {
2822    use collections::HashMap;
2823    use gpui::{KeybindingKeystroke, Keystroke, PlatformKeyboardMapper};
2824
2825    // create a mock Danish keyboard mapper
2826    // on Danish keyboards, the macOS key equivalents mapping includes: '{' -> 'Æ' and '}' -> 'Ø'
2827    // this means the `{` character is produced by the key labeled `Æ` (with shift modifier)
2828    struct DanishKeyboardMapper;
2829    impl PlatformKeyboardMapper for DanishKeyboardMapper {
2830        fn map_key_equivalent(
2831            &self,
2832            mut keystroke: Keystroke,
2833            use_key_equivalents: bool,
2834        ) -> KeybindingKeystroke {
2835            if use_key_equivalents {
2836                if keystroke.key == "{" {
2837                    keystroke.key = "Æ".to_string();
2838                }
2839                if keystroke.key == "}" {
2840                    keystroke.key = "Ø".to_string();
2841                }
2842            }
2843            KeybindingKeystroke::from_keystroke(keystroke)
2844        }
2845
2846        fn get_key_equivalents(&self) -> Option<&HashMap<char, char>> {
2847            None
2848        }
2849    }
2850
2851    let mapper = DanishKeyboardMapper;
2852
2853    let keystroke_brace = Keystroke::parse("{").unwrap();
2854    let mapped_with_bug = mapper.map_key_equivalent(keystroke_brace.clone(), true);
2855    assert_eq!(
2856        mapped_with_bug.key(),
2857        "Æ",
2858        "BUG: With use_key_equivalents=true, {{ is mapped to Æ on Danish keyboard"
2859    );
2860
2861    // Fixed behavior, where the literal `{` character is preserved
2862    let mapped_fixed = mapper.map_key_equivalent(keystroke_brace.clone(), false);
2863    assert_eq!(
2864        mapped_fixed.key(),
2865        "{",
2866        "FIX: With use_key_equivalents=false, {{ stays as {{"
2867    );
2868
2869    // Same applies to }
2870    let keystroke_close = Keystroke::parse("}").unwrap();
2871    let mapped_close_bug = mapper.map_key_equivalent(keystroke_close.clone(), true);
2872    assert_eq!(mapped_close_bug.key(), "Ø");
2873    let mapped_close_fixed = mapper.map_key_equivalent(keystroke_close.clone(), false);
2874    assert_eq!(mapped_close_fixed.key(), "}");
2875
2876    let mut cx = VimTestContext::new(cx, true).await;
2877
2878    cx.update(|_, cx| {
2879        cx.bind_keys([KeyBinding::new(
2880            "g p",
2881            workspace::SendKeystrokes("{".to_string()),
2882            Some("vim_mode == normal"),
2883        )])
2884    });
2885
2886    cx.set_state(
2887        indoc! {"
2888            first paragraph
2889
2890            second paragraphˇ
2891
2892            third paragraph
2893        "},
2894        Mode::Normal,
2895    );
2896
2897    cx.simulate_keystrokes("g p");
2898    cx.run_until_parked();
2899
2900    cx.assert_state(
2901        indoc! {"
2902            first paragraph
2903            ˇ
2904            second paragraph
2905
2906            third paragraph
2907        "},
2908        Mode::Normal,
2909    );
2910}
2911
2912#[gpui::test]
2913async fn test_project_search_opens_in_normal_mode(cx: &mut gpui::TestAppContext) {
2914    VimTestContext::init(cx);
2915
2916    let fs = FakeFs::new(cx.background_executor.clone());
2917    fs.insert_tree(
2918        path!("/dir"),
2919        json!({
2920            "file_a.rs": "// File A.",
2921            "file_b.rs": "// File B.",
2922        }),
2923    )
2924    .await;
2925
2926    let project = project::Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
2927    let window_handle =
2928        cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
2929    let workspace = window_handle
2930        .read_with(cx, |mw, _| mw.workspace().clone())
2931        .unwrap();
2932
2933    cx.update(|cx| {
2934        VimTestContext::init_keybindings(true, cx);
2935    });
2936
2937    let cx = &mut VisualTestContext::from_window(window_handle.into(), cx);
2938
2939    workspace.update_in(cx, |workspace, window, cx| {
2940        ProjectSearchView::deploy_search(workspace, &DeploySearch::default(), window, cx)
2941    });
2942
2943    let search_view = workspace.update_in(cx, |workspace, _, cx| {
2944        workspace
2945            .active_pane()
2946            .read(cx)
2947            .items()
2948            .find_map(|item| item.downcast::<ProjectSearchView>())
2949            .expect("Project search view should be active")
2950    });
2951
2952    project_search::perform_project_search(&search_view, "File A", cx);
2953
2954    search_view.update(cx, |search_view, cx| {
2955        let vim_mode = search_view
2956            .results_editor()
2957            .read(cx)
2958            .addon::<VimAddon>()
2959            .map(|addon| addon.entity.read(cx).mode);
2960
2961        assert_eq!(vim_mode, Some(Mode::Normal));
2962    });
2963}