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