paste.rs

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