test.rs

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