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 = cx.set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
 921        move |_, _, _| async move { Ok(Some(lsp::PrepareRenameResponse::Range(def_range))) },
 922    );
 923    let mut rename_request =
 924        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, params, _| async move {
 925            Ok(Some(lsp::WorkspaceEdit {
 926                changes: Some(
 927                    [(
 928                        url.clone(),
 929                        vec![
 930                            lsp::TextEdit::new(def_range, params.new_name.clone()),
 931                            lsp::TextEdit::new(tgt_range, params.new_name),
 932                        ],
 933                    )]
 934                    .into(),
 935                ),
 936                ..Default::default()
 937            }))
 938        });
 939
 940    cx.simulate_keystrokes("c d");
 941    prepare_request.next().await.unwrap();
 942    cx.simulate_input("after");
 943    cx.simulate_keystrokes("enter");
 944    rename_request.next().await.unwrap();
 945    cx.assert_state("const afterˇ = 2; console.log(after)", Mode::Normal)
 946}
 947
 948// TODO: this test is flaky on our linux CI machines
 949#[cfg(target_os = "macos")]
 950#[gpui::test]
 951async fn test_remap(cx: &mut gpui::TestAppContext) {
 952    let mut cx = VimTestContext::new(cx, true).await;
 953
 954    // test moving the cursor
 955    cx.update(|_, cx| {
 956        cx.bind_keys([KeyBinding::new(
 957            "g z",
 958            workspace::SendKeystrokes("l l l l".to_string()),
 959            None,
 960        )])
 961    });
 962    cx.set_state("ˇ123456789", Mode::Normal);
 963    cx.simulate_keystrokes("g z");
 964    cx.assert_state("1234ˇ56789", Mode::Normal);
 965
 966    // test switching modes
 967    cx.update(|_, cx| {
 968        cx.bind_keys([KeyBinding::new(
 969            "g y",
 970            workspace::SendKeystrokes("i f o o escape l".to_string()),
 971            None,
 972        )])
 973    });
 974    cx.set_state("ˇ123456789", Mode::Normal);
 975    cx.simulate_keystrokes("g y");
 976    cx.assert_state("fooˇ123456789", Mode::Normal);
 977
 978    // test recursion
 979    cx.update(|_, cx| {
 980        cx.bind_keys([KeyBinding::new(
 981            "g x",
 982            workspace::SendKeystrokes("g z g y".to_string()),
 983            None,
 984        )])
 985    });
 986    cx.set_state("ˇ123456789", Mode::Normal);
 987    cx.simulate_keystrokes("g x");
 988    cx.assert_state("1234fooˇ56789", Mode::Normal);
 989
 990    cx.executor().allow_parking();
 991
 992    // test command
 993    cx.update(|_, cx| {
 994        cx.bind_keys([KeyBinding::new(
 995            "g w",
 996            workspace::SendKeystrokes(": j enter".to_string()),
 997            None,
 998        )])
 999    });
1000    cx.set_state("ˇ1234\n56789", Mode::Normal);
1001    cx.simulate_keystrokes("g w");
1002    cx.assert_state("1234ˇ 56789", Mode::Normal);
1003
1004    // test leaving command
1005    cx.update(|_, cx| {
1006        cx.bind_keys([KeyBinding::new(
1007            "g u",
1008            workspace::SendKeystrokes("g w g z".to_string()),
1009            None,
1010        )])
1011    });
1012    cx.set_state("ˇ1234\n56789", Mode::Normal);
1013    cx.simulate_keystrokes("g u");
1014    cx.assert_state("1234 567ˇ89", Mode::Normal);
1015
1016    // test leaving command
1017    cx.update(|_, cx| {
1018        cx.bind_keys([KeyBinding::new(
1019            "g t",
1020            workspace::SendKeystrokes("i space escape".to_string()),
1021            None,
1022        )])
1023    });
1024    cx.set_state("12ˇ34", Mode::Normal);
1025    cx.simulate_keystrokes("g t");
1026    cx.assert_state("12ˇ 34", Mode::Normal);
1027}
1028
1029#[gpui::test]
1030async fn test_undo(cx: &mut gpui::TestAppContext) {
1031    let mut cx = NeovimBackedTestContext::new(cx).await;
1032
1033    cx.set_shared_state("hello quˇoel world").await;
1034    cx.simulate_shared_keystrokes("v i w s c o escape u").await;
1035    cx.shared_state().await.assert_eq("hello ˇquoel world");
1036    cx.simulate_shared_keystrokes("ctrl-r").await;
1037    cx.shared_state().await.assert_eq("hello ˇco world");
1038    cx.simulate_shared_keystrokes("a o right l escape").await;
1039    cx.shared_state().await.assert_eq("hello cooˇl world");
1040    cx.simulate_shared_keystrokes("u").await;
1041    cx.shared_state().await.assert_eq("hello cooˇ world");
1042    cx.simulate_shared_keystrokes("u").await;
1043    cx.shared_state().await.assert_eq("hello cˇo world");
1044    cx.simulate_shared_keystrokes("u").await;
1045    cx.shared_state().await.assert_eq("hello ˇquoel world");
1046
1047    cx.set_shared_state("hello quˇoel world").await;
1048    cx.simulate_shared_keystrokes("v i w ~ u").await;
1049    cx.shared_state().await.assert_eq("hello ˇquoel world");
1050
1051    cx.set_shared_state("\nhello quˇoel world\n").await;
1052    cx.simulate_shared_keystrokes("shift-v s c escape u").await;
1053    cx.shared_state().await.assert_eq("\nˇhello quoel world\n");
1054
1055    cx.set_shared_state(indoc! {"
1056        ˇ1
1057        2
1058        3"})
1059        .await;
1060
1061    cx.simulate_shared_keystrokes("ctrl-v shift-g ctrl-a").await;
1062    cx.shared_state().await.assert_eq(indoc! {"
1063        ˇ2
1064        3
1065        4"});
1066
1067    cx.simulate_shared_keystrokes("u").await;
1068    cx.shared_state().await.assert_eq(indoc! {"
1069        ˇ1
1070        2
1071        3"});
1072}
1073
1074#[gpui::test]
1075async fn test_mouse_selection(cx: &mut TestAppContext) {
1076    let mut cx = VimTestContext::new(cx, true).await;
1077
1078    cx.set_state("ˇone two three", Mode::Normal);
1079
1080    let start_point = cx.pixel_position("one twˇo three");
1081    let end_point = cx.pixel_position("one ˇtwo three");
1082
1083    cx.simulate_mouse_down(start_point, MouseButton::Left, Modifiers::none());
1084    cx.simulate_mouse_move(end_point, MouseButton::Left, Modifiers::none());
1085    cx.simulate_mouse_up(end_point, MouseButton::Left, Modifiers::none());
1086
1087    cx.assert_state("one «ˇtwo» three", Mode::Visual)
1088}
1089
1090#[gpui::test]
1091async fn test_lowercase_marks(cx: &mut TestAppContext) {
1092    let mut cx = NeovimBackedTestContext::new(cx).await;
1093
1094    cx.set_shared_state("line one\nline ˇtwo\nline three").await;
1095    cx.simulate_shared_keystrokes("m a l ' a").await;
1096    cx.shared_state()
1097        .await
1098        .assert_eq("line one\nˇline two\nline three");
1099    cx.simulate_shared_keystrokes("` a").await;
1100    cx.shared_state()
1101        .await
1102        .assert_eq("line one\nline ˇtwo\nline three");
1103
1104    cx.simulate_shared_keystrokes("^ d ` a").await;
1105    cx.shared_state()
1106        .await
1107        .assert_eq("line one\nˇtwo\nline three");
1108}
1109
1110#[gpui::test]
1111async fn test_lt_gt_marks(cx: &mut TestAppContext) {
1112    let mut cx = NeovimBackedTestContext::new(cx).await;
1113
1114    cx.set_shared_state(indoc!(
1115        "
1116        Line one
1117        Line two
1118        Line ˇthree
1119        Line four
1120        Line five
1121    "
1122    ))
1123    .await;
1124
1125    cx.simulate_shared_keystrokes("v j escape k k").await;
1126
1127    cx.simulate_shared_keystrokes("' <").await;
1128    cx.shared_state().await.assert_eq(indoc! {"
1129        Line one
1130        Line two
1131        ˇLine three
1132        Line four
1133        Line five
1134    "});
1135
1136    cx.simulate_shared_keystrokes("` <").await;
1137    cx.shared_state().await.assert_eq(indoc! {"
1138        Line one
1139        Line two
1140        Line ˇthree
1141        Line four
1142        Line five
1143    "});
1144
1145    cx.simulate_shared_keystrokes("' >").await;
1146    cx.shared_state().await.assert_eq(indoc! {"
1147        Line one
1148        Line two
1149        Line three
1150        ˇLine four
1151        Line five
1152    "
1153    });
1154
1155    cx.simulate_shared_keystrokes("` >").await;
1156    cx.shared_state().await.assert_eq(indoc! {"
1157        Line one
1158        Line two
1159        Line three
1160        Line ˇfour
1161        Line five
1162    "
1163    });
1164
1165    cx.simulate_shared_keystrokes("v i w o escape").await;
1166    cx.simulate_shared_keystrokes("` >").await;
1167    cx.shared_state().await.assert_eq(indoc! {"
1168        Line one
1169        Line two
1170        Line three
1171        Line fouˇr
1172        Line five
1173    "
1174    });
1175    cx.simulate_shared_keystrokes("` <").await;
1176    cx.shared_state().await.assert_eq(indoc! {"
1177        Line one
1178        Line two
1179        Line three
1180        Line ˇfour
1181        Line five
1182    "
1183    });
1184}
1185
1186#[gpui::test]
1187async fn test_caret_mark(cx: &mut TestAppContext) {
1188    let mut cx = NeovimBackedTestContext::new(cx).await;
1189
1190    cx.set_shared_state(indoc!(
1191        "
1192        Line one
1193        Line two
1194        Line three
1195        ˇLine four
1196        Line five
1197    "
1198    ))
1199    .await;
1200
1201    cx.simulate_shared_keystrokes("c w shift-s t r a i g h t space t h i n g escape j j")
1202        .await;
1203
1204    cx.simulate_shared_keystrokes("' ^").await;
1205    cx.shared_state().await.assert_eq(indoc! {"
1206        Line one
1207        Line two
1208        Line three
1209        ˇStraight thing four
1210        Line five
1211    "
1212    });
1213
1214    cx.simulate_shared_keystrokes("` ^").await;
1215    cx.shared_state().await.assert_eq(indoc! {"
1216        Line one
1217        Line two
1218        Line three
1219        Straight thingˇ four
1220        Line five
1221    "
1222    });
1223
1224    cx.simulate_shared_keystrokes("k a ! escape k g i ?").await;
1225    cx.shared_state().await.assert_eq(indoc! {"
1226        Line one
1227        Line two
1228        Line three!?ˇ
1229        Straight thing four
1230        Line five
1231    "
1232    });
1233}
1234
1235#[cfg(target_os = "macos")]
1236#[gpui::test]
1237async fn test_dw_eol(cx: &mut gpui::TestAppContext) {
1238    let mut cx = NeovimBackedTestContext::new(cx).await;
1239
1240    cx.set_shared_wrap(12).await;
1241    cx.set_shared_state("twelve ˇchar twelve char\ntwelve char")
1242        .await;
1243    cx.simulate_shared_keystrokes("d w").await;
1244    cx.shared_state()
1245        .await
1246        .assert_eq("twelve ˇtwelve char\ntwelve char");
1247}
1248
1249#[gpui::test]
1250async fn test_toggle_comments(cx: &mut gpui::TestAppContext) {
1251    let mut cx = VimTestContext::new(cx, true).await;
1252
1253    let language = std::sync::Arc::new(language::Language::new(
1254        language::LanguageConfig {
1255            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
1256            ..Default::default()
1257        },
1258        Some(language::tree_sitter_rust::LANGUAGE.into()),
1259    ));
1260    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
1261
1262    // works in normal model
1263    cx.set_state(
1264        indoc! {"
1265      ˇone
1266      two
1267      three
1268      "},
1269        Mode::Normal,
1270    );
1271    cx.simulate_keystrokes("g c c");
1272    cx.assert_state(
1273        indoc! {"
1274          // ˇone
1275          two
1276          three
1277          "},
1278        Mode::Normal,
1279    );
1280
1281    // works in visual mode
1282    cx.simulate_keystrokes("v j g c");
1283    cx.assert_state(
1284        indoc! {"
1285          // // ˇone
1286          // two
1287          three
1288          "},
1289        Mode::Normal,
1290    );
1291
1292    // works in visual line mode
1293    cx.simulate_keystrokes("shift-v j g c");
1294    cx.assert_state(
1295        indoc! {"
1296          // ˇone
1297          two
1298          three
1299          "},
1300        Mode::Normal,
1301    );
1302
1303    // works with count
1304    cx.simulate_keystrokes("g c 2 j");
1305    cx.assert_state(
1306        indoc! {"
1307            // // ˇone
1308            // two
1309            // three
1310            "},
1311        Mode::Normal,
1312    );
1313
1314    // works with motion object
1315    cx.simulate_keystrokes("shift-g");
1316    cx.simulate_keystrokes("g c g g");
1317    cx.assert_state(
1318        indoc! {"
1319            // one
1320            two
1321            three
1322            ˇ"},
1323        Mode::Normal,
1324    );
1325}
1326
1327#[gpui::test]
1328async fn test_find_multibyte(cx: &mut gpui::TestAppContext) {
1329    let mut cx = NeovimBackedTestContext::new(cx).await;
1330
1331    cx.set_shared_state(r#"<label for="guests">ˇPočet hostů</label>"#)
1332        .await;
1333
1334    cx.simulate_shared_keystrokes("c t < o escape").await;
1335    cx.shared_state()
1336        .await
1337        .assert_eq(r#"<label for="guests">ˇo</label>"#);
1338}
1339
1340#[gpui::test]
1341async fn test_sneak(cx: &mut gpui::TestAppContext) {
1342    let mut cx = VimTestContext::new(cx, true).await;
1343
1344    cx.update(|_window, cx| {
1345        cx.bind_keys([
1346            KeyBinding::new(
1347                "s",
1348                PushSneak { first_char: None },
1349                Some("vim_mode == normal"),
1350            ),
1351            KeyBinding::new(
1352                "shift-s",
1353                PushSneakBackward { first_char: None },
1354                Some("vim_mode == normal"),
1355            ),
1356            KeyBinding::new(
1357                "shift-s",
1358                PushSneakBackward { first_char: None },
1359                Some("vim_mode == visual"),
1360            ),
1361        ])
1362    });
1363
1364    // Sneak forwards multibyte & multiline
1365    cx.set_state(
1366        indoc! {
1367            r#"<labelˇ for="guests">
1368                    Počet hostů
1369                </label>"#
1370        },
1371        Mode::Normal,
1372    );
1373    cx.simulate_keystrokes("s t ů");
1374    cx.assert_state(
1375        indoc! {
1376            r#"<label for="guests">
1377                Počet hosˇtů
1378            </label>"#
1379        },
1380        Mode::Normal,
1381    );
1382
1383    // Visual sneak backwards multibyte & multiline
1384    cx.simulate_keystrokes("v S < l");
1385    cx.assert_state(
1386        indoc! {
1387            r#"«ˇ<label for="guests">
1388                Počet host»ů
1389            </label>"#
1390        },
1391        Mode::Visual,
1392    );
1393
1394    // Sneak backwards repeated
1395    cx.set_state(r#"11 12 13 ˇ14"#, Mode::Normal);
1396    cx.simulate_keystrokes("S space 1");
1397    cx.assert_state(r#"11 12ˇ 13 14"#, Mode::Normal);
1398    cx.simulate_keystrokes(";");
1399    cx.assert_state(r#"11ˇ 12 13 14"#, Mode::Normal);
1400}
1401
1402#[gpui::test]
1403async fn test_plus_minus(cx: &mut gpui::TestAppContext) {
1404    let mut cx = NeovimBackedTestContext::new(cx).await;
1405
1406    cx.set_shared_state(indoc! {
1407        "one
1408           two
1409        thrˇee
1410    "})
1411        .await;
1412
1413    cx.simulate_shared_keystrokes("-").await;
1414    cx.shared_state().await.assert_matches();
1415    cx.simulate_shared_keystrokes("-").await;
1416    cx.shared_state().await.assert_matches();
1417    cx.simulate_shared_keystrokes("+").await;
1418    cx.shared_state().await.assert_matches();
1419}
1420
1421#[gpui::test]
1422async fn test_command_alias(cx: &mut gpui::TestAppContext) {
1423    let mut cx = VimTestContext::new(cx, true).await;
1424    cx.update_global(|store: &mut SettingsStore, cx| {
1425        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
1426            let mut aliases = HashMap::default();
1427            aliases.insert("Q".to_string(), "upper".to_string());
1428            s.command_aliases = Some(aliases)
1429        });
1430    });
1431
1432    cx.set_state("ˇhello world", Mode::Normal);
1433    cx.simulate_keystrokes(": Q");
1434    cx.set_state("ˇHello world", Mode::Normal);
1435}
1436
1437#[gpui::test]
1438async fn test_remap_adjacent_dog_cat(cx: &mut gpui::TestAppContext) {
1439    let mut cx = NeovimBackedTestContext::new(cx).await;
1440    cx.update(|_, cx| {
1441        cx.bind_keys([
1442            KeyBinding::new(
1443                "d o g",
1444                workspace::SendKeystrokes("🐶".to_string()),
1445                Some("vim_mode == insert"),
1446            ),
1447            KeyBinding::new(
1448                "c a t",
1449                workspace::SendKeystrokes("🐱".to_string()),
1450                Some("vim_mode == insert"),
1451            ),
1452        ])
1453    });
1454    cx.neovim.exec("imap dog 🐶").await;
1455    cx.neovim.exec("imap cat 🐱").await;
1456
1457    cx.set_shared_state("ˇ").await;
1458    cx.simulate_shared_keystrokes("i d o g").await;
1459    cx.shared_state().await.assert_eq("🐶ˇ");
1460
1461    cx.set_shared_state("ˇ").await;
1462    cx.simulate_shared_keystrokes("i d o d o g").await;
1463    cx.shared_state().await.assert_eq("do🐶ˇ");
1464
1465    cx.set_shared_state("ˇ").await;
1466    cx.simulate_shared_keystrokes("i d o c a t").await;
1467    cx.shared_state().await.assert_eq("do🐱ˇ");
1468}
1469
1470#[gpui::test]
1471async fn test_remap_nested_pineapple(cx: &mut gpui::TestAppContext) {
1472    let mut cx = NeovimBackedTestContext::new(cx).await;
1473    cx.update(|_, cx| {
1474        cx.bind_keys([
1475            KeyBinding::new(
1476                "p i n",
1477                workspace::SendKeystrokes("📌".to_string()),
1478                Some("vim_mode == insert"),
1479            ),
1480            KeyBinding::new(
1481                "p i n e",
1482                workspace::SendKeystrokes("🌲".to_string()),
1483                Some("vim_mode == insert"),
1484            ),
1485            KeyBinding::new(
1486                "p i n e a p p l e",
1487                workspace::SendKeystrokes("🍍".to_string()),
1488                Some("vim_mode == insert"),
1489            ),
1490        ])
1491    });
1492    cx.neovim.exec("imap pin 📌").await;
1493    cx.neovim.exec("imap pine 🌲").await;
1494    cx.neovim.exec("imap pineapple 🍍").await;
1495
1496    cx.set_shared_state("ˇ").await;
1497    cx.simulate_shared_keystrokes("i p i n").await;
1498    cx.executor().advance_clock(Duration::from_millis(1000));
1499    cx.run_until_parked();
1500    cx.shared_state().await.assert_eq("📌ˇ");
1501
1502    cx.set_shared_state("ˇ").await;
1503    cx.simulate_shared_keystrokes("i p i n e").await;
1504    cx.executor().advance_clock(Duration::from_millis(1000));
1505    cx.run_until_parked();
1506    cx.shared_state().await.assert_eq("🌲ˇ");
1507
1508    cx.set_shared_state("ˇ").await;
1509    cx.simulate_shared_keystrokes("i p i n e a p p l e").await;
1510    cx.shared_state().await.assert_eq("🍍ˇ");
1511}
1512
1513#[gpui::test]
1514async fn test_remap_recursion(cx: &mut gpui::TestAppContext) {
1515    let mut cx = NeovimBackedTestContext::new(cx).await;
1516    cx.update(|_, cx| {
1517        cx.bind_keys([KeyBinding::new(
1518            "x",
1519            workspace::SendKeystrokes("\" _ x".to_string()),
1520            Some("VimControl"),
1521        )]);
1522        cx.bind_keys([KeyBinding::new(
1523            "y",
1524            workspace::SendKeystrokes("2 x".to_string()),
1525            Some("VimControl"),
1526        )])
1527    });
1528    cx.neovim.exec("noremap x \"_x").await;
1529    cx.neovim.exec("map y 2x").await;
1530
1531    cx.set_shared_state("ˇhello").await;
1532    cx.simulate_shared_keystrokes("d l").await;
1533    cx.shared_clipboard().await.assert_eq("h");
1534    cx.simulate_shared_keystrokes("y").await;
1535    cx.shared_clipboard().await.assert_eq("h");
1536    cx.shared_state().await.assert_eq("ˇlo");
1537}
1538
1539#[gpui::test]
1540async fn test_escape_while_waiting(cx: &mut gpui::TestAppContext) {
1541    let mut cx = NeovimBackedTestContext::new(cx).await;
1542    cx.set_shared_state("ˇhi").await;
1543    cx.simulate_shared_keystrokes("\" + escape x").await;
1544    cx.shared_state().await.assert_eq("ˇi");
1545}
1546
1547#[gpui::test]
1548async fn test_ctrl_w_override(cx: &mut gpui::TestAppContext) {
1549    let mut cx = NeovimBackedTestContext::new(cx).await;
1550    cx.update(|_, cx| {
1551        cx.bind_keys([KeyBinding::new("ctrl-w", DeleteLine, None)]);
1552    });
1553    cx.neovim.exec("map <c-w> D").await;
1554    cx.set_shared_state("ˇhi").await;
1555    cx.simulate_shared_keystrokes("ctrl-w").await;
1556    cx.shared_state().await.assert_eq("ˇ");
1557}
1558
1559#[gpui::test]
1560async fn test_visual_indent_count(cx: &mut gpui::TestAppContext) {
1561    let mut cx = VimTestContext::new(cx, true).await;
1562    cx.set_state("ˇhi", Mode::Normal);
1563    cx.simulate_keystrokes("shift-v 3 >");
1564    cx.assert_state("            ˇhi", Mode::Normal);
1565    cx.simulate_keystrokes("shift-v 2 <");
1566    cx.assert_state("    ˇhi", Mode::Normal);
1567}
1568
1569#[gpui::test]
1570async fn test_record_replay_recursion(cx: &mut gpui::TestAppContext) {
1571    let mut cx = NeovimBackedTestContext::new(cx).await;
1572
1573    cx.set_shared_state("ˇhello world").await;
1574    cx.simulate_shared_keystrokes(">").await;
1575    cx.simulate_shared_keystrokes(".").await;
1576    cx.simulate_shared_keystrokes(".").await;
1577    cx.simulate_shared_keystrokes(".").await;
1578    cx.shared_state().await.assert_eq("ˇhello world");
1579}
1580
1581#[gpui::test]
1582async fn test_blackhole_register(cx: &mut gpui::TestAppContext) {
1583    let mut cx = NeovimBackedTestContext::new(cx).await;
1584
1585    cx.set_shared_state("ˇhello world").await;
1586    cx.simulate_shared_keystrokes("d i w \" _ d a w").await;
1587    cx.simulate_shared_keystrokes("p").await;
1588    cx.shared_state().await.assert_eq("hellˇo");
1589}
1590
1591#[gpui::test]
1592async fn test_sentence_backwards(cx: &mut gpui::TestAppContext) {
1593    let mut cx = NeovimBackedTestContext::new(cx).await;
1594
1595    cx.set_shared_state("one\n\ntwo\nthree\nˇ\nfour").await;
1596    cx.simulate_shared_keystrokes("(").await;
1597    cx.shared_state()
1598        .await
1599        .assert_eq("one\n\nˇtwo\nthree\n\nfour");
1600
1601    cx.set_shared_state("hello.\n\n\nworˇld.").await;
1602    cx.simulate_shared_keystrokes("(").await;
1603    cx.shared_state().await.assert_eq("hello.\n\n\nˇworld.");
1604    cx.simulate_shared_keystrokes("(").await;
1605    cx.shared_state().await.assert_eq("hello.\n\nˇ\nworld.");
1606    cx.simulate_shared_keystrokes("(").await;
1607    cx.shared_state().await.assert_eq("ˇhello.\n\n\nworld.");
1608
1609    cx.set_shared_state("hello. worlˇd.").await;
1610    cx.simulate_shared_keystrokes("(").await;
1611    cx.shared_state().await.assert_eq("hello. ˇworld.");
1612    cx.simulate_shared_keystrokes("(").await;
1613    cx.shared_state().await.assert_eq("ˇhello. world.");
1614
1615    cx.set_shared_state(". helˇlo.").await;
1616    cx.simulate_shared_keystrokes("(").await;
1617    cx.shared_state().await.assert_eq(". ˇhello.");
1618    cx.simulate_shared_keystrokes("(").await;
1619    cx.shared_state().await.assert_eq(". ˇhello.");
1620
1621    cx.set_shared_state(indoc! {
1622        "{
1623            hello_world();
1624        ˇ}"
1625    })
1626    .await;
1627    cx.simulate_shared_keystrokes("(").await;
1628    cx.shared_state().await.assert_eq(indoc! {
1629        "ˇ{
1630            hello_world();
1631        }"
1632    });
1633
1634    cx.set_shared_state(indoc! {
1635        "Hello! World..?
1636
1637        \tHello! World... ˇ"
1638    })
1639    .await;
1640    cx.simulate_shared_keystrokes("(").await;
1641    cx.shared_state().await.assert_eq(indoc! {
1642        "Hello! World..?
1643
1644        \tHello! ˇWorld... "
1645    });
1646    cx.simulate_shared_keystrokes("(").await;
1647    cx.shared_state().await.assert_eq(indoc! {
1648        "Hello! World..?
1649
1650        \tˇHello! World... "
1651    });
1652    cx.simulate_shared_keystrokes("(").await;
1653    cx.shared_state().await.assert_eq(indoc! {
1654        "Hello! World..?
1655        ˇ
1656        \tHello! World... "
1657    });
1658    cx.simulate_shared_keystrokes("(").await;
1659    cx.shared_state().await.assert_eq(indoc! {
1660        "Hello! ˇWorld..?
1661
1662        \tHello! World... "
1663    });
1664}
1665
1666#[gpui::test]
1667async fn test_sentence_forwards(cx: &mut gpui::TestAppContext) {
1668    let mut cx = NeovimBackedTestContext::new(cx).await;
1669
1670    cx.set_shared_state("helˇlo.\n\n\nworld.").await;
1671    cx.simulate_shared_keystrokes(")").await;
1672    cx.shared_state().await.assert_eq("hello.\nˇ\n\nworld.");
1673    cx.simulate_shared_keystrokes(")").await;
1674    cx.shared_state().await.assert_eq("hello.\n\n\nˇworld.");
1675    cx.simulate_shared_keystrokes(")").await;
1676    cx.shared_state().await.assert_eq("hello.\n\n\nworldˇ.");
1677
1678    cx.set_shared_state("helˇlo.\n\n\nworld.").await;
1679}
1680
1681#[gpui::test]
1682async fn test_ctrl_o_visual(cx: &mut gpui::TestAppContext) {
1683    let mut cx = NeovimBackedTestContext::new(cx).await;
1684
1685    cx.set_shared_state("helloˇ world.").await;
1686    cx.simulate_shared_keystrokes("i ctrl-o v b r l").await;
1687    cx.shared_state().await.assert_eq("ˇllllllworld.");
1688    cx.simulate_shared_keystrokes("ctrl-o v f w d").await;
1689    cx.shared_state().await.assert_eq("ˇorld.");
1690}
1691
1692#[gpui::test]
1693async fn test_ctrl_o_position(cx: &mut gpui::TestAppContext) {
1694    let mut cx = NeovimBackedTestContext::new(cx).await;
1695
1696    cx.set_shared_state("helˇlo world.").await;
1697    cx.simulate_shared_keystrokes("i ctrl-o d i w").await;
1698    cx.shared_state().await.assert_eq("ˇ world.");
1699    cx.simulate_shared_keystrokes("ctrl-o p").await;
1700    cx.shared_state().await.assert_eq(" helloˇworld.");
1701}
1702
1703#[gpui::test]
1704async fn test_ctrl_o_dot(cx: &mut gpui::TestAppContext) {
1705    let mut cx = NeovimBackedTestContext::new(cx).await;
1706
1707    cx.set_shared_state("heˇllo world.").await;
1708    cx.simulate_shared_keystrokes("x i ctrl-o .").await;
1709    cx.shared_state().await.assert_eq("heˇo world.");
1710    cx.simulate_shared_keystrokes("l l escape .").await;
1711    cx.shared_state().await.assert_eq("hellˇllo world.");
1712}
1713
1714#[gpui::test]
1715async fn test_folded_multibuffer_excerpts(cx: &mut gpui::TestAppContext) {
1716    VimTestContext::init(cx);
1717    cx.update(|cx| {
1718        VimTestContext::init_keybindings(true, cx);
1719    });
1720    let (editor, cx) = cx.add_window_view(|window, cx| {
1721        let multi_buffer = MultiBuffer::build_multi(
1722            [
1723                ("111\n222\n333\n444\n", vec![Point::row_range(0..2)]),
1724                ("aaa\nbbb\nccc\nddd\n", vec![Point::row_range(0..2)]),
1725                ("AAA\nBBB\nCCC\nDDD\n", vec![Point::row_range(0..2)]),
1726                ("one\ntwo\nthr\nfou\n", vec![Point::row_range(0..2)]),
1727            ],
1728            cx,
1729        );
1730        let mut editor = Editor::new(EditorMode::Full, multi_buffer.clone(), None, window, cx);
1731
1732        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
1733        // fold all but the second buffer, so that we test navigating between two
1734        // adjacent folded buffers, as well as folded buffers at the start and
1735        // end the multibuffer
1736        editor.fold_buffer(buffer_ids[0], cx);
1737        editor.fold_buffer(buffer_ids[2], cx);
1738        editor.fold_buffer(buffer_ids[3], cx);
1739
1740        editor
1741    });
1742    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
1743
1744    cx.assert_excerpts_with_selections(indoc! {"
1745        [EXCERPT]
1746        ˇ[FOLDED]
1747        [EXCERPT]
1748        aaa
1749        bbb
1750        [EXCERPT]
1751        [FOLDED]
1752        [EXCERPT]
1753        [FOLDED]
1754        "
1755    });
1756    cx.simulate_keystroke("j");
1757    cx.assert_excerpts_with_selections(indoc! {"
1758        [EXCERPT]
1759        [FOLDED]
1760        [EXCERPT]
1761        ˇaaa
1762        bbb
1763        [EXCERPT]
1764        [FOLDED]
1765        [EXCERPT]
1766        [FOLDED]
1767        "
1768    });
1769    cx.simulate_keystroke("j");
1770    cx.simulate_keystroke("j");
1771    cx.assert_excerpts_with_selections(indoc! {"
1772        [EXCERPT]
1773        [FOLDED]
1774        [EXCERPT]
1775        aaa
1776        bbb
1777        ˇ[EXCERPT]
1778        [FOLDED]
1779        [EXCERPT]
1780        [FOLDED]
1781        "
1782    });
1783    cx.simulate_keystroke("j");
1784    cx.assert_excerpts_with_selections(indoc! {"
1785        [EXCERPT]
1786        [FOLDED]
1787        [EXCERPT]
1788        aaa
1789        bbb
1790        [EXCERPT]
1791        ˇ[FOLDED]
1792        [EXCERPT]
1793        [FOLDED]
1794        "
1795    });
1796    cx.simulate_keystroke("j");
1797    cx.assert_excerpts_with_selections(indoc! {"
1798        [EXCERPT]
1799        [FOLDED]
1800        [EXCERPT]
1801        aaa
1802        bbb
1803        [EXCERPT]
1804        [FOLDED]
1805        [EXCERPT]
1806        ˇ[FOLDED]
1807        "
1808    });
1809    cx.simulate_keystroke("k");
1810    cx.assert_excerpts_with_selections(indoc! {"
1811        [EXCERPT]
1812        [FOLDED]
1813        [EXCERPT]
1814        aaa
1815        bbb
1816        [EXCERPT]
1817        ˇ[FOLDED]
1818        [EXCERPT]
1819        [FOLDED]
1820        "
1821    });
1822    cx.simulate_keystroke("k");
1823    cx.simulate_keystroke("k");
1824    cx.simulate_keystroke("k");
1825    cx.assert_excerpts_with_selections(indoc! {"
1826        [EXCERPT]
1827        [FOLDED]
1828        [EXCERPT]
1829        ˇaaa
1830        bbb
1831        [EXCERPT]
1832        [FOLDED]
1833        [EXCERPT]
1834        [FOLDED]
1835        "
1836    });
1837    cx.simulate_keystroke("k");
1838    cx.assert_excerpts_with_selections(indoc! {"
1839        [EXCERPT]
1840        ˇ[FOLDED]
1841        [EXCERPT]
1842        aaa
1843        bbb
1844        [EXCERPT]
1845        [FOLDED]
1846        [EXCERPT]
1847        [FOLDED]
1848        "
1849    });
1850    cx.simulate_keystroke("shift-g");
1851    cx.assert_excerpts_with_selections(indoc! {"
1852        [EXCERPT]
1853        [FOLDED]
1854        [EXCERPT]
1855        aaa
1856        bbb
1857        [EXCERPT]
1858        [FOLDED]
1859        [EXCERPT]
1860        ˇ[FOLDED]
1861        "
1862    });
1863    cx.simulate_keystrokes("g g");
1864    cx.assert_excerpts_with_selections(indoc! {"
1865        [EXCERPT]
1866        ˇ[FOLDED]
1867        [EXCERPT]
1868        aaa
1869        bbb
1870        [EXCERPT]
1871        [FOLDED]
1872        [EXCERPT]
1873        [FOLDED]
1874        "
1875    });
1876    cx.update_editor(|editor, _, cx| {
1877        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
1878        editor.fold_buffer(buffer_ids[1], cx);
1879    });
1880
1881    cx.assert_excerpts_with_selections(indoc! {"
1882        [EXCERPT]
1883        ˇ[FOLDED]
1884        [EXCERPT]
1885        [FOLDED]
1886        [EXCERPT]
1887        [FOLDED]
1888        [EXCERPT]
1889        [FOLDED]
1890        "
1891    });
1892    cx.simulate_keystrokes("2 j");
1893    cx.assert_excerpts_with_selections(indoc! {"
1894        [EXCERPT]
1895        [FOLDED]
1896        [EXCERPT]
1897        [FOLDED]
1898        [EXCERPT]
1899        ˇ[FOLDED]
1900        [EXCERPT]
1901        [FOLDED]
1902        "
1903    });
1904}
1905
1906#[gpui::test]
1907async fn test_delete_paragraph_motion(cx: &mut gpui::TestAppContext) {
1908    let mut cx = NeovimBackedTestContext::new(cx).await;
1909    cx.set_shared_state(indoc! {
1910        "ˇhello world.
1911
1912        hello world.
1913        "
1914    })
1915    .await;
1916    cx.simulate_shared_keystrokes("y }").await;
1917    cx.shared_clipboard().await.assert_eq("hello world.\n");
1918    cx.simulate_shared_keystrokes("d }").await;
1919    cx.shared_state().await.assert_eq("ˇ\nhello world.\n");
1920    cx.shared_clipboard().await.assert_eq("hello world.\n");
1921
1922    cx.set_shared_state(indoc! {
1923        "helˇlo world.
1924
1925            hello world.
1926            "
1927    })
1928    .await;
1929    cx.simulate_shared_keystrokes("y }").await;
1930    cx.shared_clipboard().await.assert_eq("lo world.");
1931    cx.simulate_shared_keystrokes("d }").await;
1932    cx.shared_state().await.assert_eq("heˇl\n\nhello world.\n");
1933    cx.shared_clipboard().await.assert_eq("lo world.");
1934}
1935
1936#[gpui::test]
1937async fn test_delete_unmatched_brace(cx: &mut gpui::TestAppContext) {
1938    let mut cx = NeovimBackedTestContext::new(cx).await;
1939    cx.set_shared_state(indoc! {
1940        "fn o(wow: i32) {
1941          dbgˇ!(wow)
1942          dbg!(wow)
1943        }
1944        "
1945    })
1946    .await;
1947    cx.simulate_shared_keystrokes("d ] }").await;
1948    cx.shared_state().await.assert_eq(indoc! {
1949        "fn o(wow: i32) {
1950          dbˇg
1951        }
1952        "
1953    });
1954    cx.shared_clipboard().await.assert_eq("!(wow)\n  dbg!(wow)");
1955    cx.set_shared_state(indoc! {
1956        "fn o(wow: i32) {
1957          ˇdbg!(wow)
1958          dbg!(wow)
1959        }
1960        "
1961    })
1962    .await;
1963    cx.simulate_shared_keystrokes("d ] }").await;
1964    cx.shared_state().await.assert_eq(indoc! {
1965        "fn o(wow: i32) {
1966         ˇ}
1967        "
1968    });
1969    cx.shared_clipboard()
1970        .await
1971        .assert_eq("  dbg!(wow)\n  dbg!(wow)\n");
1972}