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#[gpui::test]
 404async fn test_wrapped_lines(cx: &mut gpui::TestAppContext) {
 405    let mut cx = NeovimBackedTestContext::new(cx).await;
 406
 407    cx.set_shared_wrap(12).await;
 408    // tests line wrap as follows:
 409    //  1: twelve char
 410    //     twelve char
 411    //  2: twelve char
 412    cx.set_shared_state(indoc! { "
 413        tˇwelve char twelve char
 414        twelve char
 415    "})
 416        .await;
 417    cx.simulate_shared_keystrokes("j").await;
 418    cx.shared_state().await.assert_eq(indoc! {"
 419        twelve char twelve char
 420        tˇwelve char
 421    "});
 422    cx.simulate_shared_keystrokes("k").await;
 423    cx.shared_state().await.assert_eq(indoc! {"
 424        tˇwelve char twelve char
 425        twelve char
 426    "});
 427    cx.simulate_shared_keystrokes("g j").await;
 428    cx.shared_state().await.assert_eq(indoc! {"
 429        twelve char tˇwelve char
 430        twelve char
 431    "});
 432    cx.simulate_shared_keystrokes("g j").await;
 433    cx.shared_state().await.assert_eq(indoc! {"
 434        twelve char twelve char
 435        tˇwelve char
 436    "});
 437
 438    cx.simulate_shared_keystrokes("g k").await;
 439    cx.shared_state().await.assert_eq(indoc! {"
 440        twelve char tˇwelve char
 441        twelve char
 442    "});
 443
 444    cx.simulate_shared_keystrokes("g ^").await;
 445    cx.shared_state().await.assert_eq(indoc! {"
 446        twelve char ˇtwelve char
 447        twelve char
 448    "});
 449
 450    cx.simulate_shared_keystrokes("^").await;
 451    cx.shared_state().await.assert_eq(indoc! {"
 452        ˇtwelve char twelve char
 453        twelve char
 454    "});
 455
 456    cx.simulate_shared_keystrokes("g $").await;
 457    cx.shared_state().await.assert_eq(indoc! {"
 458        twelve charˇ twelve char
 459        twelve char
 460    "});
 461    cx.simulate_shared_keystrokes("$").await;
 462    cx.shared_state().await.assert_eq(indoc! {"
 463        twelve char twelve chaˇr
 464        twelve char
 465    "});
 466
 467    cx.set_shared_state(indoc! { "
 468        tˇwelve char twelve char
 469        twelve char
 470    "})
 471        .await;
 472    cx.simulate_shared_keystrokes("enter").await;
 473    cx.shared_state().await.assert_eq(indoc! {"
 474            twelve char twelve char
 475            ˇtwelve char
 476        "});
 477
 478    cx.set_shared_state(indoc! { "
 479        twelve char
 480        tˇwelve char twelve char
 481        twelve char
 482    "})
 483        .await;
 484    cx.simulate_shared_keystrokes("o o escape").await;
 485    cx.shared_state().await.assert_eq(indoc! {"
 486        twelve char
 487        twelve char twelve char
 488        ˇo
 489        twelve char
 490    "});
 491
 492    cx.set_shared_state(indoc! { "
 493        twelve char
 494        tˇwelve char twelve char
 495        twelve char
 496    "})
 497        .await;
 498    cx.simulate_shared_keystrokes("shift-a a escape").await;
 499    cx.shared_state().await.assert_eq(indoc! {"
 500        twelve char
 501        twelve char twelve charˇa
 502        twelve char
 503    "});
 504    cx.simulate_shared_keystrokes("shift-i i escape").await;
 505    cx.shared_state().await.assert_eq(indoc! {"
 506        twelve char
 507        ˇitwelve char twelve chara
 508        twelve char
 509    "});
 510    cx.simulate_shared_keystrokes("shift-d").await;
 511    cx.shared_state().await.assert_eq(indoc! {"
 512        twelve char
 513        ˇ
 514        twelve char
 515    "});
 516
 517    cx.set_shared_state(indoc! { "
 518        twelve char
 519        twelve char tˇwelve char
 520        twelve char
 521    "})
 522        .await;
 523    cx.simulate_shared_keystrokes("shift-o o escape").await;
 524    cx.shared_state().await.assert_eq(indoc! {"
 525        twelve char
 526        ˇo
 527        twelve char twelve char
 528        twelve char
 529    "});
 530
 531    // line wraps as:
 532    // fourteen ch
 533    // ar
 534    // fourteen ch
 535    // ar
 536    cx.set_shared_state(indoc! { "
 537        fourteen chaˇr
 538        fourteen char
 539    "})
 540        .await;
 541
 542    cx.simulate_shared_keystrokes("d i w").await;
 543    cx.shared_state().await.assert_eq(indoc! {"
 544        fourteenˇ•
 545        fourteen char
 546    "});
 547    cx.simulate_shared_keystrokes("j shift-f e f r").await;
 548    cx.shared_state().await.assert_eq(indoc! {"
 549        fourteen•
 550        fourteen chaˇr
 551    "});
 552}
 553
 554#[gpui::test]
 555async fn test_folds(cx: &mut gpui::TestAppContext) {
 556    let mut cx = NeovimBackedTestContext::new(cx).await;
 557    cx.set_neovim_option("foldmethod=manual").await;
 558
 559    cx.set_shared_state(indoc! { "
 560        fn boop() {
 561          ˇbarp()
 562          bazp()
 563        }
 564    "})
 565        .await;
 566    cx.simulate_shared_keystrokes("shift-v j z f").await;
 567
 568    // visual display is now:
 569    // fn boop () {
 570    //  [FOLDED]
 571    // }
 572
 573    // TODO: this should not be needed but currently zf does not
 574    // return to normal mode.
 575    cx.simulate_shared_keystrokes("escape").await;
 576
 577    // skip over fold downward
 578    cx.simulate_shared_keystrokes("g g").await;
 579    cx.shared_state().await.assert_eq(indoc! {"
 580        ˇfn boop() {
 581          barp()
 582          bazp()
 583        }
 584    "});
 585
 586    cx.simulate_shared_keystrokes("j j").await;
 587    cx.shared_state().await.assert_eq(indoc! {"
 588        fn boop() {
 589          barp()
 590          bazp()
 591        ˇ}
 592    "});
 593
 594    // skip over fold upward
 595    cx.simulate_shared_keystrokes("2 k").await;
 596    cx.shared_state().await.assert_eq(indoc! {"
 597        ˇfn boop() {
 598          barp()
 599          bazp()
 600        }
 601    "});
 602
 603    // yank the fold
 604    cx.simulate_shared_keystrokes("down y y").await;
 605    cx.shared_clipboard()
 606        .await
 607        .assert_eq("  barp()\n  bazp()\n");
 608
 609    // re-open
 610    cx.simulate_shared_keystrokes("z o").await;
 611    cx.shared_state().await.assert_eq(indoc! {"
 612        fn boop() {
 613        ˇ  barp()
 614          bazp()
 615        }
 616    "});
 617}
 618
 619#[gpui::test]
 620async fn test_folds_panic(cx: &mut gpui::TestAppContext) {
 621    let mut cx = NeovimBackedTestContext::new(cx).await;
 622    cx.set_neovim_option("foldmethod=manual").await;
 623
 624    cx.set_shared_state(indoc! { "
 625        fn boop() {
 626          ˇbarp()
 627          bazp()
 628        }
 629    "})
 630        .await;
 631    cx.simulate_shared_keystrokes("shift-v j z f").await;
 632    cx.simulate_shared_keystrokes("escape").await;
 633    cx.simulate_shared_keystrokes("g g").await;
 634    cx.simulate_shared_keystrokes("5 d j").await;
 635    cx.shared_state().await.assert_eq("ˇ");
 636    cx.set_shared_state(indoc! {"
 637        fn boop() {
 638          ˇbarp()
 639          bazp()
 640        }
 641    "})
 642        .await;
 643    cx.simulate_shared_keystrokes("shift-v j j z f").await;
 644    cx.simulate_shared_keystrokes("escape").await;
 645    cx.simulate_shared_keystrokes("shift-g shift-v").await;
 646    cx.shared_state().await.assert_eq(indoc! {"
 647        fn boop() {
 648          barp()
 649          bazp()
 650        }
 651        ˇ"});
 652}
 653
 654#[gpui::test]
 655async fn test_clear_counts(cx: &mut gpui::TestAppContext) {
 656    let mut cx = NeovimBackedTestContext::new(cx).await;
 657
 658    cx.set_shared_state(indoc! {"
 659        The quick brown
 660        fox juˇmps over
 661        the lazy dog"})
 662        .await;
 663
 664    cx.simulate_shared_keystrokes("4 escape 3 d l").await;
 665    cx.shared_state().await.assert_eq(indoc! {"
 666        The quick brown
 667        fox juˇ over
 668        the lazy dog"});
 669}
 670
 671#[gpui::test]
 672async fn test_zero(cx: &mut gpui::TestAppContext) {
 673    let mut cx = NeovimBackedTestContext::new(cx).await;
 674
 675    cx.set_shared_state(indoc! {"
 676        The quˇick brown
 677        fox jumps over
 678        the lazy dog"})
 679        .await;
 680
 681    cx.simulate_shared_keystrokes("0").await;
 682    cx.shared_state().await.assert_eq(indoc! {"
 683        ˇThe quick brown
 684        fox jumps over
 685        the lazy dog"});
 686
 687    cx.simulate_shared_keystrokes("1 0 l").await;
 688    cx.shared_state().await.assert_eq(indoc! {"
 689        The quick ˇbrown
 690        fox jumps over
 691        the lazy dog"});
 692}
 693
 694#[gpui::test]
 695async fn test_selection_goal(cx: &mut gpui::TestAppContext) {
 696    let mut cx = NeovimBackedTestContext::new(cx).await;
 697
 698    cx.set_shared_state(indoc! {"
 699        ;;ˇ;
 700        Lorem Ipsum"})
 701        .await;
 702
 703    cx.simulate_shared_keystrokes("a down up ; down up").await;
 704    cx.shared_state().await.assert_eq(indoc! {"
 705        ;;;;ˇ
 706        Lorem Ipsum"});
 707}
 708
 709#[gpui::test]
 710async fn test_wrapped_motions(cx: &mut gpui::TestAppContext) {
 711    let mut cx = NeovimBackedTestContext::new(cx).await;
 712
 713    cx.set_shared_wrap(12).await;
 714
 715    cx.set_shared_state(indoc! {"
 716                aaˇaa
 717                😃😃"
 718    })
 719    .await;
 720    cx.simulate_shared_keystrokes("j").await;
 721    cx.shared_state().await.assert_eq(indoc! {"
 722                aaaa
 723                😃ˇ😃"
 724    });
 725
 726    cx.set_shared_state(indoc! {"
 727                123456789012aaˇaa
 728                123456789012😃😃"
 729    })
 730    .await;
 731    cx.simulate_shared_keystrokes("j").await;
 732    cx.shared_state().await.assert_eq(indoc! {"
 733        123456789012aaaa
 734        123456789012😃ˇ😃"
 735    });
 736
 737    cx.set_shared_state(indoc! {"
 738                123456789012aaˇaa
 739                123456789012😃😃"
 740    })
 741    .await;
 742    cx.simulate_shared_keystrokes("j").await;
 743    cx.shared_state().await.assert_eq(indoc! {"
 744        123456789012aaaa
 745        123456789012😃ˇ😃"
 746    });
 747
 748    cx.set_shared_state(indoc! {"
 749        123456789012aaaaˇaaaaaaaa123456789012
 750        wow
 751        123456789012😃😃😃😃😃😃123456789012"
 752    })
 753    .await;
 754    cx.simulate_shared_keystrokes("j j").await;
 755    cx.shared_state().await.assert_eq(indoc! {"
 756        123456789012aaaaaaaaaaaa123456789012
 757        wow
 758        123456789012😃😃ˇ😃😃😃😃123456789012"
 759    });
 760}
 761
 762#[gpui::test]
 763async fn test_paragraphs_dont_wrap(cx: &mut gpui::TestAppContext) {
 764    let mut cx = NeovimBackedTestContext::new(cx).await;
 765
 766    cx.set_shared_state(indoc! {"
 767        one
 768        ˇ
 769        two"})
 770        .await;
 771
 772    cx.simulate_shared_keystrokes("} }").await;
 773    cx.shared_state().await.assert_eq(indoc! {"
 774        one
 775
 776        twˇo"});
 777
 778    cx.simulate_shared_keystrokes("{ { {").await;
 779    cx.shared_state().await.assert_eq(indoc! {"
 780        ˇone
 781
 782        two"});
 783}
 784
 785#[gpui::test]
 786async fn test_select_all_issue_2170(cx: &mut gpui::TestAppContext) {
 787    let mut cx = VimTestContext::new(cx, true).await;
 788
 789    cx.set_state(
 790        indoc! {"
 791        defmodule Test do
 792            def test(a, ˇ[_, _] = b), do: IO.puts('hi')
 793        end
 794    "},
 795        Mode::Normal,
 796    );
 797    cx.simulate_keystrokes("g a");
 798    cx.assert_state(
 799        indoc! {"
 800        defmodule Test do
 801            def test(a, «[ˇ»_, _] = b), do: IO.puts('hi')
 802        end
 803    "},
 804        Mode::Visual,
 805    );
 806}
 807
 808#[gpui::test]
 809async fn test_jk(cx: &mut gpui::TestAppContext) {
 810    let mut cx = NeovimBackedTestContext::new(cx).await;
 811
 812    cx.update(|cx| {
 813        cx.bind_keys([KeyBinding::new(
 814            "j k",
 815            NormalBefore,
 816            Some("vim_mode == insert"),
 817        )])
 818    });
 819    cx.neovim.exec("imap jk <esc>").await;
 820
 821    cx.set_shared_state("ˇhello").await;
 822    cx.simulate_shared_keystrokes("i j o j k").await;
 823    cx.shared_state().await.assert_eq("jˇohello");
 824}
 825
 826#[gpui::test]
 827async fn test_jk_delay(cx: &mut gpui::TestAppContext) {
 828    let mut cx = VimTestContext::new(cx, true).await;
 829
 830    cx.update(|cx| {
 831        cx.bind_keys([KeyBinding::new(
 832            "j k",
 833            NormalBefore,
 834            Some("vim_mode == insert"),
 835        )])
 836    });
 837
 838    cx.set_state("ˇhello", Mode::Normal);
 839    cx.simulate_keystrokes("i j");
 840    cx.executor().advance_clock(Duration::from_millis(500));
 841    cx.run_until_parked();
 842    cx.assert_state("ˇhello", Mode::Insert);
 843    cx.executor().advance_clock(Duration::from_millis(500));
 844    cx.run_until_parked();
 845    cx.assert_state("jˇhello", Mode::Insert);
 846    cx.simulate_keystrokes("k j k");
 847    cx.assert_state("jˇkhello", Mode::Normal);
 848}
 849
 850#[gpui::test]
 851async fn test_comma_w(cx: &mut gpui::TestAppContext) {
 852    let mut cx = NeovimBackedTestContext::new(cx).await;
 853
 854    cx.update(|cx| {
 855        cx.bind_keys([KeyBinding::new(
 856            ", w",
 857            motion::Down {
 858                display_lines: false,
 859            },
 860            Some("vim_mode == normal"),
 861        )])
 862    });
 863    cx.neovim.exec("map ,w j").await;
 864
 865    cx.set_shared_state("ˇhello hello\nhello hello").await;
 866    cx.simulate_shared_keystrokes("f o ; , w").await;
 867    cx.shared_state()
 868        .await
 869        .assert_eq("hello hello\nhello hellˇo");
 870
 871    cx.set_shared_state("ˇhello hello\nhello hello").await;
 872    cx.simulate_shared_keystrokes("f o ; , i").await;
 873    cx.shared_state()
 874        .await
 875        .assert_eq("hellˇo hello\nhello hello");
 876}
 877
 878#[gpui::test]
 879async fn test_rename(cx: &mut gpui::TestAppContext) {
 880    let mut cx = VimTestContext::new_typescript(cx).await;
 881
 882    cx.set_state("const beˇfore = 2; console.log(before)", Mode::Normal);
 883    let def_range = cx.lsp_range("const «beforeˇ» = 2; console.log(before)");
 884    let tgt_range = cx.lsp_range("const before = 2; console.log(«beforeˇ»)");
 885    let mut prepare_request =
 886        cx.handle_request::<lsp::request::PrepareRenameRequest, _, _>(move |_, _, _| async move {
 887            Ok(Some(lsp::PrepareRenameResponse::Range(def_range)))
 888        });
 889    let mut rename_request =
 890        cx.handle_request::<lsp::request::Rename, _, _>(move |url, params, _| async move {
 891            Ok(Some(lsp::WorkspaceEdit {
 892                changes: Some(
 893                    [(
 894                        url.clone(),
 895                        vec![
 896                            lsp::TextEdit::new(def_range, params.new_name.clone()),
 897                            lsp::TextEdit::new(tgt_range, params.new_name),
 898                        ],
 899                    )]
 900                    .into(),
 901                ),
 902                ..Default::default()
 903            }))
 904        });
 905
 906    cx.simulate_keystrokes("c d");
 907    prepare_request.next().await.unwrap();
 908    cx.simulate_input("after");
 909    cx.simulate_keystrokes("enter");
 910    rename_request.next().await.unwrap();
 911    cx.assert_state("const afterˇ = 2; console.log(after)", Mode::Normal)
 912}
 913
 914#[gpui::test]
 915async fn test_remap(cx: &mut gpui::TestAppContext) {
 916    let mut cx = VimTestContext::new(cx, true).await;
 917
 918    // test moving the cursor
 919    cx.update(|cx| {
 920        cx.bind_keys([KeyBinding::new(
 921            "g z",
 922            workspace::SendKeystrokes("l l l l".to_string()),
 923            None,
 924        )])
 925    });
 926    cx.set_state("ˇ123456789", Mode::Normal);
 927    cx.simulate_keystrokes("g z");
 928    cx.assert_state("1234ˇ56789", Mode::Normal);
 929
 930    // test switching modes
 931    cx.update(|cx| {
 932        cx.bind_keys([KeyBinding::new(
 933            "g y",
 934            workspace::SendKeystrokes("i f o o escape l".to_string()),
 935            None,
 936        )])
 937    });
 938    cx.set_state("ˇ123456789", Mode::Normal);
 939    cx.simulate_keystrokes("g y");
 940    cx.assert_state("fooˇ123456789", Mode::Normal);
 941
 942    // test recursion
 943    cx.update(|cx| {
 944        cx.bind_keys([KeyBinding::new(
 945            "g x",
 946            workspace::SendKeystrokes("g z g y".to_string()),
 947            None,
 948        )])
 949    });
 950    cx.set_state("ˇ123456789", Mode::Normal);
 951    cx.simulate_keystrokes("g x");
 952    cx.assert_state("1234fooˇ56789", Mode::Normal);
 953
 954    cx.executor().allow_parking();
 955
 956    // test command
 957    cx.update(|cx| {
 958        cx.bind_keys([KeyBinding::new(
 959            "g w",
 960            workspace::SendKeystrokes(": j enter".to_string()),
 961            None,
 962        )])
 963    });
 964    cx.set_state("ˇ1234\n56789", Mode::Normal);
 965    cx.simulate_keystrokes("g w");
 966    cx.assert_state("1234ˇ 56789", Mode::Normal);
 967
 968    // test leaving command
 969    cx.update(|cx| {
 970        cx.bind_keys([KeyBinding::new(
 971            "g u",
 972            workspace::SendKeystrokes("g w g z".to_string()),
 973            None,
 974        )])
 975    });
 976    cx.set_state("ˇ1234\n56789", Mode::Normal);
 977    cx.simulate_keystrokes("g u");
 978    cx.assert_state("1234 567ˇ89", Mode::Normal);
 979
 980    // test leaving command
 981    cx.update(|cx| {
 982        cx.bind_keys([KeyBinding::new(
 983            "g t",
 984            workspace::SendKeystrokes("i space escape".to_string()),
 985            None,
 986        )])
 987    });
 988    cx.set_state("12ˇ34", Mode::Normal);
 989    cx.simulate_keystrokes("g t");
 990    cx.assert_state("12ˇ 34", Mode::Normal);
 991}
 992
 993#[gpui::test]
 994async fn test_undo(cx: &mut gpui::TestAppContext) {
 995    let mut cx = NeovimBackedTestContext::new(cx).await;
 996
 997    cx.set_shared_state("hello quˇoel world").await;
 998    cx.simulate_shared_keystrokes("v i w s c o escape u").await;
 999    cx.shared_state().await.assert_eq("hello ˇquoel world");
1000    cx.simulate_shared_keystrokes("ctrl-r").await;
1001    cx.shared_state().await.assert_eq("hello ˇco world");
1002    cx.simulate_shared_keystrokes("a o right l escape").await;
1003    cx.shared_state().await.assert_eq("hello cooˇl world");
1004    cx.simulate_shared_keystrokes("u").await;
1005    cx.shared_state().await.assert_eq("hello cooˇ world");
1006    cx.simulate_shared_keystrokes("u").await;
1007    cx.shared_state().await.assert_eq("hello cˇo world");
1008    cx.simulate_shared_keystrokes("u").await;
1009    cx.shared_state().await.assert_eq("hello ˇquoel world");
1010
1011    cx.set_shared_state("hello quˇoel world").await;
1012    cx.simulate_shared_keystrokes("v i w ~ u").await;
1013    cx.shared_state().await.assert_eq("hello ˇquoel world");
1014
1015    cx.set_shared_state("\nhello quˇoel world\n").await;
1016    cx.simulate_shared_keystrokes("shift-v s c escape u").await;
1017    cx.shared_state().await.assert_eq("\nˇhello quoel world\n");
1018
1019    cx.set_shared_state(indoc! {"
1020        ˇ1
1021        2
1022        3"})
1023        .await;
1024
1025    cx.simulate_shared_keystrokes("ctrl-v shift-g ctrl-a").await;
1026    cx.shared_state().await.assert_eq(indoc! {"
1027        ˇ2
1028        3
1029        4"});
1030
1031    cx.simulate_shared_keystrokes("u").await;
1032    cx.shared_state().await.assert_eq(indoc! {"
1033        ˇ1
1034        2
1035        3"});
1036}
1037
1038#[gpui::test]
1039async fn test_mouse_selection(cx: &mut TestAppContext) {
1040    let mut cx = VimTestContext::new(cx, true).await;
1041
1042    cx.set_state("ˇone two three", Mode::Normal);
1043
1044    let start_point = cx.pixel_position("one twˇo three");
1045    let end_point = cx.pixel_position("one ˇtwo three");
1046
1047    cx.simulate_mouse_down(start_point, MouseButton::Left, Modifiers::none());
1048    cx.simulate_mouse_move(end_point, MouseButton::Left, Modifiers::none());
1049    cx.simulate_mouse_up(end_point, MouseButton::Left, Modifiers::none());
1050
1051    cx.assert_state("one «ˇtwo» three", Mode::Visual)
1052}
1053
1054#[gpui::test]
1055async fn test_lowercase_marks(cx: &mut TestAppContext) {
1056    let mut cx = NeovimBackedTestContext::new(cx).await;
1057
1058    cx.set_shared_state("line one\nline ˇtwo\nline three").await;
1059    cx.simulate_shared_keystrokes("m a l ' a").await;
1060    cx.shared_state()
1061        .await
1062        .assert_eq("line one\nˇline two\nline three");
1063    cx.simulate_shared_keystrokes("` a").await;
1064    cx.shared_state()
1065        .await
1066        .assert_eq("line one\nline ˇtwo\nline three");
1067
1068    cx.simulate_shared_keystrokes("^ d ` a").await;
1069    cx.shared_state()
1070        .await
1071        .assert_eq("line one\nˇtwo\nline three");
1072}
1073
1074#[gpui::test]
1075async fn test_lt_gt_marks(cx: &mut TestAppContext) {
1076    let mut cx = NeovimBackedTestContext::new(cx).await;
1077
1078    cx.set_shared_state(indoc!(
1079        "
1080        Line one
1081        Line two
1082        Line ˇthree
1083        Line four
1084        Line five
1085    "
1086    ))
1087    .await;
1088
1089    cx.simulate_shared_keystrokes("v j escape k k").await;
1090
1091    cx.simulate_shared_keystrokes("' <").await;
1092    cx.shared_state().await.assert_eq(indoc! {"
1093        Line one
1094        Line two
1095        ˇLine three
1096        Line four
1097        Line five
1098    "});
1099
1100    cx.simulate_shared_keystrokes("` <").await;
1101    cx.shared_state().await.assert_eq(indoc! {"
1102        Line one
1103        Line two
1104        Line ˇthree
1105        Line four
1106        Line five
1107    "});
1108
1109    cx.simulate_shared_keystrokes("' >").await;
1110    cx.shared_state().await.assert_eq(indoc! {"
1111        Line one
1112        Line two
1113        Line three
1114        ˇLine four
1115        Line five
1116    "
1117    });
1118
1119    cx.simulate_shared_keystrokes("` >").await;
1120    cx.shared_state().await.assert_eq(indoc! {"
1121        Line one
1122        Line two
1123        Line three
1124        Line ˇfour
1125        Line five
1126    "
1127    });
1128}
1129
1130#[gpui::test]
1131async fn test_caret_mark(cx: &mut TestAppContext) {
1132    let mut cx = NeovimBackedTestContext::new(cx).await;
1133
1134    cx.set_shared_state(indoc!(
1135        "
1136        Line one
1137        Line two
1138        Line three
1139        ˇLine four
1140        Line five
1141    "
1142    ))
1143    .await;
1144
1145    cx.simulate_shared_keystrokes("c w shift-s t r a i g h t space t h i n g escape j j")
1146        .await;
1147
1148    cx.simulate_shared_keystrokes("' ^").await;
1149    cx.shared_state().await.assert_eq(indoc! {"
1150        Line one
1151        Line two
1152        Line three
1153        ˇStraight thing four
1154        Line five
1155    "
1156    });
1157
1158    cx.simulate_shared_keystrokes("` ^").await;
1159    cx.shared_state().await.assert_eq(indoc! {"
1160        Line one
1161        Line two
1162        Line three
1163        Straight thingˇ four
1164        Line five
1165    "
1166    });
1167}