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