test.rs

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