paste.rs

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