1use editor::{DisplayPoint, RowExt, SelectionEffects, display_map::ToDisplayPoint, movement};
   2use gpui::{Action, Context, Window};
   3use language::{Bias, SelectionGoal};
   4use schemars::JsonSchema;
   5use serde::Deserialize;
   6use settings::Settings;
   7use std::cmp;
   8use vim_mode_setting::HelixModeSetting;
   9
  10use crate::{
  11    Vim,
  12    motion::{Motion, MotionKind},
  13    object::Object,
  14    state::{Mode, ObjectScope, Register},
  15};
  16
  17/// Pastes text from the specified register at the cursor position.
  18#[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)]
  19#[action(namespace = vim)]
  20#[serde(deny_unknown_fields)]
  21pub struct Paste {
  22    #[serde(default)]
  23    before: bool,
  24    #[serde(default)]
  25    preserve_clipboard: bool,
  26}
  27
  28impl Vim {
  29    pub fn paste(&mut self, action: &Paste, window: &mut Window, cx: &mut Context<Self>) {
  30        self.record_current_action(cx);
  31        self.store_visual_marks(window, cx);
  32        let count = Vim::take_count(cx).unwrap_or(1);
  33        Vim::take_forced_motion(cx);
  34
  35        self.update_editor(cx, |vim, editor, cx| {
  36            let text_layout_details = editor.text_layout_details(window);
  37            editor.transact(window, cx, |editor, window, cx| {
  38                editor.set_clip_at_line_ends(false, cx);
  39
  40                let selected_register = vim.selected_register.take();
  41
  42                let Some(Register {
  43                    text,
  44                    clipboard_selections,
  45                }) = Vim::update_globals(cx, |globals, cx| {
  46                    globals.read_register(selected_register, Some(editor), cx)
  47                })
  48                .filter(|reg| !reg.text.is_empty())
  49                else {
  50                    return;
  51                };
  52                let clipboard_selections = clipboard_selections
  53                    .filter(|sel| sel.len() > 1 && vim.mode != Mode::VisualLine);
  54
  55                if !action.preserve_clipboard && vim.mode.is_visual() {
  56                    vim.copy_selections_content(editor, MotionKind::for_mode(vim.mode), window, cx);
  57                }
  58
  59                let display_map = editor.display_snapshot(cx);
  60                let current_selections = editor.selections.all_adjusted_display(&display_map);
  61
  62                // unlike zed, if you have a multi-cursor selection from vim block mode,
  63                // pasting it will paste it on subsequent lines, even if you don't yet
  64                // have a cursor there.
  65                let mut selections_to_process = Vec::new();
  66                let mut i = 0;
  67                while i < current_selections.len() {
  68                    selections_to_process
  69                        .push((current_selections[i].start..current_selections[i].end, true));
  70                    i += 1;
  71                }
  72                if let Some(clipboard_selections) = clipboard_selections.as_ref() {
  73                    let left = current_selections
  74                        .iter()
  75                        .map(|selection| cmp::min(selection.start.column(), selection.end.column()))
  76                        .min()
  77                        .unwrap();
  78                    let mut row = current_selections.last().unwrap().end.row().next_row();
  79                    while i < clipboard_selections.len() {
  80                        let cursor =
  81                            display_map.clip_point(DisplayPoint::new(row, left), Bias::Left);
  82                        selections_to_process.push((cursor..cursor, false));
  83                        i += 1;
  84                        row.0 += 1;
  85                    }
  86                }
  87
  88                let first_selection_indent_column =
  89                    clipboard_selections.as_ref().and_then(|zed_selections| {
  90                        zed_selections
  91                            .first()
  92                            .map(|selection| selection.first_line_indent)
  93                    });
  94                let before = action.before || vim.mode == Mode::VisualLine;
  95
  96                let mut edits = Vec::new();
  97                let mut new_selections = Vec::new();
  98                let mut original_indent_columns = Vec::new();
  99                let mut start_offset = 0;
 100
 101                for (ix, (selection, preserve)) in selections_to_process.iter().enumerate() {
 102                    let (mut to_insert, original_indent_column) =
 103                        if let Some(clipboard_selections) = &clipboard_selections {
 104                            if let Some(clipboard_selection) = clipboard_selections.get(ix) {
 105                                let end_offset = start_offset + clipboard_selection.len;
 106                                let text = text[start_offset..end_offset].to_string();
 107                                start_offset = end_offset + 1;
 108                                (text, Some(clipboard_selection.first_line_indent))
 109                            } else {
 110                                ("".to_string(), first_selection_indent_column)
 111                            }
 112                        } else {
 113                            (text.to_string(), first_selection_indent_column)
 114                        };
 115                    let line_mode = to_insert.ends_with('\n');
 116                    let is_multiline = to_insert.contains('\n');
 117
 118                    if line_mode && !before {
 119                        if selection.is_empty() {
 120                            to_insert =
 121                                "\n".to_owned() + &to_insert[..to_insert.len() - "\n".len()];
 122                        } else {
 123                            to_insert = "\n".to_owned() + &to_insert;
 124                        }
 125                    } else if line_mode && vim.mode == Mode::VisualLine {
 126                        to_insert.pop();
 127                    }
 128
 129                    let display_range = if !selection.is_empty() {
 130                        // If vim is in VISUAL LINE mode and the column for the
 131                        // selection's end point is 0, that means that the
 132                        // cursor is at the newline character (\n) at the end of
 133                        // the line. In this situation we'll want to move one
 134                        // position to the left, ensuring we don't join the last
 135                        // line of the selection with the line directly below.
 136                        let end_point =
 137                            if vim.mode == Mode::VisualLine && selection.end.column() == 0 {
 138                                movement::left(&display_map, selection.end)
 139                            } else {
 140                                selection.end
 141                            };
 142
 143                        selection.start..end_point
 144                    } else if line_mode {
 145                        let point = if before {
 146                            movement::line_beginning(&display_map, selection.start, false)
 147                        } else {
 148                            movement::line_end(&display_map, selection.start, false)
 149                        };
 150                        point..point
 151                    } else {
 152                        let point = if before {
 153                            selection.start
 154                        } else {
 155                            movement::saturating_right(&display_map, selection.start)
 156                        };
 157                        point..point
 158                    };
 159
 160                    let point_range = display_range.start.to_point(&display_map)
 161                        ..display_range.end.to_point(&display_map);
 162                    let anchor = if is_multiline || vim.mode == Mode::VisualLine {
 163                        display_map
 164                            .buffer_snapshot()
 165                            .anchor_before(point_range.start)
 166                    } else {
 167                        display_map.buffer_snapshot().anchor_after(point_range.end)
 168                    };
 169
 170                    if *preserve {
 171                        new_selections.push((anchor, line_mode, is_multiline));
 172                    }
 173                    edits.push((point_range, to_insert.repeat(count)));
 174                    original_indent_columns.push(original_indent_column);
 175                }
 176
 177                let cursor_offset = editor.selections.last::<usize>(&display_map).head();
 178                if editor
 179                    .buffer()
 180                    .read(cx)
 181                    .snapshot(cx)
 182                    .language_settings_at(cursor_offset, cx)
 183                    .auto_indent_on_paste
 184                {
 185                    editor.edit_with_block_indent(edits, original_indent_columns, cx);
 186                } else {
 187                    editor.edit(edits, cx);
 188                }
 189
 190                // in line_mode vim will insert the new text on the next (or previous if before) line
 191                // and put the cursor on the first non-blank character of the first inserted line (or at the end if the first line is blank).
 192                // otherwise vim will insert the next text at (or before) the current cursor position,
 193                // the cursor will go to the last (or first, if is_multiline) inserted character.
 194                editor.change_selections(Default::default(), window, cx, |s| {
 195                    s.replace_cursors_with(|map| {
 196                        let mut cursors = Vec::new();
 197                        for (anchor, line_mode, is_multiline) in &new_selections {
 198                            let mut cursor = anchor.to_display_point(map);
 199                            if *line_mode {
 200                                if !before {
 201                                    cursor = movement::down(
 202                                        map,
 203                                        cursor,
 204                                        SelectionGoal::None,
 205                                        false,
 206                                        &text_layout_details,
 207                                    )
 208                                    .0;
 209                                }
 210                                cursor = movement::indented_line_beginning(map, cursor, true, true);
 211                            } else if !is_multiline && !vim.temp_mode {
 212                                cursor = movement::saturating_left(map, cursor)
 213                            }
 214                            cursors.push(cursor);
 215                            if vim.mode == Mode::VisualBlock {
 216                                break;
 217                            }
 218                        }
 219
 220                        cursors
 221                    });
 222                })
 223            });
 224        });
 225
 226        if HelixModeSetting::get_global(cx).0 {
 227            self.switch_mode(Mode::HelixNormal, true, window, cx);
 228        } else {
 229            self.switch_mode(Mode::Normal, true, window, cx);
 230        }
 231    }
 232
 233    pub fn replace_with_register_object(
 234        &mut self,
 235        object: Object,
 236        scope: ObjectScope,
 237        window: &mut Window,
 238        cx: &mut Context<Self>,
 239    ) {
 240        self.stop_recording(cx);
 241        let selected_register = self.selected_register.take();
 242        self.update_editor(cx, |_, editor, cx| {
 243            editor.transact(window, cx, |editor, window, cx| {
 244                editor.set_clip_at_line_ends(false, cx);
 245                editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 246                    s.move_with(|map, selection| {
 247                        object.expand_selection(map, selection, &scope, None);
 248                    });
 249                });
 250
 251                let Some(Register { text, .. }) = Vim::update_globals(cx, |globals, cx| {
 252                    globals.read_register(selected_register, Some(editor), cx)
 253                })
 254                .filter(|reg| !reg.text.is_empty()) else {
 255                    return;
 256                };
 257                editor.insert(&text, window, cx);
 258                editor.set_clip_at_line_ends(true, cx);
 259                editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 260                    s.move_with(|map, selection| {
 261                        selection.start = map.clip_point(selection.start, Bias::Left);
 262                        selection.end = selection.start
 263                    })
 264                })
 265            });
 266        });
 267    }
 268
 269    pub fn replace_with_register_motion(
 270        &mut self,
 271        motion: Motion,
 272        times: Option<usize>,
 273        forced_motion: bool,
 274        window: &mut Window,
 275        cx: &mut Context<Self>,
 276    ) {
 277        self.stop_recording(cx);
 278        let selected_register = self.selected_register.take();
 279        self.update_editor(cx, |_, editor, cx| {
 280            let text_layout_details = editor.text_layout_details(window);
 281            editor.transact(window, cx, |editor, window, cx| {
 282                editor.set_clip_at_line_ends(false, cx);
 283                editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 284                    s.move_with(|map, selection| {
 285                        motion.expand_selection(
 286                            map,
 287                            selection,
 288                            times,
 289                            &text_layout_details,
 290                            forced_motion,
 291                        );
 292                    });
 293                });
 294
 295                let Some(Register { text, .. }) = Vim::update_globals(cx, |globals, cx| {
 296                    globals.read_register(selected_register, Some(editor), cx)
 297                })
 298                .filter(|reg| !reg.text.is_empty()) else {
 299                    return;
 300                };
 301                editor.insert(&text, window, cx);
 302                editor.set_clip_at_line_ends(true, cx);
 303                editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 304                    s.move_with(|map, selection| {
 305                        selection.start = map.clip_point(selection.start, Bias::Left);
 306                        selection.end = selection.start
 307                    })
 308                })
 309            });
 310        });
 311    }
 312}
 313
 314#[cfg(test)]
 315mod test {
 316    use crate::{
 317        state::{Mode, Register},
 318        test::{NeovimBackedTestContext, VimTestContext},
 319    };
 320    use gpui::ClipboardItem;
 321    use indoc::indoc;
 322    use language::{LanguageName, language_settings::LanguageSettingsContent};
 323    use settings::{SettingsStore, UseSystemClipboard};
 324
 325    #[gpui::test]
 326    async fn test_paste(cx: &mut gpui::TestAppContext) {
 327        let mut cx = NeovimBackedTestContext::new(cx).await;
 328
 329        // single line
 330        cx.set_shared_state(indoc! {"
 331            The quick brown
 332            fox ˇjumps over
 333            the lazy dog"})
 334            .await;
 335        cx.simulate_shared_keystrokes("v w y").await;
 336        cx.shared_clipboard().await.assert_eq("jumps o");
 337        cx.set_shared_state(indoc! {"
 338            The quick brown
 339            fox jumps oveˇr
 340            the lazy dog"})
 341            .await;
 342        cx.simulate_shared_keystrokes("p").await;
 343        cx.shared_state().await.assert_eq(indoc! {"
 344            The quick brown
 345            fox jumps overjumps ˇo
 346            the lazy dog"});
 347
 348        cx.set_shared_state(indoc! {"
 349            The quick brown
 350            fox jumps oveˇr
 351            the lazy dog"})
 352            .await;
 353        cx.simulate_shared_keystrokes("shift-p").await;
 354        cx.shared_state().await.assert_eq(indoc! {"
 355            The quick brown
 356            fox jumps ovejumps ˇor
 357            the lazy dog"});
 358
 359        // line mode
 360        cx.set_shared_state(indoc! {"
 361            The quick brown
 362            fox juˇmps over
 363            the lazy dog"})
 364            .await;
 365        cx.simulate_shared_keystrokes("d d").await;
 366        cx.shared_clipboard().await.assert_eq("fox jumps over\n");
 367        cx.shared_state().await.assert_eq(indoc! {"
 368            The quick brown
 369            the laˇzy dog"});
 370        cx.simulate_shared_keystrokes("p").await;
 371        cx.shared_state().await.assert_eq(indoc! {"
 372            The quick brown
 373            the lazy dog
 374            ˇfox jumps over"});
 375        cx.simulate_shared_keystrokes("k shift-p").await;
 376        cx.shared_state().await.assert_eq(indoc! {"
 377            The quick brown
 378            ˇfox jumps over
 379            the lazy dog
 380            fox jumps over"});
 381
 382        // multiline, cursor to first character of pasted text.
 383        cx.set_shared_state(indoc! {"
 384            The quick brown
 385            fox jumps ˇover
 386            the lazy dog"})
 387            .await;
 388        cx.simulate_shared_keystrokes("v j y").await;
 389        cx.shared_clipboard().await.assert_eq("over\nthe lazy do");
 390
 391        cx.simulate_shared_keystrokes("p").await;
 392        cx.shared_state().await.assert_eq(indoc! {"
 393            The quick brown
 394            fox jumps oˇover
 395            the lazy dover
 396            the lazy dog"});
 397        cx.simulate_shared_keystrokes("u shift-p").await;
 398        cx.shared_state().await.assert_eq(indoc! {"
 399            The quick brown
 400            fox jumps ˇover
 401            the lazy doover
 402            the lazy dog"});
 403    }
 404
 405    #[gpui::test]
 406    async fn test_yank_system_clipboard_never(cx: &mut gpui::TestAppContext) {
 407        let mut cx = VimTestContext::new(cx, true).await;
 408
 409        cx.update_global(|store: &mut SettingsStore, cx| {
 410            store.update_user_settings(cx, |s| {
 411                s.vim.get_or_insert_default().use_system_clipboard = Some(UseSystemClipboard::Never)
 412            });
 413        });
 414
 415        cx.set_state(
 416            indoc! {"
 417                The quick brown
 418                fox jˇumps over
 419                the lazy dog"},
 420            Mode::Normal,
 421        );
 422        cx.simulate_keystrokes("v i w y");
 423        cx.assert_state(
 424            indoc! {"
 425                The quick brown
 426                fox ˇjumps over
 427                the lazy dog"},
 428            Mode::Normal,
 429        );
 430        cx.simulate_keystrokes("p");
 431        cx.assert_state(
 432            indoc! {"
 433                The quick brown
 434                fox jjumpˇsumps over
 435                the lazy dog"},
 436            Mode::Normal,
 437        );
 438        assert_eq!(cx.read_from_clipboard(), None);
 439    }
 440
 441    #[gpui::test]
 442    async fn test_yank_system_clipboard_on_yank(cx: &mut gpui::TestAppContext) {
 443        let mut cx = VimTestContext::new(cx, true).await;
 444
 445        cx.update_global(|store: &mut SettingsStore, cx| {
 446            store.update_user_settings(cx, |s| {
 447                s.vim.get_or_insert_default().use_system_clipboard =
 448                    Some(UseSystemClipboard::OnYank)
 449            });
 450        });
 451
 452        // copy in visual mode
 453        cx.set_state(
 454            indoc! {"
 455                The quick brown
 456                fox jˇumps over
 457                the lazy dog"},
 458            Mode::Normal,
 459        );
 460        cx.simulate_keystrokes("v i w y");
 461        cx.assert_state(
 462            indoc! {"
 463                The quick brown
 464                fox ˇjumps over
 465                the lazy dog"},
 466            Mode::Normal,
 467        );
 468        cx.simulate_keystrokes("p");
 469        cx.assert_state(
 470            indoc! {"
 471                The quick brown
 472                fox jjumpˇsumps over
 473                the lazy dog"},
 474            Mode::Normal,
 475        );
 476        assert_eq!(
 477            cx.read_from_clipboard().map(|item| item.text().unwrap()),
 478            Some("jumps".into())
 479        );
 480        cx.simulate_keystrokes("d d p");
 481        cx.assert_state(
 482            indoc! {"
 483                The quick brown
 484                the lazy dog
 485                ˇfox jjumpsumps over"},
 486            Mode::Normal,
 487        );
 488        assert_eq!(
 489            cx.read_from_clipboard().map(|item| item.text().unwrap()),
 490            Some("jumps".into())
 491        );
 492        cx.write_to_clipboard(ClipboardItem::new_string("test-copy".to_string()));
 493        cx.simulate_keystrokes("shift-p");
 494        cx.assert_state(
 495            indoc! {"
 496                The quick brown
 497                the lazy dog
 498                test-copˇyfox jjumpsumps over"},
 499            Mode::Normal,
 500        );
 501    }
 502
 503    #[gpui::test]
 504    async fn test_paste_visual(cx: &mut gpui::TestAppContext) {
 505        let mut cx = NeovimBackedTestContext::new(cx).await;
 506
 507        // copy in visual mode
 508        cx.set_shared_state(indoc! {"
 509                The quick brown
 510                fox jˇumps over
 511                the lazy dog"})
 512            .await;
 513        cx.simulate_shared_keystrokes("v i w y").await;
 514        cx.shared_state().await.assert_eq(indoc! {"
 515                The quick brown
 516                fox ˇjumps over
 517                the lazy dog"});
 518        // paste in visual mode
 519        cx.simulate_shared_keystrokes("w v i w p").await;
 520        cx.shared_state().await.assert_eq(indoc! {"
 521                The quick brown
 522                fox jumps jumpˇs
 523                the lazy dog"});
 524        cx.shared_clipboard().await.assert_eq("over");
 525        // paste in visual line mode
 526        cx.simulate_shared_keystrokes("up shift-v shift-p").await;
 527        cx.shared_state().await.assert_eq(indoc! {"
 528            ˇover
 529            fox jumps jumps
 530            the lazy dog"});
 531        cx.shared_clipboard().await.assert_eq("over");
 532        // paste in visual block mode
 533        cx.simulate_shared_keystrokes("ctrl-v down down p").await;
 534        cx.shared_state().await.assert_eq(indoc! {"
 535            oveˇrver
 536            overox jumps jumps
 537            overhe lazy dog"});
 538
 539        // copy in visual line mode
 540        cx.set_shared_state(indoc! {"
 541                The quick brown
 542                fox juˇmps over
 543                the lazy dog"})
 544            .await;
 545        cx.simulate_shared_keystrokes("shift-v d").await;
 546        cx.shared_state().await.assert_eq(indoc! {"
 547                The quick brown
 548                the laˇzy dog"});
 549        // paste in visual mode
 550        cx.simulate_shared_keystrokes("v i w p").await;
 551        cx.shared_state().await.assert_eq(indoc! {"
 552                The quick brown
 553                the•
 554                ˇfox jumps over
 555                 dog"});
 556        cx.shared_clipboard().await.assert_eq("lazy");
 557        cx.set_shared_state(indoc! {"
 558            The quick brown
 559            fox juˇmps over
 560            the lazy dog"})
 561            .await;
 562        cx.simulate_shared_keystrokes("shift-v d").await;
 563        cx.shared_state().await.assert_eq(indoc! {"
 564            The quick brown
 565            the laˇzy dog"});
 566        cx.shared_clipboard().await.assert_eq("fox jumps over\n");
 567        // paste in visual line mode
 568        cx.simulate_shared_keystrokes("k shift-v p").await;
 569        cx.shared_state().await.assert_eq(indoc! {"
 570            ˇfox jumps over
 571            the lazy dog"});
 572        cx.shared_clipboard().await.assert_eq("The quick brown\n");
 573
 574        // Copy line and paste in visual mode, with cursor on newline character.
 575        cx.set_shared_state(indoc! {"
 576            ˇThe quick brown
 577            fox jumps over
 578            the lazy dog"})
 579            .await;
 580        cx.simulate_shared_keystrokes("y y shift-v j $ p").await;
 581        cx.shared_state().await.assert_eq(indoc! {"
 582            ˇThe quick brown
 583            the lazy dog"});
 584    }
 585
 586    #[gpui::test]
 587    async fn test_paste_visual_block(cx: &mut gpui::TestAppContext) {
 588        let mut cx = NeovimBackedTestContext::new(cx).await;
 589        // copy in visual block mode
 590        cx.set_shared_state(indoc! {"
 591            The ˇquick brown
 592            fox jumps over
 593            the lazy dog"})
 594            .await;
 595        cx.simulate_shared_keystrokes("ctrl-v 2 j y").await;
 596        cx.shared_clipboard().await.assert_eq("q\nj\nl");
 597        cx.simulate_shared_keystrokes("p").await;
 598        cx.shared_state().await.assert_eq(indoc! {"
 599            The qˇquick brown
 600            fox jjumps over
 601            the llazy dog"});
 602        cx.simulate_shared_keystrokes("v i w shift-p").await;
 603        cx.shared_state().await.assert_eq(indoc! {"
 604            The ˇq brown
 605            fox jjjumps over
 606            the lllazy dog"});
 607        cx.simulate_shared_keystrokes("v i w shift-p").await;
 608
 609        cx.set_shared_state(indoc! {"
 610            The ˇquick brown
 611            fox jumps over
 612            the lazy dog"})
 613            .await;
 614        cx.simulate_shared_keystrokes("ctrl-v j y").await;
 615        cx.shared_clipboard().await.assert_eq("q\nj");
 616        cx.simulate_shared_keystrokes("l ctrl-v 2 j shift-p").await;
 617        cx.shared_state().await.assert_eq(indoc! {"
 618            The qˇqick brown
 619            fox jjmps over
 620            the lzy dog"});
 621
 622        cx.simulate_shared_keystrokes("shift-v p").await;
 623        cx.shared_state().await.assert_eq(indoc! {"
 624            ˇq
 625            j
 626            fox jjmps over
 627            the lzy dog"});
 628    }
 629
 630    #[gpui::test]
 631    async fn test_paste_indent(cx: &mut gpui::TestAppContext) {
 632        let mut cx = VimTestContext::new_typescript(cx).await;
 633
 634        cx.set_state(
 635            indoc! {"
 636            class A {ˇ
 637            }
 638        "},
 639            Mode::Normal,
 640        );
 641        cx.simulate_keystrokes("o a ( ) { escape");
 642        cx.assert_state(
 643            indoc! {"
 644            class A {
 645                a()ˇ{}
 646            }
 647            "},
 648            Mode::Normal,
 649        );
 650        // cursor goes to the first non-blank character in the line;
 651        cx.simulate_keystrokes("y y p");
 652        cx.assert_state(
 653            indoc! {"
 654            class A {
 655                a(){}
 656                ˇa(){}
 657            }
 658            "},
 659            Mode::Normal,
 660        );
 661        // indentation is preserved when pasting
 662        cx.simulate_keystrokes("u shift-v up y shift-p");
 663        cx.assert_state(
 664            indoc! {"
 665                ˇclass A {
 666                    a(){}
 667                class A {
 668                    a(){}
 669                }
 670            "},
 671            Mode::Normal,
 672        );
 673    }
 674
 675    #[gpui::test]
 676    async fn test_paste_auto_indent(cx: &mut gpui::TestAppContext) {
 677        let mut cx = VimTestContext::new(cx, true).await;
 678
 679        cx.set_state(
 680            indoc! {"
 681            mod some_module {
 682                ˇfn main() {
 683                }
 684            }
 685            "},
 686            Mode::Normal,
 687        );
 688        // default auto indentation
 689        cx.simulate_keystrokes("y y p");
 690        cx.assert_state(
 691            indoc! {"
 692                mod some_module {
 693                    fn main() {
 694                        ˇfn main() {
 695                    }
 696                }
 697                "},
 698            Mode::Normal,
 699        );
 700        // back to previous state
 701        cx.simulate_keystrokes("u u");
 702        cx.assert_state(
 703            indoc! {"
 704                mod some_module {
 705                    ˇfn main() {
 706                    }
 707                }
 708                "},
 709            Mode::Normal,
 710        );
 711        cx.update_global(|store: &mut SettingsStore, cx| {
 712            store.update_user_settings(cx, |settings| {
 713                settings.project.all_languages.languages.0.insert(
 714                    LanguageName::new("Rust").0,
 715                    LanguageSettingsContent {
 716                        auto_indent_on_paste: Some(false),
 717                        ..Default::default()
 718                    },
 719                );
 720            });
 721        });
 722        // auto indentation turned off
 723        cx.simulate_keystrokes("y y p");
 724        cx.assert_state(
 725            indoc! {"
 726                mod some_module {
 727                    fn main() {
 728                    ˇfn main() {
 729                    }
 730                }
 731                "},
 732            Mode::Normal,
 733        );
 734    }
 735
 736    #[gpui::test]
 737    async fn test_paste_count(cx: &mut gpui::TestAppContext) {
 738        let mut cx = NeovimBackedTestContext::new(cx).await;
 739
 740        cx.set_shared_state(indoc! {"
 741            onˇe
 742            two
 743            three
 744        "})
 745            .await;
 746        cx.simulate_shared_keystrokes("y y 3 p").await;
 747        cx.shared_state().await.assert_eq(indoc! {"
 748            one
 749            ˇone
 750            one
 751            one
 752            two
 753            three
 754        "});
 755
 756        cx.set_shared_state(indoc! {"
 757            one
 758            ˇtwo
 759            three
 760        "})
 761            .await;
 762        cx.simulate_shared_keystrokes("y $ $ 3 p").await;
 763        cx.shared_state().await.assert_eq(indoc! {"
 764            one
 765            twotwotwotwˇo
 766            three
 767        "});
 768    }
 769
 770    #[gpui::test]
 771    async fn test_numbered_registers(cx: &mut gpui::TestAppContext) {
 772        let mut cx = NeovimBackedTestContext::new(cx).await;
 773
 774        cx.update_global(|store: &mut SettingsStore, cx| {
 775            store.update_user_settings(cx, |s| {
 776                s.vim.get_or_insert_default().use_system_clipboard = Some(UseSystemClipboard::Never)
 777            });
 778        });
 779
 780        cx.set_shared_state(indoc! {"
 781                The quick brown
 782                fox jˇumps over
 783                the lazy dog"})
 784            .await;
 785        cx.simulate_shared_keystrokes("y y \" 0 p").await;
 786        cx.shared_register('0').await.assert_eq("fox jumps over\n");
 787        cx.shared_register('"').await.assert_eq("fox jumps over\n");
 788
 789        cx.shared_state().await.assert_eq(indoc! {"
 790                The quick brown
 791                fox jumps over
 792                ˇfox jumps over
 793                the lazy dog"});
 794        cx.simulate_shared_keystrokes("k k d d").await;
 795        cx.shared_register('0').await.assert_eq("fox jumps over\n");
 796        cx.shared_register('1').await.assert_eq("The quick brown\n");
 797        cx.shared_register('"').await.assert_eq("The quick brown\n");
 798
 799        cx.simulate_shared_keystrokes("d d shift-g d d").await;
 800        cx.shared_register('0').await.assert_eq("fox jumps over\n");
 801        cx.shared_register('3').await.assert_eq("The quick brown\n");
 802        cx.shared_register('2').await.assert_eq("fox jumps over\n");
 803        cx.shared_register('1').await.assert_eq("the lazy dog\n");
 804
 805        cx.shared_state().await.assert_eq(indoc! {"
 806        ˇfox jumps over"});
 807
 808        cx.simulate_shared_keystrokes("d d \" 3 p p \" 1 p").await;
 809        cx.set_shared_state(indoc! {"
 810                The quick brown
 811                fox jumps over
 812                ˇthe lazy dog"})
 813            .await;
 814    }
 815
 816    #[gpui::test]
 817    async fn test_named_registers(cx: &mut gpui::TestAppContext) {
 818        let mut cx = NeovimBackedTestContext::new(cx).await;
 819
 820        cx.update_global(|store: &mut SettingsStore, cx| {
 821            store.update_user_settings(cx, |s| {
 822                s.vim.get_or_insert_default().use_system_clipboard = Some(UseSystemClipboard::Never)
 823            });
 824        });
 825
 826        cx.set_shared_state(indoc! {"
 827                The quick brown
 828                fox jˇumps over
 829                the lazy dog"})
 830            .await;
 831        cx.simulate_shared_keystrokes("\" a d a w").await;
 832        cx.shared_register('a').await.assert_eq("jumps ");
 833        cx.simulate_shared_keystrokes("\" shift-a d i w").await;
 834        cx.shared_register('a').await.assert_eq("jumps over");
 835        cx.shared_register('"').await.assert_eq("jumps over");
 836        cx.simulate_shared_keystrokes("\" a p").await;
 837        cx.shared_state().await.assert_eq(indoc! {"
 838                The quick brown
 839                fox jumps oveˇr
 840                the lazy dog"});
 841        cx.simulate_shared_keystrokes("\" a d a w").await;
 842        cx.shared_register('a').await.assert_eq(" over");
 843    }
 844
 845    #[gpui::test]
 846    async fn test_special_registers(cx: &mut gpui::TestAppContext) {
 847        let mut cx = NeovimBackedTestContext::new(cx).await;
 848
 849        cx.update_global(|store: &mut SettingsStore, cx| {
 850            store.update_user_settings(cx, |s| {
 851                s.vim.get_or_insert_default().use_system_clipboard = Some(UseSystemClipboard::Never)
 852            });
 853        });
 854
 855        cx.set_shared_state(indoc! {"
 856                The quick brown
 857                fox jˇumps over
 858                the lazy dog"})
 859            .await;
 860        cx.simulate_shared_keystrokes("d i w").await;
 861        cx.shared_register('-').await.assert_eq("jumps");
 862        cx.simulate_shared_keystrokes("\" _ d d").await;
 863        cx.shared_register('_').await.assert_eq("");
 864
 865        cx.simulate_shared_keystrokes("shift-v \" _ y w").await;
 866        cx.shared_register('"').await.assert_eq("jumps");
 867
 868        cx.shared_state().await.assert_eq(indoc! {"
 869                The quick brown
 870                the ˇlazy dog"});
 871        cx.simulate_shared_keystrokes("\" \" d ^").await;
 872        cx.shared_register('0').await.assert_eq("the ");
 873        cx.shared_register('"').await.assert_eq("the ");
 874
 875        cx.simulate_shared_keystrokes("^ \" + d $").await;
 876        cx.shared_clipboard().await.assert_eq("lazy dog");
 877        cx.shared_register('"').await.assert_eq("lazy dog");
 878
 879        cx.simulate_shared_keystrokes("/ d o g enter").await;
 880        cx.shared_register('/').await.assert_eq("dog");
 881        cx.simulate_shared_keystrokes("\" / shift-p").await;
 882        cx.shared_state().await.assert_eq(indoc! {"
 883                The quick brown
 884                doˇg"});
 885
 886        // not testing nvim as it doesn't have a filename
 887        cx.simulate_keystrokes("\" % p");
 888        #[cfg(not(target_os = "windows"))]
 889        cx.assert_state(
 890            indoc! {"
 891                    The quick brown
 892                    dogdir/file.rˇs"},
 893            Mode::Normal,
 894        );
 895        #[cfg(target_os = "windows")]
 896        cx.assert_state(
 897            indoc! {"
 898                    The quick brown
 899                    dogdir\\file.rˇs"},
 900            Mode::Normal,
 901        );
 902    }
 903
 904    #[gpui::test]
 905    async fn test_multicursor_paste(cx: &mut gpui::TestAppContext) {
 906        let mut cx = VimTestContext::new(cx, true).await;
 907
 908        cx.update_global(|store: &mut SettingsStore, cx| {
 909            store.update_user_settings(cx, |s| {
 910                s.vim.get_or_insert_default().use_system_clipboard = Some(UseSystemClipboard::Never)
 911            });
 912        });
 913
 914        cx.set_state(
 915            indoc! {"
 916               ˇfish one
 917               fish two
 918               fish red
 919               fish blue
 920                "},
 921            Mode::Normal,
 922        );
 923        cx.simulate_keystrokes("4 g l w escape d i w 0 shift-p");
 924        cx.assert_state(
 925            indoc! {"
 926               onˇefish•
 927               twˇofish•
 928               reˇdfish•
 929               bluˇefish•
 930                "},
 931            Mode::Normal,
 932        );
 933    }
 934
 935    #[gpui::test]
 936    async fn test_replace_with_register(cx: &mut gpui::TestAppContext) {
 937        let mut cx = VimTestContext::new(cx, true).await;
 938
 939        cx.set_state(
 940            indoc! {"
 941                   ˇfish one
 942                   two three
 943                   "},
 944            Mode::Normal,
 945        );
 946        cx.simulate_keystrokes("y i w");
 947        cx.simulate_keystrokes("w");
 948        cx.simulate_keystrokes("g shift-r i w");
 949        cx.assert_state(
 950            indoc! {"
 951                fish fisˇh
 952                two three
 953                "},
 954            Mode::Normal,
 955        );
 956        cx.simulate_keystrokes("j b g shift-r e");
 957        cx.assert_state(
 958            indoc! {"
 959            fish fish
 960            two fisˇh
 961            "},
 962            Mode::Normal,
 963        );
 964        let clipboard: Register = cx.read_from_clipboard().unwrap().into();
 965        assert_eq!(clipboard.text, "fish");
 966
 967        cx.set_state(
 968            indoc! {"
 969                   ˇfish one
 970                   two three
 971                   "},
 972            Mode::Normal,
 973        );
 974        cx.simulate_keystrokes("y i w");
 975        cx.simulate_keystrokes("w");
 976        cx.simulate_keystrokes("v i w g shift-r");
 977        cx.assert_state(
 978            indoc! {"
 979                fish fisˇh
 980                two three
 981                "},
 982            Mode::Normal,
 983        );
 984        cx.simulate_keystrokes("g shift-r r");
 985        cx.assert_state(
 986            indoc! {"
 987                fisˇh
 988                two three
 989                "},
 990            Mode::Normal,
 991        );
 992        cx.simulate_keystrokes("j w g shift-r $");
 993        cx.assert_state(
 994            indoc! {"
 995                fish
 996                two fisˇh
 997            "},
 998            Mode::Normal,
 999        );
1000        let clipboard: Register = cx.read_from_clipboard().unwrap().into();
1001        assert_eq!(clipboard.text, "fish");
1002    }
1003
1004    #[gpui::test]
1005    async fn test_replace_with_register_dot_repeat(cx: &mut gpui::TestAppContext) {
1006        let mut cx = VimTestContext::new(cx, true).await;
1007
1008        cx.set_state(
1009            indoc! {"
1010                   ˇfish one
1011                   two three
1012                   "},
1013            Mode::Normal,
1014        );
1015        cx.simulate_keystrokes("y i w");
1016        cx.simulate_keystrokes("w");
1017        cx.simulate_keystrokes("g shift-r i w");
1018        cx.assert_state(
1019            indoc! {"
1020                fish fisˇh
1021                two three
1022                "},
1023            Mode::Normal,
1024        );
1025        cx.simulate_keystrokes("j .");
1026        cx.assert_state(
1027            indoc! {"
1028                fish fish
1029                two fisˇh
1030                "},
1031            Mode::Normal,
1032        );
1033    }
1034}