normal.rs

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