paste.rs

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