normal.rs

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