normal.rs

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