test.rs

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