paste.rs

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