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
 197// Supports non empty selections so it can be bound and called from visual mode
 198fn paste(_: &mut Workspace, _: &Paste, cx: &mut ViewContext<Workspace>) {
 199    Vim::update(cx, |vim, cx| {
 200        vim.update_active_editor(cx, |editor, cx| {
 201            editor.transact(cx, |editor, cx| {
 202                if let Some(item) = cx.as_mut().read_from_clipboard() {
 203                    let mut clipboard_text = Cow::Borrowed(item.text());
 204                    if let Some(mut clipboard_selections) =
 205                        item.metadata::<Vec<ClipboardSelection>>()
 206                    {
 207                        let (display_map, selections) = editor.selections.all_display(cx);
 208                        let all_selections_were_entire_line =
 209                            clipboard_selections.iter().all(|s| s.is_entire_line);
 210                        if clipboard_selections.len() != selections.len() {
 211                            let mut newline_separated_text = String::new();
 212                            let mut clipboard_selections =
 213                                clipboard_selections.drain(..).peekable();
 214                            let mut ix = 0;
 215                            while let Some(clipboard_selection) = clipboard_selections.next() {
 216                                newline_separated_text
 217                                    .push_str(&clipboard_text[ix..ix + clipboard_selection.len]);
 218                                ix += clipboard_selection.len;
 219                                if clipboard_selections.peek().is_some() {
 220                                    newline_separated_text.push('\n');
 221                                }
 222                            }
 223                            clipboard_text = Cow::Owned(newline_separated_text);
 224                        }
 225
 226                        let mut new_selections = Vec::new();
 227                        editor.buffer().update(cx, |buffer, cx| {
 228                            let snapshot = buffer.snapshot(cx);
 229                            let mut start_offset = 0;
 230                            let mut edits = Vec::new();
 231                            for (ix, selection) in selections.iter().enumerate() {
 232                                let to_insert;
 233                                let linewise;
 234                                if let Some(clipboard_selection) = clipboard_selections.get(ix) {
 235                                    let end_offset = start_offset + clipboard_selection.len;
 236                                    to_insert = &clipboard_text[start_offset..end_offset];
 237                                    linewise = clipboard_selection.is_entire_line;
 238                                    start_offset = end_offset;
 239                                } else {
 240                                    to_insert = clipboard_text.as_str();
 241                                    linewise = all_selections_were_entire_line;
 242                                }
 243
 244                                // If the clipboard text was copied linewise, and the current selection
 245                                // is empty, then paste the text after this line and move the selection
 246                                // to the start of the pasted text
 247                                let range = if selection.is_empty() && linewise {
 248                                    let (point, _) = display_map
 249                                        .next_line_boundary(selection.start.to_point(&display_map));
 250
 251                                    if !to_insert.starts_with('\n') {
 252                                        // Add newline before pasted text so that it shows up
 253                                        edits.push((point..point, "\n"));
 254                                    }
 255                                    // Drop selection at the start of the next line
 256                                    let selection_point = Point::new(point.row + 1, 0);
 257                                    new_selections.push(selection.map(|_| selection_point.clone()));
 258                                    point..point
 259                                } else {
 260                                    let mut selection = selection.clone();
 261                                    if !selection.reversed {
 262                                        let mut adjusted = selection.end;
 263                                        // Head is at the end of the selection. Adjust the end position to
 264                                        // to include the character under the cursor.
 265                                        *adjusted.column_mut() = adjusted.column() + 1;
 266                                        adjusted = display_map.clip_point(adjusted, Bias::Right);
 267                                        // If the selection is empty, move both the start and end forward one
 268                                        // character
 269                                        if selection.is_empty() {
 270                                            selection.start = adjusted;
 271                                            selection.end = adjusted;
 272                                        } else {
 273                                            selection.end = adjusted;
 274                                        }
 275                                    }
 276
 277                                    let range = selection.map(|p| p.to_point(&display_map)).range();
 278                                    new_selections.push(selection.map(|_| range.start.clone()));
 279                                    range
 280                                };
 281
 282                                if linewise && to_insert.ends_with('\n') {
 283                                    edits.push((
 284                                        range,
 285                                        &to_insert[0..to_insert.len().saturating_sub(1)],
 286                                    ))
 287                                } else {
 288                                    edits.push((range, to_insert));
 289                                }
 290                            }
 291                            drop(snapshot);
 292                            buffer.edit_with_autoindent(edits, cx);
 293                        });
 294
 295                        editor.change_selections(Some(Autoscroll::Fit), cx, |s| {
 296                            s.select(new_selections)
 297                        });
 298                    } else {
 299                        editor.insert(&clipboard_text, cx);
 300                    }
 301                }
 302            });
 303        });
 304    });
 305}
 306
 307#[cfg(test)]
 308mod test {
 309    use indoc::indoc;
 310    use language::Selection;
 311    use util::test::marked_text;
 312
 313    use crate::{
 314        state::{
 315            Mode::{self, *},
 316            Namespace, Operator,
 317        },
 318        vim_test_context::VimTestContext,
 319    };
 320
 321    #[gpui::test]
 322    async fn test_h(cx: &mut gpui::TestAppContext) {
 323        let cx = VimTestContext::new(cx, true).await;
 324        let mut cx = cx.binding(["h"]);
 325        cx.assert("The q|uick", "The |quick");
 326        cx.assert("|The quick", "|The quick");
 327        cx.assert(
 328            indoc! {"
 329                The quick
 330                |brown"},
 331            indoc! {"
 332                The quick
 333                |brown"},
 334        );
 335    }
 336
 337    #[gpui::test]
 338    async fn test_backspace(cx: &mut gpui::TestAppContext) {
 339        let cx = VimTestContext::new(cx, true).await;
 340        let mut cx = cx.binding(["backspace"]);
 341        cx.assert("The q|uick", "The |quick");
 342        cx.assert("|The quick", "|The quick");
 343        cx.assert(
 344            indoc! {"
 345                The quick
 346                |brown"},
 347            indoc! {"
 348                The quick
 349                |brown"},
 350        );
 351    }
 352
 353    #[gpui::test]
 354    async fn test_j(cx: &mut gpui::TestAppContext) {
 355        let cx = VimTestContext::new(cx, true).await;
 356        let mut cx = cx.binding(["j"]);
 357        cx.assert(
 358            indoc! {"
 359                The |quick
 360                brown fox"},
 361            indoc! {"
 362                The quick
 363                brow|n fox"},
 364        );
 365        cx.assert(
 366            indoc! {"
 367                The quick
 368                brow|n fox"},
 369            indoc! {"
 370                The quick
 371                brow|n fox"},
 372        );
 373        cx.assert(
 374            indoc! {"
 375                The quic|k
 376                brown"},
 377            indoc! {"
 378                The quick
 379                brow|n"},
 380        );
 381        cx.assert(
 382            indoc! {"
 383                The quick
 384                |brown"},
 385            indoc! {"
 386                The quick
 387                |brown"},
 388        );
 389    }
 390
 391    #[gpui::test]
 392    async fn test_k(cx: &mut gpui::TestAppContext) {
 393        let cx = VimTestContext::new(cx, true).await;
 394        let mut cx = cx.binding(["k"]);
 395        cx.assert(
 396            indoc! {"
 397                The |quick
 398                brown fox"},
 399            indoc! {"
 400                The |quick
 401                brown fox"},
 402        );
 403        cx.assert(
 404            indoc! {"
 405                The quick
 406                brow|n fox"},
 407            indoc! {"
 408                The |quick
 409                brown fox"},
 410        );
 411        cx.assert(
 412            indoc! {"
 413                The
 414                quic|k"},
 415            indoc! {"
 416                Th|e
 417                quick"},
 418        );
 419    }
 420
 421    #[gpui::test]
 422    async fn test_l(cx: &mut gpui::TestAppContext) {
 423        let cx = VimTestContext::new(cx, true).await;
 424        let mut cx = cx.binding(["l"]);
 425        cx.assert("The q|uick", "The qu|ick");
 426        cx.assert("The quic|k", "The quic|k");
 427        cx.assert(
 428            indoc! {"
 429                The quic|k
 430                brown"},
 431            indoc! {"
 432                The quic|k
 433                brown"},
 434        );
 435    }
 436
 437    #[gpui::test]
 438    async fn test_jump_to_line_boundaries(cx: &mut gpui::TestAppContext) {
 439        let cx = VimTestContext::new(cx, true).await;
 440        let mut cx = cx.binding(["shift-$"]);
 441        cx.assert("T|est test", "Test tes|t");
 442        cx.assert("Test tes|t", "Test tes|t");
 443        cx.assert(
 444            indoc! {"
 445                The |quick
 446                brown"},
 447            indoc! {"
 448                The quic|k
 449                brown"},
 450        );
 451        cx.assert(
 452            indoc! {"
 453                The quic|k
 454                brown"},
 455            indoc! {"
 456                The quic|k
 457                brown"},
 458        );
 459
 460        let mut cx = cx.binding(["0"]);
 461        cx.assert("Test |test", "|Test test");
 462        cx.assert("|Test test", "|Test test");
 463        cx.assert(
 464            indoc! {"
 465                The |quick
 466                brown"},
 467            indoc! {"
 468                |The quick
 469                brown"},
 470        );
 471        cx.assert(
 472            indoc! {"
 473                |The quick
 474                brown"},
 475            indoc! {"
 476                |The quick
 477                brown"},
 478        );
 479    }
 480
 481    #[gpui::test]
 482    async fn test_jump_to_end(cx: &mut gpui::TestAppContext) {
 483        let cx = VimTestContext::new(cx, true).await;
 484        let mut cx = cx.binding(["shift-G"]);
 485
 486        cx.assert(
 487            indoc! {"
 488                The |quick
 489                
 490                brown fox jumps
 491                over the lazy dog"},
 492            indoc! {"
 493                The quick
 494                
 495                brown fox jumps
 496                over| the lazy dog"},
 497        );
 498        cx.assert(
 499            indoc! {"
 500                The quick
 501                
 502                brown fox jumps
 503                over| the lazy dog"},
 504            indoc! {"
 505                The quick
 506                
 507                brown fox jumps
 508                over| the lazy dog"},
 509        );
 510        cx.assert(
 511            indoc! {"
 512            The qui|ck
 513            
 514            brown"},
 515            indoc! {"
 516            The quick
 517            
 518            brow|n"},
 519        );
 520        cx.assert(
 521            indoc! {"
 522            The qui|ck
 523            
 524            "},
 525            indoc! {"
 526            The quick
 527            
 528            |"},
 529        );
 530    }
 531
 532    #[gpui::test]
 533    async fn test_w(cx: &mut gpui::TestAppContext) {
 534        let mut cx = VimTestContext::new(cx, true).await;
 535        let (_, cursor_offsets) = marked_text(indoc! {"
 536            The |quick|-|brown
 537            |
 538            |
 539            |fox_jumps |over
 540            |th||e"});
 541        cx.set_state(
 542            indoc! {"
 543            |The quick-brown
 544            
 545            
 546            fox_jumps over
 547            the"},
 548            Mode::Normal,
 549        );
 550
 551        for cursor_offset in cursor_offsets {
 552            cx.simulate_keystroke("w");
 553            cx.assert_editor_selections(vec![Selection::from_offset(cursor_offset)]);
 554        }
 555
 556        // Reset and test ignoring punctuation
 557        let (_, cursor_offsets) = marked_text(indoc! {"
 558            The |quick-brown
 559            |
 560            |
 561            |fox_jumps |over
 562            |th||e"});
 563        cx.set_state(
 564            indoc! {"
 565            |The quick-brown
 566            
 567            
 568            fox_jumps over
 569            the"},
 570            Mode::Normal,
 571        );
 572
 573        for cursor_offset in cursor_offsets {
 574            cx.simulate_keystroke("shift-W");
 575            cx.assert_editor_selections(vec![Selection::from_offset(cursor_offset)]);
 576        }
 577    }
 578
 579    #[gpui::test]
 580    async fn test_e(cx: &mut gpui::TestAppContext) {
 581        let mut cx = VimTestContext::new(cx, true).await;
 582        let (_, cursor_offsets) = marked_text(indoc! {"
 583            Th|e quic|k|-brow|n
 584            
 585            
 586            fox_jump|s ove|r
 587            th|e"});
 588        cx.set_state(
 589            indoc! {"
 590            |The quick-brown
 591            
 592            
 593            fox_jumps over
 594            the"},
 595            Mode::Normal,
 596        );
 597
 598        for cursor_offset in cursor_offsets {
 599            cx.simulate_keystroke("e");
 600            cx.assert_editor_selections(vec![Selection::from_offset(cursor_offset)]);
 601        }
 602
 603        // Reset and test ignoring punctuation
 604        let (_, cursor_offsets) = marked_text(indoc! {"
 605            Th|e quick-brow|n
 606            
 607            
 608            fox_jump|s ove|r
 609            th||e"});
 610        cx.set_state(
 611            indoc! {"
 612            |The quick-brown
 613            
 614            
 615            fox_jumps over
 616            the"},
 617            Mode::Normal,
 618        );
 619        for cursor_offset in cursor_offsets {
 620            cx.simulate_keystroke("shift-E");
 621            cx.assert_editor_selections(vec![Selection::from_offset(cursor_offset)]);
 622        }
 623    }
 624
 625    #[gpui::test]
 626    async fn test_b(cx: &mut gpui::TestAppContext) {
 627        let mut cx = VimTestContext::new(cx, true).await;
 628        let (_, cursor_offsets) = marked_text(indoc! {"
 629            ||The |quick|-|brown
 630            |
 631            |
 632            |fox_jumps |over
 633            |the"});
 634        cx.set_state(
 635            indoc! {"
 636            The quick-brown
 637            
 638            
 639            fox_jumps over
 640            th|e"},
 641            Mode::Normal,
 642        );
 643
 644        for cursor_offset in cursor_offsets.into_iter().rev() {
 645            cx.simulate_keystroke("b");
 646            cx.assert_editor_selections(vec![Selection::from_offset(cursor_offset)]);
 647        }
 648
 649        // Reset and test ignoring punctuation
 650        let (_, cursor_offsets) = marked_text(indoc! {"
 651            ||The |quick-brown
 652            |
 653            |
 654            |fox_jumps |over
 655            |the"});
 656        cx.set_state(
 657            indoc! {"
 658            The quick-brown
 659            
 660            
 661            fox_jumps over
 662            th|e"},
 663            Mode::Normal,
 664        );
 665        for cursor_offset in cursor_offsets.into_iter().rev() {
 666            cx.simulate_keystroke("shift-B");
 667            cx.assert_editor_selections(vec![Selection::from_offset(cursor_offset)]);
 668        }
 669    }
 670
 671    #[gpui::test]
 672    async fn test_g_prefix_and_abort(cx: &mut gpui::TestAppContext) {
 673        let mut cx = VimTestContext::new(cx, true).await;
 674
 675        // Can abort with escape to get back to normal mode
 676        cx.simulate_keystroke("g");
 677        assert_eq!(cx.mode(), Normal);
 678        assert_eq!(
 679            cx.active_operator(),
 680            Some(Operator::Namespace(Namespace::G))
 681        );
 682        cx.simulate_keystroke("escape");
 683        assert_eq!(cx.mode(), Normal);
 684        assert_eq!(cx.active_operator(), None);
 685    }
 686
 687    #[gpui::test]
 688    async fn test_gg(cx: &mut gpui::TestAppContext) {
 689        let cx = VimTestContext::new(cx, true).await;
 690        let mut cx = cx.binding(["g", "g"]);
 691        cx.assert(
 692            indoc! {"
 693                The quick
 694            
 695                brown fox jumps
 696                over |the lazy dog"},
 697            indoc! {"
 698                The q|uick
 699            
 700                brown fox jumps
 701                over the lazy dog"},
 702        );
 703        cx.assert(
 704            indoc! {"
 705                The q|uick
 706            
 707                brown fox jumps
 708                over the lazy dog"},
 709            indoc! {"
 710                The q|uick
 711            
 712                brown fox jumps
 713                over the lazy dog"},
 714        );
 715        cx.assert(
 716            indoc! {"
 717                The quick
 718            
 719                brown fox jumps
 720                over the la|zy dog"},
 721            indoc! {"
 722                The quic|k
 723            
 724                brown fox jumps
 725                over the lazy dog"},
 726        );
 727        cx.assert(
 728            indoc! {"
 729                
 730            
 731                brown fox jumps
 732                over the la|zy dog"},
 733            indoc! {"
 734                |
 735            
 736                brown fox jumps
 737                over the lazy dog"},
 738        );
 739    }
 740
 741    #[gpui::test]
 742    async fn test_a(cx: &mut gpui::TestAppContext) {
 743        let cx = VimTestContext::new(cx, true).await;
 744        let mut cx = cx.binding(["a"]).mode_after(Mode::Insert);
 745
 746        cx.assert("The q|uick", "The qu|ick");
 747        cx.assert("The quic|k", "The quick|");
 748    }
 749
 750    #[gpui::test]
 751    async fn test_insert_end_of_line(cx: &mut gpui::TestAppContext) {
 752        let cx = VimTestContext::new(cx, true).await;
 753        let mut cx = cx.binding(["shift-A"]).mode_after(Mode::Insert);
 754        cx.assert("The q|uick", "The quick|");
 755        cx.assert("The q|uick ", "The quick |");
 756        cx.assert("|", "|");
 757        cx.assert(
 758            indoc! {"
 759                The q|uick
 760                brown fox"},
 761            indoc! {"
 762                The quick|
 763                brown fox"},
 764        );
 765        cx.assert(
 766            indoc! {"
 767                |
 768                The quick"},
 769            indoc! {"
 770                |
 771                The quick"},
 772        );
 773    }
 774
 775    #[gpui::test]
 776    async fn test_jump_to_first_non_whitespace(cx: &mut gpui::TestAppContext) {
 777        let cx = VimTestContext::new(cx, true).await;
 778        let mut cx = cx.binding(["shift-^"]);
 779        cx.assert("The q|uick", "|The quick");
 780        cx.assert(" The q|uick", " |The quick");
 781        cx.assert("|", "|");
 782        cx.assert(
 783            indoc! {"
 784                The q|uick
 785                brown fox"},
 786            indoc! {"
 787                |The quick
 788                brown fox"},
 789        );
 790        cx.assert(
 791            indoc! {"
 792                |
 793                The quick"},
 794            indoc! {"
 795                |
 796                The quick"},
 797        );
 798        // Indoc disallows trailing whitspace.
 799        cx.assert("   | \nThe quick", "   | \nThe quick");
 800    }
 801
 802    #[gpui::test]
 803    async fn test_insert_first_non_whitespace(cx: &mut gpui::TestAppContext) {
 804        let cx = VimTestContext::new(cx, true).await;
 805        let mut cx = cx.binding(["shift-I"]).mode_after(Mode::Insert);
 806        cx.assert("The q|uick", "|The quick");
 807        cx.assert(" The q|uick", " |The quick");
 808        cx.assert("|", "|");
 809        cx.assert(
 810            indoc! {"
 811                The q|uick
 812                brown fox"},
 813            indoc! {"
 814                |The quick
 815                brown fox"},
 816        );
 817        cx.assert(
 818            indoc! {"
 819                |
 820                The quick"},
 821            indoc! {"
 822                |
 823                The quick"},
 824        );
 825    }
 826
 827    #[gpui::test]
 828    async fn test_delete_to_end_of_line(cx: &mut gpui::TestAppContext) {
 829        let cx = VimTestContext::new(cx, true).await;
 830        let mut cx = cx.binding(["shift-D"]);
 831        cx.assert(
 832            indoc! {"
 833                The q|uick
 834                brown fox"},
 835            indoc! {"
 836                The |q
 837                brown fox"},
 838        );
 839        cx.assert(
 840            indoc! {"
 841                The quick
 842                |
 843                brown fox"},
 844            indoc! {"
 845                The quick
 846                |
 847                brown fox"},
 848        );
 849    }
 850
 851    #[gpui::test]
 852    async fn test_x(cx: &mut gpui::TestAppContext) {
 853        let cx = VimTestContext::new(cx, true).await;
 854        let mut cx = cx.binding(["x"]);
 855        cx.assert("|Test", "|est");
 856        cx.assert("Te|st", "Te|t");
 857        cx.assert("Tes|t", "Te|s");
 858        cx.assert(
 859            indoc! {"
 860                Tes|t
 861                test"},
 862            indoc! {"
 863                Te|s
 864                test"},
 865        );
 866    }
 867
 868    #[gpui::test]
 869    async fn test_delete_left(cx: &mut gpui::TestAppContext) {
 870        let cx = VimTestContext::new(cx, true).await;
 871        let mut cx = cx.binding(["shift-X"]);
 872        cx.assert("Te|st", "T|st");
 873        cx.assert("T|est", "|est");
 874        cx.assert("|Test", "|Test");
 875        cx.assert(
 876            indoc! {"
 877                Test
 878                |test"},
 879            indoc! {"
 880                Test
 881                |test"},
 882        );
 883    }
 884
 885    #[gpui::test]
 886    async fn test_o(cx: &mut gpui::TestAppContext) {
 887        let cx = VimTestContext::new(cx, true).await;
 888        let mut cx = cx.binding(["o"]).mode_after(Mode::Insert);
 889
 890        cx.assert(
 891            "|",
 892            indoc! {"
 893                
 894                |"},
 895        );
 896        cx.assert(
 897            "The |quick",
 898            indoc! {"
 899                The quick
 900                |"},
 901        );
 902        cx.assert(
 903            indoc! {"
 904                The quick
 905                brown |fox
 906                jumps over"},
 907            indoc! {"
 908                The quick
 909                brown fox
 910                |
 911                jumps over"},
 912        );
 913        cx.assert(
 914            indoc! {"
 915                The quick
 916                brown fox
 917                jumps |over"},
 918            indoc! {"
 919                The quick
 920                brown fox
 921                jumps over
 922                |"},
 923        );
 924        cx.assert(
 925            indoc! {"
 926                The q|uick
 927                brown fox
 928                jumps over"},
 929            indoc! {"
 930                The quick
 931                |
 932                brown fox
 933                jumps over"},
 934        );
 935        cx.assert(
 936            indoc! {"
 937                The quick
 938                |
 939                brown fox"},
 940            indoc! {"
 941                The quick
 942                
 943                |
 944                brown fox"},
 945        );
 946        cx.assert(
 947            indoc! {"
 948                fn test()
 949                    println!(|);"},
 950            indoc! {"
 951                fn test()
 952                    println!();
 953                    |"},
 954        );
 955        cx.assert(
 956            indoc! {"
 957                fn test(|)
 958                    println!();"},
 959            indoc! {"
 960                fn test()
 961                |
 962                    println!();"},
 963        );
 964    }
 965
 966    #[gpui::test]
 967    async fn test_insert_line_above(cx: &mut gpui::TestAppContext) {
 968        let cx = VimTestContext::new(cx, true).await;
 969        let mut cx = cx.binding(["shift-O"]).mode_after(Mode::Insert);
 970
 971        cx.assert(
 972            "|",
 973            indoc! {"
 974                |
 975                "},
 976        );
 977        cx.assert(
 978            "The |quick",
 979            indoc! {"
 980                |
 981                The quick"},
 982        );
 983        cx.assert(
 984            indoc! {"
 985                The quick
 986                brown |fox
 987                jumps over"},
 988            indoc! {"
 989                The quick
 990                |
 991                brown fox
 992                jumps over"},
 993        );
 994        cx.assert(
 995            indoc! {"
 996                The quick
 997                brown fox
 998                jumps |over"},
 999            indoc! {"
1000                The quick
1001                brown fox
1002                |
1003                jumps over"},
1004        );
1005        cx.assert(
1006            indoc! {"
1007                The q|uick
1008                brown fox
1009                jumps over"},
1010            indoc! {"
1011                |
1012                The quick
1013                brown fox
1014                jumps over"},
1015        );
1016        cx.assert(
1017            indoc! {"
1018                The quick
1019                |
1020                brown fox"},
1021            indoc! {"
1022                The quick
1023                |
1024                
1025                brown fox"},
1026        );
1027        cx.assert(
1028            indoc! {"
1029                fn test()
1030                    println!(|);"},
1031            indoc! {"
1032                fn test()
1033                    |
1034                    println!();"},
1035        );
1036        cx.assert(
1037            indoc! {"
1038                fn test(|)
1039                    println!();"},
1040            indoc! {"
1041                |
1042                fn test()
1043                    println!();"},
1044        );
1045    }
1046
1047    #[gpui::test]
1048    async fn test_dd(cx: &mut gpui::TestAppContext) {
1049        let cx = VimTestContext::new(cx, true).await;
1050        let mut cx = cx.binding(["d", "d"]);
1051
1052        cx.assert("|", "|");
1053        cx.assert("The |quick", "|");
1054        cx.assert(
1055            indoc! {"
1056                The quick
1057                brown |fox
1058                jumps over"},
1059            indoc! {"
1060                The quick
1061                jumps |over"},
1062        );
1063        cx.assert(
1064            indoc! {"
1065                The quick
1066                brown fox
1067                jumps |over"},
1068            indoc! {"
1069                The quick
1070                brown |fox"},
1071        );
1072        cx.assert(
1073            indoc! {"
1074                The q|uick
1075                brown fox
1076                jumps over"},
1077            indoc! {"
1078                brown| fox
1079                jumps over"},
1080        );
1081        cx.assert(
1082            indoc! {"
1083                The quick
1084                |
1085                brown fox"},
1086            indoc! {"
1087                The quick
1088                |brown fox"},
1089        );
1090    }
1091
1092    #[gpui::test]
1093    async fn test_cc(cx: &mut gpui::TestAppContext) {
1094        let cx = VimTestContext::new(cx, true).await;
1095        let mut cx = cx.binding(["c", "c"]).mode_after(Mode::Insert);
1096
1097        cx.assert("|", "|");
1098        cx.assert("The |quick", "|");
1099        cx.assert(
1100            indoc! {"
1101                The quick
1102                brown |fox
1103                jumps over"},
1104            indoc! {"
1105                The quick
1106                |
1107                jumps over"},
1108        );
1109        cx.assert(
1110            indoc! {"
1111                The quick
1112                brown fox
1113                jumps |over"},
1114            indoc! {"
1115                The quick
1116                brown fox
1117                |"},
1118        );
1119        cx.assert(
1120            indoc! {"
1121                The q|uick
1122                brown fox
1123                jumps over"},
1124            indoc! {"
1125                |
1126                brown fox
1127                jumps over"},
1128        );
1129        cx.assert(
1130            indoc! {"
1131                The quick
1132                |
1133                brown fox"},
1134            indoc! {"
1135                The quick
1136                |
1137                brown fox"},
1138        );
1139    }
1140
1141    #[gpui::test]
1142    async fn test_p(cx: &mut gpui::TestAppContext) {
1143        let mut cx = VimTestContext::new(cx, true).await;
1144        cx.set_state(
1145            indoc! {"
1146                The quick brown
1147                fox ju|mps over
1148                the lazy dog"},
1149            Mode::Normal,
1150        );
1151
1152        cx.simulate_keystrokes(["d", "d"]);
1153        cx.assert_editor_state(indoc! {"
1154            The quick brown
1155            the la|zy dog"});
1156
1157        cx.simulate_keystroke("p");
1158        cx.assert_editor_state(indoc! {"
1159            The quick brown
1160            the lazy dog
1161            |fox jumps over"});
1162
1163        cx.set_state(
1164            indoc! {"
1165                The quick brown
1166                fox [jump}s over
1167                the lazy dog"},
1168            Mode::Normal,
1169        );
1170        cx.simulate_keystroke("y");
1171        cx.set_state(
1172            indoc! {"
1173                The quick brown
1174                fox jump|s over
1175                the lazy dog"},
1176            Mode::Normal,
1177        );
1178        cx.simulate_keystroke("p");
1179        cx.assert_editor_state(indoc! {"
1180            The quick brown
1181            fox jumps|jumps over
1182            the lazy dog"});
1183    }
1184}