test.rs

   1mod neovim_backed_test_context;
   2mod neovim_connection;
   3mod vim_test_context;
   4
   5use std::time::Duration;
   6
   7use collections::HashMap;
   8use command_palette::CommandPalette;
   9use editor::{
  10    actions::DeleteLine, display_map::DisplayRow, test::editor_test_context::EditorTestContext,
  11    DisplayPoint, Editor, EditorMode, MultiBuffer,
  12};
  13use futures::StreamExt;
  14use gpui::{KeyBinding, Modifiers, MouseButton, TestAppContext};
  15use language::Point;
  16pub use neovim_backed_test_context::*;
  17use settings::SettingsStore;
  18pub use vim_test_context::*;
  19
  20use indoc::indoc;
  21use search::BufferSearchBar;
  22use workspace::WorkspaceSettings;
  23
  24use crate::{insert::NormalBefore, motion, state::Mode, PushSneak, PushSneakBackward};
  25
  26#[gpui::test]
  27async fn test_initially_disabled(cx: &mut gpui::TestAppContext) {
  28    let mut cx = VimTestContext::new(cx, false).await;
  29    cx.simulate_keystrokes("h j k l");
  30    cx.assert_editor_state("hjklˇ");
  31}
  32
  33#[gpui::test]
  34async fn test_neovim(cx: &mut gpui::TestAppContext) {
  35    let mut cx = NeovimBackedTestContext::new(cx).await;
  36
  37    cx.simulate_shared_keystrokes("i").await;
  38    cx.shared_state().await.assert_matches();
  39    cx.simulate_shared_keystrokes("shift-t e s t space t e s t escape 0 d w")
  40        .await;
  41    cx.shared_state().await.assert_matches();
  42    cx.assert_editor_state("ˇtest");
  43}
  44
  45#[gpui::test]
  46async fn test_toggle_through_settings(cx: &mut gpui::TestAppContext) {
  47    let mut cx = VimTestContext::new(cx, true).await;
  48
  49    cx.simulate_keystrokes("i");
  50    assert_eq!(cx.mode(), Mode::Insert);
  51
  52    // Editor acts as though vim is disabled
  53    cx.disable_vim();
  54    cx.simulate_keystrokes("h j k l");
  55    cx.assert_editor_state("hjklˇ");
  56
  57    // Selections aren't changed if editor is blurred but vim-mode is still disabled.
  58    cx.cx.set_state("«hjklˇ»");
  59    cx.assert_editor_state("«hjklˇ»");
  60    cx.update_editor(|_, window, _cx| window.blur());
  61    cx.assert_editor_state("«hjklˇ»");
  62    cx.update_editor(|_, window, cx| cx.focus_self(window));
  63    cx.assert_editor_state("«hjklˇ»");
  64
  65    // Enabling dynamically sets vim mode again and restores normal mode
  66    cx.enable_vim();
  67    assert_eq!(cx.mode(), Mode::Normal);
  68    cx.simulate_keystrokes("h h h l");
  69    assert_eq!(cx.buffer_text(), "hjkl".to_owned());
  70    cx.assert_editor_state("hˇjkl");
  71    cx.simulate_keystrokes("i T e s t");
  72    cx.assert_editor_state("hTestˇjkl");
  73
  74    // Disabling and enabling resets to normal mode
  75    assert_eq!(cx.mode(), Mode::Insert);
  76    cx.disable_vim();
  77    cx.enable_vim();
  78    assert_eq!(cx.mode(), Mode::Normal);
  79}
  80
  81#[gpui::test]
  82async fn test_cancel_selection(cx: &mut gpui::TestAppContext) {
  83    let mut cx = VimTestContext::new(cx, true).await;
  84
  85    cx.set_state(
  86        indoc! {"The quick brown fox juˇmps over the lazy dog"},
  87        Mode::Normal,
  88    );
  89    // jumps
  90    cx.simulate_keystrokes("v l l");
  91    cx.assert_editor_state("The quick brown fox ju«mpsˇ» over the lazy dog");
  92
  93    cx.simulate_keystrokes("escape");
  94    cx.assert_editor_state("The quick brown fox jumpˇs over the lazy dog");
  95
  96    // go back to the same selection state
  97    cx.simulate_keystrokes("v h h");
  98    cx.assert_editor_state("The quick brown fox ju«ˇmps» over the lazy dog");
  99
 100    // Ctrl-[ should behave like Esc
 101    cx.simulate_keystrokes("ctrl-[");
 102    cx.assert_editor_state("The quick brown fox juˇmps over the lazy dog");
 103}
 104
 105#[gpui::test]
 106async fn test_buffer_search(cx: &mut gpui::TestAppContext) {
 107    let mut cx = VimTestContext::new(cx, true).await;
 108
 109    cx.set_state(
 110        indoc! {"
 111            The quick brown
 112            fox juˇmps over
 113            the lazy dog"},
 114        Mode::Normal,
 115    );
 116    cx.simulate_keystrokes("/");
 117
 118    let search_bar = cx.workspace(|workspace, _, cx| {
 119        workspace
 120            .active_pane()
 121            .read(cx)
 122            .toolbar()
 123            .read(cx)
 124            .item_of_type::<BufferSearchBar>()
 125            .expect("Buffer search bar should be deployed")
 126    });
 127
 128    cx.update_entity(search_bar, |bar, _, cx| {
 129        assert_eq!(bar.query(cx), "");
 130    })
 131}
 132
 133#[gpui::test]
 134async fn test_count_down(cx: &mut gpui::TestAppContext) {
 135    let mut cx = VimTestContext::new(cx, true).await;
 136
 137    cx.set_state(indoc! {"aˇa\nbb\ncc\ndd\nee"}, Mode::Normal);
 138    cx.simulate_keystrokes("2 down");
 139    cx.assert_editor_state("aa\nbb\ncˇc\ndd\nee");
 140    cx.simulate_keystrokes("9 down");
 141    cx.assert_editor_state("aa\nbb\ncc\ndd\neˇe");
 142}
 143
 144#[gpui::test]
 145async fn test_end_of_document_710(cx: &mut gpui::TestAppContext) {
 146    let mut cx = VimTestContext::new(cx, true).await;
 147
 148    // goes to end by default
 149    cx.set_state(indoc! {"aˇa\nbb\ncc"}, Mode::Normal);
 150    cx.simulate_keystrokes("shift-g");
 151    cx.assert_editor_state("aa\nbb\ncˇc");
 152
 153    // can go to line 1 (https://github.com/zed-industries/zed/issues/5812)
 154    cx.simulate_keystrokes("1 shift-g");
 155    cx.assert_editor_state("aˇa\nbb\ncc");
 156}
 157
 158#[gpui::test]
 159async fn test_end_of_line_with_times(cx: &mut gpui::TestAppContext) {
 160    let mut cx = VimTestContext::new(cx, true).await;
 161
 162    // goes to current line end
 163    cx.set_state(indoc! {"ˇaa\nbb\ncc"}, Mode::Normal);
 164    cx.simulate_keystrokes("$");
 165    cx.assert_editor_state("aˇa\nbb\ncc");
 166
 167    // goes to next line end
 168    cx.simulate_keystrokes("2 $");
 169    cx.assert_editor_state("aa\nbˇb\ncc");
 170
 171    // try to exceed the final line.
 172    cx.simulate_keystrokes("4 $");
 173    cx.assert_editor_state("aa\nbb\ncˇc");
 174}
 175
 176#[gpui::test]
 177async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
 178    let mut cx = VimTestContext::new(cx, true).await;
 179
 180    // works in normal mode
 181    cx.set_state(indoc! {"aa\nbˇb\ncc"}, Mode::Normal);
 182    cx.simulate_keystrokes("> >");
 183    cx.assert_editor_state("aa\n    bˇb\ncc");
 184    cx.simulate_keystrokes("< <");
 185    cx.assert_editor_state("aa\nbˇb\ncc");
 186
 187    // works in visual mode
 188    cx.simulate_keystrokes("shift-v down >");
 189    cx.assert_editor_state("aa\n    bˇb\n    cc");
 190
 191    // works as operator
 192    cx.set_state("aa\nbˇb\ncc\n", Mode::Normal);
 193    cx.simulate_keystrokes("> j");
 194    cx.assert_editor_state("aa\n    bˇb\n    cc\n");
 195    cx.simulate_keystrokes("< k");
 196    cx.assert_editor_state("aa\nbˇb\n    cc\n");
 197    cx.simulate_keystrokes("> i p");
 198    cx.assert_editor_state("    aa\n    bˇb\n        cc\n");
 199    cx.simulate_keystrokes("< i p");
 200    cx.assert_editor_state("aa\nbˇb\n    cc\n");
 201    cx.simulate_keystrokes("< i p");
 202    cx.assert_editor_state("aa\nbˇb\ncc\n");
 203
 204    cx.set_state("ˇaa\nbb\ncc\n", Mode::Normal);
 205    cx.simulate_keystrokes("> 2 j");
 206    cx.assert_editor_state("    ˇaa\n    bb\n    cc\n");
 207
 208    cx.set_state("aa\nbb\nˇcc\n", Mode::Normal);
 209    cx.simulate_keystrokes("> 2 k");
 210    cx.assert_editor_state("    aa\n    bb\n    ˇcc\n");
 211
 212    // works with repeat
 213    cx.set_state("a\nb\nccˇc\n", Mode::Normal);
 214    cx.simulate_keystrokes("> 2 k");
 215    cx.assert_editor_state("    a\n    b\n    ccˇc\n");
 216    cx.simulate_keystrokes(".");
 217    cx.assert_editor_state("        a\n        b\n        ccˇc\n");
 218    cx.simulate_keystrokes("v k <");
 219    cx.assert_editor_state("        a\n\n    ccc\n");
 220    cx.simulate_keystrokes(".");
 221    cx.assert_editor_state("        a\n\nccc\n");
 222}
 223
 224#[gpui::test]
 225async fn test_escape_command_palette(cx: &mut gpui::TestAppContext) {
 226    let mut cx = VimTestContext::new(cx, true).await;
 227
 228    cx.set_state("aˇbc\n", Mode::Normal);
 229    cx.simulate_keystrokes("i cmd-shift-p");
 230
 231    assert!(cx.workspace(|workspace, _, cx| workspace.active_modal::<CommandPalette>(cx).is_some()));
 232    cx.simulate_keystrokes("escape");
 233    cx.run_until_parked();
 234    assert!(
 235        !cx.workspace(|workspace, _, cx| workspace.active_modal::<CommandPalette>(cx).is_some())
 236    );
 237    cx.assert_state("aˇbc\n", Mode::Insert);
 238}
 239
 240#[gpui::test]
 241async fn test_escape_cancels(cx: &mut gpui::TestAppContext) {
 242    let mut cx = VimTestContext::new(cx, true).await;
 243
 244    cx.set_state("aˇbˇc", Mode::Normal);
 245    cx.simulate_keystrokes("escape");
 246
 247    cx.assert_state("aˇbc", Mode::Normal);
 248}
 249
 250#[gpui::test]
 251async fn test_selection_on_search(cx: &mut gpui::TestAppContext) {
 252    let mut cx = VimTestContext::new(cx, true).await;
 253
 254    cx.set_state(indoc! {"aa\nbˇb\ncc\ncc\ncc\n"}, Mode::Normal);
 255    cx.simulate_keystrokes("/ c c");
 256
 257    let search_bar = cx.workspace(|workspace, _, cx| {
 258        workspace
 259            .active_pane()
 260            .read(cx)
 261            .toolbar()
 262            .read(cx)
 263            .item_of_type::<BufferSearchBar>()
 264            .expect("Buffer search bar should be deployed")
 265    });
 266
 267    cx.update_entity(search_bar, |bar, _, cx| {
 268        assert_eq!(bar.query(cx), "cc");
 269    });
 270
 271    cx.update_editor(|editor, window, cx| {
 272        let highlights = editor.all_text_background_highlights(window, cx);
 273        assert_eq!(3, highlights.len());
 274        assert_eq!(
 275            DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 2),
 276            highlights[0].0
 277        )
 278    });
 279    cx.simulate_keystrokes("enter");
 280
 281    cx.assert_state(indoc! {"aa\nbb\nˇcc\ncc\ncc\n"}, Mode::Normal);
 282    cx.simulate_keystrokes("n");
 283    cx.assert_state(indoc! {"aa\nbb\ncc\nˇcc\ncc\n"}, Mode::Normal);
 284    cx.simulate_keystrokes("shift-n");
 285    cx.assert_state(indoc! {"aa\nbb\nˇcc\ncc\ncc\n"}, Mode::Normal);
 286}
 287
 288#[gpui::test]
 289async fn test_word_characters(cx: &mut gpui::TestAppContext) {
 290    let mut cx = VimTestContext::new_typescript(cx).await;
 291    cx.set_state(
 292        indoc! { "
 293        class A {
 294            #ˇgoop = 99;
 295            $ˇgoop () { return this.#gˇoop };
 296        };
 297        console.log(new A().$gooˇp())
 298    "},
 299        Mode::Normal,
 300    );
 301    cx.simulate_keystrokes("v i w");
 302    cx.assert_state(
 303        indoc! {"
 304        class A {
 305            «#goopˇ» = 99;
 306            «$goopˇ» () { return this.«#goopˇ» };
 307        };
 308        console.log(new A().«$goopˇ»())
 309    "},
 310        Mode::Visual,
 311    )
 312}
 313
 314#[gpui::test]
 315async fn test_kebab_case(cx: &mut gpui::TestAppContext) {
 316    let mut cx = VimTestContext::new_html(cx).await;
 317    cx.set_state(
 318        indoc! { r#"
 319            <div><a class="bg-rˇed"></a></div>
 320            "#},
 321        Mode::Normal,
 322    );
 323    cx.simulate_keystrokes("v i w");
 324    cx.assert_state(
 325        indoc! { r#"
 326        <div><a class="bg-«redˇ»"></a></div>
 327        "#
 328        },
 329        Mode::Visual,
 330    )
 331}
 332
 333#[gpui::test]
 334async fn test_join_lines(cx: &mut gpui::TestAppContext) {
 335    let mut cx = NeovimBackedTestContext::new(cx).await;
 336
 337    cx.set_shared_state(indoc! {"
 338      ˇone
 339      two
 340      three
 341      four
 342      five
 343      six
 344      "})
 345        .await;
 346    cx.simulate_shared_keystrokes("shift-j").await;
 347    cx.shared_state().await.assert_eq(indoc! {"
 348          oneˇ two
 349          three
 350          four
 351          five
 352          six
 353          "});
 354    cx.simulate_shared_keystrokes("3 shift-j").await;
 355    cx.shared_state().await.assert_eq(indoc! {"
 356          one two threeˇ four
 357          five
 358          six
 359          "});
 360
 361    cx.set_shared_state(indoc! {"
 362      ˇone
 363      two
 364      three
 365      four
 366      five
 367      six
 368      "})
 369        .await;
 370    cx.simulate_shared_keystrokes("j v 3 j shift-j").await;
 371    cx.shared_state().await.assert_eq(indoc! {"
 372      one
 373      two three fourˇ five
 374      six
 375      "});
 376
 377    cx.set_shared_state(indoc! {"
 378      ˇone
 379      two
 380      three
 381      four
 382      five
 383      six
 384      "})
 385        .await;
 386    cx.simulate_shared_keystrokes("g shift-j").await;
 387    cx.shared_state().await.assert_eq(indoc! {"
 388          oneˇtwo
 389          three
 390          four
 391          five
 392          six
 393          "});
 394    cx.simulate_shared_keystrokes("3 g shift-j").await;
 395    cx.shared_state().await.assert_eq(indoc! {"
 396          onetwothreeˇfour
 397          five
 398          six
 399          "});
 400
 401    cx.set_shared_state(indoc! {"
 402      ˇone
 403      two
 404      three
 405      four
 406      five
 407      six
 408      "})
 409        .await;
 410    cx.simulate_shared_keystrokes("j v 3 j g shift-j").await;
 411    cx.shared_state().await.assert_eq(indoc! {"
 412      one
 413      twothreefourˇfive
 414      six
 415      "});
 416}
 417
 418#[cfg(target_os = "macos")]
 419#[gpui::test]
 420async fn test_wrapped_lines(cx: &mut gpui::TestAppContext) {
 421    let mut cx = NeovimBackedTestContext::new(cx).await;
 422
 423    cx.set_shared_wrap(12).await;
 424    // tests line wrap as follows:
 425    //  1: twelve char
 426    //     twelve char
 427    //  2: twelve char
 428    cx.set_shared_state(indoc! { "
 429        tˇwelve char twelve char
 430        twelve char
 431    "})
 432        .await;
 433    cx.simulate_shared_keystrokes("j").await;
 434    cx.shared_state().await.assert_eq(indoc! {"
 435        twelve char twelve char
 436        tˇwelve char
 437    "});
 438    cx.simulate_shared_keystrokes("k").await;
 439    cx.shared_state().await.assert_eq(indoc! {"
 440        tˇwelve char twelve char
 441        twelve char
 442    "});
 443    cx.simulate_shared_keystrokes("g j").await;
 444    cx.shared_state().await.assert_eq(indoc! {"
 445        twelve char tˇwelve char
 446        twelve char
 447    "});
 448    cx.simulate_shared_keystrokes("g j").await;
 449    cx.shared_state().await.assert_eq(indoc! {"
 450        twelve char twelve char
 451        tˇwelve char
 452    "});
 453
 454    cx.simulate_shared_keystrokes("g k").await;
 455    cx.shared_state().await.assert_eq(indoc! {"
 456        twelve char tˇwelve char
 457        twelve char
 458    "});
 459
 460    cx.simulate_shared_keystrokes("g ^").await;
 461    cx.shared_state().await.assert_eq(indoc! {"
 462        twelve char ˇtwelve char
 463        twelve char
 464    "});
 465
 466    cx.simulate_shared_keystrokes("^").await;
 467    cx.shared_state().await.assert_eq(indoc! {"
 468        ˇtwelve char twelve char
 469        twelve char
 470    "});
 471
 472    cx.simulate_shared_keystrokes("g $").await;
 473    cx.shared_state().await.assert_eq(indoc! {"
 474        twelve charˇ twelve char
 475        twelve char
 476    "});
 477    cx.simulate_shared_keystrokes("$").await;
 478    cx.shared_state().await.assert_eq(indoc! {"
 479        twelve char twelve chaˇr
 480        twelve char
 481    "});
 482
 483    cx.set_shared_state(indoc! { "
 484        tˇwelve char twelve char
 485        twelve char
 486    "})
 487        .await;
 488    cx.simulate_shared_keystrokes("enter").await;
 489    cx.shared_state().await.assert_eq(indoc! {"
 490            twelve char twelve char
 491            ˇtwelve char
 492        "});
 493
 494    cx.set_shared_state(indoc! { "
 495        twelve char
 496        tˇwelve char twelve char
 497        twelve char
 498    "})
 499        .await;
 500    cx.simulate_shared_keystrokes("o o escape").await;
 501    cx.shared_state().await.assert_eq(indoc! {"
 502        twelve char
 503        twelve char twelve char
 504        ˇo
 505        twelve char
 506    "});
 507
 508    cx.set_shared_state(indoc! { "
 509        twelve char
 510        tˇwelve char twelve char
 511        twelve char
 512    "})
 513        .await;
 514    cx.simulate_shared_keystrokes("shift-a a escape").await;
 515    cx.shared_state().await.assert_eq(indoc! {"
 516        twelve char
 517        twelve char twelve charˇa
 518        twelve char
 519    "});
 520    cx.simulate_shared_keystrokes("shift-i i escape").await;
 521    cx.shared_state().await.assert_eq(indoc! {"
 522        twelve char
 523        ˇitwelve char twelve chara
 524        twelve char
 525    "});
 526    cx.simulate_shared_keystrokes("shift-d").await;
 527    cx.shared_state().await.assert_eq(indoc! {"
 528        twelve char
 529        ˇ
 530        twelve char
 531    "});
 532
 533    cx.set_shared_state(indoc! { "
 534        twelve char
 535        twelve char tˇwelve char
 536        twelve char
 537    "})
 538        .await;
 539    cx.simulate_shared_keystrokes("shift-o o escape").await;
 540    cx.shared_state().await.assert_eq(indoc! {"
 541        twelve char
 542        ˇo
 543        twelve char twelve char
 544        twelve char
 545    "});
 546
 547    // line wraps as:
 548    // fourteen ch
 549    // ar
 550    // fourteen ch
 551    // ar
 552    cx.set_shared_state(indoc! { "
 553        fourteen chaˇr
 554        fourteen char
 555    "})
 556        .await;
 557
 558    cx.simulate_shared_keystrokes("d i w").await;
 559    cx.shared_state().await.assert_eq(indoc! {"
 560        fourteenˇ•
 561        fourteen char
 562    "});
 563    cx.simulate_shared_keystrokes("j shift-f e f r").await;
 564    cx.shared_state().await.assert_eq(indoc! {"
 565        fourteen•
 566        fourteen chaˇr
 567    "});
 568}
 569
 570#[gpui::test]
 571async fn test_folds(cx: &mut gpui::TestAppContext) {
 572    let mut cx = NeovimBackedTestContext::new(cx).await;
 573    cx.set_neovim_option("foldmethod=manual").await;
 574
 575    cx.set_shared_state(indoc! { "
 576        fn boop() {
 577          ˇbarp()
 578          bazp()
 579        }
 580    "})
 581        .await;
 582    cx.simulate_shared_keystrokes("shift-v j z f").await;
 583
 584    // visual display is now:
 585    // fn boop () {
 586    //  [FOLDED]
 587    // }
 588
 589    // TODO: this should not be needed but currently zf does not
 590    // return to normal mode.
 591    cx.simulate_shared_keystrokes("escape").await;
 592
 593    // skip over fold downward
 594    cx.simulate_shared_keystrokes("g g").await;
 595    cx.shared_state().await.assert_eq(indoc! {"
 596        ˇfn boop() {
 597          barp()
 598          bazp()
 599        }
 600    "});
 601
 602    cx.simulate_shared_keystrokes("j j").await;
 603    cx.shared_state().await.assert_eq(indoc! {"
 604        fn boop() {
 605          barp()
 606          bazp()
 607        ˇ}
 608    "});
 609
 610    // skip over fold upward
 611    cx.simulate_shared_keystrokes("2 k").await;
 612    cx.shared_state().await.assert_eq(indoc! {"
 613        ˇfn boop() {
 614          barp()
 615          bazp()
 616        }
 617    "});
 618
 619    // yank the fold
 620    cx.simulate_shared_keystrokes("down y y").await;
 621    cx.shared_clipboard()
 622        .await
 623        .assert_eq("  barp()\n  bazp()\n");
 624
 625    // re-open
 626    cx.simulate_shared_keystrokes("z o").await;
 627    cx.shared_state().await.assert_eq(indoc! {"
 628        fn boop() {
 629        ˇ  barp()
 630          bazp()
 631        }
 632    "});
 633}
 634
 635#[gpui::test]
 636async fn test_folds_panic(cx: &mut gpui::TestAppContext) {
 637    let mut cx = NeovimBackedTestContext::new(cx).await;
 638    cx.set_neovim_option("foldmethod=manual").await;
 639
 640    cx.set_shared_state(indoc! { "
 641        fn boop() {
 642          ˇbarp()
 643          bazp()
 644        }
 645    "})
 646        .await;
 647    cx.simulate_shared_keystrokes("shift-v j z f").await;
 648    cx.simulate_shared_keystrokes("escape").await;
 649    cx.simulate_shared_keystrokes("g g").await;
 650    cx.simulate_shared_keystrokes("5 d j").await;
 651    cx.shared_state().await.assert_eq("ˇ");
 652    cx.set_shared_state(indoc! {"
 653        fn boop() {
 654          ˇbarp()
 655          bazp()
 656        }
 657    "})
 658        .await;
 659    cx.simulate_shared_keystrokes("shift-v j j z f").await;
 660    cx.simulate_shared_keystrokes("escape").await;
 661    cx.simulate_shared_keystrokes("shift-g shift-v").await;
 662    cx.shared_state().await.assert_eq(indoc! {"
 663        fn boop() {
 664          barp()
 665          bazp()
 666        }
 667        ˇ"});
 668}
 669
 670#[gpui::test]
 671async fn test_clear_counts(cx: &mut gpui::TestAppContext) {
 672    let mut cx = NeovimBackedTestContext::new(cx).await;
 673
 674    cx.set_shared_state(indoc! {"
 675        The quick brown
 676        fox juˇmps over
 677        the lazy dog"})
 678        .await;
 679
 680    cx.simulate_shared_keystrokes("4 escape 3 d l").await;
 681    cx.shared_state().await.assert_eq(indoc! {"
 682        The quick brown
 683        fox juˇ over
 684        the lazy dog"});
 685}
 686
 687#[gpui::test]
 688async fn test_zero(cx: &mut gpui::TestAppContext) {
 689    let mut cx = NeovimBackedTestContext::new(cx).await;
 690
 691    cx.set_shared_state(indoc! {"
 692        The quˇick brown
 693        fox jumps over
 694        the lazy dog"})
 695        .await;
 696
 697    cx.simulate_shared_keystrokes("0").await;
 698    cx.shared_state().await.assert_eq(indoc! {"
 699        ˇThe quick brown
 700        fox jumps over
 701        the lazy dog"});
 702
 703    cx.simulate_shared_keystrokes("1 0 l").await;
 704    cx.shared_state().await.assert_eq(indoc! {"
 705        The quick ˇbrown
 706        fox jumps over
 707        the lazy dog"});
 708}
 709
 710#[gpui::test]
 711async fn test_selection_goal(cx: &mut gpui::TestAppContext) {
 712    let mut cx = NeovimBackedTestContext::new(cx).await;
 713
 714    cx.set_shared_state(indoc! {"
 715        ;;ˇ;
 716        Lorem Ipsum"})
 717        .await;
 718
 719    cx.simulate_shared_keystrokes("a down up ; down up").await;
 720    cx.shared_state().await.assert_eq(indoc! {"
 721        ;;;;ˇ
 722        Lorem Ipsum"});
 723}
 724
 725#[cfg(target_os = "macos")]
 726#[gpui::test]
 727async fn test_wrapped_motions(cx: &mut gpui::TestAppContext) {
 728    let mut cx = NeovimBackedTestContext::new(cx).await;
 729
 730    cx.set_shared_wrap(12).await;
 731
 732    cx.set_shared_state(indoc! {"
 733                aaˇaa
 734                😃😃"
 735    })
 736    .await;
 737    cx.simulate_shared_keystrokes("j").await;
 738    cx.shared_state().await.assert_eq(indoc! {"
 739                aaaa
 740                😃ˇ😃"
 741    });
 742
 743    cx.set_shared_state(indoc! {"
 744                123456789012aaˇaa
 745                123456789012😃😃"
 746    })
 747    .await;
 748    cx.simulate_shared_keystrokes("j").await;
 749    cx.shared_state().await.assert_eq(indoc! {"
 750        123456789012aaaa
 751        123456789012😃ˇ😃"
 752    });
 753
 754    cx.set_shared_state(indoc! {"
 755                123456789012aaˇaa
 756                123456789012😃😃"
 757    })
 758    .await;
 759    cx.simulate_shared_keystrokes("j").await;
 760    cx.shared_state().await.assert_eq(indoc! {"
 761        123456789012aaaa
 762        123456789012😃ˇ😃"
 763    });
 764
 765    cx.set_shared_state(indoc! {"
 766        123456789012aaaaˇaaaaaaaa123456789012
 767        wow
 768        123456789012😃😃😃😃😃😃123456789012"
 769    })
 770    .await;
 771    cx.simulate_shared_keystrokes("j j").await;
 772    cx.shared_state().await.assert_eq(indoc! {"
 773        123456789012aaaaaaaaaaaa123456789012
 774        wow
 775        123456789012😃😃ˇ😃😃😃😃123456789012"
 776    });
 777}
 778
 779#[gpui::test]
 780async fn test_wrapped_delete_end_document(cx: &mut gpui::TestAppContext) {
 781    let mut cx = NeovimBackedTestContext::new(cx).await;
 782
 783    cx.set_shared_wrap(12).await;
 784
 785    cx.set_shared_state(indoc! {"
 786                aaˇaaaaaaaaaaaaaaaaaa
 787                bbbbbbbbbbbbbbbbbbbb
 788                cccccccccccccccccccc"
 789    })
 790    .await;
 791    cx.simulate_shared_keystrokes("d shift-g i z z z").await;
 792    cx.shared_state().await.assert_eq(indoc! {"
 793                zzzˇ"
 794    });
 795}
 796
 797#[gpui::test]
 798async fn test_paragraphs_dont_wrap(cx: &mut gpui::TestAppContext) {
 799    let mut cx = NeovimBackedTestContext::new(cx).await;
 800
 801    cx.set_shared_state(indoc! {"
 802        one
 803        ˇ
 804        two"})
 805        .await;
 806
 807    cx.simulate_shared_keystrokes("} }").await;
 808    cx.shared_state().await.assert_eq(indoc! {"
 809        one
 810
 811        twˇo"});
 812
 813    cx.simulate_shared_keystrokes("{ { {").await;
 814    cx.shared_state().await.assert_eq(indoc! {"
 815        ˇone
 816
 817        two"});
 818}
 819
 820#[gpui::test]
 821async fn test_select_all_issue_2170(cx: &mut gpui::TestAppContext) {
 822    let mut cx = VimTestContext::new(cx, true).await;
 823
 824    cx.set_state(
 825        indoc! {"
 826        defmodule Test do
 827            def test(a, ˇ[_, _] = b), do: IO.puts('hi')
 828        end
 829    "},
 830        Mode::Normal,
 831    );
 832    cx.simulate_keystrokes("g a");
 833    cx.assert_state(
 834        indoc! {"
 835        defmodule Test do
 836            def test(a, «[ˇ»_, _] = b), do: IO.puts('hi')
 837        end
 838    "},
 839        Mode::Visual,
 840    );
 841}
 842
 843#[gpui::test]
 844async fn test_jk(cx: &mut gpui::TestAppContext) {
 845    let mut cx = NeovimBackedTestContext::new(cx).await;
 846
 847    cx.update(|_, cx| {
 848        cx.bind_keys([KeyBinding::new(
 849            "j k",
 850            NormalBefore,
 851            Some("vim_mode == insert"),
 852        )])
 853    });
 854    cx.neovim.exec("imap jk <esc>").await;
 855
 856    cx.set_shared_state("ˇhello").await;
 857    cx.simulate_shared_keystrokes("i j o j k").await;
 858    cx.shared_state().await.assert_eq("jˇohello");
 859}
 860
 861#[gpui::test]
 862async fn test_jk_delay(cx: &mut gpui::TestAppContext) {
 863    let mut cx = VimTestContext::new(cx, true).await;
 864
 865    cx.update(|_, cx| {
 866        cx.bind_keys([KeyBinding::new(
 867            "j k",
 868            NormalBefore,
 869            Some("vim_mode == insert"),
 870        )])
 871    });
 872
 873    cx.set_state("ˇhello", Mode::Normal);
 874    cx.simulate_keystrokes("i j");
 875    cx.executor().advance_clock(Duration::from_millis(500));
 876    cx.run_until_parked();
 877    cx.assert_state("ˇhello", Mode::Insert);
 878    cx.executor().advance_clock(Duration::from_millis(500));
 879    cx.run_until_parked();
 880    cx.assert_state("jˇhello", Mode::Insert);
 881    cx.simulate_keystrokes("k j k");
 882    cx.assert_state("jˇkhello", Mode::Normal);
 883}
 884
 885#[gpui::test]
 886async fn test_comma_w(cx: &mut gpui::TestAppContext) {
 887    let mut cx = NeovimBackedTestContext::new(cx).await;
 888
 889    cx.update(|_, cx| {
 890        cx.bind_keys([KeyBinding::new(
 891            ", w",
 892            motion::Down {
 893                display_lines: false,
 894            },
 895            Some("vim_mode == normal"),
 896        )])
 897    });
 898    cx.neovim.exec("map ,w j").await;
 899
 900    cx.set_shared_state("ˇhello hello\nhello hello").await;
 901    cx.simulate_shared_keystrokes("f o ; , w").await;
 902    cx.shared_state()
 903        .await
 904        .assert_eq("hello hello\nhello hellˇo");
 905
 906    cx.set_shared_state("ˇhello hello\nhello hello").await;
 907    cx.simulate_shared_keystrokes("f o ; , i").await;
 908    cx.shared_state()
 909        .await
 910        .assert_eq("hellˇo hello\nhello hello");
 911}
 912
 913#[gpui::test]
 914async fn test_rename(cx: &mut gpui::TestAppContext) {
 915    let mut cx = VimTestContext::new_typescript(cx).await;
 916
 917    cx.set_state("const beˇfore = 2; console.log(before)", Mode::Normal);
 918    let def_range = cx.lsp_range("const «beforeˇ» = 2; console.log(before)");
 919    let tgt_range = cx.lsp_range("const before = 2; console.log(«beforeˇ»)");
 920    let mut prepare_request =
 921        cx.handle_request::<lsp::request::PrepareRenameRequest, _, _>(move |_, _, _| async move {
 922            Ok(Some(lsp::PrepareRenameResponse::Range(def_range)))
 923        });
 924    let mut rename_request =
 925        cx.handle_request::<lsp::request::Rename, _, _>(move |url, params, _| async move {
 926            Ok(Some(lsp::WorkspaceEdit {
 927                changes: Some(
 928                    [(
 929                        url.clone(),
 930                        vec![
 931                            lsp::TextEdit::new(def_range, params.new_name.clone()),
 932                            lsp::TextEdit::new(tgt_range, params.new_name),
 933                        ],
 934                    )]
 935                    .into(),
 936                ),
 937                ..Default::default()
 938            }))
 939        });
 940
 941    cx.simulate_keystrokes("c d");
 942    prepare_request.next().await.unwrap();
 943    cx.simulate_input("after");
 944    cx.simulate_keystrokes("enter");
 945    rename_request.next().await.unwrap();
 946    cx.assert_state("const afterˇ = 2; console.log(after)", Mode::Normal)
 947}
 948
 949// TODO: this test is flaky on our linux CI machines
 950#[cfg(target_os = "macos")]
 951#[gpui::test]
 952async fn test_remap(cx: &mut gpui::TestAppContext) {
 953    let mut cx = VimTestContext::new(cx, true).await;
 954
 955    // test moving the cursor
 956    cx.update(|_, cx| {
 957        cx.bind_keys([KeyBinding::new(
 958            "g z",
 959            workspace::SendKeystrokes("l l l l".to_string()),
 960            None,
 961        )])
 962    });
 963    cx.set_state("ˇ123456789", Mode::Normal);
 964    cx.simulate_keystrokes("g z");
 965    cx.assert_state("1234ˇ56789", Mode::Normal);
 966
 967    // test switching modes
 968    cx.update(|_, cx| {
 969        cx.bind_keys([KeyBinding::new(
 970            "g y",
 971            workspace::SendKeystrokes("i f o o escape l".to_string()),
 972            None,
 973        )])
 974    });
 975    cx.set_state("ˇ123456789", Mode::Normal);
 976    cx.simulate_keystrokes("g y");
 977    cx.assert_state("fooˇ123456789", Mode::Normal);
 978
 979    // test recursion
 980    cx.update(|_, cx| {
 981        cx.bind_keys([KeyBinding::new(
 982            "g x",
 983            workspace::SendKeystrokes("g z g y".to_string()),
 984            None,
 985        )])
 986    });
 987    cx.set_state("ˇ123456789", Mode::Normal);
 988    cx.simulate_keystrokes("g x");
 989    cx.assert_state("1234fooˇ56789", Mode::Normal);
 990
 991    cx.executor().allow_parking();
 992
 993    // test command
 994    cx.update(|_, cx| {
 995        cx.bind_keys([KeyBinding::new(
 996            "g w",
 997            workspace::SendKeystrokes(": j enter".to_string()),
 998            None,
 999        )])
1000    });
1001    cx.set_state("ˇ1234\n56789", Mode::Normal);
1002    cx.simulate_keystrokes("g w");
1003    cx.assert_state("1234ˇ 56789", Mode::Normal);
1004
1005    // test leaving command
1006    cx.update(|_, cx| {
1007        cx.bind_keys([KeyBinding::new(
1008            "g u",
1009            workspace::SendKeystrokes("g w g z".to_string()),
1010            None,
1011        )])
1012    });
1013    cx.set_state("ˇ1234\n56789", Mode::Normal);
1014    cx.simulate_keystrokes("g u");
1015    cx.assert_state("1234 567ˇ89", Mode::Normal);
1016
1017    // test leaving command
1018    cx.update(|_, cx| {
1019        cx.bind_keys([KeyBinding::new(
1020            "g t",
1021            workspace::SendKeystrokes("i space escape".to_string()),
1022            None,
1023        )])
1024    });
1025    cx.set_state("12ˇ34", Mode::Normal);
1026    cx.simulate_keystrokes("g t");
1027    cx.assert_state("12ˇ 34", Mode::Normal);
1028}
1029
1030#[gpui::test]
1031async fn test_undo(cx: &mut gpui::TestAppContext) {
1032    let mut cx = NeovimBackedTestContext::new(cx).await;
1033
1034    cx.set_shared_state("hello quˇoel world").await;
1035    cx.simulate_shared_keystrokes("v i w s c o escape u").await;
1036    cx.shared_state().await.assert_eq("hello ˇquoel world");
1037    cx.simulate_shared_keystrokes("ctrl-r").await;
1038    cx.shared_state().await.assert_eq("hello ˇco world");
1039    cx.simulate_shared_keystrokes("a o right l escape").await;
1040    cx.shared_state().await.assert_eq("hello cooˇl world");
1041    cx.simulate_shared_keystrokes("u").await;
1042    cx.shared_state().await.assert_eq("hello cooˇ world");
1043    cx.simulate_shared_keystrokes("u").await;
1044    cx.shared_state().await.assert_eq("hello cˇo world");
1045    cx.simulate_shared_keystrokes("u").await;
1046    cx.shared_state().await.assert_eq("hello ˇquoel world");
1047
1048    cx.set_shared_state("hello quˇoel world").await;
1049    cx.simulate_shared_keystrokes("v i w ~ u").await;
1050    cx.shared_state().await.assert_eq("hello ˇquoel world");
1051
1052    cx.set_shared_state("\nhello quˇoel world\n").await;
1053    cx.simulate_shared_keystrokes("shift-v s c escape u").await;
1054    cx.shared_state().await.assert_eq("\nˇhello quoel world\n");
1055
1056    cx.set_shared_state(indoc! {"
1057        ˇ1
1058        2
1059        3"})
1060        .await;
1061
1062    cx.simulate_shared_keystrokes("ctrl-v shift-g ctrl-a").await;
1063    cx.shared_state().await.assert_eq(indoc! {"
1064        ˇ2
1065        3
1066        4"});
1067
1068    cx.simulate_shared_keystrokes("u").await;
1069    cx.shared_state().await.assert_eq(indoc! {"
1070        ˇ1
1071        2
1072        3"});
1073}
1074
1075#[gpui::test]
1076async fn test_mouse_selection(cx: &mut TestAppContext) {
1077    let mut cx = VimTestContext::new(cx, true).await;
1078
1079    cx.set_state("ˇone two three", Mode::Normal);
1080
1081    let start_point = cx.pixel_position("one twˇo three");
1082    let end_point = cx.pixel_position("one ˇtwo three");
1083
1084    cx.simulate_mouse_down(start_point, MouseButton::Left, Modifiers::none());
1085    cx.simulate_mouse_move(end_point, MouseButton::Left, Modifiers::none());
1086    cx.simulate_mouse_up(end_point, MouseButton::Left, Modifiers::none());
1087
1088    cx.assert_state("one «ˇtwo» three", Mode::Visual)
1089}
1090
1091#[gpui::test]
1092async fn test_lowercase_marks(cx: &mut TestAppContext) {
1093    let mut cx = NeovimBackedTestContext::new(cx).await;
1094
1095    cx.set_shared_state("line one\nline ˇtwo\nline three").await;
1096    cx.simulate_shared_keystrokes("m a l ' a").await;
1097    cx.shared_state()
1098        .await
1099        .assert_eq("line one\nˇline two\nline three");
1100    cx.simulate_shared_keystrokes("` a").await;
1101    cx.shared_state()
1102        .await
1103        .assert_eq("line one\nline ˇtwo\nline three");
1104
1105    cx.simulate_shared_keystrokes("^ d ` a").await;
1106    cx.shared_state()
1107        .await
1108        .assert_eq("line one\nˇtwo\nline three");
1109}
1110
1111#[gpui::test]
1112async fn test_lt_gt_marks(cx: &mut TestAppContext) {
1113    let mut cx = NeovimBackedTestContext::new(cx).await;
1114
1115    cx.set_shared_state(indoc!(
1116        "
1117        Line one
1118        Line two
1119        Line ˇthree
1120        Line four
1121        Line five
1122    "
1123    ))
1124    .await;
1125
1126    cx.simulate_shared_keystrokes("v j escape k k").await;
1127
1128    cx.simulate_shared_keystrokes("' <").await;
1129    cx.shared_state().await.assert_eq(indoc! {"
1130        Line one
1131        Line two
1132        ˇLine three
1133        Line four
1134        Line five
1135    "});
1136
1137    cx.simulate_shared_keystrokes("` <").await;
1138    cx.shared_state().await.assert_eq(indoc! {"
1139        Line one
1140        Line two
1141        Line ˇthree
1142        Line four
1143        Line five
1144    "});
1145
1146    cx.simulate_shared_keystrokes("' >").await;
1147    cx.shared_state().await.assert_eq(indoc! {"
1148        Line one
1149        Line two
1150        Line three
1151        ˇLine four
1152        Line five
1153    "
1154    });
1155
1156    cx.simulate_shared_keystrokes("` >").await;
1157    cx.shared_state().await.assert_eq(indoc! {"
1158        Line one
1159        Line two
1160        Line three
1161        Line ˇfour
1162        Line five
1163    "
1164    });
1165
1166    cx.simulate_shared_keystrokes("v i w o escape").await;
1167    cx.simulate_shared_keystrokes("` >").await;
1168    cx.shared_state().await.assert_eq(indoc! {"
1169        Line one
1170        Line two
1171        Line three
1172        Line fouˇr
1173        Line five
1174    "
1175    });
1176    cx.simulate_shared_keystrokes("` <").await;
1177    cx.shared_state().await.assert_eq(indoc! {"
1178        Line one
1179        Line two
1180        Line three
1181        Line ˇfour
1182        Line five
1183    "
1184    });
1185}
1186
1187#[gpui::test]
1188async fn test_caret_mark(cx: &mut TestAppContext) {
1189    let mut cx = NeovimBackedTestContext::new(cx).await;
1190
1191    cx.set_shared_state(indoc!(
1192        "
1193        Line one
1194        Line two
1195        Line three
1196        ˇLine four
1197        Line five
1198    "
1199    ))
1200    .await;
1201
1202    cx.simulate_shared_keystrokes("c w shift-s t r a i g h t space t h i n g escape j j")
1203        .await;
1204
1205    cx.simulate_shared_keystrokes("' ^").await;
1206    cx.shared_state().await.assert_eq(indoc! {"
1207        Line one
1208        Line two
1209        Line three
1210        ˇStraight thing four
1211        Line five
1212    "
1213    });
1214
1215    cx.simulate_shared_keystrokes("` ^").await;
1216    cx.shared_state().await.assert_eq(indoc! {"
1217        Line one
1218        Line two
1219        Line three
1220        Straight thingˇ four
1221        Line five
1222    "
1223    });
1224
1225    cx.simulate_shared_keystrokes("k a ! escape k g i ?").await;
1226    cx.shared_state().await.assert_eq(indoc! {"
1227        Line one
1228        Line two
1229        Line three!?ˇ
1230        Straight thing four
1231        Line five
1232    "
1233    });
1234}
1235
1236#[cfg(target_os = "macos")]
1237#[gpui::test]
1238async fn test_dw_eol(cx: &mut gpui::TestAppContext) {
1239    let mut cx = NeovimBackedTestContext::new(cx).await;
1240
1241    cx.set_shared_wrap(12).await;
1242    cx.set_shared_state("twelve ˇchar twelve char\ntwelve char")
1243        .await;
1244    cx.simulate_shared_keystrokes("d w").await;
1245    cx.shared_state()
1246        .await
1247        .assert_eq("twelve ˇtwelve char\ntwelve char");
1248}
1249
1250#[gpui::test]
1251async fn test_toggle_comments(cx: &mut gpui::TestAppContext) {
1252    let mut cx = VimTestContext::new(cx, true).await;
1253
1254    let language = std::sync::Arc::new(language::Language::new(
1255        language::LanguageConfig {
1256            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
1257            ..Default::default()
1258        },
1259        Some(language::tree_sitter_rust::LANGUAGE.into()),
1260    ));
1261    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
1262
1263    // works in normal model
1264    cx.set_state(
1265        indoc! {"
1266      ˇone
1267      two
1268      three
1269      "},
1270        Mode::Normal,
1271    );
1272    cx.simulate_keystrokes("g c c");
1273    cx.assert_state(
1274        indoc! {"
1275          // ˇone
1276          two
1277          three
1278          "},
1279        Mode::Normal,
1280    );
1281
1282    // works in visual mode
1283    cx.simulate_keystrokes("v j g c");
1284    cx.assert_state(
1285        indoc! {"
1286          // // ˇone
1287          // two
1288          three
1289          "},
1290        Mode::Normal,
1291    );
1292
1293    // works in visual line mode
1294    cx.simulate_keystrokes("shift-v j g c");
1295    cx.assert_state(
1296        indoc! {"
1297          // ˇone
1298          two
1299          three
1300          "},
1301        Mode::Normal,
1302    );
1303
1304    // works with count
1305    cx.simulate_keystrokes("g c 2 j");
1306    cx.assert_state(
1307        indoc! {"
1308            // // ˇone
1309            // two
1310            // three
1311            "},
1312        Mode::Normal,
1313    );
1314
1315    // works with motion object
1316    cx.simulate_keystrokes("shift-g");
1317    cx.simulate_keystrokes("g c g g");
1318    cx.assert_state(
1319        indoc! {"
1320            // one
1321            two
1322            three
1323            ˇ"},
1324        Mode::Normal,
1325    );
1326}
1327
1328#[gpui::test]
1329async fn test_find_multibyte(cx: &mut gpui::TestAppContext) {
1330    let mut cx = NeovimBackedTestContext::new(cx).await;
1331
1332    cx.set_shared_state(r#"<label for="guests">ˇPočet hostů</label>"#)
1333        .await;
1334
1335    cx.simulate_shared_keystrokes("c t < o escape").await;
1336    cx.shared_state()
1337        .await
1338        .assert_eq(r#"<label for="guests">ˇo</label>"#);
1339}
1340
1341#[gpui::test]
1342async fn test_sneak(cx: &mut gpui::TestAppContext) {
1343    let mut cx = VimTestContext::new(cx, true).await;
1344
1345    cx.update(|_window, cx| {
1346        cx.bind_keys([
1347            KeyBinding::new(
1348                "s",
1349                PushSneak { first_char: None },
1350                Some("vim_mode == normal"),
1351            ),
1352            KeyBinding::new(
1353                "S",
1354                PushSneakBackward { first_char: None },
1355                Some("vim_mode == normal"),
1356            ),
1357            KeyBinding::new(
1358                "S",
1359                PushSneakBackward { first_char: None },
1360                Some("vim_mode == visual"),
1361            ),
1362        ])
1363    });
1364
1365    // Sneak forwards multibyte & multiline
1366    cx.set_state(
1367        indoc! {
1368            r#"<labelˇ for="guests">
1369                    Počet hostů
1370                </label>"#
1371        },
1372        Mode::Normal,
1373    );
1374    cx.simulate_keystrokes("s t ů");
1375    cx.assert_state(
1376        indoc! {
1377            r#"<label for="guests">
1378                Počet hosˇtů
1379            </label>"#
1380        },
1381        Mode::Normal,
1382    );
1383
1384    // Visual sneak backwards multibyte & multiline
1385    cx.simulate_keystrokes("v S < l");
1386    cx.assert_state(
1387        indoc! {
1388            r#"«ˇ<label for="guests">
1389                Počet host»ů
1390            </label>"#
1391        },
1392        Mode::Visual,
1393    );
1394
1395    // Sneak backwards repeated
1396    cx.set_state(r#"11 12 13 ˇ14"#, Mode::Normal);
1397    cx.simulate_keystrokes("S space 1");
1398    cx.assert_state(r#"11 12ˇ 13 14"#, Mode::Normal);
1399    cx.simulate_keystrokes(";");
1400    cx.assert_state(r#"11ˇ 12 13 14"#, Mode::Normal);
1401}
1402
1403#[gpui::test]
1404async fn test_plus_minus(cx: &mut gpui::TestAppContext) {
1405    let mut cx = NeovimBackedTestContext::new(cx).await;
1406
1407    cx.set_shared_state(indoc! {
1408        "one
1409           two
1410        thrˇee
1411    "})
1412        .await;
1413
1414    cx.simulate_shared_keystrokes("-").await;
1415    cx.shared_state().await.assert_matches();
1416    cx.simulate_shared_keystrokes("-").await;
1417    cx.shared_state().await.assert_matches();
1418    cx.simulate_shared_keystrokes("+").await;
1419    cx.shared_state().await.assert_matches();
1420}
1421
1422#[gpui::test]
1423async fn test_command_alias(cx: &mut gpui::TestAppContext) {
1424    let mut cx = VimTestContext::new(cx, true).await;
1425    cx.update_global(|store: &mut SettingsStore, cx| {
1426        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
1427            let mut aliases = HashMap::default();
1428            aliases.insert("Q".to_string(), "upper".to_string());
1429            s.command_aliases = Some(aliases)
1430        });
1431    });
1432
1433    cx.set_state("ˇhello world", Mode::Normal);
1434    cx.simulate_keystrokes(": Q");
1435    cx.set_state("ˇHello world", Mode::Normal);
1436}
1437
1438#[gpui::test]
1439async fn test_remap_adjacent_dog_cat(cx: &mut gpui::TestAppContext) {
1440    let mut cx = NeovimBackedTestContext::new(cx).await;
1441    cx.update(|_, cx| {
1442        cx.bind_keys([
1443            KeyBinding::new(
1444                "d o g",
1445                workspace::SendKeystrokes("🐶".to_string()),
1446                Some("vim_mode == insert"),
1447            ),
1448            KeyBinding::new(
1449                "c a t",
1450                workspace::SendKeystrokes("🐱".to_string()),
1451                Some("vim_mode == insert"),
1452            ),
1453        ])
1454    });
1455    cx.neovim.exec("imap dog 🐶").await;
1456    cx.neovim.exec("imap cat 🐱").await;
1457
1458    cx.set_shared_state("ˇ").await;
1459    cx.simulate_shared_keystrokes("i d o g").await;
1460    cx.shared_state().await.assert_eq("🐶ˇ");
1461
1462    cx.set_shared_state("ˇ").await;
1463    cx.simulate_shared_keystrokes("i d o d o g").await;
1464    cx.shared_state().await.assert_eq("do🐶ˇ");
1465
1466    cx.set_shared_state("ˇ").await;
1467    cx.simulate_shared_keystrokes("i d o c a t").await;
1468    cx.shared_state().await.assert_eq("do🐱ˇ");
1469}
1470
1471#[gpui::test]
1472async fn test_remap_nested_pineapple(cx: &mut gpui::TestAppContext) {
1473    let mut cx = NeovimBackedTestContext::new(cx).await;
1474    cx.update(|_, cx| {
1475        cx.bind_keys([
1476            KeyBinding::new(
1477                "p i n",
1478                workspace::SendKeystrokes("📌".to_string()),
1479                Some("vim_mode == insert"),
1480            ),
1481            KeyBinding::new(
1482                "p i n e",
1483                workspace::SendKeystrokes("🌲".to_string()),
1484                Some("vim_mode == insert"),
1485            ),
1486            KeyBinding::new(
1487                "p i n e a p p l e",
1488                workspace::SendKeystrokes("🍍".to_string()),
1489                Some("vim_mode == insert"),
1490            ),
1491        ])
1492    });
1493    cx.neovim.exec("imap pin 📌").await;
1494    cx.neovim.exec("imap pine 🌲").await;
1495    cx.neovim.exec("imap pineapple 🍍").await;
1496
1497    cx.set_shared_state("ˇ").await;
1498    cx.simulate_shared_keystrokes("i p i n").await;
1499    cx.executor().advance_clock(Duration::from_millis(1000));
1500    cx.run_until_parked();
1501    cx.shared_state().await.assert_eq("📌ˇ");
1502
1503    cx.set_shared_state("ˇ").await;
1504    cx.simulate_shared_keystrokes("i p i n e").await;
1505    cx.executor().advance_clock(Duration::from_millis(1000));
1506    cx.run_until_parked();
1507    cx.shared_state().await.assert_eq("🌲ˇ");
1508
1509    cx.set_shared_state("ˇ").await;
1510    cx.simulate_shared_keystrokes("i p i n e a p p l e").await;
1511    cx.shared_state().await.assert_eq("🍍ˇ");
1512}
1513
1514#[gpui::test]
1515async fn test_remap_recursion(cx: &mut gpui::TestAppContext) {
1516    let mut cx = NeovimBackedTestContext::new(cx).await;
1517    cx.update(|_, cx| {
1518        cx.bind_keys([KeyBinding::new(
1519            "x",
1520            workspace::SendKeystrokes("\" _ x".to_string()),
1521            Some("VimControl"),
1522        )]);
1523        cx.bind_keys([KeyBinding::new(
1524            "y",
1525            workspace::SendKeystrokes("2 x".to_string()),
1526            Some("VimControl"),
1527        )])
1528    });
1529    cx.neovim.exec("noremap x \"_x").await;
1530    cx.neovim.exec("map y 2x").await;
1531
1532    cx.set_shared_state("ˇhello").await;
1533    cx.simulate_shared_keystrokes("d l").await;
1534    cx.shared_clipboard().await.assert_eq("h");
1535    cx.simulate_shared_keystrokes("y").await;
1536    cx.shared_clipboard().await.assert_eq("h");
1537    cx.shared_state().await.assert_eq("ˇlo");
1538}
1539
1540#[gpui::test]
1541async fn test_escape_while_waiting(cx: &mut gpui::TestAppContext) {
1542    let mut cx = NeovimBackedTestContext::new(cx).await;
1543    cx.set_shared_state("ˇhi").await;
1544    cx.simulate_shared_keystrokes("\" + escape x").await;
1545    cx.shared_state().await.assert_eq("ˇi");
1546}
1547
1548#[gpui::test]
1549async fn test_ctrl_w_override(cx: &mut gpui::TestAppContext) {
1550    let mut cx = NeovimBackedTestContext::new(cx).await;
1551    cx.update(|_, cx| {
1552        cx.bind_keys([KeyBinding::new("ctrl-w", DeleteLine, None)]);
1553    });
1554    cx.neovim.exec("map <c-w> D").await;
1555    cx.set_shared_state("ˇhi").await;
1556    cx.simulate_shared_keystrokes("ctrl-w").await;
1557    cx.shared_state().await.assert_eq("ˇ");
1558}
1559
1560#[gpui::test]
1561async fn test_visual_indent_count(cx: &mut gpui::TestAppContext) {
1562    let mut cx = VimTestContext::new(cx, true).await;
1563    cx.set_state("ˇhi", Mode::Normal);
1564    cx.simulate_keystrokes("shift-v 3 >");
1565    cx.assert_state("            ˇhi", Mode::Normal);
1566    cx.simulate_keystrokes("shift-v 2 <");
1567    cx.assert_state("    ˇhi", Mode::Normal);
1568}
1569
1570#[gpui::test]
1571async fn test_record_replay_recursion(cx: &mut gpui::TestAppContext) {
1572    let mut cx = NeovimBackedTestContext::new(cx).await;
1573
1574    cx.set_shared_state("ˇhello world").await;
1575    cx.simulate_shared_keystrokes(">").await;
1576    cx.simulate_shared_keystrokes(".").await;
1577    cx.simulate_shared_keystrokes(".").await;
1578    cx.simulate_shared_keystrokes(".").await;
1579    cx.shared_state().await.assert_eq("ˇhello world");
1580}
1581
1582#[gpui::test]
1583async fn test_blackhole_register(cx: &mut gpui::TestAppContext) {
1584    let mut cx = NeovimBackedTestContext::new(cx).await;
1585
1586    cx.set_shared_state("ˇhello world").await;
1587    cx.simulate_shared_keystrokes("d i w \" _ d a w").await;
1588    cx.simulate_shared_keystrokes("p").await;
1589    cx.shared_state().await.assert_eq("hellˇo");
1590}
1591
1592#[gpui::test]
1593async fn test_sentence_backwards(cx: &mut gpui::TestAppContext) {
1594    let mut cx = NeovimBackedTestContext::new(cx).await;
1595
1596    cx.set_shared_state("one\n\ntwo\nthree\nˇ\nfour").await;
1597    cx.simulate_shared_keystrokes("(").await;
1598    cx.shared_state()
1599        .await
1600        .assert_eq("one\n\nˇtwo\nthree\n\nfour");
1601
1602    cx.set_shared_state("hello.\n\n\nworˇld.").await;
1603    cx.simulate_shared_keystrokes("(").await;
1604    cx.shared_state().await.assert_eq("hello.\n\n\nˇworld.");
1605    cx.simulate_shared_keystrokes("(").await;
1606    cx.shared_state().await.assert_eq("hello.\n\nˇ\nworld.");
1607    cx.simulate_shared_keystrokes("(").await;
1608    cx.shared_state().await.assert_eq("ˇhello.\n\n\nworld.");
1609
1610    cx.set_shared_state("hello. worlˇd.").await;
1611    cx.simulate_shared_keystrokes("(").await;
1612    cx.shared_state().await.assert_eq("hello. ˇworld.");
1613    cx.simulate_shared_keystrokes("(").await;
1614    cx.shared_state().await.assert_eq("ˇhello. world.");
1615
1616    cx.set_shared_state(". helˇlo.").await;
1617    cx.simulate_shared_keystrokes("(").await;
1618    cx.shared_state().await.assert_eq(". ˇhello.");
1619    cx.simulate_shared_keystrokes("(").await;
1620    cx.shared_state().await.assert_eq(". ˇhello.");
1621
1622    cx.set_shared_state(indoc! {
1623        "{
1624            hello_world();
1625        ˇ}"
1626    })
1627    .await;
1628    cx.simulate_shared_keystrokes("(").await;
1629    cx.shared_state().await.assert_eq(indoc! {
1630        "ˇ{
1631            hello_world();
1632        }"
1633    });
1634
1635    cx.set_shared_state(indoc! {
1636        "Hello! World..?
1637
1638        \tHello! World... ˇ"
1639    })
1640    .await;
1641    cx.simulate_shared_keystrokes("(").await;
1642    cx.shared_state().await.assert_eq(indoc! {
1643        "Hello! World..?
1644
1645        \tHello! ˇWorld... "
1646    });
1647    cx.simulate_shared_keystrokes("(").await;
1648    cx.shared_state().await.assert_eq(indoc! {
1649        "Hello! World..?
1650
1651        \tˇHello! World... "
1652    });
1653    cx.simulate_shared_keystrokes("(").await;
1654    cx.shared_state().await.assert_eq(indoc! {
1655        "Hello! World..?
1656        ˇ
1657        \tHello! World... "
1658    });
1659    cx.simulate_shared_keystrokes("(").await;
1660    cx.shared_state().await.assert_eq(indoc! {
1661        "Hello! ˇWorld..?
1662
1663        \tHello! World... "
1664    });
1665}
1666
1667#[gpui::test]
1668async fn test_sentence_forwards(cx: &mut gpui::TestAppContext) {
1669    let mut cx = NeovimBackedTestContext::new(cx).await;
1670
1671    cx.set_shared_state("helˇlo.\n\n\nworld.").await;
1672    cx.simulate_shared_keystrokes(")").await;
1673    cx.shared_state().await.assert_eq("hello.\nˇ\n\nworld.");
1674    cx.simulate_shared_keystrokes(")").await;
1675    cx.shared_state().await.assert_eq("hello.\n\n\nˇworld.");
1676    cx.simulate_shared_keystrokes(")").await;
1677    cx.shared_state().await.assert_eq("hello.\n\n\nworldˇ.");
1678
1679    cx.set_shared_state("helˇlo.\n\n\nworld.").await;
1680}
1681
1682#[gpui::test]
1683async fn test_ctrl_o_visual(cx: &mut gpui::TestAppContext) {
1684    let mut cx = NeovimBackedTestContext::new(cx).await;
1685
1686    cx.set_shared_state("helloˇ world.").await;
1687    cx.simulate_shared_keystrokes("i ctrl-o v b r l").await;
1688    cx.shared_state().await.assert_eq("ˇllllllworld.");
1689    cx.simulate_shared_keystrokes("ctrl-o v f w d").await;
1690    cx.shared_state().await.assert_eq("ˇorld.");
1691}
1692
1693#[gpui::test]
1694async fn test_ctrl_o_position(cx: &mut gpui::TestAppContext) {
1695    let mut cx = NeovimBackedTestContext::new(cx).await;
1696
1697    cx.set_shared_state("helˇlo world.").await;
1698    cx.simulate_shared_keystrokes("i ctrl-o d i w").await;
1699    cx.shared_state().await.assert_eq("ˇ world.");
1700    cx.simulate_shared_keystrokes("ctrl-o p").await;
1701    cx.shared_state().await.assert_eq(" helloˇworld.");
1702}
1703
1704#[gpui::test]
1705async fn test_ctrl_o_dot(cx: &mut gpui::TestAppContext) {
1706    let mut cx = NeovimBackedTestContext::new(cx).await;
1707
1708    cx.set_shared_state("heˇllo world.").await;
1709    cx.simulate_shared_keystrokes("x i ctrl-o .").await;
1710    cx.shared_state().await.assert_eq("heˇo world.");
1711    cx.simulate_shared_keystrokes("l l escape .").await;
1712    cx.shared_state().await.assert_eq("hellˇllo world.");
1713}
1714
1715#[gpui::test]
1716async fn test_folded_multibuffer_excerpts(cx: &mut gpui::TestAppContext) {
1717    VimTestContext::init(cx);
1718    cx.update(|cx| {
1719        VimTestContext::init_keybindings(true, cx);
1720    });
1721    let (editor, cx) = cx.add_window_view(|window, cx| {
1722        let multi_buffer = MultiBuffer::build_multi(
1723            [
1724                ("111\n222\n333\n444\n", vec![Point::row_range(0..2)]),
1725                ("aaa\nbbb\nccc\nddd\n", vec![Point::row_range(0..2)]),
1726                ("AAA\nBBB\nCCC\nDDD\n", vec![Point::row_range(0..2)]),
1727                ("one\ntwo\nthr\nfou\n", vec![Point::row_range(0..2)]),
1728            ],
1729            cx,
1730        );
1731        let mut editor = Editor::new(EditorMode::Full, multi_buffer.clone(), None, window, cx);
1732
1733        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
1734        // fold all but the second buffer, so that we test navigating between two
1735        // adjacent folded buffers, as well as folded buffers at the start and
1736        // end the multibuffer
1737        editor.fold_buffer(buffer_ids[0], cx);
1738        editor.fold_buffer(buffer_ids[2], cx);
1739        editor.fold_buffer(buffer_ids[3], cx);
1740
1741        editor
1742    });
1743    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
1744
1745    cx.assert_excerpts_with_selections(indoc! {"
1746        [EXCERPT]
1747        ˇ[FOLDED]
1748        [EXCERPT]
1749        aaa
1750        bbb
1751        [EXCERPT]
1752        [FOLDED]
1753        [EXCERPT]
1754        [FOLDED]
1755        "
1756    });
1757    cx.simulate_keystroke("j");
1758    cx.assert_excerpts_with_selections(indoc! {"
1759        [EXCERPT]
1760        [FOLDED]
1761        [EXCERPT]
1762        ˇaaa
1763        bbb
1764        [EXCERPT]
1765        [FOLDED]
1766        [EXCERPT]
1767        [FOLDED]
1768        "
1769    });
1770    cx.simulate_keystroke("j");
1771    cx.simulate_keystroke("j");
1772    cx.assert_excerpts_with_selections(indoc! {"
1773        [EXCERPT]
1774        [FOLDED]
1775        [EXCERPT]
1776        aaa
1777        bbb
1778        ˇ[EXCERPT]
1779        [FOLDED]
1780        [EXCERPT]
1781        [FOLDED]
1782        "
1783    });
1784    cx.simulate_keystroke("j");
1785    cx.assert_excerpts_with_selections(indoc! {"
1786        [EXCERPT]
1787        [FOLDED]
1788        [EXCERPT]
1789        aaa
1790        bbb
1791        [EXCERPT]
1792        ˇ[FOLDED]
1793        [EXCERPT]
1794        [FOLDED]
1795        "
1796    });
1797    cx.simulate_keystroke("j");
1798    cx.assert_excerpts_with_selections(indoc! {"
1799        [EXCERPT]
1800        [FOLDED]
1801        [EXCERPT]
1802        aaa
1803        bbb
1804        [EXCERPT]
1805        [FOLDED]
1806        [EXCERPT]
1807        ˇ[FOLDED]
1808        "
1809    });
1810    cx.simulate_keystroke("k");
1811    cx.assert_excerpts_with_selections(indoc! {"
1812        [EXCERPT]
1813        [FOLDED]
1814        [EXCERPT]
1815        aaa
1816        bbb
1817        [EXCERPT]
1818        ˇ[FOLDED]
1819        [EXCERPT]
1820        [FOLDED]
1821        "
1822    });
1823    cx.simulate_keystroke("k");
1824    cx.simulate_keystroke("k");
1825    cx.simulate_keystroke("k");
1826    cx.assert_excerpts_with_selections(indoc! {"
1827        [EXCERPT]
1828        [FOLDED]
1829        [EXCERPT]
1830        ˇaaa
1831        bbb
1832        [EXCERPT]
1833        [FOLDED]
1834        [EXCERPT]
1835        [FOLDED]
1836        "
1837    });
1838    cx.simulate_keystroke("k");
1839    cx.assert_excerpts_with_selections(indoc! {"
1840        [EXCERPT]
1841        ˇ[FOLDED]
1842        [EXCERPT]
1843        aaa
1844        bbb
1845        [EXCERPT]
1846        [FOLDED]
1847        [EXCERPT]
1848        [FOLDED]
1849        "
1850    });
1851    cx.simulate_keystroke("shift-g");
1852    cx.assert_excerpts_with_selections(indoc! {"
1853        [EXCERPT]
1854        [FOLDED]
1855        [EXCERPT]
1856        aaa
1857        bbb
1858        [EXCERPT]
1859        [FOLDED]
1860        [EXCERPT]
1861        ˇ[FOLDED]
1862        "
1863    });
1864    cx.simulate_keystrokes("g g");
1865    cx.assert_excerpts_with_selections(indoc! {"
1866        [EXCERPT]
1867        ˇ[FOLDED]
1868        [EXCERPT]
1869        aaa
1870        bbb
1871        [EXCERPT]
1872        [FOLDED]
1873        [EXCERPT]
1874        [FOLDED]
1875        "
1876    });
1877    cx.update_editor(|editor, _, cx| {
1878        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
1879        editor.fold_buffer(buffer_ids[1], cx);
1880    });
1881
1882    cx.assert_excerpts_with_selections(indoc! {"
1883        [EXCERPT]
1884        ˇ[FOLDED]
1885        [EXCERPT]
1886        [FOLDED]
1887        [EXCERPT]
1888        [FOLDED]
1889        [EXCERPT]
1890        [FOLDED]
1891        "
1892    });
1893    cx.simulate_keystrokes("2 j");
1894    cx.assert_excerpts_with_selections(indoc! {"
1895        [EXCERPT]
1896        [FOLDED]
1897        [EXCERPT]
1898        [FOLDED]
1899        [EXCERPT]
1900        ˇ[FOLDED]
1901        [EXCERPT]
1902        [FOLDED]
1903        "
1904    });
1905}