normal.rs

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