test.rs

   1mod neovim_backed_binding_test_context;
   2mod neovim_backed_test_context;
   3mod neovim_connection;
   4mod vim_test_context;
   5
   6use std::time::Duration;
   7
   8use command_palette::CommandPalette;
   9use editor::DisplayPoint;
  10use futures::StreamExt;
  11use gpui::KeyBinding;
  12pub use neovim_backed_binding_test_context::*;
  13pub use neovim_backed_test_context::*;
  14pub use vim_test_context::*;
  15
  16use indoc::indoc;
  17use search::BufferSearchBar;
  18
  19use crate::{insert::NormalBefore, motion, state::Mode, ModeIndicator};
  20
  21#[gpui::test]
  22async fn test_initially_disabled(cx: &mut gpui::TestAppContext) {
  23    let mut cx = VimTestContext::new(cx, false).await;
  24    cx.simulate_keystrokes(["h", "j", "k", "l"]);
  25    cx.assert_editor_state("hjklˇ");
  26}
  27
  28#[gpui::test]
  29async fn test_neovim(cx: &mut gpui::TestAppContext) {
  30    let mut cx = NeovimBackedTestContext::new(cx).await;
  31
  32    cx.simulate_shared_keystroke("i").await;
  33    cx.assert_state_matches().await;
  34    cx.simulate_shared_keystrokes([
  35        "shift-T", "e", "s", "t", " ", "t", "e", "s", "t", "escape", "0", "d", "w",
  36    ])
  37    .await;
  38    cx.assert_state_matches().await;
  39    cx.assert_editor_state("ˇtest");
  40}
  41
  42#[gpui::test]
  43async fn test_toggle_through_settings(cx: &mut gpui::TestAppContext) {
  44    let mut cx = VimTestContext::new(cx, true).await;
  45
  46    cx.simulate_keystroke("i");
  47    assert_eq!(cx.mode(), Mode::Insert);
  48
  49    // Editor acts as though vim is disabled
  50    cx.disable_vim();
  51    cx.simulate_keystrokes(["h", "j", "k", "l"]);
  52    cx.assert_editor_state("hjklˇ");
  53
  54    // Selections aren't changed if editor is blurred but vim-mode is still disabled.
  55    cx.set_state("«hjklˇ»", Mode::Normal);
  56    cx.assert_editor_state("«hjklˇ»");
  57    cx.update_editor(|_, cx| cx.blur());
  58    cx.assert_editor_state("«hjklˇ»");
  59    cx.update_editor(|_, cx| cx.focus_self());
  60    cx.assert_editor_state("«hjklˇ»");
  61
  62    // Enabling dynamically sets vim mode again and restores normal mode
  63    cx.enable_vim();
  64    assert_eq!(cx.mode(), Mode::Normal);
  65    cx.simulate_keystrokes(["h", "h", "h", "l"]);
  66    assert_eq!(cx.buffer_text(), "hjkl".to_owned());
  67    cx.assert_editor_state("hˇjkl");
  68    cx.simulate_keystrokes(["i", "T", "e", "s", "t"]);
  69    cx.assert_editor_state("hTestˇjkl");
  70
  71    // Disabling and enabling resets to normal mode
  72    assert_eq!(cx.mode(), Mode::Insert);
  73    cx.disable_vim();
  74    cx.enable_vim();
  75    assert_eq!(cx.mode(), Mode::Normal);
  76}
  77
  78#[gpui::test]
  79async fn test_cancel_selection(cx: &mut gpui::TestAppContext) {
  80    let mut cx = VimTestContext::new(cx, true).await;
  81
  82    cx.set_state(
  83        indoc! {"The quick brown fox juˇmps over the lazy dog"},
  84        Mode::Normal,
  85    );
  86    // jumps
  87    cx.simulate_keystrokes(["v", "l", "l"]);
  88    cx.assert_editor_state("The quick brown fox ju«mpsˇ» over the lazy dog");
  89
  90    cx.simulate_keystrokes(["escape"]);
  91    cx.assert_editor_state("The quick brown fox jumpˇs over the lazy dog");
  92
  93    // go back to the same selection state
  94    cx.simulate_keystrokes(["v", "h", "h"]);
  95    cx.assert_editor_state("The quick brown fox ju«ˇmps» over the lazy dog");
  96
  97    // Ctrl-[ should behave like Esc
  98    cx.simulate_keystrokes(["ctrl-["]);
  99    cx.assert_editor_state("The quick brown fox juˇmps over the lazy dog");
 100}
 101
 102#[gpui::test]
 103async fn test_buffer_search(cx: &mut gpui::TestAppContext) {
 104    let mut cx = VimTestContext::new(cx, true).await;
 105
 106    cx.set_state(
 107        indoc! {"
 108            The quick brown
 109            fox juˇmps over
 110            the lazy dog"},
 111        Mode::Normal,
 112    );
 113    cx.simulate_keystroke("/");
 114
 115    let search_bar = cx.workspace(|workspace, cx| {
 116        workspace
 117            .active_pane()
 118            .read(cx)
 119            .toolbar()
 120            .read(cx)
 121            .item_of_type::<BufferSearchBar>()
 122            .expect("Buffer search bar should be deployed")
 123    });
 124
 125    cx.update_view(search_bar, |bar, cx| {
 126        assert_eq!(bar.query(cx), "");
 127    })
 128}
 129
 130#[gpui::test]
 131async fn test_count_down(cx: &mut gpui::TestAppContext) {
 132    let mut cx = VimTestContext::new(cx, true).await;
 133
 134    cx.set_state(indoc! {"aˇa\nbb\ncc\ndd\nee"}, Mode::Normal);
 135    cx.simulate_keystrokes(["2", "down"]);
 136    cx.assert_editor_state("aa\nbb\ncˇc\ndd\nee");
 137    cx.simulate_keystrokes(["9", "down"]);
 138    cx.assert_editor_state("aa\nbb\ncc\ndd\neˇe");
 139}
 140
 141#[gpui::test]
 142async fn test_end_of_document_710(cx: &mut gpui::TestAppContext) {
 143    let mut cx = VimTestContext::new(cx, true).await;
 144
 145    // goes to end by default
 146    cx.set_state(indoc! {"aˇa\nbb\ncc"}, Mode::Normal);
 147    cx.simulate_keystrokes(["shift-g"]);
 148    cx.assert_editor_state("aa\nbb\ncˇc");
 149
 150    // can go to line 1 (https://github.com/zed-industries/zed/issues/5812)
 151    cx.simulate_keystrokes(["1", "shift-g"]);
 152    cx.assert_editor_state("aˇa\nbb\ncc");
 153}
 154
 155#[gpui::test]
 156async fn test_end_of_line_with_times(cx: &mut gpui::TestAppContext) {
 157    let mut cx = VimTestContext::new(cx, true).await;
 158
 159    // goes to current line end
 160    cx.set_state(indoc! {"ˇaa\nbb\ncc"}, Mode::Normal);
 161    cx.simulate_keystrokes(["$"]);
 162    cx.assert_editor_state("aˇa\nbb\ncc");
 163
 164    // goes to next line end
 165    cx.simulate_keystrokes(["2", "$"]);
 166    cx.assert_editor_state("aa\nbˇb\ncc");
 167
 168    // try to exceed the final line.
 169    cx.simulate_keystrokes(["4", "$"]);
 170    cx.assert_editor_state("aa\nbb\ncˇc");
 171}
 172
 173#[gpui::test]
 174async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
 175    let mut cx = VimTestContext::new(cx, true).await;
 176
 177    // works in normal mode
 178    cx.set_state(indoc! {"aa\nbˇb\ncc"}, Mode::Normal);
 179    cx.simulate_keystrokes([">", ">"]);
 180    cx.assert_editor_state("aa\n    bˇb\ncc");
 181    cx.simulate_keystrokes(["<", "<"]);
 182    cx.assert_editor_state("aa\nbˇb\ncc");
 183
 184    // works in visual mode
 185    cx.simulate_keystrokes(["shift-v", "down", ">"]);
 186    cx.assert_editor_state("aa\n    bb\n    cˇc");
 187}
 188
 189#[gpui::test]
 190async fn test_escape_command_palette(cx: &mut gpui::TestAppContext) {
 191    let mut cx = VimTestContext::new(cx, true).await;
 192
 193    cx.set_state("aˇbc\n", Mode::Normal);
 194    cx.simulate_keystrokes(["i", "cmd-shift-p"]);
 195
 196    assert!(cx.workspace(|workspace, cx| workspace.active_modal::<CommandPalette>(cx).is_some()));
 197    cx.simulate_keystroke("escape");
 198    cx.run_until_parked();
 199    assert!(!cx.workspace(|workspace, cx| workspace.active_modal::<CommandPalette>(cx).is_some()));
 200    cx.assert_state("aˇbc\n", Mode::Insert);
 201}
 202
 203#[gpui::test]
 204async fn test_escape_cancels(cx: &mut gpui::TestAppContext) {
 205    let mut cx = VimTestContext::new(cx, true).await;
 206
 207    cx.set_state("aˇbˇc", Mode::Normal);
 208    cx.simulate_keystrokes(["escape"]);
 209
 210    cx.assert_state("aˇbc", Mode::Normal);
 211}
 212
 213#[gpui::test]
 214async fn test_selection_on_search(cx: &mut gpui::TestAppContext) {
 215    let mut cx = VimTestContext::new(cx, true).await;
 216
 217    cx.set_state(indoc! {"aa\nbˇb\ncc\ncc\ncc\n"}, Mode::Normal);
 218    cx.simulate_keystrokes(["/", "c", "c"]);
 219
 220    let search_bar = cx.workspace(|workspace, cx| {
 221        workspace
 222            .active_pane()
 223            .read(cx)
 224            .toolbar()
 225            .read(cx)
 226            .item_of_type::<BufferSearchBar>()
 227            .expect("Buffer search bar should be deployed")
 228    });
 229
 230    cx.update_view(search_bar, |bar, cx| {
 231        assert_eq!(bar.query(cx), "cc");
 232    });
 233
 234    cx.update_editor(|editor, cx| {
 235        let highlights = editor.all_text_background_highlights(cx);
 236        assert_eq!(3, highlights.len());
 237        assert_eq!(
 238            DisplayPoint::new(2, 0)..DisplayPoint::new(2, 2),
 239            highlights[0].0
 240        )
 241    });
 242    cx.simulate_keystrokes(["enter"]);
 243
 244    cx.assert_state(indoc! {"aa\nbb\nˇcc\ncc\ncc\n"}, Mode::Normal);
 245    cx.simulate_keystrokes(["n"]);
 246    cx.assert_state(indoc! {"aa\nbb\ncc\nˇcc\ncc\n"}, Mode::Normal);
 247    cx.simulate_keystrokes(["shift-n"]);
 248    cx.assert_state(indoc! {"aa\nbb\nˇcc\ncc\ncc\n"}, Mode::Normal);
 249}
 250
 251#[gpui::test]
 252async fn test_status_indicator(cx: &mut gpui::TestAppContext) {
 253    let mut cx = VimTestContext::new(cx, true).await;
 254
 255    let mode_indicator = cx.workspace(|workspace, cx| {
 256        let status_bar = workspace.status_bar().read(cx);
 257        let mode_indicator = status_bar.item_of_type::<ModeIndicator>();
 258        assert!(mode_indicator.is_some());
 259        mode_indicator.unwrap()
 260    });
 261
 262    assert_eq!(
 263        cx.workspace(|_, cx| mode_indicator.read(cx).mode),
 264        Some(Mode::Normal)
 265    );
 266
 267    // shows the correct mode
 268    cx.simulate_keystrokes(["i"]);
 269    assert_eq!(
 270        cx.workspace(|_, cx| mode_indicator.read(cx).mode),
 271        Some(Mode::Insert)
 272    );
 273    cx.simulate_keystrokes(["escape", "shift-r"]);
 274    assert_eq!(
 275        cx.workspace(|_, cx| mode_indicator.read(cx).mode),
 276        Some(Mode::Replace)
 277    );
 278
 279    // shows even in search
 280    cx.simulate_keystrokes(["escape", "v", "/"]);
 281    assert_eq!(
 282        cx.workspace(|_, cx| mode_indicator.read(cx).mode),
 283        Some(Mode::Visual)
 284    );
 285
 286    // hides if vim mode is disabled
 287    cx.disable_vim();
 288    cx.run_until_parked();
 289    cx.workspace(|workspace, cx| {
 290        let status_bar = workspace.status_bar().read(cx);
 291        let mode_indicator = status_bar.item_of_type::<ModeIndicator>().unwrap();
 292        assert!(mode_indicator.read(cx).mode.is_none());
 293    });
 294
 295    cx.enable_vim();
 296    cx.run_until_parked();
 297    cx.workspace(|workspace, cx| {
 298        let status_bar = workspace.status_bar().read(cx);
 299        let mode_indicator = status_bar.item_of_type::<ModeIndicator>().unwrap();
 300        assert!(mode_indicator.read(cx).mode.is_some());
 301    });
 302}
 303
 304#[gpui::test]
 305async fn test_word_characters(cx: &mut gpui::TestAppContext) {
 306    let mut cx = VimTestContext::new_typescript(cx).await;
 307    cx.set_state(
 308        indoc! { "
 309        class A {
 310            #ˇgoop = 99;
 311            $ˇgoop () { return this.#gˇoop };
 312        };
 313        console.log(new A().$gooˇp())
 314    "},
 315        Mode::Normal,
 316    );
 317    cx.simulate_keystrokes(["v", "i", "w"]);
 318    cx.assert_state(
 319        indoc! {"
 320        class A {
 321            «#goopˇ» = 99;
 322            «$goopˇ» () { return this.«#goopˇ» };
 323        };
 324        console.log(new A().«$goopˇ»())
 325    "},
 326        Mode::Visual,
 327    )
 328}
 329
 330#[gpui::test]
 331async fn test_join_lines(cx: &mut gpui::TestAppContext) {
 332    let mut cx = NeovimBackedTestContext::new(cx).await;
 333
 334    cx.set_shared_state(indoc! {"
 335      ˇone
 336      two
 337      three
 338      four
 339      five
 340      six
 341      "})
 342        .await;
 343    cx.simulate_shared_keystrokes(["shift-j"]).await;
 344    cx.assert_shared_state(indoc! {"
 345          oneˇ two
 346          three
 347          four
 348          five
 349          six
 350          "})
 351        .await;
 352    cx.simulate_shared_keystrokes(["3", "shift-j"]).await;
 353    cx.assert_shared_state(indoc! {"
 354          one two threeˇ four
 355          five
 356          six
 357          "})
 358        .await;
 359
 360    cx.set_shared_state(indoc! {"
 361      ˇone
 362      two
 363      three
 364      four
 365      five
 366      six
 367      "})
 368        .await;
 369    cx.simulate_shared_keystrokes(["j", "v", "3", "j", "shift-j"])
 370        .await;
 371    cx.assert_shared_state(indoc! {"
 372      one
 373      two three fourˇ five
 374      six
 375      "})
 376        .await;
 377}
 378
 379#[gpui::test]
 380async fn test_wrapped_lines(cx: &mut gpui::TestAppContext) {
 381    let mut cx = NeovimBackedTestContext::new(cx).await;
 382
 383    cx.set_shared_wrap(12).await;
 384    // tests line wrap as follows:
 385    //  1: twelve char
 386    //     twelve char
 387    //  2: twelve char
 388    cx.set_shared_state(indoc! { "
 389        tˇwelve char twelve char
 390        twelve char
 391    "})
 392        .await;
 393    cx.simulate_shared_keystrokes(["j"]).await;
 394    cx.assert_shared_state(indoc! { "
 395        twelve char twelve char
 396        tˇwelve char
 397    "})
 398        .await;
 399    cx.simulate_shared_keystrokes(["k"]).await;
 400    cx.assert_shared_state(indoc! { "
 401        tˇwelve char twelve char
 402        twelve char
 403    "})
 404        .await;
 405    cx.simulate_shared_keystrokes(["g", "j"]).await;
 406    cx.assert_shared_state(indoc! { "
 407        twelve char tˇwelve char
 408        twelve char
 409    "})
 410        .await;
 411    cx.simulate_shared_keystrokes(["g", "j"]).await;
 412    cx.assert_shared_state(indoc! { "
 413        twelve char twelve char
 414        tˇwelve char
 415    "})
 416        .await;
 417
 418    cx.simulate_shared_keystrokes(["g", "k"]).await;
 419    cx.assert_shared_state(indoc! { "
 420        twelve char tˇwelve char
 421        twelve char
 422    "})
 423        .await;
 424
 425    cx.simulate_shared_keystrokes(["g", "^"]).await;
 426    cx.assert_shared_state(indoc! { "
 427        twelve char ˇtwelve char
 428        twelve char
 429    "})
 430        .await;
 431
 432    cx.simulate_shared_keystrokes(["^"]).await;
 433    cx.assert_shared_state(indoc! { "
 434        ˇtwelve char twelve char
 435        twelve char
 436    "})
 437        .await;
 438
 439    cx.simulate_shared_keystrokes(["g", "$"]).await;
 440    cx.assert_shared_state(indoc! { "
 441        twelve charˇ twelve char
 442        twelve char
 443    "})
 444        .await;
 445    cx.simulate_shared_keystrokes(["$"]).await;
 446    cx.assert_shared_state(indoc! { "
 447        twelve char twelve chaˇr
 448        twelve char
 449    "})
 450        .await;
 451
 452    cx.set_shared_state(indoc! { "
 453        tˇwelve char twelve char
 454        twelve char
 455    "})
 456        .await;
 457    cx.simulate_shared_keystrokes(["enter"]).await;
 458    cx.assert_shared_state(indoc! { "
 459            twelve char twelve char
 460            ˇtwelve char
 461        "})
 462        .await;
 463
 464    cx.set_shared_state(indoc! { "
 465        twelve char
 466        tˇwelve char twelve char
 467        twelve char
 468    "})
 469        .await;
 470    cx.simulate_shared_keystrokes(["o", "o", "escape"]).await;
 471    cx.assert_shared_state(indoc! { "
 472        twelve char
 473        twelve char twelve char
 474        ˇo
 475        twelve char
 476    "})
 477        .await;
 478
 479    cx.set_shared_state(indoc! { "
 480        twelve char
 481        tˇwelve char twelve char
 482        twelve char
 483    "})
 484        .await;
 485    cx.simulate_shared_keystrokes(["shift-a", "a", "escape"])
 486        .await;
 487    cx.assert_shared_state(indoc! { "
 488        twelve char
 489        twelve char twelve charˇa
 490        twelve char
 491    "})
 492        .await;
 493    cx.simulate_shared_keystrokes(["shift-i", "i", "escape"])
 494        .await;
 495    cx.assert_shared_state(indoc! { "
 496        twelve char
 497        ˇitwelve char twelve chara
 498        twelve char
 499    "})
 500        .await;
 501    cx.simulate_shared_keystrokes(["shift-d"]).await;
 502    cx.assert_shared_state(indoc! { "
 503        twelve char
 504        ˇ
 505        twelve char
 506    "})
 507        .await;
 508
 509    cx.set_shared_state(indoc! { "
 510        twelve char
 511        twelve char tˇwelve char
 512        twelve char
 513    "})
 514        .await;
 515    cx.simulate_shared_keystrokes(["shift-o", "o", "escape"])
 516        .await;
 517    cx.assert_shared_state(indoc! { "
 518        twelve char
 519        ˇo
 520        twelve char twelve char
 521        twelve char
 522    "})
 523        .await;
 524
 525    // line wraps as:
 526    // fourteen ch
 527    // ar
 528    // fourteen ch
 529    // ar
 530    cx.set_shared_state(indoc! { "
 531        fourteen chaˇr
 532        fourteen char
 533    "})
 534        .await;
 535
 536    cx.simulate_shared_keystrokes(["d", "i", "w"]).await;
 537    cx.assert_shared_state(indoc! {"
 538        fourteenˇ•
 539        fourteen char
 540    "})
 541        .await;
 542    cx.simulate_shared_keystrokes(["j", "shift-f", "e", "f", "r"])
 543        .await;
 544    cx.assert_shared_state(indoc! {"
 545        fourteen•
 546        fourteen chaˇr
 547    "})
 548        .await;
 549}
 550
 551#[gpui::test]
 552async fn test_folds(cx: &mut gpui::TestAppContext) {
 553    let mut cx = NeovimBackedTestContext::new(cx).await;
 554    cx.set_neovim_option("foldmethod=manual").await;
 555
 556    cx.set_shared_state(indoc! { "
 557        fn boop() {
 558          ˇbarp()
 559          bazp()
 560        }
 561    "})
 562        .await;
 563    cx.simulate_shared_keystrokes(["shift-v", "j", "z", "f"])
 564        .await;
 565
 566    // visual display is now:
 567    // fn boop () {
 568    //  [FOLDED]
 569    // }
 570
 571    // TODO: this should not be needed but currently zf does not
 572    // return to normal mode.
 573    cx.simulate_shared_keystrokes(["escape"]).await;
 574
 575    // skip over fold downward
 576    cx.simulate_shared_keystrokes(["g", "g"]).await;
 577    cx.assert_shared_state(indoc! { "
 578        ˇfn boop() {
 579          barp()
 580          bazp()
 581        }
 582    "})
 583        .await;
 584
 585    cx.simulate_shared_keystrokes(["j", "j"]).await;
 586    cx.assert_shared_state(indoc! { "
 587        fn boop() {
 588          barp()
 589          bazp()
 590        ˇ}
 591    "})
 592        .await;
 593
 594    // skip over fold upward
 595    cx.simulate_shared_keystrokes(["2", "k"]).await;
 596    cx.assert_shared_state(indoc! { "
 597        ˇfn boop() {
 598          barp()
 599          bazp()
 600        }
 601    "})
 602        .await;
 603
 604    // yank the fold
 605    cx.simulate_shared_keystrokes(["down", "y", "y"]).await;
 606    cx.assert_shared_clipboard("  barp()\n  bazp()\n").await;
 607
 608    // re-open
 609    cx.simulate_shared_keystrokes(["z", "o"]).await;
 610    cx.assert_shared_state(indoc! { "
 611        fn boop() {
 612        ˇ  barp()
 613          bazp()
 614        }
 615    "})
 616        .await;
 617}
 618
 619#[gpui::test]
 620async fn test_folds_panic(cx: &mut gpui::TestAppContext) {
 621    let mut cx = NeovimBackedTestContext::new(cx).await;
 622    cx.set_neovim_option("foldmethod=manual").await;
 623
 624    cx.set_shared_state(indoc! { "
 625        fn boop() {
 626          ˇbarp()
 627          bazp()
 628        }
 629    "})
 630        .await;
 631    cx.simulate_shared_keystrokes(["shift-v", "j", "z", "f"])
 632        .await;
 633    cx.simulate_shared_keystrokes(["escape"]).await;
 634    cx.simulate_shared_keystrokes(["g", "g"]).await;
 635    cx.simulate_shared_keystrokes(["5", "d", "j"]).await;
 636    cx.assert_shared_state(indoc! { "ˇ"}).await;
 637
 638    cx.set_shared_state(indoc! { "
 639            fn boop() {
 640              ˇbarp()
 641              bazp()
 642            }
 643        "})
 644        .await;
 645    cx.simulate_shared_keystrokes(["shift-v", "j", "j", "z", "f"])
 646        .await;
 647    cx.simulate_shared_keystrokes(["escape"]).await;
 648    cx.simulate_shared_keystrokes(["shift-g", "shift-v"]).await;
 649    cx.assert_shared_state(indoc! { "
 650            fn boop() {
 651              barp()
 652              bazp()
 653            }
 654            ˇ"})
 655        .await;
 656}
 657
 658#[gpui::test]
 659async fn test_clear_counts(cx: &mut gpui::TestAppContext) {
 660    let mut cx = NeovimBackedTestContext::new(cx).await;
 661
 662    cx.set_shared_state(indoc! {"
 663        The quick brown
 664        fox juˇmps over
 665        the lazy dog"})
 666        .await;
 667
 668    cx.simulate_shared_keystrokes(["4", "escape", "3", "d", "l"])
 669        .await;
 670    cx.assert_shared_state(indoc! {"
 671        The quick brown
 672        fox juˇ over
 673        the lazy dog"})
 674        .await;
 675}
 676
 677#[gpui::test]
 678async fn test_zero(cx: &mut gpui::TestAppContext) {
 679    let mut cx = NeovimBackedTestContext::new(cx).await;
 680
 681    cx.set_shared_state(indoc! {"
 682        The quˇick brown
 683        fox jumps over
 684        the lazy dog"})
 685        .await;
 686
 687    cx.simulate_shared_keystrokes(["0"]).await;
 688    cx.assert_shared_state(indoc! {"
 689        ˇThe quick brown
 690        fox jumps over
 691        the lazy dog"})
 692        .await;
 693
 694    cx.simulate_shared_keystrokes(["1", "0", "l"]).await;
 695    cx.assert_shared_state(indoc! {"
 696        The quick ˇbrown
 697        fox jumps over
 698        the lazy dog"})
 699        .await;
 700}
 701
 702#[gpui::test]
 703async fn test_selection_goal(cx: &mut gpui::TestAppContext) {
 704    let mut cx = NeovimBackedTestContext::new(cx).await;
 705
 706    cx.set_shared_state(indoc! {"
 707        ;;ˇ;
 708        Lorem Ipsum"})
 709        .await;
 710
 711    cx.simulate_shared_keystrokes(["a", "down", "up", ";", "down", "up"])
 712        .await;
 713    cx.assert_shared_state(indoc! {"
 714        ;;;;ˇ
 715        Lorem Ipsum"})
 716        .await;
 717}
 718
 719#[gpui::test]
 720async fn test_wrapped_motions(cx: &mut gpui::TestAppContext) {
 721    let mut cx = NeovimBackedTestContext::new(cx).await;
 722
 723    cx.set_shared_wrap(12).await;
 724
 725    cx.set_shared_state(indoc! {"
 726                aaˇaa
 727                😃😃"
 728    })
 729    .await;
 730    cx.simulate_shared_keystrokes(["j"]).await;
 731    cx.assert_shared_state(indoc! {"
 732                aaaa
 733                😃ˇ😃"
 734    })
 735    .await;
 736
 737    cx.set_shared_state(indoc! {"
 738                123456789012aaˇaa
 739                123456789012😃😃"
 740    })
 741    .await;
 742    cx.simulate_shared_keystrokes(["j"]).await;
 743    cx.assert_shared_state(indoc! {"
 744        123456789012aaaa
 745        123456789012😃ˇ😃"
 746    })
 747    .await;
 748
 749    cx.set_shared_state(indoc! {"
 750                123456789012aaˇaa
 751                123456789012😃😃"
 752    })
 753    .await;
 754    cx.simulate_shared_keystrokes(["j"]).await;
 755    cx.assert_shared_state(indoc! {"
 756        123456789012aaaa
 757        123456789012😃ˇ😃"
 758    })
 759    .await;
 760
 761    cx.set_shared_state(indoc! {"
 762        123456789012aaaaˇaaaaaaaa123456789012
 763        wow
 764        123456789012😃😃😃😃😃😃123456789012"
 765    })
 766    .await;
 767    cx.simulate_shared_keystrokes(["j", "j"]).await;
 768    cx.assert_shared_state(indoc! {"
 769        123456789012aaaaaaaaaaaa123456789012
 770        wow
 771        123456789012😃😃ˇ😃😃😃😃123456789012"
 772    })
 773    .await;
 774}
 775
 776#[gpui::test]
 777async fn test_paragraphs_dont_wrap(cx: &mut gpui::TestAppContext) {
 778    let mut cx = NeovimBackedTestContext::new(cx).await;
 779
 780    cx.set_shared_state(indoc! {"
 781        one
 782        ˇ
 783        two"})
 784        .await;
 785
 786    cx.simulate_shared_keystrokes(["}", "}"]).await;
 787    cx.assert_shared_state(indoc! {"
 788        one
 789
 790        twˇo"})
 791        .await;
 792
 793    cx.simulate_shared_keystrokes(["{", "{", "{"]).await;
 794    cx.assert_shared_state(indoc! {"
 795        ˇone
 796
 797        two"})
 798        .await;
 799}
 800
 801#[gpui::test]
 802async fn test_select_all_issue_2170(cx: &mut gpui::TestAppContext) {
 803    let mut cx = VimTestContext::new(cx, true).await;
 804
 805    cx.set_state(
 806        indoc! {"
 807        defmodule Test do
 808            def test(a, ˇ[_, _] = b), do: IO.puts('hi')
 809        end
 810    "},
 811        Mode::Normal,
 812    );
 813    cx.simulate_keystrokes(["g", "a"]);
 814    cx.assert_state(
 815        indoc! {"
 816        defmodule Test do
 817            def test(a, «[ˇ»_, _] = b), do: IO.puts('hi')
 818        end
 819    "},
 820        Mode::Visual,
 821    );
 822}
 823
 824#[gpui::test]
 825async fn test_jk(cx: &mut gpui::TestAppContext) {
 826    let mut cx = NeovimBackedTestContext::new(cx).await;
 827
 828    cx.update(|cx| {
 829        cx.bind_keys([KeyBinding::new(
 830            "j k",
 831            NormalBefore,
 832            Some("vim_mode == insert"),
 833        )])
 834    });
 835    cx.neovim.exec("imap jk <esc>").await;
 836
 837    cx.set_shared_state("ˇhello").await;
 838    cx.simulate_shared_keystrokes(["i", "j", "o", "j", "k"])
 839        .await;
 840    cx.assert_shared_state("jˇohello").await;
 841}
 842
 843#[gpui::test]
 844async fn test_jk_delay(cx: &mut gpui::TestAppContext) {
 845    let mut cx = VimTestContext::new(cx, true).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
 855    cx.set_state("ˇhello", Mode::Normal);
 856    cx.simulate_keystrokes(["i", "j"]);
 857    cx.executor().advance_clock(Duration::from_millis(500));
 858    cx.run_until_parked();
 859    cx.assert_state("ˇhello", Mode::Insert);
 860    cx.executor().advance_clock(Duration::from_millis(500));
 861    cx.run_until_parked();
 862    cx.assert_state("jˇhello", Mode::Insert);
 863    cx.simulate_keystrokes(["k", "j", "k"]);
 864    cx.assert_state("jˇkhello", Mode::Normal);
 865}
 866
 867#[gpui::test]
 868async fn test_comma_w(cx: &mut gpui::TestAppContext) {
 869    let mut cx = NeovimBackedTestContext::new(cx).await;
 870
 871    cx.update(|cx| {
 872        cx.bind_keys([KeyBinding::new(
 873            ", w",
 874            motion::Down {
 875                display_lines: false,
 876            },
 877            Some("vim_mode == normal"),
 878        )])
 879    });
 880    cx.neovim.exec("map ,w j").await;
 881
 882    cx.set_shared_state("ˇhello hello\nhello hello").await;
 883    cx.simulate_shared_keystrokes(["f", "o", ";", ",", "w"])
 884        .await;
 885    cx.assert_shared_state("hello hello\nhello hellˇo").await;
 886
 887    cx.set_shared_state("ˇhello hello\nhello hello").await;
 888    cx.simulate_shared_keystrokes(["f", "o", ";", ",", "i"])
 889        .await;
 890    cx.assert_shared_state("hellˇo hello\nhello hello").await;
 891    cx.assert_shared_mode(Mode::Insert).await;
 892}
 893
 894#[gpui::test]
 895async fn test_rename(cx: &mut gpui::TestAppContext) {
 896    let mut cx = VimTestContext::new_typescript(cx).await;
 897
 898    cx.set_state("const beˇfore = 2; console.log(before)", Mode::Normal);
 899    let def_range = cx.lsp_range("const «beforeˇ» = 2; console.log(before)");
 900    let tgt_range = cx.lsp_range("const before = 2; console.log(«beforeˇ»)");
 901    let mut prepare_request =
 902        cx.handle_request::<lsp::request::PrepareRenameRequest, _, _>(move |_, _, _| async move {
 903            Ok(Some(lsp::PrepareRenameResponse::Range(def_range)))
 904        });
 905    let mut rename_request =
 906        cx.handle_request::<lsp::request::Rename, _, _>(move |url, params, _| async move {
 907            Ok(Some(lsp::WorkspaceEdit {
 908                changes: Some(
 909                    [(
 910                        url.clone(),
 911                        vec![
 912                            lsp::TextEdit::new(def_range, params.new_name.clone()),
 913                            lsp::TextEdit::new(tgt_range, params.new_name),
 914                        ],
 915                    )]
 916                    .into(),
 917                ),
 918                ..Default::default()
 919            }))
 920        });
 921
 922    cx.simulate_keystrokes(["c", "d"]);
 923    prepare_request.next().await.unwrap();
 924    cx.simulate_input("after");
 925    cx.simulate_keystrokes(["enter"]);
 926    rename_request.next().await.unwrap();
 927    cx.assert_state("const afterˇ = 2; console.log(after)", Mode::Normal)
 928}
 929
 930#[gpui::test]
 931async fn test_remap(cx: &mut gpui::TestAppContext) {
 932    let mut cx = VimTestContext::new(cx, true).await;
 933
 934    // test moving the cursor
 935    cx.update(|cx| {
 936        cx.bind_keys([KeyBinding::new(
 937            "g z",
 938            workspace::SendKeystrokes("l l l l".to_string()),
 939            None,
 940        )])
 941    });
 942    cx.set_state("ˇ123456789", Mode::Normal);
 943    cx.simulate_keystrokes(["g", "z"]);
 944    cx.assert_state("1234ˇ56789", Mode::Normal);
 945
 946    // test switching modes
 947    cx.update(|cx| {
 948        cx.bind_keys([KeyBinding::new(
 949            "g y",
 950            workspace::SendKeystrokes("i f o o escape l".to_string()),
 951            None,
 952        )])
 953    });
 954    cx.set_state("ˇ123456789", Mode::Normal);
 955    cx.simulate_keystrokes(["g", "y"]);
 956    cx.assert_state("fooˇ123456789", Mode::Normal);
 957
 958    // test recursion
 959    cx.update(|cx| {
 960        cx.bind_keys([KeyBinding::new(
 961            "g x",
 962            workspace::SendKeystrokes("g z g y".to_string()),
 963            None,
 964        )])
 965    });
 966    cx.set_state("ˇ123456789", Mode::Normal);
 967    cx.simulate_keystrokes(["g", "x"]);
 968    cx.assert_state("1234fooˇ56789", Mode::Normal);
 969
 970    cx.executor().allow_parking();
 971
 972    // test command
 973    cx.update(|cx| {
 974        cx.bind_keys([KeyBinding::new(
 975            "g w",
 976            workspace::SendKeystrokes(": j enter".to_string()),
 977            None,
 978        )])
 979    });
 980    cx.set_state("ˇ1234\n56789", Mode::Normal);
 981    cx.simulate_keystrokes(["g", "w"]);
 982    cx.assert_state("1234ˇ 56789", Mode::Normal);
 983
 984    // test leaving command
 985    cx.update(|cx| {
 986        cx.bind_keys([KeyBinding::new(
 987            "g u",
 988            workspace::SendKeystrokes("g w g z".to_string()),
 989            None,
 990        )])
 991    });
 992    cx.set_state("ˇ1234\n56789", Mode::Normal);
 993    cx.simulate_keystrokes(["g", "u"]);
 994    cx.assert_state("1234 567ˇ89", Mode::Normal);
 995
 996    // test leaving command
 997    cx.update(|cx| {
 998        cx.bind_keys([KeyBinding::new(
 999            "g t",
1000            workspace::SendKeystrokes("i space escape".to_string()),
1001            None,
1002        )])
1003    });
1004    cx.set_state("12ˇ34", Mode::Normal);
1005    cx.simulate_keystrokes(["g", "t"]);
1006    cx.assert_state("12ˇ 34", Mode::Normal);
1007}
1008
1009#[gpui::test]
1010async fn test_undo(cx: &mut gpui::TestAppContext) {
1011    let mut cx = NeovimBackedTestContext::new(cx).await;
1012
1013    cx.set_shared_state("hello quˇoel world").await;
1014    cx.simulate_shared_keystrokes(["v", "i", "w", "s", "c", "o", "escape", "u"])
1015        .await;
1016    cx.assert_shared_state("hello ˇquoel world").await;
1017    cx.simulate_shared_keystrokes(["ctrl-r"]).await;
1018    cx.assert_shared_state("hello ˇco world").await;
1019    cx.simulate_shared_keystrokes(["a", "o", "right", "l", "escape"])
1020        .await;
1021    cx.assert_shared_state("hello cooˇl world").await;
1022    cx.simulate_shared_keystrokes(["u"]).await;
1023    cx.assert_shared_state("hello cooˇ world").await;
1024    cx.simulate_shared_keystrokes(["u"]).await;
1025    cx.assert_shared_state("hello cˇo world").await;
1026    cx.simulate_shared_keystrokes(["u"]).await;
1027    cx.assert_shared_state("hello ˇquoel world").await;
1028
1029    cx.set_shared_state("hello quˇoel world").await;
1030    cx.simulate_shared_keystrokes(["v", "i", "w", "~", "u"])
1031        .await;
1032    cx.assert_shared_state("hello ˇquoel world").await;
1033
1034    cx.set_shared_state("\nhello quˇoel world\n").await;
1035    cx.simulate_shared_keystrokes(["shift-v", "s", "c", "escape", "u"])
1036        .await;
1037    cx.assert_shared_state("\nˇhello quoel world\n").await;
1038
1039    cx.set_shared_state(indoc! {"
1040        ˇ1
1041        2
1042        3"})
1043        .await;
1044
1045    cx.simulate_shared_keystrokes(["ctrl-v", "shift-g", "ctrl-a"])
1046        .await;
1047    cx.assert_shared_state(indoc! {"
1048        ˇ2
1049        3
1050        4"})
1051        .await;
1052
1053    cx.simulate_shared_keystrokes(["u"]).await;
1054    cx.assert_shared_state(indoc! {"
1055        ˇ1
1056        2
1057        3"})
1058        .await;
1059}