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