paste.rs

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