paste.rs

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