normal.rs

   1mod change;
   2mod delete;
   3mod yank;
   4
   5use std::borrow::Cow;
   6
   7use crate::{
   8    motion::Motion,
   9    state::{Mode, Operator},
  10    Vim,
  11};
  12use change::init as change_init;
  13use collections::HashSet;
  14use editor::{Autoscroll, Bias, ClipboardSelection, DisplayPoint};
  15use gpui::{actions, MutableAppContext, ViewContext};
  16use language::{Point, SelectionGoal};
  17use workspace::Workspace;
  18
  19use self::{change::change_over, delete::delete_over, yank::yank_over};
  20
  21actions!(
  22    vim,
  23    [
  24        InsertAfter,
  25        InsertFirstNonWhitespace,
  26        InsertEndOfLine,
  27        InsertLineAbove,
  28        InsertLineBelow,
  29        DeleteLeft,
  30        DeleteRight,
  31        ChangeToEndOfLine,
  32        DeleteToEndOfLine,
  33        Paste,
  34        Yank,
  35    ]
  36);
  37
  38pub fn init(cx: &mut MutableAppContext) {
  39    cx.add_action(insert_after);
  40    cx.add_action(insert_first_non_whitespace);
  41    cx.add_action(insert_end_of_line);
  42    cx.add_action(insert_line_above);
  43    cx.add_action(insert_line_below);
  44    cx.add_action(|_: &mut Workspace, _: &DeleteLeft, cx| {
  45        Vim::update(cx, |vim, cx| {
  46            delete_over(vim, Motion::Left, cx);
  47        })
  48    });
  49    cx.add_action(|_: &mut Workspace, _: &DeleteRight, cx| {
  50        Vim::update(cx, |vim, cx| {
  51            delete_over(vim, Motion::Right, cx);
  52        })
  53    });
  54    cx.add_action(|_: &mut Workspace, _: &ChangeToEndOfLine, cx| {
  55        Vim::update(cx, |vim, cx| {
  56            change_over(vim, Motion::EndOfLine, cx);
  57        })
  58    });
  59    cx.add_action(|_: &mut Workspace, _: &DeleteToEndOfLine, cx| {
  60        Vim::update(cx, |vim, cx| {
  61            delete_over(vim, Motion::EndOfLine, cx);
  62        })
  63    });
  64    cx.add_action(paste);
  65
  66    change_init(cx);
  67}
  68
  69pub fn normal_motion(motion: Motion, cx: &mut MutableAppContext) {
  70    Vim::update(cx, |vim, cx| {
  71        match vim.state.operator_stack.pop() {
  72            None => move_cursor(vim, motion, cx),
  73            Some(Operator::Namespace(_)) => {
  74                // Can't do anything for a namespace operator. Ignoring
  75            }
  76            Some(Operator::Change) => change_over(vim, motion, cx),
  77            Some(Operator::Delete) => delete_over(vim, motion, cx),
  78            Some(Operator::Yank) => yank_over(vim, motion, cx),
  79        }
  80        vim.clear_operator(cx);
  81    });
  82}
  83
  84fn move_cursor(vim: &mut Vim, motion: Motion, cx: &mut MutableAppContext) {
  85    vim.update_active_editor(cx, |editor, cx| {
  86        editor.change_selections(Some(Autoscroll::Fit), cx, |s| {
  87            s.move_cursors_with(|map, cursor, goal| motion.move_point(map, cursor, goal))
  88        })
  89    });
  90}
  91
  92fn insert_after(_: &mut Workspace, _: &InsertAfter, cx: &mut ViewContext<Workspace>) {
  93    Vim::update(cx, |vim, cx| {
  94        vim.switch_mode(Mode::Insert, cx);
  95        vim.update_active_editor(cx, |editor, cx| {
  96            editor.change_selections(Some(Autoscroll::Fit), cx, |s| {
  97                s.move_cursors_with(|map, cursor, goal| {
  98                    Motion::Right.move_point(map, cursor, goal)
  99                });
 100            });
 101        });
 102    });
 103}
 104
 105fn insert_first_non_whitespace(
 106    _: &mut Workspace,
 107    _: &InsertFirstNonWhitespace,
 108    cx: &mut ViewContext<Workspace>,
 109) {
 110    Vim::update(cx, |vim, cx| {
 111        vim.switch_mode(Mode::Insert, cx);
 112        vim.update_active_editor(cx, |editor, cx| {
 113            editor.change_selections(Some(Autoscroll::Fit), cx, |s| {
 114                s.move_cursors_with(|map, cursor, goal| {
 115                    Motion::FirstNonWhitespace.move_point(map, cursor, goal)
 116                });
 117            });
 118        });
 119    });
 120}
 121
 122fn insert_end_of_line(_: &mut Workspace, _: &InsertEndOfLine, cx: &mut ViewContext<Workspace>) {
 123    Vim::update(cx, |vim, cx| {
 124        vim.switch_mode(Mode::Insert, cx);
 125        vim.update_active_editor(cx, |editor, cx| {
 126            editor.change_selections(Some(Autoscroll::Fit), cx, |s| {
 127                s.move_cursors_with(|map, cursor, goal| {
 128                    Motion::EndOfLine.move_point(map, cursor, goal)
 129                });
 130            });
 131        });
 132    });
 133}
 134
 135fn insert_line_above(_: &mut Workspace, _: &InsertLineAbove, cx: &mut ViewContext<Workspace>) {
 136    Vim::update(cx, |vim, cx| {
 137        vim.switch_mode(Mode::Insert, cx);
 138        vim.update_active_editor(cx, |editor, cx| {
 139            editor.transact(cx, |editor, cx| {
 140                let (map, old_selections) = editor.selections.all_display(cx);
 141                let selection_start_rows: HashSet<u32> = old_selections
 142                    .into_iter()
 143                    .map(|selection| selection.start.row())
 144                    .collect();
 145                let edits = selection_start_rows.into_iter().map(|row| {
 146                    let (indent, _) = map.line_indent(row);
 147                    let start_of_line = map
 148                        .clip_point(DisplayPoint::new(row, 0), Bias::Left)
 149                        .to_point(&map);
 150                    let mut new_text = " ".repeat(indent as usize);
 151                    new_text.push('\n');
 152                    (start_of_line..start_of_line, new_text)
 153                });
 154                editor.edit_with_autoindent(edits, cx);
 155                editor.change_selections(Some(Autoscroll::Fit), cx, |s| {
 156                    s.move_cursors_with(|map, mut cursor, _| {
 157                        *cursor.row_mut() -= 1;
 158                        *cursor.column_mut() = map.line_len(cursor.row());
 159                        (map.clip_point(cursor, Bias::Left), SelectionGoal::None)
 160                    });
 161                });
 162            });
 163        });
 164    });
 165}
 166
 167fn insert_line_below(_: &mut Workspace, _: &InsertLineBelow, cx: &mut ViewContext<Workspace>) {
 168    Vim::update(cx, |vim, cx| {
 169        vim.switch_mode(Mode::Insert, cx);
 170        vim.update_active_editor(cx, |editor, cx| {
 171            editor.transact(cx, |editor, cx| {
 172                let (map, old_selections) = editor.selections.all_display(cx);
 173                let selection_end_rows: HashSet<u32> = old_selections
 174                    .into_iter()
 175                    .map(|selection| selection.end.row())
 176                    .collect();
 177                let edits = selection_end_rows.into_iter().map(|row| {
 178                    let (indent, _) = map.line_indent(row);
 179                    let end_of_line = map
 180                        .clip_point(DisplayPoint::new(row, map.line_len(row)), Bias::Left)
 181                        .to_point(&map);
 182                    let mut new_text = "\n".to_string();
 183                    new_text.push_str(&" ".repeat(indent as usize));
 184                    (end_of_line..end_of_line, new_text)
 185                });
 186                editor.change_selections(Some(Autoscroll::Fit), cx, |s| {
 187                    s.move_cursors_with(|map, cursor, goal| {
 188                        Motion::EndOfLine.move_point(map, cursor, goal)
 189                    });
 190                });
 191                editor.edit_with_autoindent(edits, cx);
 192            });
 193        });
 194    });
 195}
 196
 197fn paste(_: &mut Workspace, _: &Paste, cx: &mut ViewContext<Workspace>) {
 198    Vim::update(cx, |vim, cx| {
 199        vim.update_active_editor(cx, |editor, cx| {
 200            editor.transact(cx, |editor, cx| {
 201                if let Some(item) = cx.as_mut().read_from_clipboard() {
 202                    let mut clipboard_text = Cow::Borrowed(item.text());
 203                    if let Some(mut clipboard_selections) =
 204                        item.metadata::<Vec<ClipboardSelection>>()
 205                    {
 206                        let (display_map, selections) = editor.selections.all_display(cx);
 207                        let all_selections_were_entire_line =
 208                            clipboard_selections.iter().all(|s| s.is_entire_line);
 209                        if clipboard_selections.len() != selections.len() {
 210                            let mut newline_separated_text = String::new();
 211                            let mut clipboard_selections =
 212                                clipboard_selections.drain(..).peekable();
 213                            let mut ix = 0;
 214                            while let Some(clipboard_selection) = clipboard_selections.next() {
 215                                newline_separated_text
 216                                    .push_str(&clipboard_text[ix..ix + clipboard_selection.len]);
 217                                ix += clipboard_selection.len;
 218                                if clipboard_selections.peek().is_some() {
 219                                    newline_separated_text.push('\n');
 220                                }
 221                            }
 222                            clipboard_text = Cow::Owned(newline_separated_text);
 223                        }
 224
 225                        let mut new_selections = Vec::new();
 226                        editor.buffer().update(cx, |buffer, cx| {
 227                            let snapshot = buffer.snapshot(cx);
 228                            let mut start_offset = 0;
 229                            let mut edits = Vec::new();
 230                            for (ix, selection) in selections.iter().enumerate() {
 231                                let to_insert;
 232                                let linewise;
 233                                if let Some(clipboard_selection) = clipboard_selections.get(ix) {
 234                                    let end_offset = start_offset + clipboard_selection.len;
 235                                    to_insert = &clipboard_text[start_offset..end_offset];
 236                                    linewise = clipboard_selection.is_entire_line;
 237                                    start_offset = end_offset;
 238                                } else {
 239                                    to_insert = clipboard_text.as_str();
 240                                    linewise = all_selections_were_entire_line;
 241                                }
 242
 243                                // If the clipboard text was copied linewise, and the current selection
 244                                // is empty, then paste the text after this line and move the selection
 245                                // to the start of the pasted text
 246                                let range = if selection.is_empty() && linewise {
 247                                    let (point, _) = display_map
 248                                        .next_line_boundary(selection.start.to_point(&display_map));
 249
 250                                    if !to_insert.starts_with('\n') {
 251                                        // Add newline before pasted text so that it shows up
 252                                        edits.push((point..point, "\n"));
 253                                    }
 254                                    // Drop selection at the start of the next line
 255                                    let selection_point = Point::new(point.row + 1, 0);
 256                                    new_selections.push(selection.map(|_| selection_point.clone()));
 257                                    point..point
 258                                } else {
 259                                    let range = selection.map(|p| p.to_point(&display_map)).range();
 260                                    new_selections.push(selection.map(|_| range.start.clone()));
 261                                    range
 262                                };
 263
 264                                if linewise && to_insert.ends_with('\n') {
 265                                    edits.push((
 266                                        range,
 267                                        &to_insert[0..to_insert.len().saturating_sub(1)],
 268                                    ))
 269                                } else {
 270                                    edits.push((range, to_insert));
 271                                }
 272                            }
 273                            drop(snapshot);
 274                            buffer.edit_with_autoindent(edits, cx);
 275                        });
 276
 277                        editor.change_selections(Some(Autoscroll::Fit), cx, |s| {
 278                            s.select(new_selections)
 279                        });
 280                    } else {
 281                        editor.insert(&clipboard_text, cx);
 282                    }
 283                }
 284            });
 285        });
 286    });
 287}
 288
 289#[cfg(test)]
 290mod test {
 291    use indoc::indoc;
 292    use language::Selection;
 293    use util::test::marked_text;
 294
 295    use crate::{
 296        state::{
 297            Mode::{self, *},
 298            Namespace, Operator,
 299        },
 300        vim_test_context::VimTestContext,
 301    };
 302
 303    #[gpui::test]
 304    async fn test_h(cx: &mut gpui::TestAppContext) {
 305        let cx = VimTestContext::new(cx, true).await;
 306        let mut cx = cx.binding(["h"]);
 307        cx.assert("The q|uick", "The |quick");
 308        cx.assert("|The quick", "|The quick");
 309        cx.assert(
 310            indoc! {"
 311                The quick
 312                |brown"},
 313            indoc! {"
 314                The quick
 315                |brown"},
 316        );
 317    }
 318
 319    #[gpui::test]
 320    async fn test_backspace(cx: &mut gpui::TestAppContext) {
 321        let cx = VimTestContext::new(cx, true).await;
 322        let mut cx = cx.binding(["backspace"]);
 323        cx.assert("The q|uick", "The |quick");
 324        cx.assert("|The quick", "|The quick");
 325        cx.assert(
 326            indoc! {"
 327                The quick
 328                |brown"},
 329            indoc! {"
 330                The quick
 331                |brown"},
 332        );
 333    }
 334
 335    #[gpui::test]
 336    async fn test_j(cx: &mut gpui::TestAppContext) {
 337        let cx = VimTestContext::new(cx, true).await;
 338        let mut cx = cx.binding(["j"]);
 339        cx.assert(
 340            indoc! {"
 341                The |quick
 342                brown fox"},
 343            indoc! {"
 344                The quick
 345                brow|n fox"},
 346        );
 347        cx.assert(
 348            indoc! {"
 349                The quick
 350                brow|n fox"},
 351            indoc! {"
 352                The quick
 353                brow|n fox"},
 354        );
 355        cx.assert(
 356            indoc! {"
 357                The quic|k
 358                brown"},
 359            indoc! {"
 360                The quick
 361                brow|n"},
 362        );
 363        cx.assert(
 364            indoc! {"
 365                The quick
 366                |brown"},
 367            indoc! {"
 368                The quick
 369                |brown"},
 370        );
 371    }
 372
 373    #[gpui::test]
 374    async fn test_k(cx: &mut gpui::TestAppContext) {
 375        let cx = VimTestContext::new(cx, true).await;
 376        let mut cx = cx.binding(["k"]);
 377        cx.assert(
 378            indoc! {"
 379                The |quick
 380                brown fox"},
 381            indoc! {"
 382                The |quick
 383                brown fox"},
 384        );
 385        cx.assert(
 386            indoc! {"
 387                The quick
 388                brow|n fox"},
 389            indoc! {"
 390                The |quick
 391                brown fox"},
 392        );
 393        cx.assert(
 394            indoc! {"
 395                The
 396                quic|k"},
 397            indoc! {"
 398                Th|e
 399                quick"},
 400        );
 401    }
 402
 403    #[gpui::test]
 404    async fn test_l(cx: &mut gpui::TestAppContext) {
 405        let cx = VimTestContext::new(cx, true).await;
 406        let mut cx = cx.binding(["l"]);
 407        cx.assert("The q|uick", "The qu|ick");
 408        cx.assert("The quic|k", "The quic|k");
 409        cx.assert(
 410            indoc! {"
 411                The quic|k
 412                brown"},
 413            indoc! {"
 414                The quic|k
 415                brown"},
 416        );
 417    }
 418
 419    #[gpui::test]
 420    async fn test_jump_to_line_boundaries(cx: &mut gpui::TestAppContext) {
 421        let cx = VimTestContext::new(cx, true).await;
 422        let mut cx = cx.binding(["shift-$"]);
 423        cx.assert("T|est test", "Test tes|t");
 424        cx.assert("Test tes|t", "Test tes|t");
 425        cx.assert(
 426            indoc! {"
 427                The |quick
 428                brown"},
 429            indoc! {"
 430                The quic|k
 431                brown"},
 432        );
 433        cx.assert(
 434            indoc! {"
 435                The quic|k
 436                brown"},
 437            indoc! {"
 438                The quic|k
 439                brown"},
 440        );
 441
 442        let mut cx = cx.binding(["0"]);
 443        cx.assert("Test |test", "|Test test");
 444        cx.assert("|Test test", "|Test test");
 445        cx.assert(
 446            indoc! {"
 447                The |quick
 448                brown"},
 449            indoc! {"
 450                |The quick
 451                brown"},
 452        );
 453        cx.assert(
 454            indoc! {"
 455                |The quick
 456                brown"},
 457            indoc! {"
 458                |The quick
 459                brown"},
 460        );
 461    }
 462
 463    #[gpui::test]
 464    async fn test_jump_to_end(cx: &mut gpui::TestAppContext) {
 465        let cx = VimTestContext::new(cx, true).await;
 466        let mut cx = cx.binding(["shift-G"]);
 467
 468        cx.assert(
 469            indoc! {"
 470                The |quick
 471                
 472                brown fox jumps
 473                over the lazy dog"},
 474            indoc! {"
 475                The quick
 476                
 477                brown fox jumps
 478                over| the lazy dog"},
 479        );
 480        cx.assert(
 481            indoc! {"
 482                The quick
 483                
 484                brown fox jumps
 485                over| the lazy dog"},
 486            indoc! {"
 487                The quick
 488                
 489                brown fox jumps
 490                over| the lazy dog"},
 491        );
 492        cx.assert(
 493            indoc! {"
 494            The qui|ck
 495            
 496            brown"},
 497            indoc! {"
 498            The quick
 499            
 500            brow|n"},
 501        );
 502        cx.assert(
 503            indoc! {"
 504            The qui|ck
 505            
 506            "},
 507            indoc! {"
 508            The quick
 509            
 510            |"},
 511        );
 512    }
 513
 514    #[gpui::test]
 515    async fn test_w(cx: &mut gpui::TestAppContext) {
 516        let mut cx = VimTestContext::new(cx, true).await;
 517        let (_, cursor_offsets) = marked_text(indoc! {"
 518            The |quick|-|brown
 519            |
 520            |
 521            |fox_jumps |over
 522            |th||e"});
 523        cx.set_state(
 524            indoc! {"
 525            |The quick-brown
 526            
 527            
 528            fox_jumps over
 529            the"},
 530            Mode::Normal,
 531        );
 532
 533        for cursor_offset in cursor_offsets {
 534            cx.simulate_keystroke("w");
 535            cx.assert_editor_selections(vec![Selection::from_offset(cursor_offset)]);
 536        }
 537
 538        // Reset and test ignoring punctuation
 539        let (_, cursor_offsets) = marked_text(indoc! {"
 540            The |quick-brown
 541            |
 542            |
 543            |fox_jumps |over
 544            |th||e"});
 545        cx.set_state(
 546            indoc! {"
 547            |The quick-brown
 548            
 549            
 550            fox_jumps over
 551            the"},
 552            Mode::Normal,
 553        );
 554
 555        for cursor_offset in cursor_offsets {
 556            cx.simulate_keystroke("shift-W");
 557            cx.assert_editor_selections(vec![Selection::from_offset(cursor_offset)]);
 558        }
 559    }
 560
 561    #[gpui::test]
 562    async fn test_e(cx: &mut gpui::TestAppContext) {
 563        let mut cx = VimTestContext::new(cx, true).await;
 564        let (_, cursor_offsets) = marked_text(indoc! {"
 565            Th|e quic|k|-brow|n
 566            
 567            
 568            fox_jump|s ove|r
 569            th|e"});
 570        cx.set_state(
 571            indoc! {"
 572            |The quick-brown
 573            
 574            
 575            fox_jumps over
 576            the"},
 577            Mode::Normal,
 578        );
 579
 580        for cursor_offset in cursor_offsets {
 581            cx.simulate_keystroke("e");
 582            cx.assert_editor_selections(vec![Selection::from_offset(cursor_offset)]);
 583        }
 584
 585        // Reset and test ignoring punctuation
 586        let (_, cursor_offsets) = marked_text(indoc! {"
 587            Th|e quick-brow|n
 588            
 589            
 590            fox_jump|s ove|r
 591            th||e"});
 592        cx.set_state(
 593            indoc! {"
 594            |The quick-brown
 595            
 596            
 597            fox_jumps over
 598            the"},
 599            Mode::Normal,
 600        );
 601        for cursor_offset in cursor_offsets {
 602            cx.simulate_keystroke("shift-E");
 603            cx.assert_editor_selections(vec![Selection::from_offset(cursor_offset)]);
 604        }
 605    }
 606
 607    #[gpui::test]
 608    async fn test_b(cx: &mut gpui::TestAppContext) {
 609        let mut cx = VimTestContext::new(cx, true).await;
 610        let (_, cursor_offsets) = marked_text(indoc! {"
 611            ||The |quick|-|brown
 612            |
 613            |
 614            |fox_jumps |over
 615            |the"});
 616        cx.set_state(
 617            indoc! {"
 618            The quick-brown
 619            
 620            
 621            fox_jumps over
 622            th|e"},
 623            Mode::Normal,
 624        );
 625
 626        for cursor_offset in cursor_offsets.into_iter().rev() {
 627            cx.simulate_keystroke("b");
 628            cx.assert_editor_selections(vec![Selection::from_offset(cursor_offset)]);
 629        }
 630
 631        // Reset and test ignoring punctuation
 632        let (_, cursor_offsets) = marked_text(indoc! {"
 633            ||The |quick-brown
 634            |
 635            |
 636            |fox_jumps |over
 637            |the"});
 638        cx.set_state(
 639            indoc! {"
 640            The quick-brown
 641            
 642            
 643            fox_jumps over
 644            th|e"},
 645            Mode::Normal,
 646        );
 647        for cursor_offset in cursor_offsets.into_iter().rev() {
 648            cx.simulate_keystroke("shift-B");
 649            cx.assert_editor_selections(vec![Selection::from_offset(cursor_offset)]);
 650        }
 651    }
 652
 653    #[gpui::test]
 654    async fn test_g_prefix_and_abort(cx: &mut gpui::TestAppContext) {
 655        let mut cx = VimTestContext::new(cx, true).await;
 656
 657        // Can abort with escape to get back to normal mode
 658        cx.simulate_keystroke("g");
 659        assert_eq!(cx.mode(), Normal);
 660        assert_eq!(
 661            cx.active_operator(),
 662            Some(Operator::Namespace(Namespace::G))
 663        );
 664        cx.simulate_keystroke("escape");
 665        assert_eq!(cx.mode(), Normal);
 666        assert_eq!(cx.active_operator(), None);
 667    }
 668
 669    #[gpui::test]
 670    async fn test_gg(cx: &mut gpui::TestAppContext) {
 671        let cx = VimTestContext::new(cx, true).await;
 672        let mut cx = cx.binding(["g", "g"]);
 673        cx.assert(
 674            indoc! {"
 675                The quick
 676            
 677                brown fox jumps
 678                over |the lazy dog"},
 679            indoc! {"
 680                The q|uick
 681            
 682                brown fox jumps
 683                over the lazy dog"},
 684        );
 685        cx.assert(
 686            indoc! {"
 687                The q|uick
 688            
 689                brown fox jumps
 690                over the lazy dog"},
 691            indoc! {"
 692                The q|uick
 693            
 694                brown fox jumps
 695                over the lazy dog"},
 696        );
 697        cx.assert(
 698            indoc! {"
 699                The quick
 700            
 701                brown fox jumps
 702                over the la|zy dog"},
 703            indoc! {"
 704                The quic|k
 705            
 706                brown fox jumps
 707                over the lazy dog"},
 708        );
 709        cx.assert(
 710            indoc! {"
 711                
 712            
 713                brown fox jumps
 714                over the la|zy dog"},
 715            indoc! {"
 716                |
 717            
 718                brown fox jumps
 719                over the lazy dog"},
 720        );
 721    }
 722
 723    #[gpui::test]
 724    async fn test_a(cx: &mut gpui::TestAppContext) {
 725        let cx = VimTestContext::new(cx, true).await;
 726        let mut cx = cx.binding(["a"]).mode_after(Mode::Insert);
 727
 728        cx.assert("The q|uick", "The qu|ick");
 729        cx.assert("The quic|k", "The quick|");
 730    }
 731
 732    #[gpui::test]
 733    async fn test_insert_end_of_line(cx: &mut gpui::TestAppContext) {
 734        let cx = VimTestContext::new(cx, true).await;
 735        let mut cx = cx.binding(["shift-A"]).mode_after(Mode::Insert);
 736        cx.assert("The q|uick", "The quick|");
 737        cx.assert("The q|uick ", "The quick |");
 738        cx.assert("|", "|");
 739        cx.assert(
 740            indoc! {"
 741                The q|uick
 742                brown fox"},
 743            indoc! {"
 744                The quick|
 745                brown fox"},
 746        );
 747        cx.assert(
 748            indoc! {"
 749                |
 750                The quick"},
 751            indoc! {"
 752                |
 753                The quick"},
 754        );
 755    }
 756
 757    #[gpui::test]
 758    async fn test_jump_to_first_non_whitespace(cx: &mut gpui::TestAppContext) {
 759        let cx = VimTestContext::new(cx, true).await;
 760        let mut cx = cx.binding(["shift-^"]);
 761        cx.assert("The q|uick", "|The quick");
 762        cx.assert(" The q|uick", " |The quick");
 763        cx.assert("|", "|");
 764        cx.assert(
 765            indoc! {"
 766                The q|uick
 767                brown fox"},
 768            indoc! {"
 769                |The quick
 770                brown fox"},
 771        );
 772        cx.assert(
 773            indoc! {"
 774                |
 775                The quick"},
 776            indoc! {"
 777                |
 778                The quick"},
 779        );
 780        // Indoc disallows trailing whitspace.
 781        cx.assert("   | \nThe quick", "   | \nThe quick");
 782    }
 783
 784    #[gpui::test]
 785    async fn test_insert_first_non_whitespace(cx: &mut gpui::TestAppContext) {
 786        let cx = VimTestContext::new(cx, true).await;
 787        let mut cx = cx.binding(["shift-I"]).mode_after(Mode::Insert);
 788        cx.assert("The q|uick", "|The quick");
 789        cx.assert(" The q|uick", " |The quick");
 790        cx.assert("|", "|");
 791        cx.assert(
 792            indoc! {"
 793                The q|uick
 794                brown fox"},
 795            indoc! {"
 796                |The quick
 797                brown fox"},
 798        );
 799        cx.assert(
 800            indoc! {"
 801                |
 802                The quick"},
 803            indoc! {"
 804                |
 805                The quick"},
 806        );
 807    }
 808
 809    #[gpui::test]
 810    async fn test_delete_to_end_of_line(cx: &mut gpui::TestAppContext) {
 811        let cx = VimTestContext::new(cx, true).await;
 812        let mut cx = cx.binding(["shift-D"]);
 813        cx.assert(
 814            indoc! {"
 815                The q|uick
 816                brown fox"},
 817            indoc! {"
 818                The |q
 819                brown fox"},
 820        );
 821        cx.assert(
 822            indoc! {"
 823                The quick
 824                |
 825                brown fox"},
 826            indoc! {"
 827                The quick
 828                |
 829                brown fox"},
 830        );
 831    }
 832
 833    #[gpui::test]
 834    async fn test_x(cx: &mut gpui::TestAppContext) {
 835        let cx = VimTestContext::new(cx, true).await;
 836        let mut cx = cx.binding(["x"]);
 837        cx.assert("|Test", "|est");
 838        cx.assert("Te|st", "Te|t");
 839        cx.assert("Tes|t", "Te|s");
 840        cx.assert(
 841            indoc! {"
 842                Tes|t
 843                test"},
 844            indoc! {"
 845                Te|s
 846                test"},
 847        );
 848    }
 849
 850    #[gpui::test]
 851    async fn test_delete_left(cx: &mut gpui::TestAppContext) {
 852        let cx = VimTestContext::new(cx, true).await;
 853        let mut cx = cx.binding(["shift-X"]);
 854        cx.assert("Te|st", "T|st");
 855        cx.assert("T|est", "|est");
 856        cx.assert("|Test", "|Test");
 857        cx.assert(
 858            indoc! {"
 859                Test
 860                |test"},
 861            indoc! {"
 862                Test
 863                |test"},
 864        );
 865    }
 866
 867    #[gpui::test]
 868    async fn test_o(cx: &mut gpui::TestAppContext) {
 869        let cx = VimTestContext::new(cx, true).await;
 870        let mut cx = cx.binding(["o"]).mode_after(Mode::Insert);
 871
 872        cx.assert(
 873            "|",
 874            indoc! {"
 875                
 876                |"},
 877        );
 878        cx.assert(
 879            "The |quick",
 880            indoc! {"
 881                The quick
 882                |"},
 883        );
 884        cx.assert(
 885            indoc! {"
 886                The quick
 887                brown |fox
 888                jumps over"},
 889            indoc! {"
 890                The quick
 891                brown fox
 892                |
 893                jumps over"},
 894        );
 895        cx.assert(
 896            indoc! {"
 897                The quick
 898                brown fox
 899                jumps |over"},
 900            indoc! {"
 901                The quick
 902                brown fox
 903                jumps over
 904                |"},
 905        );
 906        cx.assert(
 907            indoc! {"
 908                The q|uick
 909                brown fox
 910                jumps over"},
 911            indoc! {"
 912                The quick
 913                |
 914                brown fox
 915                jumps over"},
 916        );
 917        cx.assert(
 918            indoc! {"
 919                The quick
 920                |
 921                brown fox"},
 922            indoc! {"
 923                The quick
 924                
 925                |
 926                brown fox"},
 927        );
 928        cx.assert(
 929            indoc! {"
 930                fn test()
 931                    println!(|);"},
 932            indoc! {"
 933                fn test()
 934                    println!();
 935                    |"},
 936        );
 937        cx.assert(
 938            indoc! {"
 939                fn test(|)
 940                    println!();"},
 941            indoc! {"
 942                fn test()
 943                |
 944                    println!();"},
 945        );
 946    }
 947
 948    #[gpui::test]
 949    async fn test_insert_line_above(cx: &mut gpui::TestAppContext) {
 950        let cx = VimTestContext::new(cx, true).await;
 951        let mut cx = cx.binding(["shift-O"]).mode_after(Mode::Insert);
 952
 953        cx.assert(
 954            "|",
 955            indoc! {"
 956                |
 957                "},
 958        );
 959        cx.assert(
 960            "The |quick",
 961            indoc! {"
 962                |
 963                The quick"},
 964        );
 965        cx.assert(
 966            indoc! {"
 967                The quick
 968                brown |fox
 969                jumps over"},
 970            indoc! {"
 971                The quick
 972                |
 973                brown fox
 974                jumps over"},
 975        );
 976        cx.assert(
 977            indoc! {"
 978                The quick
 979                brown fox
 980                jumps |over"},
 981            indoc! {"
 982                The quick
 983                brown fox
 984                |
 985                jumps over"},
 986        );
 987        cx.assert(
 988            indoc! {"
 989                The q|uick
 990                brown fox
 991                jumps over"},
 992            indoc! {"
 993                |
 994                The quick
 995                brown fox
 996                jumps over"},
 997        );
 998        cx.assert(
 999            indoc! {"
1000                The quick
1001                |
1002                brown fox"},
1003            indoc! {"
1004                The quick
1005                |
1006                
1007                brown fox"},
1008        );
1009        cx.assert(
1010            indoc! {"
1011                fn test()
1012                    println!(|);"},
1013            indoc! {"
1014                fn test()
1015                    |
1016                    println!();"},
1017        );
1018        cx.assert(
1019            indoc! {"
1020                fn test(|)
1021                    println!();"},
1022            indoc! {"
1023                |
1024                fn test()
1025                    println!();"},
1026        );
1027    }
1028
1029    #[gpui::test]
1030    async fn test_dd(cx: &mut gpui::TestAppContext) {
1031        let cx = VimTestContext::new(cx, true).await;
1032        let mut cx = cx.binding(["d", "d"]);
1033
1034        cx.assert("|", "|");
1035        cx.assert("The |quick", "|");
1036        cx.assert(
1037            indoc! {"
1038                The quick
1039                brown |fox
1040                jumps over"},
1041            indoc! {"
1042                The quick
1043                jumps |over"},
1044        );
1045        cx.assert(
1046            indoc! {"
1047                The quick
1048                brown fox
1049                jumps |over"},
1050            indoc! {"
1051                The quick
1052                brown |fox"},
1053        );
1054        cx.assert(
1055            indoc! {"
1056                The q|uick
1057                brown fox
1058                jumps over"},
1059            indoc! {"
1060                brown| fox
1061                jumps over"},
1062        );
1063        cx.assert(
1064            indoc! {"
1065                The quick
1066                |
1067                brown fox"},
1068            indoc! {"
1069                The quick
1070                |brown fox"},
1071        );
1072    }
1073
1074    #[gpui::test]
1075    async fn test_cc(cx: &mut gpui::TestAppContext) {
1076        let cx = VimTestContext::new(cx, true).await;
1077        let mut cx = cx.binding(["c", "c"]).mode_after(Mode::Insert);
1078
1079        cx.assert("|", "|");
1080        cx.assert("The |quick", "|");
1081        cx.assert(
1082            indoc! {"
1083                The quick
1084                brown |fox
1085                jumps over"},
1086            indoc! {"
1087                The quick
1088                |
1089                jumps over"},
1090        );
1091        cx.assert(
1092            indoc! {"
1093                The quick
1094                brown fox
1095                jumps |over"},
1096            indoc! {"
1097                The quick
1098                brown fox
1099                |"},
1100        );
1101        cx.assert(
1102            indoc! {"
1103                The q|uick
1104                brown fox
1105                jumps over"},
1106            indoc! {"
1107                |
1108                brown fox
1109                jumps over"},
1110        );
1111        cx.assert(
1112            indoc! {"
1113                The quick
1114                |
1115                brown fox"},
1116            indoc! {"
1117                The quick
1118                |
1119                brown fox"},
1120        );
1121    }
1122
1123    #[gpui::test]
1124    async fn test_p(cx: &mut gpui::TestAppContext) {
1125        let mut cx = VimTestContext::new(cx, true).await;
1126        cx.set_state(
1127            indoc! {"
1128                The quick brown
1129                fox ju|mps over
1130                the lazy dog"},
1131            Mode::Normal,
1132        );
1133
1134        cx.simulate_keystrokes(["d", "d"]);
1135        cx.assert_editor_state(indoc! {"
1136            The quick brown
1137            the la|zy dog"});
1138
1139        cx.simulate_keystroke("p");
1140        cx.assert_editor_state(indoc! {"
1141            The quick brown
1142            the lazy dog
1143            |fox jumps over"});
1144    }
1145}