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