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