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