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