test.rs

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