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        cx.assert(
 781            indoc! {"
 782                    |
 783                The quick"},
 784            indoc! {"
 785                    |
 786                The quick"},
 787        );
 788    }
 789
 790    #[gpui::test]
 791    async fn test_insert_first_non_whitespace(cx: &mut gpui::TestAppContext) {
 792        let cx = VimTestContext::new(cx, true).await;
 793        let mut cx = cx.binding(["shift-I"]).mode_after(Mode::Insert);
 794        cx.assert("The q|uick", "|The quick");
 795        cx.assert(" The q|uick", " |The quick");
 796        cx.assert("|", "|");
 797        cx.assert(
 798            indoc! {"
 799                The q|uick
 800                brown fox"},
 801            indoc! {"
 802                |The quick
 803                brown fox"},
 804        );
 805        cx.assert(
 806            indoc! {"
 807                |
 808                The quick"},
 809            indoc! {"
 810                |
 811                The quick"},
 812        );
 813    }
 814
 815    #[gpui::test]
 816    async fn test_delete_to_end_of_line(cx: &mut gpui::TestAppContext) {
 817        let cx = VimTestContext::new(cx, true).await;
 818        let mut cx = cx.binding(["shift-D"]);
 819        cx.assert(
 820            indoc! {"
 821                The q|uick
 822                brown fox"},
 823            indoc! {"
 824                The |q
 825                brown fox"},
 826        );
 827        cx.assert(
 828            indoc! {"
 829                The quick
 830                |
 831                brown fox"},
 832            indoc! {"
 833                The quick
 834                |
 835                brown fox"},
 836        );
 837    }
 838
 839    #[gpui::test]
 840    async fn test_x(cx: &mut gpui::TestAppContext) {
 841        let cx = VimTestContext::new(cx, true).await;
 842        let mut cx = cx.binding(["x"]);
 843        cx.assert("|Test", "|est");
 844        cx.assert("Te|st", "Te|t");
 845        cx.assert("Tes|t", "Te|s");
 846        cx.assert(
 847            indoc! {"
 848                Tes|t
 849                test"},
 850            indoc! {"
 851                Te|s
 852                test"},
 853        );
 854    }
 855
 856    #[gpui::test]
 857    async fn test_delete_left(cx: &mut gpui::TestAppContext) {
 858        let cx = VimTestContext::new(cx, true).await;
 859        let mut cx = cx.binding(["shift-X"]);
 860        cx.assert("Te|st", "T|st");
 861        cx.assert("T|est", "|est");
 862        cx.assert("|Test", "|Test");
 863        cx.assert(
 864            indoc! {"
 865                Test
 866                |test"},
 867            indoc! {"
 868                Test
 869                |test"},
 870        );
 871    }
 872
 873    #[gpui::test]
 874    async fn test_o(cx: &mut gpui::TestAppContext) {
 875        let cx = VimTestContext::new(cx, true).await;
 876        let mut cx = cx.binding(["o"]).mode_after(Mode::Insert);
 877
 878        cx.assert(
 879            "|",
 880            indoc! {"
 881                
 882                |"},
 883        );
 884        cx.assert(
 885            "The |quick",
 886            indoc! {"
 887                The quick
 888                |"},
 889        );
 890        cx.assert(
 891            indoc! {"
 892                The quick
 893                brown |fox
 894                jumps over"},
 895            indoc! {"
 896                The quick
 897                brown fox
 898                |
 899                jumps over"},
 900        );
 901        cx.assert(
 902            indoc! {"
 903                The quick
 904                brown fox
 905                jumps |over"},
 906            indoc! {"
 907                The quick
 908                brown fox
 909                jumps over
 910                |"},
 911        );
 912        cx.assert(
 913            indoc! {"
 914                The q|uick
 915                brown fox
 916                jumps over"},
 917            indoc! {"
 918                The quick
 919                |
 920                brown fox
 921                jumps over"},
 922        );
 923        cx.assert(
 924            indoc! {"
 925                The quick
 926                |
 927                brown fox"},
 928            indoc! {"
 929                The quick
 930                
 931                |
 932                brown fox"},
 933        );
 934        cx.assert(
 935            indoc! {"
 936                fn test()
 937                    println!(|);"},
 938            indoc! {"
 939                fn test()
 940                    println!();
 941                    |"},
 942        );
 943        cx.assert(
 944            indoc! {"
 945                fn test(|)
 946                    println!();"},
 947            indoc! {"
 948                fn test()
 949                |
 950                    println!();"},
 951        );
 952    }
 953
 954    #[gpui::test]
 955    async fn test_insert_line_above(cx: &mut gpui::TestAppContext) {
 956        let cx = VimTestContext::new(cx, true).await;
 957        let mut cx = cx.binding(["shift-O"]).mode_after(Mode::Insert);
 958
 959        cx.assert(
 960            "|",
 961            indoc! {"
 962                |
 963                "},
 964        );
 965        cx.assert(
 966            "The |quick",
 967            indoc! {"
 968                |
 969                The quick"},
 970        );
 971        cx.assert(
 972            indoc! {"
 973                The quick
 974                brown |fox
 975                jumps over"},
 976            indoc! {"
 977                The quick
 978                |
 979                brown fox
 980                jumps over"},
 981        );
 982        cx.assert(
 983            indoc! {"
 984                The quick
 985                brown fox
 986                jumps |over"},
 987            indoc! {"
 988                The quick
 989                brown fox
 990                |
 991                jumps over"},
 992        );
 993        cx.assert(
 994            indoc! {"
 995                The q|uick
 996                brown fox
 997                jumps over"},
 998            indoc! {"
 999                |
1000                The quick
1001                brown fox
1002                jumps over"},
1003        );
1004        cx.assert(
1005            indoc! {"
1006                The quick
1007                |
1008                brown fox"},
1009            indoc! {"
1010                The quick
1011                |
1012                
1013                brown fox"},
1014        );
1015        cx.assert(
1016            indoc! {"
1017                fn test()
1018                    println!(|);"},
1019            indoc! {"
1020                fn test()
1021                    |
1022                    println!();"},
1023        );
1024        cx.assert(
1025            indoc! {"
1026                fn test(|)
1027                    println!();"},
1028            indoc! {"
1029                |
1030                fn test()
1031                    println!();"},
1032        );
1033    }
1034
1035    #[gpui::test]
1036    async fn test_dd(cx: &mut gpui::TestAppContext) {
1037        let cx = VimTestContext::new(cx, true).await;
1038        let mut cx = cx.binding(["d", "d"]);
1039
1040        cx.assert("|", "|");
1041        cx.assert("The |quick", "|");
1042        cx.assert(
1043            indoc! {"
1044                The quick
1045                brown |fox
1046                jumps over"},
1047            indoc! {"
1048                The quick
1049                jumps |over"},
1050        );
1051        cx.assert(
1052            indoc! {"
1053                The quick
1054                brown fox
1055                jumps |over"},
1056            indoc! {"
1057                The quick
1058                brown |fox"},
1059        );
1060        cx.assert(
1061            indoc! {"
1062                The q|uick
1063                brown fox
1064                jumps over"},
1065            indoc! {"
1066                brown| fox
1067                jumps over"},
1068        );
1069        cx.assert(
1070            indoc! {"
1071                The quick
1072                |
1073                brown fox"},
1074            indoc! {"
1075                The quick
1076                |brown fox"},
1077        );
1078    }
1079
1080    #[gpui::test]
1081    async fn test_cc(cx: &mut gpui::TestAppContext) {
1082        let cx = VimTestContext::new(cx, true).await;
1083        let mut cx = cx.binding(["c", "c"]).mode_after(Mode::Insert);
1084
1085        cx.assert("|", "|");
1086        cx.assert("The |quick", "|");
1087        cx.assert(
1088            indoc! {"
1089                The quick
1090                brown |fox
1091                jumps over"},
1092            indoc! {"
1093                The quick
1094                |
1095                jumps over"},
1096        );
1097        cx.assert(
1098            indoc! {"
1099                The quick
1100                brown fox
1101                jumps |over"},
1102            indoc! {"
1103                The quick
1104                brown fox
1105                |"},
1106        );
1107        cx.assert(
1108            indoc! {"
1109                The q|uick
1110                brown fox
1111                jumps over"},
1112            indoc! {"
1113                |
1114                brown fox
1115                jumps over"},
1116        );
1117        cx.assert(
1118            indoc! {"
1119                The quick
1120                |
1121                brown fox"},
1122            indoc! {"
1123                The quick
1124                |
1125                brown fox"},
1126        );
1127    }
1128
1129    #[gpui::test]
1130    async fn test_p(cx: &mut gpui::TestAppContext) {
1131        let mut cx = VimTestContext::new(cx, true).await;
1132        cx.set_state(
1133            indoc! {"
1134            The quick brown
1135            fox ju|mps over
1136            the lazy dog"},
1137            Mode::Normal,
1138        );
1139
1140        cx.simulate_keystrokes(["d", "d"]);
1141        cx.assert_editor_state(indoc! {"
1142            The quick brown
1143            the la|zy dog"});
1144
1145        cx.simulate_keystroke("p");
1146        cx.assert_editor_state(indoc! {"
1147            The quick brown
1148            the lazy dog
1149            |fox jumps over"});
1150    }
1151}