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}