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