paste.rs

  1use editor::{display_map::ToDisplayPoint, movement, scroll::Autoscroll, DisplayPoint, RowExt};
  2use gpui::{impl_actions, Context, Window};
  3use language::{Bias, SelectionGoal};
  4use schemars::JsonSchema;
  5use serde::Deserialize;
  6use std::cmp;
  7
  8use crate::{
  9    state::{Mode, Register},
 10    Vim,
 11};
 12
 13#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
 14#[serde(rename_all = "camelCase")]
 15pub struct Paste {
 16    #[serde(default)]
 17    before: bool,
 18    #[serde(default)]
 19    preserve_clipboard: bool,
 20}
 21
 22impl_actions!(vim, [Paste]);
 23
 24impl Vim {
 25    pub fn paste(&mut self, action: &Paste, window: &mut Window, cx: &mut Context<Self>) {
 26        self.record_current_action(cx);
 27        self.store_visual_marks(window, cx);
 28        let count = Vim::take_count(cx).unwrap_or(1);
 29
 30        self.update_editor(window, cx, |vim, editor, window, cx| {
 31            let text_layout_details = editor.text_layout_details(window);
 32            editor.transact(window, cx, |editor, window, cx| {
 33                editor.set_clip_at_line_ends(false, cx);
 34
 35                let selected_register = vim.selected_register.take();
 36
 37                let Some(Register {
 38                    text,
 39                    clipboard_selections,
 40                }) = Vim::update_globals(cx, |globals, cx| {
 41                    globals.read_register(selected_register, Some(editor), cx)
 42                })
 43                .filter(|reg| !reg.text.is_empty())
 44                else {
 45                    return;
 46                };
 47                let clipboard_selections = clipboard_selections
 48                    .filter(|sel| sel.len() > 1 && vim.mode != Mode::VisualLine);
 49
 50                if !action.preserve_clipboard && vim.mode.is_visual() {
 51                    vim.copy_selections_content(editor, vim.mode == Mode::VisualLine, cx);
 52                }
 53
 54                let (display_map, current_selections) = editor.selections.all_adjusted_display(cx);
 55
 56                // unlike zed, if you have a multi-cursor selection from vim block mode,
 57                // pasting it will paste it on subsequent lines, even if you don't yet
 58                // have a cursor there.
 59                let mut selections_to_process = Vec::new();
 60                let mut i = 0;
 61                while i < current_selections.len() {
 62                    selections_to_process
 63                        .push((current_selections[i].start..current_selections[i].end, true));
 64                    i += 1;
 65                }
 66                if let Some(clipboard_selections) = clipboard_selections.as_ref() {
 67                    let left = current_selections
 68                        .iter()
 69                        .map(|selection| cmp::min(selection.start.column(), selection.end.column()))
 70                        .min()
 71                        .unwrap();
 72                    let mut row = current_selections.last().unwrap().end.row().next_row();
 73                    while i < clipboard_selections.len() {
 74                        let cursor =
 75                            display_map.clip_point(DisplayPoint::new(row, left), Bias::Left);
 76                        selections_to_process.push((cursor..cursor, false));
 77                        i += 1;
 78                        row.0 += 1;
 79                    }
 80                }
 81
 82                let first_selection_indent_column =
 83                    clipboard_selections.as_ref().and_then(|zed_selections| {
 84                        zed_selections
 85                            .first()
 86                            .map(|selection| selection.first_line_indent)
 87                    });
 88                let before = action.before || vim.mode == Mode::VisualLine;
 89
 90                let mut edits = Vec::new();
 91                let mut new_selections = Vec::new();
 92                let mut original_indent_columns = Vec::new();
 93                let mut start_offset = 0;
 94
 95                for (ix, (selection, preserve)) in selections_to_process.iter().enumerate() {
 96                    let (mut to_insert, original_indent_column) =
 97                        if let Some(clipboard_selections) = &clipboard_selections {
 98                            if let Some(clipboard_selection) = clipboard_selections.get(ix) {
 99                                let end_offset = start_offset + clipboard_selection.len;
100                                let text = text[start_offset..end_offset].to_string();
101                                start_offset = end_offset + 1;
102                                (text, Some(clipboard_selection.first_line_indent))
103                            } else {
104                                ("".to_string(), first_selection_indent_column)
105                            }
106                        } else {
107                            (text.to_string(), first_selection_indent_column)
108                        };
109                    let line_mode = to_insert.ends_with('\n');
110                    let is_multiline = to_insert.contains('\n');
111
112                    if line_mode && !before {
113                        if selection.is_empty() {
114                            to_insert =
115                                "\n".to_owned() + &to_insert[..to_insert.len() - "\n".len()];
116                        } else {
117                            to_insert = "\n".to_owned() + &to_insert;
118                        }
119                    } else if !line_mode && vim.mode == Mode::VisualLine {
120                        to_insert += "\n";
121                    }
122
123                    let display_range = if !selection.is_empty() {
124                        selection.start..selection.end
125                    } else if line_mode {
126                        let point = if before {
127                            movement::line_beginning(&display_map, selection.start, false)
128                        } else {
129                            movement::line_end(&display_map, selection.start, false)
130                        };
131                        point..point
132                    } else {
133                        let point = if before {
134                            selection.start
135                        } else {
136                            movement::saturating_right(&display_map, selection.start)
137                        };
138                        point..point
139                    };
140
141                    let point_range = display_range.start.to_point(&display_map)
142                        ..display_range.end.to_point(&display_map);
143                    let anchor = if is_multiline || vim.mode == Mode::VisualLine {
144                        display_map.buffer_snapshot.anchor_before(point_range.start)
145                    } else {
146                        display_map.buffer_snapshot.anchor_after(point_range.end)
147                    };
148
149                    if *preserve {
150                        new_selections.push((anchor, line_mode, is_multiline));
151                    }
152                    edits.push((point_range, to_insert.repeat(count)));
153                    original_indent_columns.extend(original_indent_column);
154                }
155
156                editor.edit_with_block_indent(edits, original_indent_columns, cx);
157
158                // in line_mode vim will insert the new text on the next (or previous if before) line
159                // 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).
160                // otherwise vim will insert the next text at (or before) the current cursor position,
161                // the cursor will go to the last (or first, if is_multiline) inserted character.
162                editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
163                    s.replace_cursors_with(|map| {
164                        let mut cursors = Vec::new();
165                        for (anchor, line_mode, is_multiline) in &new_selections {
166                            let mut cursor = anchor.to_display_point(map);
167                            if *line_mode {
168                                if !before {
169                                    cursor = movement::down(
170                                        map,
171                                        cursor,
172                                        SelectionGoal::None,
173                                        false,
174                                        &text_layout_details,
175                                    )
176                                    .0;
177                                }
178                                cursor = movement::indented_line_beginning(map, cursor, true);
179                            } else if !is_multiline && !vim.temp_mode {
180                                cursor = movement::saturating_left(map, cursor)
181                            }
182                            cursors.push(cursor);
183                            if vim.mode == Mode::VisualBlock {
184                                break;
185                            }
186                        }
187
188                        cursors
189                    });
190                })
191            });
192        });
193        self.switch_mode(Mode::Normal, true, window, cx);
194    }
195}
196
197#[cfg(test)]
198mod test {
199    use crate::{
200        state::Mode,
201        test::{NeovimBackedTestContext, VimTestContext},
202        UseSystemClipboard, VimSettings,
203    };
204    use gpui::ClipboardItem;
205    use indoc::indoc;
206    use settings::SettingsStore;
207
208    #[gpui::test]
209    async fn test_paste(cx: &mut gpui::TestAppContext) {
210        let mut cx = NeovimBackedTestContext::new(cx).await;
211
212        // single line
213        cx.set_shared_state(indoc! {"
214            The quick brown
215            fox ˇjumps over
216            the lazy dog"})
217            .await;
218        cx.simulate_shared_keystrokes("v w y").await;
219        cx.shared_clipboard().await.assert_eq("jumps o");
220        cx.set_shared_state(indoc! {"
221            The quick brown
222            fox jumps oveˇr
223            the lazy dog"})
224            .await;
225        cx.simulate_shared_keystrokes("p").await;
226        cx.shared_state().await.assert_eq(indoc! {"
227            The quick brown
228            fox jumps overjumps ˇo
229            the lazy dog"});
230
231        cx.set_shared_state(indoc! {"
232            The quick brown
233            fox jumps oveˇr
234            the lazy dog"})
235            .await;
236        cx.simulate_shared_keystrokes("shift-p").await;
237        cx.shared_state().await.assert_eq(indoc! {"
238            The quick brown
239            fox jumps ovejumps ˇor
240            the lazy dog"});
241
242        // line mode
243        cx.set_shared_state(indoc! {"
244            The quick brown
245            fox juˇmps over
246            the lazy dog"})
247            .await;
248        cx.simulate_shared_keystrokes("d d").await;
249        cx.shared_clipboard().await.assert_eq("fox jumps over\n");
250        cx.shared_state().await.assert_eq(indoc! {"
251            The quick brown
252            the laˇzy dog"});
253        cx.simulate_shared_keystrokes("p").await;
254        cx.shared_state().await.assert_eq(indoc! {"
255            The quick brown
256            the lazy dog
257            ˇfox jumps over"});
258        cx.simulate_shared_keystrokes("k shift-p").await;
259        cx.shared_state().await.assert_eq(indoc! {"
260            The quick brown
261            ˇfox jumps over
262            the lazy dog
263            fox jumps over"});
264
265        // multiline, cursor to first character of pasted text.
266        cx.set_shared_state(indoc! {"
267            The quick brown
268            fox jumps ˇover
269            the lazy dog"})
270            .await;
271        cx.simulate_shared_keystrokes("v j y").await;
272        cx.shared_clipboard().await.assert_eq("over\nthe lazy do");
273
274        cx.simulate_shared_keystrokes("p").await;
275        cx.shared_state().await.assert_eq(indoc! {"
276            The quick brown
277            fox jumps oˇover
278            the lazy dover
279            the lazy dog"});
280        cx.simulate_shared_keystrokes("u shift-p").await;
281        cx.shared_state().await.assert_eq(indoc! {"
282            The quick brown
283            fox jumps ˇover
284            the lazy doover
285            the lazy dog"});
286    }
287
288    #[gpui::test]
289    async fn test_yank_system_clipboard_never(cx: &mut gpui::TestAppContext) {
290        let mut cx = VimTestContext::new(cx, true).await;
291
292        cx.update_global(|store: &mut SettingsStore, cx| {
293            store.update_user_settings::<VimSettings>(cx, |s| {
294                s.use_system_clipboard = Some(UseSystemClipboard::Never)
295            });
296        });
297
298        cx.set_state(
299            indoc! {"
300                The quick brown
301                fox jˇumps over
302                the lazy dog"},
303            Mode::Normal,
304        );
305        cx.simulate_keystrokes("v i w y");
306        cx.assert_state(
307            indoc! {"
308                The quick brown
309                fox ˇjumps over
310                the lazy dog"},
311            Mode::Normal,
312        );
313        cx.simulate_keystrokes("p");
314        cx.assert_state(
315            indoc! {"
316                The quick brown
317                fox jjumpˇsumps over
318                the lazy dog"},
319            Mode::Normal,
320        );
321        assert_eq!(cx.read_from_clipboard(), None);
322    }
323
324    #[gpui::test]
325    async fn test_yank_system_clipboard_on_yank(cx: &mut gpui::TestAppContext) {
326        let mut cx = VimTestContext::new(cx, true).await;
327
328        cx.update_global(|store: &mut SettingsStore, cx| {
329            store.update_user_settings::<VimSettings>(cx, |s| {
330                s.use_system_clipboard = Some(UseSystemClipboard::OnYank)
331            });
332        });
333
334        // copy in visual mode
335        cx.set_state(
336            indoc! {"
337                The quick brown
338                fox jˇumps over
339                the lazy dog"},
340            Mode::Normal,
341        );
342        cx.simulate_keystrokes("v i w y");
343        cx.assert_state(
344            indoc! {"
345                The quick brown
346                fox ˇjumps over
347                the lazy dog"},
348            Mode::Normal,
349        );
350        cx.simulate_keystrokes("p");
351        cx.assert_state(
352            indoc! {"
353                The quick brown
354                fox jjumpˇsumps over
355                the lazy dog"},
356            Mode::Normal,
357        );
358        assert_eq!(
359            cx.read_from_clipboard()
360                .map(|item| item.text().unwrap().to_string()),
361            Some("jumps".into())
362        );
363        cx.simulate_keystrokes("d d p");
364        cx.assert_state(
365            indoc! {"
366                The quick brown
367                the lazy dog
368                ˇfox jjumpsumps over"},
369            Mode::Normal,
370        );
371        assert_eq!(
372            cx.read_from_clipboard()
373                .map(|item| item.text().unwrap().to_string()),
374            Some("jumps".into())
375        );
376        cx.write_to_clipboard(ClipboardItem::new_string("test-copy".to_string()));
377        cx.simulate_keystrokes("shift-p");
378        cx.assert_state(
379            indoc! {"
380                The quick brown
381                the lazy dog
382                test-copˇyfox jjumpsumps over"},
383            Mode::Normal,
384        );
385    }
386
387    #[gpui::test]
388    async fn test_paste_visual(cx: &mut gpui::TestAppContext) {
389        let mut cx = NeovimBackedTestContext::new(cx).await;
390
391        // copy in visual mode
392        cx.set_shared_state(indoc! {"
393                The quick brown
394                fox jˇumps over
395                the lazy dog"})
396            .await;
397        cx.simulate_shared_keystrokes("v i w y").await;
398        cx.shared_state().await.assert_eq(indoc! {"
399                The quick brown
400                fox ˇjumps over
401                the lazy dog"});
402        // paste in visual mode
403        cx.simulate_shared_keystrokes("w v i w p").await;
404        cx.shared_state().await.assert_eq(indoc! {"
405                The quick brown
406                fox jumps jumpˇs
407                the lazy dog"});
408        cx.shared_clipboard().await.assert_eq("over");
409        // paste in visual line mode
410        cx.simulate_shared_keystrokes("up shift-v shift-p").await;
411        cx.shared_state().await.assert_eq(indoc! {"
412            ˇover
413            fox jumps jumps
414            the lazy dog"});
415        cx.shared_clipboard().await.assert_eq("over");
416        // paste in visual block mode
417        cx.simulate_shared_keystrokes("ctrl-v down down p").await;
418        cx.shared_state().await.assert_eq(indoc! {"
419            oveˇrver
420            overox jumps jumps
421            overhe lazy dog"});
422
423        // copy in visual line mode
424        cx.set_shared_state(indoc! {"
425                The quick brown
426                fox juˇmps over
427                the lazy dog"})
428            .await;
429        cx.simulate_shared_keystrokes("shift-v d").await;
430        cx.shared_state().await.assert_eq(indoc! {"
431                The quick brown
432                the laˇzy dog"});
433        // paste in visual mode
434        cx.simulate_shared_keystrokes("v i w p").await;
435        cx.shared_state().await.assert_eq(indoc! {"
436                The quick brown
437                the•
438                ˇfox jumps over
439                 dog"});
440        cx.shared_clipboard().await.assert_eq("lazy");
441        cx.set_shared_state(indoc! {"
442            The quick brown
443            fox juˇmps over
444            the lazy dog"})
445            .await;
446        cx.simulate_shared_keystrokes("shift-v d").await;
447        cx.shared_state().await.assert_eq(indoc! {"
448            The quick brown
449            the laˇzy dog"});
450        // paste in visual line mode
451        cx.simulate_shared_keystrokes("k shift-v p").await;
452        cx.shared_state().await.assert_eq(indoc! {"
453            ˇfox jumps over
454            the lazy dog"});
455        cx.shared_clipboard().await.assert_eq("The quick brown\n");
456    }
457
458    #[gpui::test]
459    async fn test_paste_visual_block(cx: &mut gpui::TestAppContext) {
460        let mut cx = NeovimBackedTestContext::new(cx).await;
461        // copy in visual block mode
462        cx.set_shared_state(indoc! {"
463            The ˇquick brown
464            fox jumps over
465            the lazy dog"})
466            .await;
467        cx.simulate_shared_keystrokes("ctrl-v 2 j y").await;
468        cx.shared_clipboard().await.assert_eq("q\nj\nl");
469        cx.simulate_shared_keystrokes("p").await;
470        cx.shared_state().await.assert_eq(indoc! {"
471            The qˇquick brown
472            fox jjumps over
473            the llazy dog"});
474        cx.simulate_shared_keystrokes("v i w shift-p").await;
475        cx.shared_state().await.assert_eq(indoc! {"
476            The ˇq brown
477            fox jjjumps over
478            the lllazy dog"});
479        cx.simulate_shared_keystrokes("v i w shift-p").await;
480
481        cx.set_shared_state(indoc! {"
482            The ˇquick brown
483            fox jumps over
484            the lazy dog"})
485            .await;
486        cx.simulate_shared_keystrokes("ctrl-v j y").await;
487        cx.shared_clipboard().await.assert_eq("q\nj");
488        cx.simulate_shared_keystrokes("l ctrl-v 2 j shift-p").await;
489        cx.shared_state().await.assert_eq(indoc! {"
490            The qˇqick brown
491            fox jjmps over
492            the lzy dog"});
493
494        cx.simulate_shared_keystrokes("shift-v p").await;
495        cx.shared_state().await.assert_eq(indoc! {"
496            ˇq
497            j
498            fox jjmps over
499            the lzy dog"});
500    }
501
502    #[gpui::test]
503    async fn test_paste_indent(cx: &mut gpui::TestAppContext) {
504        let mut cx = VimTestContext::new_typescript(cx).await;
505
506        cx.set_state(
507            indoc! {"
508            class A {ˇ
509            }
510        "},
511            Mode::Normal,
512        );
513        cx.simulate_keystrokes("o a ( ) { escape");
514        cx.assert_state(
515            indoc! {"
516            class A {
517                a()ˇ{}
518            }
519            "},
520            Mode::Normal,
521        );
522        // cursor goes to the first non-blank character in the line;
523        cx.simulate_keystrokes("y y p");
524        cx.assert_state(
525            indoc! {"
526            class A {
527                a(){}
528                ˇa(){}
529            }
530            "},
531            Mode::Normal,
532        );
533        // indentation is preserved when pasting
534        cx.simulate_keystrokes("u shift-v up y shift-p");
535        cx.assert_state(
536            indoc! {"
537                ˇclass A {
538                    a(){}
539                class A {
540                    a(){}
541                }
542                "},
543            Mode::Normal,
544        );
545    }
546
547    #[gpui::test]
548    async fn test_paste_count(cx: &mut gpui::TestAppContext) {
549        let mut cx = NeovimBackedTestContext::new(cx).await;
550
551        cx.set_shared_state(indoc! {"
552            onˇe
553            two
554            three
555        "})
556            .await;
557        cx.simulate_shared_keystrokes("y y 3 p").await;
558        cx.shared_state().await.assert_eq(indoc! {"
559            one
560            ˇone
561            one
562            one
563            two
564            three
565        "});
566
567        cx.set_shared_state(indoc! {"
568            one
569            ˇtwo
570            three
571        "})
572            .await;
573        cx.simulate_shared_keystrokes("y $ $ 3 p").await;
574        cx.shared_state().await.assert_eq(indoc! {"
575            one
576            twotwotwotwˇo
577            three
578        "});
579    }
580
581    #[gpui::test]
582    async fn test_numbered_registers(cx: &mut gpui::TestAppContext) {
583        let mut cx = NeovimBackedTestContext::new(cx).await;
584
585        cx.update_global(|store: &mut SettingsStore, cx| {
586            store.update_user_settings::<VimSettings>(cx, |s| {
587                s.use_system_clipboard = Some(UseSystemClipboard::Never)
588            });
589        });
590
591        cx.set_shared_state(indoc! {"
592                The quick brown
593                fox jˇumps over
594                the lazy dog"})
595            .await;
596        cx.simulate_shared_keystrokes("y y \" 0 p").await;
597        cx.shared_register('0').await.assert_eq("fox jumps over\n");
598        cx.shared_register('"').await.assert_eq("fox jumps over\n");
599
600        cx.shared_state().await.assert_eq(indoc! {"
601                The quick brown
602                fox jumps over
603                ˇfox jumps over
604                the lazy dog"});
605        cx.simulate_shared_keystrokes("k k d d").await;
606        cx.shared_register('0').await.assert_eq("fox jumps over\n");
607        cx.shared_register('1').await.assert_eq("The quick brown\n");
608        cx.shared_register('"').await.assert_eq("The quick brown\n");
609
610        cx.simulate_shared_keystrokes("d d shift-g d d").await;
611        cx.shared_register('0').await.assert_eq("fox jumps over\n");
612        cx.shared_register('3').await.assert_eq("The quick brown\n");
613        cx.shared_register('2').await.assert_eq("fox jumps over\n");
614        cx.shared_register('1').await.assert_eq("the lazy dog\n");
615
616        cx.shared_state().await.assert_eq(indoc! {"
617        ˇfox jumps over"});
618
619        cx.simulate_shared_keystrokes("d d \" 3 p p \" 1 p").await;
620        cx.set_shared_state(indoc! {"
621                The quick brown
622                fox jumps over
623                ˇthe lazy dog"})
624            .await;
625    }
626
627    #[gpui::test]
628    async fn test_named_registers(cx: &mut gpui::TestAppContext) {
629        let mut cx = NeovimBackedTestContext::new(cx).await;
630
631        cx.update_global(|store: &mut SettingsStore, cx| {
632            store.update_user_settings::<VimSettings>(cx, |s| {
633                s.use_system_clipboard = Some(UseSystemClipboard::Never)
634            });
635        });
636
637        cx.set_shared_state(indoc! {"
638                The quick brown
639                fox jˇumps over
640                the lazy dog"})
641            .await;
642        cx.simulate_shared_keystrokes("\" a d a w").await;
643        cx.shared_register('a').await.assert_eq("jumps ");
644        cx.simulate_shared_keystrokes("\" shift-a d i w").await;
645        cx.shared_register('a').await.assert_eq("jumps over");
646        cx.shared_register('"').await.assert_eq("jumps over");
647        cx.simulate_shared_keystrokes("\" a p").await;
648        cx.shared_state().await.assert_eq(indoc! {"
649                The quick brown
650                fox jumps oveˇr
651                the lazy dog"});
652        cx.simulate_shared_keystrokes("\" a d a w").await;
653        cx.shared_register('a').await.assert_eq(" over");
654    }
655
656    #[gpui::test]
657    async fn test_special_registers(cx: &mut gpui::TestAppContext) {
658        let mut cx = NeovimBackedTestContext::new(cx).await;
659
660        cx.update_global(|store: &mut SettingsStore, cx| {
661            store.update_user_settings::<VimSettings>(cx, |s| {
662                s.use_system_clipboard = Some(UseSystemClipboard::Never)
663            });
664        });
665
666        cx.set_shared_state(indoc! {"
667                The quick brown
668                fox jˇumps over
669                the lazy dog"})
670            .await;
671        cx.simulate_shared_keystrokes("d i w").await;
672        cx.shared_register('-').await.assert_eq("jumps");
673        cx.simulate_shared_keystrokes("\" _ d d").await;
674        cx.shared_register('_').await.assert_eq("");
675
676        cx.simulate_shared_keystrokes("shift-v \" _ y w").await;
677        cx.shared_register('"').await.assert_eq("jumps");
678
679        cx.shared_state().await.assert_eq(indoc! {"
680                The quick brown
681                the ˇlazy dog"});
682        cx.simulate_shared_keystrokes("\" \" d ^").await;
683        cx.shared_register('0').await.assert_eq("the ");
684        cx.shared_register('"').await.assert_eq("the ");
685
686        cx.simulate_shared_keystrokes("^ \" + d $").await;
687        cx.shared_clipboard().await.assert_eq("lazy dog");
688        cx.shared_register('"').await.assert_eq("lazy dog");
689
690        cx.simulate_shared_keystrokes("/ d o g enter").await;
691        cx.shared_register('/').await.assert_eq("dog");
692        cx.simulate_shared_keystrokes("\" / shift-p").await;
693        cx.shared_state().await.assert_eq(indoc! {"
694                The quick brown
695                doˇg"});
696
697        // not testing nvim as it doesn't have a filename
698        cx.simulate_keystrokes("\" % p");
699        #[cfg(not(target_os = "windows"))]
700        cx.assert_state(
701            indoc! {"
702                    The quick brown
703                    dogdir/file.rˇs"},
704            Mode::Normal,
705        );
706        #[cfg(target_os = "windows")]
707        cx.assert_state(
708            indoc! {"
709                    The quick brown
710                    dogdir\\file.rˇs"},
711            Mode::Normal,
712        );
713    }
714
715    #[gpui::test]
716    async fn test_multicursor_paste(cx: &mut gpui::TestAppContext) {
717        let mut cx = VimTestContext::new(cx, true).await;
718
719        cx.update_global(|store: &mut SettingsStore, cx| {
720            store.update_user_settings::<VimSettings>(cx, |s| {
721                s.use_system_clipboard = Some(UseSystemClipboard::Never)
722            });
723        });
724
725        cx.set_state(
726            indoc! {"
727               ˇfish one
728               fish two
729               fish red
730               fish blue
731                "},
732            Mode::Normal,
733        );
734        cx.simulate_keystrokes("4 g l w escape d i w 0 shift-p");
735        cx.assert_state(
736            indoc! {"
737               onˇefish•
738               twˇofish•
739               reˇdfish•
740               bluˇefish•
741                "},
742            Mode::Normal,
743        );
744    }
745}