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