test.rs

   1mod neovim_backed_test_context;
   2mod neovim_connection;
   3mod vim_test_context;
   4
   5use std::time::Duration;
   6
   7use collections::HashMap;
   8use command_palette::CommandPalette;
   9use editor::{
  10    AnchorRangeExt, DisplayPoint, Editor, EditorMode, MultiBuffer, actions::DeleteLine,
  11    code_context_menus::CodeContextMenu, display_map::DisplayRow,
  12    test::editor_test_context::EditorTestContext,
  13};
  14use futures::StreamExt;
  15use gpui::{KeyBinding, Modifiers, MouseButton, TestAppContext, px};
  16use language::Point;
  17pub use neovim_backed_test_context::*;
  18use settings::SettingsStore;
  19use ui::Pixels;
  20use util::test::marked_text_ranges;
  21pub use vim_test_context::*;
  22
  23use indoc::indoc;
  24use search::BufferSearchBar;
  25
  26use crate::{PushSneak, PushSneakBackward, insert::NormalBefore, motion, state::Mode};
  27
  28use util_macros::perf;
  29
  30#[perf]
  31#[gpui::test]
  32async fn test_initially_disabled(cx: &mut gpui::TestAppContext) {
  33    let mut cx = VimTestContext::new(cx, false).await;
  34    cx.simulate_keystrokes("h j k l");
  35    cx.assert_editor_state("hjklˇ");
  36}
  37
  38#[perf]
  39#[gpui::test]
  40async fn test_neovim(cx: &mut gpui::TestAppContext) {
  41    let mut cx = NeovimBackedTestContext::new(cx).await;
  42
  43    cx.simulate_shared_keystrokes("i").await;
  44    cx.shared_state().await.assert_matches();
  45    cx.simulate_shared_keystrokes("shift-t e s t space t e s t escape 0 d w")
  46        .await;
  47    cx.shared_state().await.assert_matches();
  48    cx.assert_editor_state("ˇtest");
  49}
  50
  51#[perf]
  52#[gpui::test]
  53async fn test_toggle_through_settings(cx: &mut gpui::TestAppContext) {
  54    let mut cx = VimTestContext::new(cx, true).await;
  55
  56    cx.simulate_keystrokes("i");
  57    assert_eq!(cx.mode(), Mode::Insert);
  58
  59    // Editor acts as though vim is disabled
  60    cx.disable_vim();
  61    cx.simulate_keystrokes("h j k l");
  62    cx.assert_editor_state("hjklˇ");
  63
  64    // Selections aren't changed if editor is blurred but vim-mode is still disabled.
  65    cx.cx.set_state("«hjklˇ»");
  66    cx.assert_editor_state("«hjklˇ»");
  67    cx.update_editor(|_, window, _cx| window.blur());
  68    cx.assert_editor_state("«hjklˇ»");
  69    cx.update_editor(|_, window, cx| cx.focus_self(window));
  70    cx.assert_editor_state("«hjklˇ»");
  71
  72    // Enabling dynamically sets vim mode again and restores normal mode
  73    cx.enable_vim();
  74    assert_eq!(cx.mode(), Mode::Normal);
  75    cx.simulate_keystrokes("h h h l");
  76    assert_eq!(cx.buffer_text(), "hjkl".to_owned());
  77    cx.assert_editor_state("hˇjkl");
  78    cx.simulate_keystrokes("i T e s t");
  79    cx.assert_editor_state("hTestˇjkl");
  80
  81    // Disabling and enabling resets to normal mode
  82    assert_eq!(cx.mode(), Mode::Insert);
  83    cx.disable_vim();
  84    cx.enable_vim();
  85    assert_eq!(cx.mode(), Mode::Normal);
  86}
  87
  88#[perf]
  89#[gpui::test]
  90async fn test_cancel_selection(cx: &mut gpui::TestAppContext) {
  91    let mut cx = VimTestContext::new(cx, true).await;
  92
  93    cx.set_state(
  94        indoc! {"The quick brown fox juˇmps over the lazy dog"},
  95        Mode::Normal,
  96    );
  97    // jumps
  98    cx.simulate_keystrokes("v l l");
  99    cx.assert_editor_state("The quick brown fox ju«mpsˇ» over the lazy dog");
 100
 101    cx.simulate_keystrokes("escape");
 102    cx.assert_editor_state("The quick brown fox jumpˇs over the lazy dog");
 103
 104    // go back to the same selection state
 105    cx.simulate_keystrokes("v h h");
 106    cx.assert_editor_state("The quick brown fox ju«ˇmps» over the lazy dog");
 107
 108    // Ctrl-[ should behave like Esc
 109    cx.simulate_keystrokes("ctrl-[");
 110    cx.assert_editor_state("The quick brown fox juˇmps over the lazy dog");
 111}
 112
 113#[perf]
 114#[gpui::test]
 115async fn test_buffer_search(cx: &mut gpui::TestAppContext) {
 116    let mut cx = VimTestContext::new(cx, true).await;
 117
 118    cx.set_state(
 119        indoc! {"
 120            The quick brown
 121            fox juˇmps over
 122            the lazy dog"},
 123        Mode::Normal,
 124    );
 125    cx.simulate_keystrokes("/");
 126
 127    let search_bar = cx.workspace(|workspace, _, cx| {
 128        workspace
 129            .active_pane()
 130            .read(cx)
 131            .toolbar()
 132            .read(cx)
 133            .item_of_type::<BufferSearchBar>()
 134            .expect("Buffer search bar should be deployed")
 135    });
 136
 137    cx.update_entity(search_bar, |bar, _, cx| {
 138        assert_eq!(bar.query(cx), "");
 139    })
 140}
 141
 142#[perf]
 143#[gpui::test]
 144async fn test_count_down(cx: &mut gpui::TestAppContext) {
 145    let mut cx = VimTestContext::new(cx, true).await;
 146
 147    cx.set_state(indoc! {"aˇa\nbb\ncc\ndd\nee"}, Mode::Normal);
 148    cx.simulate_keystrokes("2 down");
 149    cx.assert_editor_state("aa\nbb\ncˇc\ndd\nee");
 150    cx.simulate_keystrokes("9 down");
 151    cx.assert_editor_state("aa\nbb\ncc\ndd\neˇe");
 152}
 153
 154#[perf]
 155#[gpui::test]
 156async fn test_end_of_document_710(cx: &mut gpui::TestAppContext) {
 157    let mut cx = VimTestContext::new(cx, true).await;
 158
 159    // goes to end by default
 160    cx.set_state(indoc! {"aˇa\nbb\ncc"}, Mode::Normal);
 161    cx.simulate_keystrokes("shift-g");
 162    cx.assert_editor_state("aa\nbb\ncˇc");
 163
 164    // can go to line 1 (https://github.com/zed-industries/zed/issues/5812)
 165    cx.simulate_keystrokes("1 shift-g");
 166    cx.assert_editor_state("aˇa\nbb\ncc");
 167}
 168
 169#[perf]
 170#[gpui::test]
 171async fn test_end_of_line_with_times(cx: &mut gpui::TestAppContext) {
 172    let mut cx = VimTestContext::new(cx, true).await;
 173
 174    // goes to current line end
 175    cx.set_state(indoc! {"ˇaa\nbb\ncc"}, Mode::Normal);
 176    cx.simulate_keystrokes("$");
 177    cx.assert_editor_state("aˇa\nbb\ncc");
 178
 179    // goes to next line end
 180    cx.simulate_keystrokes("2 $");
 181    cx.assert_editor_state("aa\nbˇb\ncc");
 182
 183    // try to exceed the final line.
 184    cx.simulate_keystrokes("4 $");
 185    cx.assert_editor_state("aa\nbb\ncˇc");
 186}
 187
 188#[perf]
 189#[gpui::test]
 190async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
 191    let mut cx = VimTestContext::new(cx, true).await;
 192
 193    // works in normal mode
 194    cx.set_state(indoc! {"aa\nbˇb\ncc"}, Mode::Normal);
 195    cx.simulate_keystrokes("> >");
 196    cx.assert_editor_state("aa\n    bˇb\ncc");
 197    cx.simulate_keystrokes("< <");
 198    cx.assert_editor_state("aa\nbˇb\ncc");
 199
 200    // works in visual mode
 201    cx.simulate_keystrokes("shift-v down >");
 202    cx.assert_editor_state("aa\n    bˇb\n    cc");
 203
 204    // works as operator
 205    cx.set_state("aa\nbˇb\ncc\n", Mode::Normal);
 206    cx.simulate_keystrokes("> j");
 207    cx.assert_editor_state("aa\n    bˇb\n    cc\n");
 208    cx.simulate_keystrokes("< k");
 209    cx.assert_editor_state("aa\nbˇb\n    cc\n");
 210    cx.simulate_keystrokes("> i p");
 211    cx.assert_editor_state("    aa\n    bˇb\n        cc\n");
 212    cx.simulate_keystrokes("< i p");
 213    cx.assert_editor_state("aa\nbˇb\n    cc\n");
 214    cx.simulate_keystrokes("< i p");
 215    cx.assert_editor_state("aa\nbˇb\ncc\n");
 216
 217    cx.set_state("ˇaa\nbb\ncc\n", Mode::Normal);
 218    cx.simulate_keystrokes("> 2 j");
 219    cx.assert_editor_state("    ˇaa\n    bb\n    cc\n");
 220
 221    cx.set_state("aa\nbb\nˇcc\n", Mode::Normal);
 222    cx.simulate_keystrokes("> 2 k");
 223    cx.assert_editor_state("    aa\n    bb\n    ˇcc\n");
 224
 225    // works with repeat
 226    cx.set_state("a\nb\nccˇc\n", Mode::Normal);
 227    cx.simulate_keystrokes("> 2 k");
 228    cx.assert_editor_state("    a\n    b\n    ccˇc\n");
 229    cx.simulate_keystrokes(".");
 230    cx.assert_editor_state("        a\n        b\n        ccˇc\n");
 231    cx.simulate_keystrokes("v k <");
 232    cx.assert_editor_state("        a\n\n    ccc\n");
 233    cx.simulate_keystrokes(".");
 234    cx.assert_editor_state("        a\n\nccc\n");
 235}
 236
 237#[perf]
 238#[gpui::test]
 239async fn test_escape_command_palette(cx: &mut gpui::TestAppContext) {
 240    let mut cx = VimTestContext::new(cx, true).await;
 241
 242    cx.set_state("aˇbc\n", Mode::Normal);
 243    cx.simulate_keystrokes("i cmd-shift-p");
 244
 245    assert!(
 246        cx.workspace(|workspace, _, cx| workspace.active_modal::<CommandPalette>(cx).is_some())
 247    );
 248    cx.simulate_keystrokes("escape");
 249    cx.run_until_parked();
 250    assert!(
 251        !cx.workspace(|workspace, _, cx| workspace.active_modal::<CommandPalette>(cx).is_some())
 252    );
 253    cx.assert_state("aˇbc\n", Mode::Insert);
 254}
 255
 256#[perf]
 257#[gpui::test]
 258async fn test_escape_cancels(cx: &mut gpui::TestAppContext) {
 259    let mut cx = VimTestContext::new(cx, true).await;
 260
 261    cx.set_state("aˇbˇc", Mode::Normal);
 262    cx.simulate_keystrokes("escape");
 263
 264    cx.assert_state("aˇbc", Mode::Normal);
 265}
 266
 267#[perf]
 268#[gpui::test]
 269async fn test_selection_on_search(cx: &mut gpui::TestAppContext) {
 270    let mut cx = VimTestContext::new(cx, true).await;
 271
 272    cx.set_state(indoc! {"aa\nbˇb\ncc\ncc\ncc\n"}, Mode::Normal);
 273    cx.simulate_keystrokes("/ c c");
 274
 275    let search_bar = cx.workspace(|workspace, _, cx| {
 276        workspace
 277            .active_pane()
 278            .read(cx)
 279            .toolbar()
 280            .read(cx)
 281            .item_of_type::<BufferSearchBar>()
 282            .expect("Buffer search bar should be deployed")
 283    });
 284
 285    cx.update_entity(search_bar, |bar, _, cx| {
 286        assert_eq!(bar.query(cx), "cc");
 287    });
 288
 289    cx.update_editor(|editor, window, cx| {
 290        let highlights = editor.all_text_background_highlights(window, cx);
 291        assert_eq!(3, highlights.len());
 292        assert_eq!(
 293            DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 2),
 294            highlights[0].0
 295        )
 296    });
 297    cx.simulate_keystrokes("enter");
 298
 299    cx.assert_state(indoc! {"aa\nbb\nˇcc\ncc\ncc\n"}, Mode::Normal);
 300    cx.simulate_keystrokes("n");
 301    cx.assert_state(indoc! {"aa\nbb\ncc\nˇcc\ncc\n"}, Mode::Normal);
 302    cx.simulate_keystrokes("shift-n");
 303    cx.assert_state(indoc! {"aa\nbb\nˇcc\ncc\ncc\n"}, Mode::Normal);
 304}
 305
 306#[perf]
 307#[gpui::test]
 308async fn test_word_characters(cx: &mut gpui::TestAppContext) {
 309    let mut cx = VimTestContext::new_typescript(cx).await;
 310    cx.set_state(
 311        indoc! { "
 312        class A {
 313            #ˇgoop = 99;
 314            $ˇgoop () { return this.#gˇoop };
 315        };
 316        console.log(new A().$gooˇp())
 317    "},
 318        Mode::Normal,
 319    );
 320    cx.simulate_keystrokes("v i w");
 321    cx.assert_state(
 322        indoc! {"
 323        class A {
 324            «#goopˇ» = 99;
 325            «$goopˇ» () { return this.«#goopˇ» };
 326        };
 327        console.log(new A().«$goopˇ»())
 328    "},
 329        Mode::Visual,
 330    )
 331}
 332
 333#[perf]
 334#[gpui::test]
 335async fn test_kebab_case(cx: &mut gpui::TestAppContext) {
 336    let mut cx = VimTestContext::new_html(cx).await;
 337    cx.set_state(
 338        indoc! { r#"
 339            <div><a class="bg-rˇed"></a></div>
 340            "#},
 341        Mode::Normal,
 342    );
 343    cx.simulate_keystrokes("v i w");
 344    cx.assert_state(
 345        indoc! { r#"
 346        <div><a class="bg-«redˇ»"></a></div>
 347        "#
 348        },
 349        Mode::Visual,
 350    )
 351}
 352
 353#[perf]
 354#[gpui::test]
 355async fn test_join_lines(cx: &mut gpui::TestAppContext) {
 356    let mut cx = NeovimBackedTestContext::new(cx).await;
 357
 358    cx.set_shared_state(indoc! {"
 359      ˇone
 360      two
 361      three
 362      four
 363      five
 364      six
 365      "})
 366        .await;
 367    cx.simulate_shared_keystrokes("shift-j").await;
 368    cx.shared_state().await.assert_eq(indoc! {"
 369          oneˇ two
 370          three
 371          four
 372          five
 373          six
 374          "});
 375    cx.simulate_shared_keystrokes("3 shift-j").await;
 376    cx.shared_state().await.assert_eq(indoc! {"
 377          one two threeˇ four
 378          five
 379          six
 380          "});
 381
 382    cx.set_shared_state(indoc! {"
 383      ˇone
 384      two
 385      three
 386      four
 387      five
 388      six
 389      "})
 390        .await;
 391    cx.simulate_shared_keystrokes("j v 3 j shift-j").await;
 392    cx.shared_state().await.assert_eq(indoc! {"
 393      one
 394      two three fourˇ five
 395      six
 396      "});
 397
 398    cx.set_shared_state(indoc! {"
 399      ˇone
 400      two
 401      three
 402      four
 403      five
 404      six
 405      "})
 406        .await;
 407    cx.simulate_shared_keystrokes("g shift-j").await;
 408    cx.shared_state().await.assert_eq(indoc! {"
 409          oneˇtwo
 410          three
 411          four
 412          five
 413          six
 414          "});
 415    cx.simulate_shared_keystrokes("3 g shift-j").await;
 416    cx.shared_state().await.assert_eq(indoc! {"
 417          onetwothreeˇfour
 418          five
 419          six
 420          "});
 421
 422    cx.set_shared_state(indoc! {"
 423      ˇone
 424      two
 425      three
 426      four
 427      five
 428      six
 429      "})
 430        .await;
 431    cx.simulate_shared_keystrokes("j v 3 j g shift-j").await;
 432    cx.shared_state().await.assert_eq(indoc! {"
 433      one
 434      twothreefourˇfive
 435      six
 436      "});
 437}
 438
 439#[cfg(target_os = "macos")]
 440#[perf]
 441#[gpui::test]
 442async fn test_wrapped_lines(cx: &mut gpui::TestAppContext) {
 443    let mut cx = NeovimBackedTestContext::new(cx).await;
 444
 445    cx.set_shared_wrap(12).await;
 446    // tests line wrap as follows:
 447    //  1: twelve char
 448    //     twelve char
 449    //  2: twelve char
 450    cx.set_shared_state(indoc! { "
 451        tˇwelve char twelve char
 452        twelve char
 453    "})
 454        .await;
 455    cx.simulate_shared_keystrokes("j").await;
 456    cx.shared_state().await.assert_eq(indoc! {"
 457        twelve char twelve char
 458        tˇwelve char
 459    "});
 460    cx.simulate_shared_keystrokes("k").await;
 461    cx.shared_state().await.assert_eq(indoc! {"
 462        tˇwelve char twelve char
 463        twelve char
 464    "});
 465    cx.simulate_shared_keystrokes("g j").await;
 466    cx.shared_state().await.assert_eq(indoc! {"
 467        twelve char tˇwelve char
 468        twelve char
 469    "});
 470    cx.simulate_shared_keystrokes("g j").await;
 471    cx.shared_state().await.assert_eq(indoc! {"
 472        twelve char twelve char
 473        tˇwelve char
 474    "});
 475
 476    cx.simulate_shared_keystrokes("g k").await;
 477    cx.shared_state().await.assert_eq(indoc! {"
 478        twelve char tˇwelve char
 479        twelve char
 480    "});
 481
 482    cx.simulate_shared_keystrokes("g ^").await;
 483    cx.shared_state().await.assert_eq(indoc! {"
 484        twelve char ˇtwelve char
 485        twelve char
 486    "});
 487
 488    cx.simulate_shared_keystrokes("^").await;
 489    cx.shared_state().await.assert_eq(indoc! {"
 490        ˇtwelve char twelve char
 491        twelve char
 492    "});
 493
 494    cx.simulate_shared_keystrokes("g $").await;
 495    cx.shared_state().await.assert_eq(indoc! {"
 496        twelve charˇ twelve char
 497        twelve char
 498    "});
 499    cx.simulate_shared_keystrokes("$").await;
 500    cx.shared_state().await.assert_eq(indoc! {"
 501        twelve char twelve chaˇr
 502        twelve char
 503    "});
 504
 505    cx.set_shared_state(indoc! { "
 506        tˇwelve char twelve char
 507        twelve char
 508    "})
 509        .await;
 510    cx.simulate_shared_keystrokes("enter").await;
 511    cx.shared_state().await.assert_eq(indoc! {"
 512            twelve char twelve char
 513            ˇtwelve char
 514        "});
 515
 516    cx.set_shared_state(indoc! { "
 517        twelve char
 518        tˇwelve char twelve char
 519        twelve char
 520    "})
 521        .await;
 522    cx.simulate_shared_keystrokes("o o escape").await;
 523    cx.shared_state().await.assert_eq(indoc! {"
 524        twelve char
 525        twelve char twelve char
 526        ˇo
 527        twelve char
 528    "});
 529
 530    cx.set_shared_state(indoc! { "
 531        twelve char
 532        tˇwelve char twelve char
 533        twelve char
 534    "})
 535        .await;
 536    cx.simulate_shared_keystrokes("shift-a a escape").await;
 537    cx.shared_state().await.assert_eq(indoc! {"
 538        twelve char
 539        twelve char twelve charˇa
 540        twelve char
 541    "});
 542    cx.simulate_shared_keystrokes("shift-i i escape").await;
 543    cx.shared_state().await.assert_eq(indoc! {"
 544        twelve char
 545        ˇitwelve char twelve chara
 546        twelve char
 547    "});
 548    cx.simulate_shared_keystrokes("shift-d").await;
 549    cx.shared_state().await.assert_eq(indoc! {"
 550        twelve char
 551        ˇ
 552        twelve char
 553    "});
 554
 555    cx.set_shared_state(indoc! { "
 556        twelve char
 557        twelve char tˇwelve char
 558        twelve char
 559    "})
 560        .await;
 561    cx.simulate_shared_keystrokes("shift-o o escape").await;
 562    cx.shared_state().await.assert_eq(indoc! {"
 563        twelve char
 564        ˇo
 565        twelve char twelve char
 566        twelve char
 567    "});
 568
 569    // line wraps as:
 570    // fourteen ch
 571    // ar
 572    // fourteen ch
 573    // ar
 574    cx.set_shared_state(indoc! { "
 575        fourteen chaˇr
 576        fourteen char
 577    "})
 578        .await;
 579
 580    cx.simulate_shared_keystrokes("d i w").await;
 581    cx.shared_state().await.assert_eq(indoc! {"
 582        fourteenˇ•
 583        fourteen char
 584    "});
 585    cx.simulate_shared_keystrokes("j shift-f e f r").await;
 586    cx.shared_state().await.assert_eq(indoc! {"
 587        fourteen•
 588        fourteen chaˇr
 589    "});
 590}
 591
 592#[perf]
 593#[gpui::test]
 594async fn test_folds(cx: &mut gpui::TestAppContext) {
 595    let mut cx = NeovimBackedTestContext::new(cx).await;
 596    cx.set_neovim_option("foldmethod=manual").await;
 597
 598    cx.set_shared_state(indoc! { "
 599        fn boop() {
 600          ˇbarp()
 601          bazp()
 602        }
 603    "})
 604        .await;
 605    cx.simulate_shared_keystrokes("shift-v j z f").await;
 606
 607    // visual display is now:
 608    // fn boop () {
 609    //  [FOLDED]
 610    // }
 611
 612    // TODO: this should not be needed but currently zf does not
 613    // return to normal mode.
 614    cx.simulate_shared_keystrokes("escape").await;
 615
 616    // skip over fold downward
 617    cx.simulate_shared_keystrokes("g g").await;
 618    cx.shared_state().await.assert_eq(indoc! {"
 619        ˇfn boop() {
 620          barp()
 621          bazp()
 622        }
 623    "});
 624
 625    cx.simulate_shared_keystrokes("j j").await;
 626    cx.shared_state().await.assert_eq(indoc! {"
 627        fn boop() {
 628          barp()
 629          bazp()
 630        ˇ}
 631    "});
 632
 633    // skip over fold upward
 634    cx.simulate_shared_keystrokes("2 k").await;
 635    cx.shared_state().await.assert_eq(indoc! {"
 636        ˇfn boop() {
 637          barp()
 638          bazp()
 639        }
 640    "});
 641
 642    // yank the fold
 643    cx.simulate_shared_keystrokes("down y y").await;
 644    cx.shared_clipboard()
 645        .await
 646        .assert_eq("  barp()\n  bazp()\n");
 647
 648    // re-open
 649    cx.simulate_shared_keystrokes("z o").await;
 650    cx.shared_state().await.assert_eq(indoc! {"
 651        fn boop() {
 652        ˇ  barp()
 653          bazp()
 654        }
 655    "});
 656}
 657
 658#[perf]
 659#[gpui::test]
 660async fn test_folds_panic(cx: &mut gpui::TestAppContext) {
 661    let mut cx = NeovimBackedTestContext::new(cx).await;
 662    cx.set_neovim_option("foldmethod=manual").await;
 663
 664    cx.set_shared_state(indoc! { "
 665        fn boop() {
 666          ˇbarp()
 667          bazp()
 668        }
 669    "})
 670        .await;
 671    cx.simulate_shared_keystrokes("shift-v j z f").await;
 672    cx.simulate_shared_keystrokes("escape").await;
 673    cx.simulate_shared_keystrokes("g g").await;
 674    cx.simulate_shared_keystrokes("5 d j").await;
 675    cx.shared_state().await.assert_eq("ˇ");
 676    cx.set_shared_state(indoc! {"
 677        fn boop() {
 678          ˇbarp()
 679          bazp()
 680        }
 681    "})
 682        .await;
 683    cx.simulate_shared_keystrokes("shift-v j j z f").await;
 684    cx.simulate_shared_keystrokes("escape").await;
 685    cx.simulate_shared_keystrokes("shift-g shift-v").await;
 686    cx.shared_state().await.assert_eq(indoc! {"
 687        fn boop() {
 688          barp()
 689          bazp()
 690        }
 691        ˇ"});
 692}
 693
 694#[perf]
 695#[gpui::test]
 696async fn test_clear_counts(cx: &mut gpui::TestAppContext) {
 697    let mut cx = NeovimBackedTestContext::new(cx).await;
 698
 699    cx.set_shared_state(indoc! {"
 700        The quick brown
 701        fox juˇmps over
 702        the lazy dog"})
 703        .await;
 704
 705    cx.simulate_shared_keystrokes("4 escape 3 d l").await;
 706    cx.shared_state().await.assert_eq(indoc! {"
 707        The quick brown
 708        fox juˇ over
 709        the lazy dog"});
 710}
 711
 712#[perf]
 713#[gpui::test]
 714async fn test_zero(cx: &mut gpui::TestAppContext) {
 715    let mut cx = NeovimBackedTestContext::new(cx).await;
 716
 717    cx.set_shared_state(indoc! {"
 718        The quˇick brown
 719        fox jumps over
 720        the lazy dog"})
 721        .await;
 722
 723    cx.simulate_shared_keystrokes("0").await;
 724    cx.shared_state().await.assert_eq(indoc! {"
 725        ˇThe quick brown
 726        fox jumps over
 727        the lazy dog"});
 728
 729    cx.simulate_shared_keystrokes("1 0 l").await;
 730    cx.shared_state().await.assert_eq(indoc! {"
 731        The quick ˇbrown
 732        fox jumps over
 733        the lazy dog"});
 734}
 735
 736#[perf]
 737#[gpui::test]
 738async fn test_selection_goal(cx: &mut gpui::TestAppContext) {
 739    let mut cx = NeovimBackedTestContext::new(cx).await;
 740
 741    cx.set_shared_state(indoc! {"
 742        ;;ˇ;
 743        Lorem Ipsum"})
 744        .await;
 745
 746    cx.simulate_shared_keystrokes("a down up ; down up").await;
 747    cx.shared_state().await.assert_eq(indoc! {"
 748        ;;;;ˇ
 749        Lorem Ipsum"});
 750}
 751
 752#[cfg(target_os = "macos")]
 753#[perf]
 754#[gpui::test]
 755async fn test_wrapped_motions(cx: &mut gpui::TestAppContext) {
 756    let mut cx = NeovimBackedTestContext::new(cx).await;
 757
 758    cx.set_shared_wrap(12).await;
 759
 760    cx.set_shared_state(indoc! {"
 761                aaˇaa
 762                😃😃"
 763    })
 764    .await;
 765    cx.simulate_shared_keystrokes("j").await;
 766    cx.shared_state().await.assert_eq(indoc! {"
 767                aaaa
 768                😃ˇ😃"
 769    });
 770
 771    cx.set_shared_state(indoc! {"
 772                123456789012aaˇaa
 773                123456789012😃😃"
 774    })
 775    .await;
 776    cx.simulate_shared_keystrokes("j").await;
 777    cx.shared_state().await.assert_eq(indoc! {"
 778        123456789012aaaa
 779        123456789012😃ˇ😃"
 780    });
 781
 782    cx.set_shared_state(indoc! {"
 783                123456789012aaˇaa
 784                123456789012😃😃"
 785    })
 786    .await;
 787    cx.simulate_shared_keystrokes("j").await;
 788    cx.shared_state().await.assert_eq(indoc! {"
 789        123456789012aaaa
 790        123456789012😃ˇ😃"
 791    });
 792
 793    cx.set_shared_state(indoc! {"
 794        123456789012aaaaˇaaaaaaaa123456789012
 795        wow
 796        123456789012😃😃😃😃😃😃123456789012"
 797    })
 798    .await;
 799    cx.simulate_shared_keystrokes("j j").await;
 800    cx.shared_state().await.assert_eq(indoc! {"
 801        123456789012aaaaaaaaaaaa123456789012
 802        wow
 803        123456789012😃😃ˇ😃😃😃😃123456789012"
 804    });
 805}
 806
 807#[perf]
 808#[gpui::test]
 809async fn test_wrapped_delete_end_document(cx: &mut gpui::TestAppContext) {
 810    let mut cx = NeovimBackedTestContext::new(cx).await;
 811
 812    cx.set_shared_wrap(12).await;
 813
 814    cx.set_shared_state(indoc! {"
 815                aaˇaaaaaaaaaaaaaaaaaa
 816                bbbbbbbbbbbbbbbbbbbb
 817                cccccccccccccccccccc"
 818    })
 819    .await;
 820    cx.simulate_shared_keystrokes("d shift-g i z z z").await;
 821    cx.shared_state().await.assert_eq(indoc! {"
 822                zzzˇ"
 823    });
 824}
 825
 826#[perf]
 827#[gpui::test]
 828async fn test_paragraphs_dont_wrap(cx: &mut gpui::TestAppContext) {
 829    let mut cx = NeovimBackedTestContext::new(cx).await;
 830
 831    cx.set_shared_state(indoc! {"
 832        one
 833        ˇ
 834        two"})
 835        .await;
 836
 837    cx.simulate_shared_keystrokes("} }").await;
 838    cx.shared_state().await.assert_eq(indoc! {"
 839        one
 840
 841        twˇo"});
 842
 843    cx.simulate_shared_keystrokes("{ { {").await;
 844    cx.shared_state().await.assert_eq(indoc! {"
 845        ˇone
 846
 847        two"});
 848}
 849
 850#[perf]
 851#[gpui::test]
 852async fn test_select_all_issue_2170(cx: &mut gpui::TestAppContext) {
 853    let mut cx = VimTestContext::new(cx, true).await;
 854
 855    cx.set_state(
 856        indoc! {"
 857        defmodule Test do
 858            def test(a, ˇ[_, _] = b), do: IO.puts('hi')
 859        end
 860    "},
 861        Mode::Normal,
 862    );
 863    cx.simulate_keystrokes("g a");
 864    cx.assert_state(
 865        indoc! {"
 866        defmodule Test do
 867            def test(a, «[ˇ»_, _] = b), do: IO.puts('hi')
 868        end
 869    "},
 870        Mode::Visual,
 871    );
 872}
 873
 874#[perf]
 875#[gpui::test]
 876async fn test_jk(cx: &mut gpui::TestAppContext) {
 877    let mut cx = NeovimBackedTestContext::new(cx).await;
 878
 879    cx.update(|_, cx| {
 880        cx.bind_keys([KeyBinding::new(
 881            "j k",
 882            NormalBefore,
 883            Some("vim_mode == insert"),
 884        )])
 885    });
 886    cx.neovim.exec("imap jk <esc>").await;
 887
 888    cx.set_shared_state("ˇhello").await;
 889    cx.simulate_shared_keystrokes("i j o j k").await;
 890    cx.shared_state().await.assert_eq("jˇohello");
 891}
 892
 893fn assert_pending_input(cx: &mut VimTestContext, expected: &str) {
 894    cx.update_editor(|editor, window, cx| {
 895        let snapshot = editor.snapshot(window, cx);
 896        let highlights = editor
 897            .text_highlights::<editor::PendingInput>(cx)
 898            .unwrap()
 899            .1;
 900        let (_, ranges) = marked_text_ranges(expected, false);
 901
 902        assert_eq!(
 903            highlights
 904                .iter()
 905                .map(|highlight| highlight.to_offset(&snapshot.buffer_snapshot))
 906                .collect::<Vec<_>>(),
 907            ranges
 908        )
 909    });
 910}
 911
 912#[perf]
 913#[gpui::test]
 914async fn test_jk_multi(cx: &mut gpui::TestAppContext) {
 915    let mut cx = VimTestContext::new(cx, true).await;
 916
 917    cx.update(|_, cx| {
 918        cx.bind_keys([KeyBinding::new(
 919            "j k l",
 920            NormalBefore,
 921            Some("vim_mode == insert"),
 922        )])
 923    });
 924
 925    cx.set_state("ˇone ˇone ˇone", Mode::Normal);
 926    cx.simulate_keystrokes("i j");
 927    cx.simulate_keystrokes("k");
 928    cx.assert_state("ˇjkone ˇjkone ˇjkone", Mode::Insert);
 929    assert_pending_input(&mut cx, "«jk»one «jk»one «jk»one");
 930    cx.simulate_keystrokes("o j k");
 931    cx.assert_state("jkoˇjkone jkoˇjkone jkoˇjkone", Mode::Insert);
 932    assert_pending_input(&mut cx, "jko«jk»one jko«jk»one jko«jk»one");
 933    cx.simulate_keystrokes("l");
 934    cx.assert_state("jkˇoone jkˇoone jkˇoone", Mode::Normal);
 935}
 936
 937#[perf]
 938#[gpui::test]
 939async fn test_jk_delay(cx: &mut gpui::TestAppContext) {
 940    let mut cx = VimTestContext::new(cx, true).await;
 941
 942    cx.update(|_, cx| {
 943        cx.bind_keys([KeyBinding::new(
 944            "j k",
 945            NormalBefore,
 946            Some("vim_mode == insert"),
 947        )])
 948    });
 949
 950    cx.set_state("ˇhello", Mode::Normal);
 951    cx.simulate_keystrokes("i j");
 952    cx.executor().advance_clock(Duration::from_millis(500));
 953    cx.run_until_parked();
 954    cx.assert_state("ˇjhello", Mode::Insert);
 955    cx.update_editor(|editor, window, cx| {
 956        let snapshot = editor.snapshot(window, cx);
 957        let highlights = editor
 958            .text_highlights::<editor::PendingInput>(cx)
 959            .unwrap()
 960            .1;
 961
 962        assert_eq!(
 963            highlights
 964                .iter()
 965                .map(|highlight| highlight.to_offset(&snapshot.buffer_snapshot))
 966                .collect::<Vec<_>>(),
 967            vec![0..1]
 968        )
 969    });
 970    cx.executor().advance_clock(Duration::from_millis(500));
 971    cx.run_until_parked();
 972    cx.assert_state("jˇhello", Mode::Insert);
 973    cx.simulate_keystrokes("k j k");
 974    cx.assert_state("jˇkhello", Mode::Normal);
 975}
 976
 977#[perf]
 978#[gpui::test]
 979async fn test_comma_w(cx: &mut gpui::TestAppContext) {
 980    let mut cx = NeovimBackedTestContext::new(cx).await;
 981
 982    cx.update(|_, cx| {
 983        cx.bind_keys([KeyBinding::new(
 984            ", w",
 985            motion::Down {
 986                display_lines: false,
 987            },
 988            Some("vim_mode == normal"),
 989        )])
 990    });
 991    cx.neovim.exec("map ,w j").await;
 992
 993    cx.set_shared_state("ˇhello hello\nhello hello").await;
 994    cx.simulate_shared_keystrokes("f o ; , w").await;
 995    cx.shared_state()
 996        .await
 997        .assert_eq("hello hello\nhello hellˇo");
 998
 999    cx.set_shared_state("ˇhello hello\nhello hello").await;
1000    cx.simulate_shared_keystrokes("f o ; , i").await;
1001    cx.shared_state()
1002        .await
1003        .assert_eq("hellˇo hello\nhello hello");
1004}
1005
1006#[perf]
1007#[gpui::test]
1008async fn test_completion_menu_scroll_aside(cx: &mut TestAppContext) {
1009    let mut cx = VimTestContext::new_typescript(cx).await;
1010
1011    cx.lsp
1012        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
1013            Ok(Some(lsp::CompletionResponse::Array(vec![
1014                lsp::CompletionItem {
1015                    label: "Test Item".to_string(),
1016                    documentation: Some(lsp::Documentation::String(
1017                        "This is some very long documentation content that will be displayed in the aside panel for scrolling.\n".repeat(50)
1018                    )),
1019                    ..Default::default()
1020                },
1021            ])))
1022        });
1023
1024    cx.set_state("variableˇ", Mode::Insert);
1025    cx.simulate_keystroke(".");
1026    cx.executor().run_until_parked();
1027
1028    let mut initial_offset: Pixels = px(0.0);
1029
1030    cx.update_editor(|editor, _, _| {
1031        let binding = editor.context_menu().borrow();
1032        let Some(CodeContextMenu::Completions(menu)) = binding.as_ref() else {
1033            panic!("Should have completions menu open");
1034        };
1035
1036        initial_offset = menu.scroll_handle_aside.offset().y;
1037    });
1038
1039    // The `ctrl-e` shortcut should scroll the completion menu's aside content
1040    // down, so the updated offset should be lower than the initial offset.
1041    cx.simulate_keystroke("ctrl-e");
1042    cx.update_editor(|editor, _, _| {
1043        let binding = editor.context_menu().borrow();
1044        let Some(CodeContextMenu::Completions(menu)) = binding.as_ref() else {
1045            panic!("Should have completions menu open");
1046        };
1047
1048        assert!(menu.scroll_handle_aside.offset().y < initial_offset);
1049    });
1050
1051    // The `ctrl-y` shortcut should do the inverse scrolling as `ctrl-e`, so the
1052    // offset should now be the same as the initial offset.
1053    cx.simulate_keystroke("ctrl-y");
1054    cx.update_editor(|editor, _, _| {
1055        let binding = editor.context_menu().borrow();
1056        let Some(CodeContextMenu::Completions(menu)) = binding.as_ref() else {
1057            panic!("Should have completions menu open");
1058        };
1059
1060        assert_eq!(menu.scroll_handle_aside.offset().y, initial_offset);
1061    });
1062
1063    // The `ctrl-d` shortcut should scroll the completion menu's aside content
1064    // down, so the updated offset should be lower than the initial offset.
1065    cx.simulate_keystroke("ctrl-d");
1066    cx.update_editor(|editor, _, _| {
1067        let binding = editor.context_menu().borrow();
1068        let Some(CodeContextMenu::Completions(menu)) = binding.as_ref() else {
1069            panic!("Should have completions menu open");
1070        };
1071
1072        assert!(menu.scroll_handle_aside.offset().y < initial_offset);
1073    });
1074
1075    // The `ctrl-u` shortcut should do the inverse scrolling as `ctrl-u`, so the
1076    // offset should now be the same as the initial offset.
1077    cx.simulate_keystroke("ctrl-u");
1078    cx.update_editor(|editor, _, _| {
1079        let binding = editor.context_menu().borrow();
1080        let Some(CodeContextMenu::Completions(menu)) = binding.as_ref() else {
1081            panic!("Should have completions menu open");
1082        };
1083
1084        assert_eq!(menu.scroll_handle_aside.offset().y, initial_offset);
1085    });
1086}
1087
1088#[perf]
1089#[gpui::test]
1090async fn test_rename(cx: &mut gpui::TestAppContext) {
1091    let mut cx = VimTestContext::new_typescript(cx).await;
1092
1093    cx.set_state("const beˇfore = 2; console.log(before)", Mode::Normal);
1094    let def_range = cx.lsp_range("const «beforeˇ» = 2; console.log(before)");
1095    let tgt_range = cx.lsp_range("const before = 2; console.log(«beforeˇ»)");
1096    let mut prepare_request = cx.set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
1097        move |_, _, _| async move { Ok(Some(lsp::PrepareRenameResponse::Range(def_range))) },
1098    );
1099    let mut rename_request =
1100        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, params, _| async move {
1101            Ok(Some(lsp::WorkspaceEdit {
1102                changes: Some(
1103                    [(
1104                        url.clone(),
1105                        vec![
1106                            lsp::TextEdit::new(def_range, params.new_name.clone()),
1107                            lsp::TextEdit::new(tgt_range, params.new_name),
1108                        ],
1109                    )]
1110                    .into(),
1111                ),
1112                ..Default::default()
1113            }))
1114        });
1115
1116    cx.simulate_keystrokes("c d");
1117    prepare_request.next().await.unwrap();
1118    cx.simulate_input("after");
1119    cx.simulate_keystrokes("enter");
1120    rename_request.next().await.unwrap();
1121    cx.assert_state("const afterˇ = 2; console.log(after)", Mode::Normal)
1122}
1123
1124#[perf]
1125#[gpui::test]
1126async fn test_remap(cx: &mut gpui::TestAppContext) {
1127    let mut cx = VimTestContext::new(cx, true).await;
1128
1129    // test moving the cursor
1130    cx.update(|_, cx| {
1131        cx.bind_keys([KeyBinding::new(
1132            "g z",
1133            workspace::SendKeystrokes("l l l l".to_string()),
1134            None,
1135        )])
1136    });
1137    cx.set_state("ˇ123456789", Mode::Normal);
1138    cx.simulate_keystrokes("g z");
1139    cx.assert_state("1234ˇ56789", Mode::Normal);
1140
1141    // test switching modes
1142    cx.update(|_, cx| {
1143        cx.bind_keys([KeyBinding::new(
1144            "g y",
1145            workspace::SendKeystrokes("i f o o escape l".to_string()),
1146            None,
1147        )])
1148    });
1149    cx.set_state("ˇ123456789", Mode::Normal);
1150    cx.simulate_keystrokes("g y");
1151    cx.assert_state("fooˇ123456789", Mode::Normal);
1152
1153    // test recursion
1154    cx.update(|_, cx| {
1155        cx.bind_keys([KeyBinding::new(
1156            "g x",
1157            workspace::SendKeystrokes("g z g y".to_string()),
1158            None,
1159        )])
1160    });
1161    cx.set_state("ˇ123456789", Mode::Normal);
1162    cx.simulate_keystrokes("g x");
1163    cx.assert_state("1234fooˇ56789", Mode::Normal);
1164
1165    // test command
1166    cx.update(|_, cx| {
1167        cx.bind_keys([KeyBinding::new(
1168            "g w",
1169            workspace::SendKeystrokes(": j enter".to_string()),
1170            None,
1171        )])
1172    });
1173    cx.set_state("ˇ1234\n56789", Mode::Normal);
1174    cx.simulate_keystrokes("g w");
1175    cx.assert_state("1234ˇ 56789", Mode::Normal);
1176
1177    // test leaving command
1178    cx.update(|_, cx| {
1179        cx.bind_keys([KeyBinding::new(
1180            "g u",
1181            workspace::SendKeystrokes("g w g z".to_string()),
1182            None,
1183        )])
1184    });
1185    cx.set_state("ˇ1234\n56789", Mode::Normal);
1186    cx.simulate_keystrokes("g u");
1187    cx.assert_state("1234 567ˇ89", Mode::Normal);
1188
1189    // test leaving command
1190    cx.update(|_, cx| {
1191        cx.bind_keys([KeyBinding::new(
1192            "g t",
1193            workspace::SendKeystrokes("i space escape".to_string()),
1194            None,
1195        )])
1196    });
1197    cx.set_state("12ˇ34", Mode::Normal);
1198    cx.simulate_keystrokes("g t");
1199    cx.assert_state("12ˇ 34", Mode::Normal);
1200}
1201
1202#[perf]
1203#[gpui::test]
1204async fn test_undo(cx: &mut gpui::TestAppContext) {
1205    let mut cx = NeovimBackedTestContext::new(cx).await;
1206
1207    cx.set_shared_state("hello quˇoel world").await;
1208    cx.simulate_shared_keystrokes("v i w s c o escape u").await;
1209    cx.shared_state().await.assert_eq("hello ˇquoel world");
1210    cx.simulate_shared_keystrokes("ctrl-r").await;
1211    cx.shared_state().await.assert_eq("hello ˇco world");
1212    cx.simulate_shared_keystrokes("a o right l escape").await;
1213    cx.shared_state().await.assert_eq("hello cooˇl world");
1214    cx.simulate_shared_keystrokes("u").await;
1215    cx.shared_state().await.assert_eq("hello cooˇ world");
1216    cx.simulate_shared_keystrokes("u").await;
1217    cx.shared_state().await.assert_eq("hello cˇo world");
1218    cx.simulate_shared_keystrokes("u").await;
1219    cx.shared_state().await.assert_eq("hello ˇquoel world");
1220
1221    cx.set_shared_state("hello quˇoel world").await;
1222    cx.simulate_shared_keystrokes("v i w ~ u").await;
1223    cx.shared_state().await.assert_eq("hello ˇquoel world");
1224
1225    cx.set_shared_state("\nhello quˇoel world\n").await;
1226    cx.simulate_shared_keystrokes("shift-v s c escape u").await;
1227    cx.shared_state().await.assert_eq("\nˇhello quoel world\n");
1228
1229    cx.set_shared_state(indoc! {"
1230        ˇ1
1231        2
1232        3"})
1233        .await;
1234
1235    cx.simulate_shared_keystrokes("ctrl-v shift-g ctrl-a").await;
1236    cx.shared_state().await.assert_eq(indoc! {"
1237        ˇ2
1238        3
1239        4"});
1240
1241    cx.simulate_shared_keystrokes("u").await;
1242    cx.shared_state().await.assert_eq(indoc! {"
1243        ˇ1
1244        2
1245        3"});
1246}
1247
1248#[perf]
1249#[gpui::test]
1250async fn test_mouse_selection(cx: &mut TestAppContext) {
1251    let mut cx = VimTestContext::new(cx, true).await;
1252
1253    cx.set_state("ˇone two three", Mode::Normal);
1254
1255    let start_point = cx.pixel_position("one twˇo three");
1256    let end_point = cx.pixel_position("one ˇtwo three");
1257
1258    cx.simulate_mouse_down(start_point, MouseButton::Left, Modifiers::none());
1259    cx.simulate_mouse_move(end_point, MouseButton::Left, Modifiers::none());
1260    cx.simulate_mouse_up(end_point, MouseButton::Left, Modifiers::none());
1261
1262    cx.assert_state("one «ˇtwo» three", Mode::Visual)
1263}
1264
1265#[perf]
1266#[gpui::test]
1267async fn test_lowercase_marks(cx: &mut TestAppContext) {
1268    let mut cx = NeovimBackedTestContext::new(cx).await;
1269
1270    cx.set_shared_state("line one\nline ˇtwo\nline three").await;
1271    cx.simulate_shared_keystrokes("m a l ' a").await;
1272    cx.shared_state()
1273        .await
1274        .assert_eq("line one\nˇline two\nline three");
1275    cx.simulate_shared_keystrokes("` a").await;
1276    cx.shared_state()
1277        .await
1278        .assert_eq("line one\nline ˇtwo\nline three");
1279
1280    cx.simulate_shared_keystrokes("^ d ` a").await;
1281    cx.shared_state()
1282        .await
1283        .assert_eq("line one\nˇtwo\nline three");
1284}
1285
1286#[perf]
1287#[gpui::test]
1288async fn test_lt_gt_marks(cx: &mut TestAppContext) {
1289    let mut cx = NeovimBackedTestContext::new(cx).await;
1290
1291    cx.set_shared_state(indoc!(
1292        "
1293        Line one
1294        Line two
1295        Line ˇthree
1296        Line four
1297        Line five
1298    "
1299    ))
1300    .await;
1301
1302    cx.simulate_shared_keystrokes("v j escape k k").await;
1303
1304    cx.simulate_shared_keystrokes("' <").await;
1305    cx.shared_state().await.assert_eq(indoc! {"
1306        Line one
1307        Line two
1308        ˇLine three
1309        Line four
1310        Line five
1311    "});
1312
1313    cx.simulate_shared_keystrokes("` <").await;
1314    cx.shared_state().await.assert_eq(indoc! {"
1315        Line one
1316        Line two
1317        Line ˇthree
1318        Line four
1319        Line five
1320    "});
1321
1322    cx.simulate_shared_keystrokes("' >").await;
1323    cx.shared_state().await.assert_eq(indoc! {"
1324        Line one
1325        Line two
1326        Line three
1327        ˇLine four
1328        Line five
1329    "
1330    });
1331
1332    cx.simulate_shared_keystrokes("` >").await;
1333    cx.shared_state().await.assert_eq(indoc! {"
1334        Line one
1335        Line two
1336        Line three
1337        Line ˇfour
1338        Line five
1339    "
1340    });
1341
1342    cx.simulate_shared_keystrokes("v i w o escape").await;
1343    cx.simulate_shared_keystrokes("` >").await;
1344    cx.shared_state().await.assert_eq(indoc! {"
1345        Line one
1346        Line two
1347        Line three
1348        Line fouˇr
1349        Line five
1350    "
1351    });
1352    cx.simulate_shared_keystrokes("` <").await;
1353    cx.shared_state().await.assert_eq(indoc! {"
1354        Line one
1355        Line two
1356        Line three
1357        Line ˇfour
1358        Line five
1359    "
1360    });
1361}
1362
1363#[perf]
1364#[gpui::test]
1365async fn test_caret_mark(cx: &mut TestAppContext) {
1366    let mut cx = NeovimBackedTestContext::new(cx).await;
1367
1368    cx.set_shared_state(indoc!(
1369        "
1370        Line one
1371        Line two
1372        Line three
1373        ˇLine four
1374        Line five
1375    "
1376    ))
1377    .await;
1378
1379    cx.simulate_shared_keystrokes("c w shift-s t r a i g h t space t h i n g escape j j")
1380        .await;
1381
1382    cx.simulate_shared_keystrokes("' ^").await;
1383    cx.shared_state().await.assert_eq(indoc! {"
1384        Line one
1385        Line two
1386        Line three
1387        ˇStraight thing four
1388        Line five
1389    "
1390    });
1391
1392    cx.simulate_shared_keystrokes("` ^").await;
1393    cx.shared_state().await.assert_eq(indoc! {"
1394        Line one
1395        Line two
1396        Line three
1397        Straight thingˇ four
1398        Line five
1399    "
1400    });
1401
1402    cx.simulate_shared_keystrokes("k a ! escape k g i ?").await;
1403    cx.shared_state().await.assert_eq(indoc! {"
1404        Line one
1405        Line two
1406        Line three!?ˇ
1407        Straight thing four
1408        Line five
1409    "
1410    });
1411}
1412
1413#[cfg(target_os = "macos")]
1414#[perf]
1415#[gpui::test]
1416async fn test_dw_eol(cx: &mut gpui::TestAppContext) {
1417    let mut cx = NeovimBackedTestContext::new(cx).await;
1418
1419    cx.set_shared_wrap(12).await;
1420    cx.set_shared_state("twelve ˇchar twelve char\ntwelve char")
1421        .await;
1422    cx.simulate_shared_keystrokes("d w").await;
1423    cx.shared_state()
1424        .await
1425        .assert_eq("twelve ˇtwelve char\ntwelve char");
1426}
1427
1428#[perf]
1429#[gpui::test]
1430async fn test_toggle_comments(cx: &mut gpui::TestAppContext) {
1431    let mut cx = VimTestContext::new(cx, true).await;
1432
1433    let language = std::sync::Arc::new(language::Language::new(
1434        language::LanguageConfig {
1435            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
1436            ..Default::default()
1437        },
1438        Some(language::tree_sitter_rust::LANGUAGE.into()),
1439    ));
1440    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
1441
1442    // works in normal model
1443    cx.set_state(
1444        indoc! {"
1445      ˇone
1446      two
1447      three
1448      "},
1449        Mode::Normal,
1450    );
1451    cx.simulate_keystrokes("g c c");
1452    cx.assert_state(
1453        indoc! {"
1454          // ˇone
1455          two
1456          three
1457          "},
1458        Mode::Normal,
1459    );
1460
1461    // works in visual mode
1462    cx.simulate_keystrokes("v j g c");
1463    cx.assert_state(
1464        indoc! {"
1465          // // ˇone
1466          // two
1467          three
1468          "},
1469        Mode::Normal,
1470    );
1471
1472    // works in visual line mode
1473    cx.simulate_keystrokes("shift-v j g c");
1474    cx.assert_state(
1475        indoc! {"
1476          // ˇone
1477          two
1478          three
1479          "},
1480        Mode::Normal,
1481    );
1482
1483    // works with count
1484    cx.simulate_keystrokes("g c 2 j");
1485    cx.assert_state(
1486        indoc! {"
1487            // // ˇone
1488            // two
1489            // three
1490            "},
1491        Mode::Normal,
1492    );
1493
1494    // works with motion object
1495    cx.simulate_keystrokes("shift-g");
1496    cx.simulate_keystrokes("g c g g");
1497    cx.assert_state(
1498        indoc! {"
1499            // one
1500            two
1501            three
1502            ˇ"},
1503        Mode::Normal,
1504    );
1505}
1506
1507#[perf]
1508#[gpui::test]
1509async fn test_find_multibyte(cx: &mut gpui::TestAppContext) {
1510    let mut cx = NeovimBackedTestContext::new(cx).await;
1511
1512    cx.set_shared_state(r#"<label for="guests">ˇPočet hostů</label>"#)
1513        .await;
1514
1515    cx.simulate_shared_keystrokes("c t < o escape").await;
1516    cx.shared_state()
1517        .await
1518        .assert_eq(r#"<label for="guests">ˇo</label>"#);
1519}
1520
1521#[perf]
1522#[gpui::test]
1523async fn test_sneak(cx: &mut gpui::TestAppContext) {
1524    let mut cx = VimTestContext::new(cx, true).await;
1525
1526    cx.update(|_window, cx| {
1527        cx.bind_keys([
1528            KeyBinding::new(
1529                "s",
1530                PushSneak { first_char: None },
1531                Some("vim_mode == normal"),
1532            ),
1533            KeyBinding::new(
1534                "shift-s",
1535                PushSneakBackward { first_char: None },
1536                Some("vim_mode == normal"),
1537            ),
1538            KeyBinding::new(
1539                "shift-s",
1540                PushSneakBackward { first_char: None },
1541                Some("vim_mode == visual"),
1542            ),
1543        ])
1544    });
1545
1546    // Sneak forwards multibyte & multiline
1547    cx.set_state(
1548        indoc! {
1549            r#"<labelˇ for="guests">
1550                    Počet hostů
1551                </label>"#
1552        },
1553        Mode::Normal,
1554    );
1555    cx.simulate_keystrokes("s t ů");
1556    cx.assert_state(
1557        indoc! {
1558            r#"<label for="guests">
1559                Počet hosˇtů
1560            </label>"#
1561        },
1562        Mode::Normal,
1563    );
1564
1565    // Visual sneak backwards multibyte & multiline
1566    cx.simulate_keystrokes("v S < l");
1567    cx.assert_state(
1568        indoc! {
1569            r#"«ˇ<label for="guests">
1570                Počet host»ů
1571            </label>"#
1572        },
1573        Mode::Visual,
1574    );
1575
1576    // Sneak backwards repeated
1577    cx.set_state(r#"11 12 13 ˇ14"#, Mode::Normal);
1578    cx.simulate_keystrokes("S space 1");
1579    cx.assert_state(r#"11 12ˇ 13 14"#, Mode::Normal);
1580    cx.simulate_keystrokes(";");
1581    cx.assert_state(r#"11ˇ 12 13 14"#, Mode::Normal);
1582}
1583
1584#[perf]
1585#[gpui::test]
1586async fn test_plus_minus(cx: &mut gpui::TestAppContext) {
1587    let mut cx = NeovimBackedTestContext::new(cx).await;
1588
1589    cx.set_shared_state(indoc! {
1590        "one
1591           two
1592        thrˇee
1593    "})
1594        .await;
1595
1596    cx.simulate_shared_keystrokes("-").await;
1597    cx.shared_state().await.assert_matches();
1598    cx.simulate_shared_keystrokes("-").await;
1599    cx.shared_state().await.assert_matches();
1600    cx.simulate_shared_keystrokes("+").await;
1601    cx.shared_state().await.assert_matches();
1602}
1603
1604#[perf]
1605#[gpui::test]
1606async fn test_command_alias(cx: &mut gpui::TestAppContext) {
1607    let mut cx = VimTestContext::new(cx, true).await;
1608    cx.update_global(|store: &mut SettingsStore, cx| {
1609        store.update_user_settings(cx, |s| {
1610            let mut aliases = HashMap::default();
1611            aliases.insert("Q".to_string(), "upper".to_string());
1612            s.workspace.command_aliases = aliases
1613        });
1614    });
1615
1616    cx.set_state("ˇhello world", Mode::Normal);
1617    cx.simulate_keystrokes(": Q");
1618    cx.set_state("ˇHello world", Mode::Normal);
1619}
1620
1621#[perf]
1622#[gpui::test]
1623async fn test_remap_adjacent_dog_cat(cx: &mut gpui::TestAppContext) {
1624    let mut cx = NeovimBackedTestContext::new(cx).await;
1625    cx.update(|_, cx| {
1626        cx.bind_keys([
1627            KeyBinding::new(
1628                "d o g",
1629                workspace::SendKeystrokes("🐶".to_string()),
1630                Some("vim_mode == insert"),
1631            ),
1632            KeyBinding::new(
1633                "c a t",
1634                workspace::SendKeystrokes("🐱".to_string()),
1635                Some("vim_mode == insert"),
1636            ),
1637        ])
1638    });
1639    cx.neovim.exec("imap dog 🐶").await;
1640    cx.neovim.exec("imap cat 🐱").await;
1641
1642    cx.set_shared_state("ˇ").await;
1643    cx.simulate_shared_keystrokes("i d o g").await;
1644    cx.shared_state().await.assert_eq("🐶ˇ");
1645
1646    cx.set_shared_state("ˇ").await;
1647    cx.simulate_shared_keystrokes("i d o d o g").await;
1648    cx.shared_state().await.assert_eq("do🐶ˇ");
1649
1650    cx.set_shared_state("ˇ").await;
1651    cx.simulate_shared_keystrokes("i d o c a t").await;
1652    cx.shared_state().await.assert_eq("do🐱ˇ");
1653}
1654
1655#[perf]
1656#[gpui::test]
1657async fn test_remap_nested_pineapple(cx: &mut gpui::TestAppContext) {
1658    let mut cx = NeovimBackedTestContext::new(cx).await;
1659    cx.update(|_, cx| {
1660        cx.bind_keys([
1661            KeyBinding::new(
1662                "p i n",
1663                workspace::SendKeystrokes("📌".to_string()),
1664                Some("vim_mode == insert"),
1665            ),
1666            KeyBinding::new(
1667                "p i n e",
1668                workspace::SendKeystrokes("🌲".to_string()),
1669                Some("vim_mode == insert"),
1670            ),
1671            KeyBinding::new(
1672                "p i n e a p p l e",
1673                workspace::SendKeystrokes("🍍".to_string()),
1674                Some("vim_mode == insert"),
1675            ),
1676        ])
1677    });
1678    cx.neovim.exec("imap pin 📌").await;
1679    cx.neovim.exec("imap pine 🌲").await;
1680    cx.neovim.exec("imap pineapple 🍍").await;
1681
1682    cx.set_shared_state("ˇ").await;
1683    cx.simulate_shared_keystrokes("i p i n").await;
1684    cx.executor().advance_clock(Duration::from_millis(1000));
1685    cx.run_until_parked();
1686    cx.shared_state().await.assert_eq("📌ˇ");
1687
1688    cx.set_shared_state("ˇ").await;
1689    cx.simulate_shared_keystrokes("i p i n e").await;
1690    cx.executor().advance_clock(Duration::from_millis(1000));
1691    cx.run_until_parked();
1692    cx.shared_state().await.assert_eq("🌲ˇ");
1693
1694    cx.set_shared_state("ˇ").await;
1695    cx.simulate_shared_keystrokes("i p i n e a p p l e").await;
1696    cx.shared_state().await.assert_eq("🍍ˇ");
1697}
1698
1699#[perf]
1700#[gpui::test]
1701async fn test_remap_recursion(cx: &mut gpui::TestAppContext) {
1702    let mut cx = NeovimBackedTestContext::new(cx).await;
1703    cx.update(|_, cx| {
1704        cx.bind_keys([KeyBinding::new(
1705            "x",
1706            workspace::SendKeystrokes("\" _ x".to_string()),
1707            Some("VimControl"),
1708        )]);
1709        cx.bind_keys([KeyBinding::new(
1710            "y",
1711            workspace::SendKeystrokes("2 x".to_string()),
1712            Some("VimControl"),
1713        )])
1714    });
1715    cx.neovim.exec("noremap x \"_x").await;
1716    cx.neovim.exec("map y 2x").await;
1717
1718    cx.set_shared_state("ˇhello").await;
1719    cx.simulate_shared_keystrokes("d l").await;
1720    cx.shared_clipboard().await.assert_eq("h");
1721    cx.simulate_shared_keystrokes("y").await;
1722    cx.shared_clipboard().await.assert_eq("h");
1723    cx.shared_state().await.assert_eq("ˇlo");
1724}
1725
1726#[perf]
1727#[gpui::test]
1728async fn test_escape_while_waiting(cx: &mut gpui::TestAppContext) {
1729    let mut cx = NeovimBackedTestContext::new(cx).await;
1730    cx.set_shared_state("ˇhi").await;
1731    cx.simulate_shared_keystrokes("\" + escape x").await;
1732    cx.shared_state().await.assert_eq("ˇi");
1733}
1734
1735#[perf]
1736#[gpui::test]
1737async fn test_ctrl_w_override(cx: &mut gpui::TestAppContext) {
1738    let mut cx = NeovimBackedTestContext::new(cx).await;
1739    cx.update(|_, cx| {
1740        cx.bind_keys([KeyBinding::new("ctrl-w", DeleteLine, None)]);
1741    });
1742    cx.neovim.exec("map <c-w> D").await;
1743    cx.set_shared_state("ˇhi").await;
1744    cx.simulate_shared_keystrokes("ctrl-w").await;
1745    cx.shared_state().await.assert_eq("ˇ");
1746}
1747
1748#[perf]
1749#[gpui::test]
1750async fn test_visual_indent_count(cx: &mut gpui::TestAppContext) {
1751    let mut cx = VimTestContext::new(cx, true).await;
1752    cx.set_state("ˇhi", Mode::Normal);
1753    cx.simulate_keystrokes("shift-v 3 >");
1754    cx.assert_state("            ˇhi", Mode::Normal);
1755    cx.simulate_keystrokes("shift-v 2 <");
1756    cx.assert_state("    ˇhi", Mode::Normal);
1757}
1758
1759#[perf]
1760#[gpui::test]
1761async fn test_record_replay_recursion(cx: &mut gpui::TestAppContext) {
1762    let mut cx = NeovimBackedTestContext::new(cx).await;
1763
1764    cx.set_shared_state("ˇhello world").await;
1765    cx.simulate_shared_keystrokes(">").await;
1766    cx.simulate_shared_keystrokes(".").await;
1767    cx.simulate_shared_keystrokes(".").await;
1768    cx.simulate_shared_keystrokes(".").await;
1769    cx.shared_state().await.assert_eq("ˇhello world");
1770}
1771
1772#[perf]
1773#[gpui::test]
1774async fn test_blackhole_register(cx: &mut gpui::TestAppContext) {
1775    let mut cx = NeovimBackedTestContext::new(cx).await;
1776
1777    cx.set_shared_state("ˇhello world").await;
1778    cx.simulate_shared_keystrokes("d i w \" _ d a w").await;
1779    cx.simulate_shared_keystrokes("p").await;
1780    cx.shared_state().await.assert_eq("hellˇo");
1781}
1782
1783#[perf]
1784#[gpui::test]
1785async fn test_sentence_backwards(cx: &mut gpui::TestAppContext) {
1786    let mut cx = NeovimBackedTestContext::new(cx).await;
1787
1788    cx.set_shared_state("one\n\ntwo\nthree\nˇ\nfour").await;
1789    cx.simulate_shared_keystrokes("(").await;
1790    cx.shared_state()
1791        .await
1792        .assert_eq("one\n\nˇtwo\nthree\n\nfour");
1793
1794    cx.set_shared_state("hello.\n\n\nworˇld.").await;
1795    cx.simulate_shared_keystrokes("(").await;
1796    cx.shared_state().await.assert_eq("hello.\n\n\nˇworld.");
1797    cx.simulate_shared_keystrokes("(").await;
1798    cx.shared_state().await.assert_eq("hello.\n\nˇ\nworld.");
1799    cx.simulate_shared_keystrokes("(").await;
1800    cx.shared_state().await.assert_eq("ˇhello.\n\n\nworld.");
1801
1802    cx.set_shared_state("hello. worlˇd.").await;
1803    cx.simulate_shared_keystrokes("(").await;
1804    cx.shared_state().await.assert_eq("hello. ˇworld.");
1805    cx.simulate_shared_keystrokes("(").await;
1806    cx.shared_state().await.assert_eq("ˇhello. world.");
1807
1808    cx.set_shared_state(". helˇlo.").await;
1809    cx.simulate_shared_keystrokes("(").await;
1810    cx.shared_state().await.assert_eq(". ˇhello.");
1811    cx.simulate_shared_keystrokes("(").await;
1812    cx.shared_state().await.assert_eq(". ˇhello.");
1813
1814    cx.set_shared_state(indoc! {
1815        "{
1816            hello_world();
1817        ˇ}"
1818    })
1819    .await;
1820    cx.simulate_shared_keystrokes("(").await;
1821    cx.shared_state().await.assert_eq(indoc! {
1822        "ˇ{
1823            hello_world();
1824        }"
1825    });
1826
1827    cx.set_shared_state(indoc! {
1828        "Hello! World..?
1829
1830        \tHello! World... ˇ"
1831    })
1832    .await;
1833    cx.simulate_shared_keystrokes("(").await;
1834    cx.shared_state().await.assert_eq(indoc! {
1835        "Hello! World..?
1836
1837        \tHello! ˇWorld... "
1838    });
1839    cx.simulate_shared_keystrokes("(").await;
1840    cx.shared_state().await.assert_eq(indoc! {
1841        "Hello! World..?
1842
1843        \tˇHello! World... "
1844    });
1845    cx.simulate_shared_keystrokes("(").await;
1846    cx.shared_state().await.assert_eq(indoc! {
1847        "Hello! World..?
1848        ˇ
1849        \tHello! World... "
1850    });
1851    cx.simulate_shared_keystrokes("(").await;
1852    cx.shared_state().await.assert_eq(indoc! {
1853        "Hello! ˇWorld..?
1854
1855        \tHello! World... "
1856    });
1857}
1858
1859#[perf]
1860#[gpui::test]
1861async fn test_sentence_forwards(cx: &mut gpui::TestAppContext) {
1862    let mut cx = NeovimBackedTestContext::new(cx).await;
1863
1864    cx.set_shared_state("helˇlo.\n\n\nworld.").await;
1865    cx.simulate_shared_keystrokes(")").await;
1866    cx.shared_state().await.assert_eq("hello.\nˇ\n\nworld.");
1867    cx.simulate_shared_keystrokes(")").await;
1868    cx.shared_state().await.assert_eq("hello.\n\n\nˇworld.");
1869    cx.simulate_shared_keystrokes(")").await;
1870    cx.shared_state().await.assert_eq("hello.\n\n\nworldˇ.");
1871
1872    cx.set_shared_state("helˇlo.\n\n\nworld.").await;
1873}
1874
1875#[perf]
1876#[gpui::test]
1877async fn test_ctrl_o_visual(cx: &mut gpui::TestAppContext) {
1878    let mut cx = NeovimBackedTestContext::new(cx).await;
1879
1880    cx.set_shared_state("helloˇ world.").await;
1881    cx.simulate_shared_keystrokes("i ctrl-o v b r l").await;
1882    cx.shared_state().await.assert_eq("ˇllllllworld.");
1883    cx.simulate_shared_keystrokes("ctrl-o v f w d").await;
1884    cx.shared_state().await.assert_eq("ˇorld.");
1885}
1886
1887#[perf]
1888#[gpui::test]
1889async fn test_ctrl_o_position(cx: &mut gpui::TestAppContext) {
1890    let mut cx = NeovimBackedTestContext::new(cx).await;
1891
1892    cx.set_shared_state("helˇlo world.").await;
1893    cx.simulate_shared_keystrokes("i ctrl-o d i w").await;
1894    cx.shared_state().await.assert_eq("ˇ world.");
1895    cx.simulate_shared_keystrokes("ctrl-o p").await;
1896    cx.shared_state().await.assert_eq(" helloˇworld.");
1897}
1898
1899#[perf]
1900#[gpui::test]
1901async fn test_ctrl_o_dot(cx: &mut gpui::TestAppContext) {
1902    let mut cx = NeovimBackedTestContext::new(cx).await;
1903
1904    cx.set_shared_state("heˇllo world.").await;
1905    cx.simulate_shared_keystrokes("x i ctrl-o .").await;
1906    cx.shared_state().await.assert_eq("heˇo world.");
1907    cx.simulate_shared_keystrokes("l l escape .").await;
1908    cx.shared_state().await.assert_eq("hellˇllo world.");
1909}
1910
1911#[perf(iterations = 1)]
1912#[gpui::test]
1913async fn test_folded_multibuffer_excerpts(cx: &mut gpui::TestAppContext) {
1914    VimTestContext::init(cx);
1915    cx.update(|cx| {
1916        VimTestContext::init_keybindings(true, cx);
1917    });
1918    let (editor, cx) = cx.add_window_view(|window, cx| {
1919        let multi_buffer = MultiBuffer::build_multi(
1920            [
1921                ("111\n222\n333\n444\n", vec![Point::row_range(0..2)]),
1922                ("aaa\nbbb\nccc\nddd\n", vec![Point::row_range(0..2)]),
1923                ("AAA\nBBB\nCCC\nDDD\n", vec![Point::row_range(0..2)]),
1924                ("one\ntwo\nthr\nfou\n", vec![Point::row_range(0..2)]),
1925            ],
1926            cx,
1927        );
1928        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
1929
1930        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
1931        // fold all but the second buffer, so that we test navigating between two
1932        // adjacent folded buffers, as well as folded buffers at the start and
1933        // end the multibuffer
1934        editor.fold_buffer(buffer_ids[0], cx);
1935        editor.fold_buffer(buffer_ids[2], cx);
1936        editor.fold_buffer(buffer_ids[3], cx);
1937
1938        editor
1939    });
1940    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
1941
1942    cx.assert_excerpts_with_selections(indoc! {"
1943        [EXCERPT]
1944        ˇ[FOLDED]
1945        [EXCERPT]
1946        aaa
1947        bbb
1948        [EXCERPT]
1949        [FOLDED]
1950        [EXCERPT]
1951        [FOLDED]
1952        "
1953    });
1954    cx.simulate_keystroke("j");
1955    cx.assert_excerpts_with_selections(indoc! {"
1956        [EXCERPT]
1957        [FOLDED]
1958        [EXCERPT]
1959        ˇaaa
1960        bbb
1961        [EXCERPT]
1962        [FOLDED]
1963        [EXCERPT]
1964        [FOLDED]
1965        "
1966    });
1967    cx.simulate_keystroke("j");
1968    cx.simulate_keystroke("j");
1969    cx.assert_excerpts_with_selections(indoc! {"
1970        [EXCERPT]
1971        [FOLDED]
1972        [EXCERPT]
1973        aaa
1974        bbb
1975        ˇ[EXCERPT]
1976        [FOLDED]
1977        [EXCERPT]
1978        [FOLDED]
1979        "
1980    });
1981    cx.simulate_keystroke("j");
1982    cx.assert_excerpts_with_selections(indoc! {"
1983        [EXCERPT]
1984        [FOLDED]
1985        [EXCERPT]
1986        aaa
1987        bbb
1988        [EXCERPT]
1989        ˇ[FOLDED]
1990        [EXCERPT]
1991        [FOLDED]
1992        "
1993    });
1994    cx.simulate_keystroke("j");
1995    cx.assert_excerpts_with_selections(indoc! {"
1996        [EXCERPT]
1997        [FOLDED]
1998        [EXCERPT]
1999        aaa
2000        bbb
2001        [EXCERPT]
2002        [FOLDED]
2003        [EXCERPT]
2004        ˇ[FOLDED]
2005        "
2006    });
2007    cx.simulate_keystroke("k");
2008    cx.assert_excerpts_with_selections(indoc! {"
2009        [EXCERPT]
2010        [FOLDED]
2011        [EXCERPT]
2012        aaa
2013        bbb
2014        [EXCERPT]
2015        ˇ[FOLDED]
2016        [EXCERPT]
2017        [FOLDED]
2018        "
2019    });
2020    cx.simulate_keystroke("k");
2021    cx.simulate_keystroke("k");
2022    cx.simulate_keystroke("k");
2023    cx.assert_excerpts_with_selections(indoc! {"
2024        [EXCERPT]
2025        [FOLDED]
2026        [EXCERPT]
2027        ˇaaa
2028        bbb
2029        [EXCERPT]
2030        [FOLDED]
2031        [EXCERPT]
2032        [FOLDED]
2033        "
2034    });
2035    cx.simulate_keystroke("k");
2036    cx.assert_excerpts_with_selections(indoc! {"
2037        [EXCERPT]
2038        ˇ[FOLDED]
2039        [EXCERPT]
2040        aaa
2041        bbb
2042        [EXCERPT]
2043        [FOLDED]
2044        [EXCERPT]
2045        [FOLDED]
2046        "
2047    });
2048    cx.simulate_keystroke("shift-g");
2049    cx.assert_excerpts_with_selections(indoc! {"
2050        [EXCERPT]
2051        [FOLDED]
2052        [EXCERPT]
2053        aaa
2054        bbb
2055        [EXCERPT]
2056        [FOLDED]
2057        [EXCERPT]
2058        ˇ[FOLDED]
2059        "
2060    });
2061    cx.simulate_keystrokes("g g");
2062    cx.assert_excerpts_with_selections(indoc! {"
2063        [EXCERPT]
2064        ˇ[FOLDED]
2065        [EXCERPT]
2066        aaa
2067        bbb
2068        [EXCERPT]
2069        [FOLDED]
2070        [EXCERPT]
2071        [FOLDED]
2072        "
2073    });
2074    cx.update_editor(|editor, _, cx| {
2075        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
2076        editor.fold_buffer(buffer_ids[1], cx);
2077    });
2078
2079    cx.assert_excerpts_with_selections(indoc! {"
2080        [EXCERPT]
2081        ˇ[FOLDED]
2082        [EXCERPT]
2083        [FOLDED]
2084        [EXCERPT]
2085        [FOLDED]
2086        [EXCERPT]
2087        [FOLDED]
2088        "
2089    });
2090    cx.simulate_keystrokes("2 j");
2091    cx.assert_excerpts_with_selections(indoc! {"
2092        [EXCERPT]
2093        [FOLDED]
2094        [EXCERPT]
2095        [FOLDED]
2096        [EXCERPT]
2097        ˇ[FOLDED]
2098        [EXCERPT]
2099        [FOLDED]
2100        "
2101    });
2102}
2103
2104#[perf]
2105#[gpui::test]
2106async fn test_delete_paragraph_motion(cx: &mut gpui::TestAppContext) {
2107    let mut cx = NeovimBackedTestContext::new(cx).await;
2108    cx.set_shared_state(indoc! {
2109        "ˇhello world.
2110
2111        hello world.
2112        "
2113    })
2114    .await;
2115    cx.simulate_shared_keystrokes("y }").await;
2116    cx.shared_clipboard().await.assert_eq("hello world.\n");
2117    cx.simulate_shared_keystrokes("d }").await;
2118    cx.shared_state().await.assert_eq("ˇ\nhello world.\n");
2119    cx.shared_clipboard().await.assert_eq("hello world.\n");
2120
2121    cx.set_shared_state(indoc! {
2122        "helˇlo world.
2123
2124            hello world.
2125            "
2126    })
2127    .await;
2128    cx.simulate_shared_keystrokes("y }").await;
2129    cx.shared_clipboard().await.assert_eq("lo world.");
2130    cx.simulate_shared_keystrokes("d }").await;
2131    cx.shared_state().await.assert_eq("heˇl\n\nhello world.\n");
2132    cx.shared_clipboard().await.assert_eq("lo world.");
2133}
2134
2135#[perf]
2136#[gpui::test]
2137async fn test_delete_unmatched_brace(cx: &mut gpui::TestAppContext) {
2138    let mut cx = NeovimBackedTestContext::new(cx).await;
2139    cx.set_shared_state(indoc! {
2140        "fn o(wow: i32) {
2141          othˇ(wow)
2142          oth(wow)
2143        }
2144        "
2145    })
2146    .await;
2147    cx.simulate_shared_keystrokes("d ] }").await;
2148    cx.shared_state().await.assert_eq(indoc! {
2149        "fn o(wow: i32) {
2150          otˇh
2151        }
2152        "
2153    });
2154    cx.shared_clipboard().await.assert_eq("(wow)\n  oth(wow)");
2155    cx.set_shared_state(indoc! {
2156        "fn o(wow: i32) {
2157          ˇoth(wow)
2158          oth(wow)
2159        }
2160        "
2161    })
2162    .await;
2163    cx.simulate_shared_keystrokes("d ] }").await;
2164    cx.shared_state().await.assert_eq(indoc! {
2165        "fn o(wow: i32) {
2166         ˇ}
2167        "
2168    });
2169    cx.shared_clipboard()
2170        .await
2171        .assert_eq("  oth(wow)\n  oth(wow)\n");
2172}
2173
2174#[perf]
2175#[gpui::test]
2176async fn test_paragraph_multi_delete(cx: &mut gpui::TestAppContext) {
2177    let mut cx = NeovimBackedTestContext::new(cx).await;
2178    cx.set_shared_state(indoc! {
2179        "
2180        Emacs is
2181        ˇa great
2182
2183        operating system
2184
2185        all it lacks
2186        is a
2187
2188        decent text editor
2189        "
2190    })
2191    .await;
2192
2193    cx.simulate_shared_keystrokes("2 d a p").await;
2194    cx.shared_state().await.assert_eq(indoc! {
2195        "
2196        ˇall it lacks
2197        is a
2198
2199        decent text editor
2200        "
2201    });
2202
2203    cx.simulate_shared_keystrokes("d a p").await;
2204    cx.shared_clipboard()
2205        .await
2206        .assert_eq("all it lacks\nis a\n\n");
2207
2208    //reset to initial state
2209    cx.simulate_shared_keystrokes("2 u").await;
2210
2211    cx.simulate_shared_keystrokes("4 d a p").await;
2212    cx.shared_state().await.assert_eq(indoc! {"ˇ"});
2213}
2214
2215#[perf]
2216#[gpui::test]
2217async fn test_multi_cursor_replay(cx: &mut gpui::TestAppContext) {
2218    let mut cx = VimTestContext::new(cx, true).await;
2219    cx.set_state(
2220        indoc! {
2221            "
2222        oˇne one one
2223
2224        two two two
2225        "
2226        },
2227        Mode::Normal,
2228    );
2229
2230    cx.simulate_keystrokes("3 g l s wow escape escape");
2231    cx.assert_state(
2232        indoc! {
2233            "
2234        woˇw wow wow
2235
2236        two two two
2237        "
2238        },
2239        Mode::Normal,
2240    );
2241
2242    cx.simulate_keystrokes("2 j 3 g l .");
2243    cx.assert_state(
2244        indoc! {
2245            "
2246        wow wow wow
2247
2248        woˇw woˇw woˇw
2249        "
2250        },
2251        Mode::Normal,
2252    );
2253}
2254
2255#[gpui::test]
2256async fn test_clipping_on_mode_change(cx: &mut gpui::TestAppContext) {
2257    let mut cx = VimTestContext::new(cx, true).await;
2258
2259    cx.set_state(
2260        indoc! {
2261        "
2262        ˇverylongline
2263        andsomelinebelow
2264        "
2265        },
2266        Mode::Normal,
2267    );
2268
2269    cx.simulate_keystrokes("v e");
2270    cx.assert_state(
2271        indoc! {
2272        "
2273        «verylonglineˇ»
2274        andsomelinebelow
2275        "
2276        },
2277        Mode::Visual,
2278    );
2279
2280    let mut pixel_position = cx.update_editor(|editor, window, cx| {
2281        let snapshot = editor.snapshot(window, cx);
2282        let current_head = editor.selections.newest_display(cx).end;
2283        editor.last_bounds().unwrap().origin
2284            + editor
2285                .display_to_pixel_point(current_head, &snapshot, window)
2286                .unwrap()
2287    });
2288    pixel_position.x += px(100.);
2289    // click beyond end of the line
2290    cx.simulate_click(pixel_position, Modifiers::default());
2291    cx.run_until_parked();
2292
2293    cx.assert_state(
2294        indoc! {
2295        "
2296        verylonglinˇe
2297        andsomelinebelow
2298        "
2299        },
2300        Mode::Normal,
2301    );
2302}