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
 639#[gpui::test]
 640async fn test_clear_counts(cx: &mut gpui::TestAppContext) {
 641    let mut cx = NeovimBackedTestContext::new(cx).await;
 642
 643    cx.set_shared_state(indoc! {"
 644        The quick brown
 645        fox juˇmps over
 646        the lazy dog"})
 647        .await;
 648
 649    cx.simulate_shared_keystrokes(["4", "escape", "3", "d", "l"])
 650        .await;
 651    cx.assert_shared_state(indoc! {"
 652        The quick brown
 653        fox juˇ over
 654        the lazy dog"})
 655        .await;
 656}
 657
 658#[gpui::test]
 659async fn test_zero(cx: &mut gpui::TestAppContext) {
 660    let mut cx = NeovimBackedTestContext::new(cx).await;
 661
 662    cx.set_shared_state(indoc! {"
 663        The quˇick brown
 664        fox jumps over
 665        the lazy dog"})
 666        .await;
 667
 668    cx.simulate_shared_keystrokes(["0"]).await;
 669    cx.assert_shared_state(indoc! {"
 670        ˇThe quick brown
 671        fox jumps over
 672        the lazy dog"})
 673        .await;
 674
 675    cx.simulate_shared_keystrokes(["1", "0", "l"]).await;
 676    cx.assert_shared_state(indoc! {"
 677        The quick ˇbrown
 678        fox jumps over
 679        the lazy dog"})
 680        .await;
 681}
 682
 683#[gpui::test]
 684async fn test_selection_goal(cx: &mut gpui::TestAppContext) {
 685    let mut cx = NeovimBackedTestContext::new(cx).await;
 686
 687    cx.set_shared_state(indoc! {"
 688        ;;ˇ;
 689        Lorem Ipsum"})
 690        .await;
 691
 692    cx.simulate_shared_keystrokes(["a", "down", "up", ";", "down", "up"])
 693        .await;
 694    cx.assert_shared_state(indoc! {"
 695        ;;;;ˇ
 696        Lorem Ipsum"})
 697        .await;
 698}
 699
 700#[gpui::test]
 701async fn test_wrapped_motions(cx: &mut gpui::TestAppContext) {
 702    let mut cx = NeovimBackedTestContext::new(cx).await;
 703
 704    cx.set_shared_wrap(12).await;
 705
 706    cx.set_shared_state(indoc! {"
 707                aaˇaa
 708                😃😃"
 709    })
 710    .await;
 711    cx.simulate_shared_keystrokes(["j"]).await;
 712    cx.assert_shared_state(indoc! {"
 713                aaaa
 714                😃ˇ😃"
 715    })
 716    .await;
 717
 718    cx.set_shared_state(indoc! {"
 719                123456789012aaˇaa
 720                123456789012😃😃"
 721    })
 722    .await;
 723    cx.simulate_shared_keystrokes(["j"]).await;
 724    cx.assert_shared_state(indoc! {"
 725        123456789012aaaa
 726        123456789012😃ˇ😃"
 727    })
 728    .await;
 729
 730    cx.set_shared_state(indoc! {"
 731                123456789012aaˇaa
 732                123456789012😃😃"
 733    })
 734    .await;
 735    cx.simulate_shared_keystrokes(["j"]).await;
 736    cx.assert_shared_state(indoc! {"
 737        123456789012aaaa
 738        123456789012😃ˇ😃"
 739    })
 740    .await;
 741
 742    cx.set_shared_state(indoc! {"
 743        123456789012aaaaˇaaaaaaaa123456789012
 744        wow
 745        123456789012😃😃😃😃😃😃123456789012"
 746    })
 747    .await;
 748    cx.simulate_shared_keystrokes(["j", "j"]).await;
 749    cx.assert_shared_state(indoc! {"
 750        123456789012aaaaaaaaaaaa123456789012
 751        wow
 752        123456789012😃😃ˇ😃😃😃😃123456789012"
 753    })
 754    .await;
 755}
 756
 757#[gpui::test]
 758async fn test_paragraphs_dont_wrap(cx: &mut gpui::TestAppContext) {
 759    let mut cx = NeovimBackedTestContext::new(cx).await;
 760
 761    cx.set_shared_state(indoc! {"
 762        one
 763        ˇ
 764        two"})
 765        .await;
 766
 767    cx.simulate_shared_keystrokes(["}", "}"]).await;
 768    cx.assert_shared_state(indoc! {"
 769        one
 770
 771        twˇo"})
 772        .await;
 773
 774    cx.simulate_shared_keystrokes(["{", "{", "{"]).await;
 775    cx.assert_shared_state(indoc! {"
 776        ˇone
 777
 778        two"})
 779        .await;
 780}
 781
 782#[gpui::test]
 783async fn test_select_all_issue_2170(cx: &mut gpui::TestAppContext) {
 784    let mut cx = VimTestContext::new(cx, true).await;
 785
 786    cx.set_state(
 787        indoc! {"
 788        defmodule Test do
 789            def test(a, ˇ[_, _] = b), do: IO.puts('hi')
 790        end
 791    "},
 792        Mode::Normal,
 793    );
 794    cx.simulate_keystrokes(["g", "a"]);
 795    cx.assert_state(
 796        indoc! {"
 797        defmodule Test do
 798            def test(a, «[ˇ»_, _] = b), do: IO.puts('hi')
 799        end
 800    "},
 801        Mode::Visual,
 802    );
 803}
 804
 805#[gpui::test]
 806async fn test_jk(cx: &mut gpui::TestAppContext) {
 807    let mut cx = NeovimBackedTestContext::new(cx).await;
 808
 809    cx.update(|cx| {
 810        cx.bind_keys([KeyBinding::new(
 811            "j k",
 812            NormalBefore,
 813            Some("vim_mode == insert"),
 814        )])
 815    });
 816    cx.neovim.exec("imap jk <esc>").await;
 817
 818    cx.set_shared_state("ˇhello").await;
 819    cx.simulate_shared_keystrokes(["i", "j", "o", "j", "k"])
 820        .await;
 821    cx.assert_shared_state("jˇohello").await;
 822}
 823
 824#[gpui::test]
 825async fn test_jk_delay(cx: &mut gpui::TestAppContext) {
 826    let mut cx = VimTestContext::new(cx, true).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
 836    cx.set_state("ˇhello", Mode::Normal);
 837    cx.simulate_keystrokes(["i", "j"]);
 838    cx.executor().advance_clock(Duration::from_millis(500));
 839    cx.run_until_parked();
 840    cx.assert_state("ˇhello", Mode::Insert);
 841    cx.executor().advance_clock(Duration::from_millis(500));
 842    cx.run_until_parked();
 843    cx.assert_state("jˇhello", Mode::Insert);
 844    cx.simulate_keystrokes(["k", "j", "k"]);
 845    cx.assert_state("jˇkhello", Mode::Normal);
 846}
 847
 848#[gpui::test]
 849async fn test_comma_w(cx: &mut gpui::TestAppContext) {
 850    let mut cx = NeovimBackedTestContext::new(cx).await;
 851
 852    cx.update(|cx| {
 853        cx.bind_keys([KeyBinding::new(
 854            ", w",
 855            motion::Down {
 856                display_lines: false,
 857            },
 858            Some("vim_mode == normal"),
 859        )])
 860    });
 861    cx.neovim.exec("map ,w j").await;
 862
 863    cx.set_shared_state("ˇhello hello\nhello hello").await;
 864    cx.simulate_shared_keystrokes(["f", "o", ";", ",", "w"])
 865        .await;
 866    cx.assert_shared_state("hello hello\nhello hellˇo").await;
 867
 868    cx.set_shared_state("ˇhello hello\nhello hello").await;
 869    cx.simulate_shared_keystrokes(["f", "o", ";", ",", "i"])
 870        .await;
 871    cx.assert_shared_state("hellˇo hello\nhello hello").await;
 872    cx.assert_shared_mode(Mode::Insert).await;
 873}
 874
 875#[gpui::test]
 876async fn test_rename(cx: &mut gpui::TestAppContext) {
 877    let mut cx = VimTestContext::new_typescript(cx).await;
 878
 879    cx.set_state("const beˇfore = 2; console.log(before)", Mode::Normal);
 880    let def_range = cx.lsp_range("const «beforeˇ» = 2; console.log(before)");
 881    let tgt_range = cx.lsp_range("const before = 2; console.log(«beforeˇ»)");
 882    let mut prepare_request =
 883        cx.handle_request::<lsp::request::PrepareRenameRequest, _, _>(move |_, _, _| async move {
 884            Ok(Some(lsp::PrepareRenameResponse::Range(def_range)))
 885        });
 886    let mut rename_request =
 887        cx.handle_request::<lsp::request::Rename, _, _>(move |url, params, _| async move {
 888            Ok(Some(lsp::WorkspaceEdit {
 889                changes: Some(
 890                    [(
 891                        url.clone(),
 892                        vec![
 893                            lsp::TextEdit::new(def_range, params.new_name.clone()),
 894                            lsp::TextEdit::new(tgt_range, params.new_name),
 895                        ],
 896                    )]
 897                    .into(),
 898                ),
 899                ..Default::default()
 900            }))
 901        });
 902
 903    cx.simulate_keystrokes(["c", "d"]);
 904    prepare_request.next().await.unwrap();
 905    cx.simulate_input("after");
 906    cx.simulate_keystrokes(["enter"]);
 907    rename_request.next().await.unwrap();
 908    cx.assert_state("const afterˇ = 2; console.log(after)", Mode::Normal)
 909}
 910
 911#[gpui::test]
 912async fn test_remap(cx: &mut gpui::TestAppContext) {
 913    let mut cx = VimTestContext::new(cx, true).await;
 914
 915    // test moving the cursor
 916    cx.update(|cx| {
 917        cx.bind_keys([KeyBinding::new(
 918            "g z",
 919            workspace::SendKeystrokes("l l l l".to_string()),
 920            None,
 921        )])
 922    });
 923    cx.set_state("ˇ123456789", Mode::Normal);
 924    cx.simulate_keystrokes(["g", "z"]);
 925    cx.assert_state("1234ˇ56789", Mode::Normal);
 926
 927    // test switching modes
 928    cx.update(|cx| {
 929        cx.bind_keys([KeyBinding::new(
 930            "g y",
 931            workspace::SendKeystrokes("i f o o escape l".to_string()),
 932            None,
 933        )])
 934    });
 935    cx.set_state("ˇ123456789", Mode::Normal);
 936    cx.simulate_keystrokes(["g", "y"]);
 937    cx.assert_state("fooˇ123456789", Mode::Normal);
 938
 939    // test recursion
 940    cx.update(|cx| {
 941        cx.bind_keys([KeyBinding::new(
 942            "g x",
 943            workspace::SendKeystrokes("g z g y".to_string()),
 944            None,
 945        )])
 946    });
 947    cx.set_state("ˇ123456789", Mode::Normal);
 948    cx.simulate_keystrokes(["g", "x"]);
 949    cx.assert_state("1234fooˇ56789", Mode::Normal);
 950
 951    cx.executor().allow_parking();
 952
 953    // test command
 954    cx.update(|cx| {
 955        cx.bind_keys([KeyBinding::new(
 956            "g w",
 957            workspace::SendKeystrokes(": j enter".to_string()),
 958            None,
 959        )])
 960    });
 961    cx.set_state("ˇ1234\n56789", Mode::Normal);
 962    cx.simulate_keystrokes(["g", "w"]);
 963    cx.assert_state("1234ˇ 56789", Mode::Normal);
 964
 965    // test leaving command
 966    cx.update(|cx| {
 967        cx.bind_keys([KeyBinding::new(
 968            "g u",
 969            workspace::SendKeystrokes("g w g z".to_string()),
 970            None,
 971        )])
 972    });
 973    cx.set_state("ˇ1234\n56789", Mode::Normal);
 974    cx.simulate_keystrokes(["g", "u"]);
 975    cx.assert_state("1234 567ˇ89", Mode::Normal);
 976
 977    // test leaving command
 978    cx.update(|cx| {
 979        cx.bind_keys([KeyBinding::new(
 980            "g t",
 981            workspace::SendKeystrokes("i space escape".to_string()),
 982            None,
 983        )])
 984    });
 985    cx.set_state("12ˇ34", Mode::Normal);
 986    cx.simulate_keystrokes(["g", "t"]);
 987    cx.assert_state("12ˇ 34", Mode::Normal);
 988}
 989
 990#[gpui::test]
 991async fn test_undo(cx: &mut gpui::TestAppContext) {
 992    let mut cx = NeovimBackedTestContext::new(cx).await;
 993
 994    cx.set_shared_state("hello quˇoel world").await;
 995    cx.simulate_shared_keystrokes(["v", "i", "w", "s", "c", "o", "escape", "u"])
 996        .await;
 997    cx.assert_shared_state("hello ˇquoel world").await;
 998    cx.simulate_shared_keystrokes(["ctrl-r"]).await;
 999    cx.assert_shared_state("hello ˇco world").await;
1000    cx.simulate_shared_keystrokes(["a", "o", "right", "l", "escape"])
1001        .await;
1002    cx.assert_shared_state("hello cooˇl world").await;
1003    cx.simulate_shared_keystrokes(["u"]).await;
1004    cx.assert_shared_state("hello cooˇ world").await;
1005    cx.simulate_shared_keystrokes(["u"]).await;
1006    cx.assert_shared_state("hello cˇo world").await;
1007    cx.simulate_shared_keystrokes(["u"]).await;
1008    cx.assert_shared_state("hello ˇquoel world").await;
1009
1010    cx.set_shared_state("hello quˇoel world").await;
1011    cx.simulate_shared_keystrokes(["v", "i", "w", "~", "u"])
1012        .await;
1013    cx.assert_shared_state("hello ˇquoel world").await;
1014
1015    cx.set_shared_state("\nhello quˇoel world\n").await;
1016    cx.simulate_shared_keystrokes(["shift-v", "s", "c", "escape", "u"])
1017        .await;
1018    cx.assert_shared_state("\nˇhello quoel world\n").await;
1019
1020    cx.set_shared_state(indoc! {"
1021        ˇ1
1022        2
1023        3"})
1024        .await;
1025
1026    cx.simulate_shared_keystrokes(["ctrl-v", "shift-g", "ctrl-a"])
1027        .await;
1028    cx.assert_shared_state(indoc! {"
1029        ˇ2
1030        3
1031        4"})
1032        .await;
1033
1034    cx.simulate_shared_keystrokes(["u"]).await;
1035    cx.assert_shared_state(indoc! {"
1036        ˇ1
1037        2
1038        3"})
1039        .await;
1040}