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