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