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