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