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