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