test.rs

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