1use editor::{display_map::ToDisplayPoint, movement, scroll::Autoscroll, DisplayPoint, RowExt};
2use gpui::{impl_actions, Context, Window};
3use language::{Bias, SelectionGoal};
4use schemars::JsonSchema;
5use serde::Deserialize;
6use std::cmp;
7
8use crate::{
9 motion::Motion,
10 object::Object,
11 state::{Mode, Register},
12 Vim,
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, vim.mode == Mode::VisualLine, 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_start_column =
85 clipboard_selections.as_ref().and_then(|zed_selections| {
86 zed_selections
87 .first()
88 .map(|selection| selection.start_column)
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_start_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_start_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.start_column))
105 } else {
106 ("".to_string(), first_selection_start_column)
107 }
108 } else {
109 (text.to_string(), first_selection_start_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 += "\n";
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_start_columns.extend(original_start_column);
156 }
157
158 editor.edit_with_block_indent(edits, original_start_columns, cx);
159
160 // in line_mode vim will insert the new text on the next (or previous if before) line
161 // 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).
162 // otherwise vim will insert the next text at (or before) the current cursor position,
163 // the cursor will go to the last (or first, if is_multiline) inserted character.
164 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
165 s.replace_cursors_with(|map| {
166 let mut cursors = Vec::new();
167 for (anchor, line_mode, is_multiline) in &new_selections {
168 let mut cursor = anchor.to_display_point(map);
169 if *line_mode {
170 if !before {
171 cursor = movement::down(
172 map,
173 cursor,
174 SelectionGoal::None,
175 false,
176 &text_layout_details,
177 )
178 .0;
179 }
180 cursor = movement::indented_line_beginning(map, cursor, true);
181 } else if !is_multiline && !vim.temp_mode {
182 cursor = movement::saturating_left(map, cursor)
183 }
184 cursors.push(cursor);
185 if vim.mode == Mode::VisualBlock {
186 break;
187 }
188 }
189
190 cursors
191 });
192 })
193 });
194 });
195 self.switch_mode(Mode::Normal, true, window, cx);
196 }
197
198 pub fn replace_with_register_object(
199 &mut self,
200 object: Object,
201 around: bool,
202 window: &mut Window,
203 cx: &mut Context<Self>,
204 ) {
205 self.stop_recording(cx);
206 let selected_register = self.selected_register.take();
207 self.update_editor(window, cx, |_, editor, window, cx| {
208 editor.transact(window, cx, |editor, window, cx| {
209 editor.set_clip_at_line_ends(false, cx);
210 editor.change_selections(None, window, cx, |s| {
211 s.move_with(|map, selection| {
212 object.expand_selection(map, selection, around);
213 });
214 });
215
216 let Some(Register { text, .. }) = Vim::update_globals(cx, |globals, cx| {
217 globals.read_register(selected_register, Some(editor), cx)
218 })
219 .filter(|reg| !reg.text.is_empty()) else {
220 return;
221 };
222 editor.insert(&text, window, cx);
223 editor.set_clip_at_line_ends(true, cx);
224 editor.change_selections(None, window, cx, |s| {
225 s.move_with(|map, selection| {
226 selection.start = map.clip_point(selection.start, Bias::Left);
227 selection.end = selection.start
228 })
229 })
230 });
231 });
232 }
233
234 pub fn replace_with_register_motion(
235 &mut self,
236 motion: Motion,
237 times: Option<usize>,
238 window: &mut Window,
239 cx: &mut Context<Self>,
240 ) {
241 self.stop_recording(cx);
242 let selected_register = self.selected_register.take();
243 self.update_editor(window, cx, |_, editor, window, cx| {
244 let text_layout_details = editor.text_layout_details(window);
245 editor.transact(window, cx, |editor, window, cx| {
246 editor.set_clip_at_line_ends(false, cx);
247 editor.change_selections(None, window, cx, |s| {
248 s.move_with(|map, selection| {
249 motion.expand_selection(map, selection, times, false, &text_layout_details);
250 });
251 });
252
253 let Some(Register { text, .. }) = Vim::update_globals(cx, |globals, cx| {
254 globals.read_register(selected_register, Some(editor), cx)
255 })
256 .filter(|reg| !reg.text.is_empty()) else {
257 return;
258 };
259 editor.insert(&text, window, cx);
260 editor.set_clip_at_line_ends(true, cx);
261 editor.change_selections(None, window, cx, |s| {
262 s.move_with(|map, selection| {
263 selection.start = map.clip_point(selection.start, Bias::Left);
264 selection.end = selection.start
265 })
266 })
267 });
268 });
269 }
270}
271
272#[cfg(test)]
273mod test {
274 use crate::{
275 state::{Mode, Register},
276 test::{NeovimBackedTestContext, VimTestContext},
277 UseSystemClipboard, VimSettings,
278 };
279 use gpui::ClipboardItem;
280 use indoc::indoc;
281 use settings::SettingsStore;
282
283 #[gpui::test]
284 async fn test_paste(cx: &mut gpui::TestAppContext) {
285 let mut cx = NeovimBackedTestContext::new(cx).await;
286
287 // single line
288 cx.set_shared_state(indoc! {"
289 The quick brown
290 fox ˇjumps over
291 the lazy dog"})
292 .await;
293 cx.simulate_shared_keystrokes("v w y").await;
294 cx.shared_clipboard().await.assert_eq("jumps o");
295 cx.set_shared_state(indoc! {"
296 The quick brown
297 fox jumps oveˇr
298 the lazy dog"})
299 .await;
300 cx.simulate_shared_keystrokes("p").await;
301 cx.shared_state().await.assert_eq(indoc! {"
302 The quick brown
303 fox jumps overjumps ˇo
304 the lazy dog"});
305
306 cx.set_shared_state(indoc! {"
307 The quick brown
308 fox jumps oveˇr
309 the lazy dog"})
310 .await;
311 cx.simulate_shared_keystrokes("shift-p").await;
312 cx.shared_state().await.assert_eq(indoc! {"
313 The quick brown
314 fox jumps ovejumps ˇor
315 the lazy dog"});
316
317 // line mode
318 cx.set_shared_state(indoc! {"
319 The quick brown
320 fox juˇmps over
321 the lazy dog"})
322 .await;
323 cx.simulate_shared_keystrokes("d d").await;
324 cx.shared_clipboard().await.assert_eq("fox jumps over\n");
325 cx.shared_state().await.assert_eq(indoc! {"
326 The quick brown
327 the laˇzy dog"});
328 cx.simulate_shared_keystrokes("p").await;
329 cx.shared_state().await.assert_eq(indoc! {"
330 The quick brown
331 the lazy dog
332 ˇfox jumps over"});
333 cx.simulate_shared_keystrokes("k shift-p").await;
334 cx.shared_state().await.assert_eq(indoc! {"
335 The quick brown
336 ˇfox jumps over
337 the lazy dog
338 fox jumps over"});
339
340 // multiline, cursor to first character of pasted text.
341 cx.set_shared_state(indoc! {"
342 The quick brown
343 fox jumps ˇover
344 the lazy dog"})
345 .await;
346 cx.simulate_shared_keystrokes("v j y").await;
347 cx.shared_clipboard().await.assert_eq("over\nthe lazy do");
348
349 cx.simulate_shared_keystrokes("p").await;
350 cx.shared_state().await.assert_eq(indoc! {"
351 The quick brown
352 fox jumps oˇover
353 the lazy dover
354 the lazy dog"});
355 cx.simulate_shared_keystrokes("u shift-p").await;
356 cx.shared_state().await.assert_eq(indoc! {"
357 The quick brown
358 fox jumps ˇover
359 the lazy doover
360 the lazy dog"});
361 }
362
363 #[gpui::test]
364 async fn test_yank_system_clipboard_never(cx: &mut gpui::TestAppContext) {
365 let mut cx = VimTestContext::new(cx, true).await;
366
367 cx.update_global(|store: &mut SettingsStore, cx| {
368 store.update_user_settings::<VimSettings>(cx, |s| {
369 s.use_system_clipboard = Some(UseSystemClipboard::Never)
370 });
371 });
372
373 cx.set_state(
374 indoc! {"
375 The quick brown
376 fox jˇumps over
377 the lazy dog"},
378 Mode::Normal,
379 );
380 cx.simulate_keystrokes("v i w y");
381 cx.assert_state(
382 indoc! {"
383 The quick brown
384 fox ˇjumps over
385 the lazy dog"},
386 Mode::Normal,
387 );
388 cx.simulate_keystrokes("p");
389 cx.assert_state(
390 indoc! {"
391 The quick brown
392 fox jjumpˇsumps over
393 the lazy dog"},
394 Mode::Normal,
395 );
396 assert_eq!(cx.read_from_clipboard(), None);
397 }
398
399 #[gpui::test]
400 async fn test_yank_system_clipboard_on_yank(cx: &mut gpui::TestAppContext) {
401 let mut cx = VimTestContext::new(cx, true).await;
402
403 cx.update_global(|store: &mut SettingsStore, cx| {
404 store.update_user_settings::<VimSettings>(cx, |s| {
405 s.use_system_clipboard = Some(UseSystemClipboard::OnYank)
406 });
407 });
408
409 // copy in visual mode
410 cx.set_state(
411 indoc! {"
412 The quick brown
413 fox jˇumps over
414 the lazy dog"},
415 Mode::Normal,
416 );
417 cx.simulate_keystrokes("v i w y");
418 cx.assert_state(
419 indoc! {"
420 The quick brown
421 fox ˇjumps over
422 the lazy dog"},
423 Mode::Normal,
424 );
425 cx.simulate_keystrokes("p");
426 cx.assert_state(
427 indoc! {"
428 The quick brown
429 fox jjumpˇsumps over
430 the lazy dog"},
431 Mode::Normal,
432 );
433 assert_eq!(
434 cx.read_from_clipboard()
435 .map(|item| item.text().unwrap().to_string()),
436 Some("jumps".into())
437 );
438 cx.simulate_keystrokes("d d p");
439 cx.assert_state(
440 indoc! {"
441 The quick brown
442 the lazy dog
443 ˇfox jjumpsumps over"},
444 Mode::Normal,
445 );
446 assert_eq!(
447 cx.read_from_clipboard()
448 .map(|item| item.text().unwrap().to_string()),
449 Some("jumps".into())
450 );
451 cx.write_to_clipboard(ClipboardItem::new_string("test-copy".to_string()));
452 cx.simulate_keystrokes("shift-p");
453 cx.assert_state(
454 indoc! {"
455 The quick brown
456 the lazy dog
457 test-copˇyfox jjumpsumps over"},
458 Mode::Normal,
459 );
460 }
461
462 #[gpui::test]
463 async fn test_paste_visual(cx: &mut gpui::TestAppContext) {
464 let mut cx = NeovimBackedTestContext::new(cx).await;
465
466 // copy in visual mode
467 cx.set_shared_state(indoc! {"
468 The quick brown
469 fox jˇumps over
470 the lazy dog"})
471 .await;
472 cx.simulate_shared_keystrokes("v i w y").await;
473 cx.shared_state().await.assert_eq(indoc! {"
474 The quick brown
475 fox ˇjumps over
476 the lazy dog"});
477 // paste in visual mode
478 cx.simulate_shared_keystrokes("w v i w p").await;
479 cx.shared_state().await.assert_eq(indoc! {"
480 The quick brown
481 fox jumps jumpˇs
482 the lazy dog"});
483 cx.shared_clipboard().await.assert_eq("over");
484 // paste in visual line mode
485 cx.simulate_shared_keystrokes("up shift-v shift-p").await;
486 cx.shared_state().await.assert_eq(indoc! {"
487 ˇover
488 fox jumps jumps
489 the lazy dog"});
490 cx.shared_clipboard().await.assert_eq("over");
491 // paste in visual block mode
492 cx.simulate_shared_keystrokes("ctrl-v down down p").await;
493 cx.shared_state().await.assert_eq(indoc! {"
494 oveˇrver
495 overox jumps jumps
496 overhe lazy dog"});
497
498 // copy in visual line mode
499 cx.set_shared_state(indoc! {"
500 The quick brown
501 fox juˇmps over
502 the lazy dog"})
503 .await;
504 cx.simulate_shared_keystrokes("shift-v d").await;
505 cx.shared_state().await.assert_eq(indoc! {"
506 The quick brown
507 the laˇzy dog"});
508 // paste in visual mode
509 cx.simulate_shared_keystrokes("v i w p").await;
510 cx.shared_state().await.assert_eq(indoc! {"
511 The quick brown
512 the•
513 ˇfox jumps over
514 dog"});
515 cx.shared_clipboard().await.assert_eq("lazy");
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 line mode
526 cx.simulate_shared_keystrokes("k shift-v p").await;
527 cx.shared_state().await.assert_eq(indoc! {"
528 ˇfox jumps over
529 the lazy dog"});
530 cx.shared_clipboard().await.assert_eq("The quick brown\n");
531 }
532
533 #[gpui::test]
534 async fn test_paste_visual_block(cx: &mut gpui::TestAppContext) {
535 let mut cx = NeovimBackedTestContext::new(cx).await;
536 // copy in visual block mode
537 cx.set_shared_state(indoc! {"
538 The ˇquick brown
539 fox jumps over
540 the lazy dog"})
541 .await;
542 cx.simulate_shared_keystrokes("ctrl-v 2 j y").await;
543 cx.shared_clipboard().await.assert_eq("q\nj\nl");
544 cx.simulate_shared_keystrokes("p").await;
545 cx.shared_state().await.assert_eq(indoc! {"
546 The qˇquick brown
547 fox jjumps over
548 the llazy dog"});
549 cx.simulate_shared_keystrokes("v i w shift-p").await;
550 cx.shared_state().await.assert_eq(indoc! {"
551 The ˇq brown
552 fox jjjumps over
553 the lllazy dog"});
554 cx.simulate_shared_keystrokes("v i w shift-p").await;
555
556 cx.set_shared_state(indoc! {"
557 The ˇquick brown
558 fox jumps over
559 the lazy dog"})
560 .await;
561 cx.simulate_shared_keystrokes("ctrl-v j y").await;
562 cx.shared_clipboard().await.assert_eq("q\nj");
563 cx.simulate_shared_keystrokes("l ctrl-v 2 j shift-p").await;
564 cx.shared_state().await.assert_eq(indoc! {"
565 The qˇqick brown
566 fox jjmps over
567 the lzy dog"});
568
569 cx.simulate_shared_keystrokes("shift-v p").await;
570 cx.shared_state().await.assert_eq(indoc! {"
571 ˇq
572 j
573 fox jjmps over
574 the lzy dog"});
575 }
576
577 #[gpui::test]
578 async fn test_paste_indent(cx: &mut gpui::TestAppContext) {
579 let mut cx = VimTestContext::new_typescript(cx).await;
580
581 cx.set_state(
582 indoc! {"
583 class A {ˇ
584 }
585 "},
586 Mode::Normal,
587 );
588 cx.simulate_keystrokes("o a ( ) { escape");
589 cx.assert_state(
590 indoc! {"
591 class A {
592 a()ˇ{}
593 }
594 "},
595 Mode::Normal,
596 );
597 // cursor goes to the first non-blank character in the line;
598 cx.simulate_keystrokes("y y p");
599 cx.assert_state(
600 indoc! {"
601 class A {
602 a(){}
603 ˇa(){}
604 }
605 "},
606 Mode::Normal,
607 );
608 // indentation is preserved when pasting
609 cx.simulate_keystrokes("u shift-v up y shift-p");
610 cx.assert_state(
611 indoc! {"
612 ˇclass A {
613 a(){}
614 class A {
615 a(){}
616 }
617 "},
618 Mode::Normal,
619 );
620 }
621
622 #[gpui::test]
623 async fn test_paste_count(cx: &mut gpui::TestAppContext) {
624 let mut cx = NeovimBackedTestContext::new(cx).await;
625
626 cx.set_shared_state(indoc! {"
627 onˇe
628 two
629 three
630 "})
631 .await;
632 cx.simulate_shared_keystrokes("y y 3 p").await;
633 cx.shared_state().await.assert_eq(indoc! {"
634 one
635 ˇone
636 one
637 one
638 two
639 three
640 "});
641
642 cx.set_shared_state(indoc! {"
643 one
644 ˇtwo
645 three
646 "})
647 .await;
648 cx.simulate_shared_keystrokes("y $ $ 3 p").await;
649 cx.shared_state().await.assert_eq(indoc! {"
650 one
651 twotwotwotwˇo
652 three
653 "});
654 }
655
656 #[gpui::test]
657 async fn test_numbered_registers(cx: &mut gpui::TestAppContext) {
658 let mut cx = NeovimBackedTestContext::new(cx).await;
659
660 cx.update_global(|store: &mut SettingsStore, cx| {
661 store.update_user_settings::<VimSettings>(cx, |s| {
662 s.use_system_clipboard = Some(UseSystemClipboard::Never)
663 });
664 });
665
666 cx.set_shared_state(indoc! {"
667 The quick brown
668 fox jˇumps over
669 the lazy dog"})
670 .await;
671 cx.simulate_shared_keystrokes("y y \" 0 p").await;
672 cx.shared_register('0').await.assert_eq("fox jumps over\n");
673 cx.shared_register('"').await.assert_eq("fox jumps over\n");
674
675 cx.shared_state().await.assert_eq(indoc! {"
676 The quick brown
677 fox jumps over
678 ˇfox jumps over
679 the lazy dog"});
680 cx.simulate_shared_keystrokes("k k d d").await;
681 cx.shared_register('0').await.assert_eq("fox jumps over\n");
682 cx.shared_register('1').await.assert_eq("The quick brown\n");
683 cx.shared_register('"').await.assert_eq("The quick brown\n");
684
685 cx.simulate_shared_keystrokes("d d shift-g d d").await;
686 cx.shared_register('0').await.assert_eq("fox jumps over\n");
687 cx.shared_register('3').await.assert_eq("The quick brown\n");
688 cx.shared_register('2').await.assert_eq("fox jumps over\n");
689 cx.shared_register('1').await.assert_eq("the lazy dog\n");
690
691 cx.shared_state().await.assert_eq(indoc! {"
692 ˇfox jumps over"});
693
694 cx.simulate_shared_keystrokes("d d \" 3 p p \" 1 p").await;
695 cx.set_shared_state(indoc! {"
696 The quick brown
697 fox jumps over
698 ˇthe lazy dog"})
699 .await;
700 }
701
702 #[gpui::test]
703 async fn test_named_registers(cx: &mut gpui::TestAppContext) {
704 let mut cx = NeovimBackedTestContext::new(cx).await;
705
706 cx.update_global(|store: &mut SettingsStore, cx| {
707 store.update_user_settings::<VimSettings>(cx, |s| {
708 s.use_system_clipboard = Some(UseSystemClipboard::Never)
709 });
710 });
711
712 cx.set_shared_state(indoc! {"
713 The quick brown
714 fox jˇumps over
715 the lazy dog"})
716 .await;
717 cx.simulate_shared_keystrokes("\" a d a w").await;
718 cx.shared_register('a').await.assert_eq("jumps ");
719 cx.simulate_shared_keystrokes("\" shift-a d i w").await;
720 cx.shared_register('a').await.assert_eq("jumps over");
721 cx.shared_register('"').await.assert_eq("jumps over");
722 cx.simulate_shared_keystrokes("\" a p").await;
723 cx.shared_state().await.assert_eq(indoc! {"
724 The quick brown
725 fox jumps oveˇr
726 the lazy dog"});
727 cx.simulate_shared_keystrokes("\" a d a w").await;
728 cx.shared_register('a').await.assert_eq(" over");
729 }
730
731 #[gpui::test]
732 async fn test_special_registers(cx: &mut gpui::TestAppContext) {
733 let mut cx = NeovimBackedTestContext::new(cx).await;
734
735 cx.update_global(|store: &mut SettingsStore, cx| {
736 store.update_user_settings::<VimSettings>(cx, |s| {
737 s.use_system_clipboard = Some(UseSystemClipboard::Never)
738 });
739 });
740
741 cx.set_shared_state(indoc! {"
742 The quick brown
743 fox jˇumps over
744 the lazy dog"})
745 .await;
746 cx.simulate_shared_keystrokes("d i w").await;
747 cx.shared_register('-').await.assert_eq("jumps");
748 cx.simulate_shared_keystrokes("\" _ d d").await;
749 cx.shared_register('_').await.assert_eq("");
750
751 cx.simulate_shared_keystrokes("shift-v \" _ y w").await;
752 cx.shared_register('"').await.assert_eq("jumps");
753
754 cx.shared_state().await.assert_eq(indoc! {"
755 The quick brown
756 the ˇlazy dog"});
757 cx.simulate_shared_keystrokes("\" \" d ^").await;
758 cx.shared_register('0').await.assert_eq("the ");
759 cx.shared_register('"').await.assert_eq("the ");
760
761 cx.simulate_shared_keystrokes("^ \" + d $").await;
762 cx.shared_clipboard().await.assert_eq("lazy dog");
763 cx.shared_register('"').await.assert_eq("lazy dog");
764
765 cx.simulate_shared_keystrokes("/ d o g enter").await;
766 cx.shared_register('/').await.assert_eq("dog");
767 cx.simulate_shared_keystrokes("\" / shift-p").await;
768 cx.shared_state().await.assert_eq(indoc! {"
769 The quick brown
770 doˇg"});
771
772 // not testing nvim as it doesn't have a filename
773 cx.simulate_keystrokes("\" % p");
774 #[cfg(not(target_os = "windows"))]
775 cx.assert_state(
776 indoc! {"
777 The quick brown
778 dogdir/file.rˇs"},
779 Mode::Normal,
780 );
781 #[cfg(target_os = "windows")]
782 cx.assert_state(
783 indoc! {"
784 The quick brown
785 dogdir\\file.rˇs"},
786 Mode::Normal,
787 );
788 }
789
790 #[gpui::test]
791 async fn test_multicursor_paste(cx: &mut gpui::TestAppContext) {
792 let mut cx = VimTestContext::new(cx, true).await;
793
794 cx.update_global(|store: &mut SettingsStore, cx| {
795 store.update_user_settings::<VimSettings>(cx, |s| {
796 s.use_system_clipboard = Some(UseSystemClipboard::Never)
797 });
798 });
799
800 cx.set_state(
801 indoc! {"
802 ˇfish one
803 fish two
804 fish red
805 fish blue
806 "},
807 Mode::Normal,
808 );
809 cx.simulate_keystrokes("4 g l w escape d i w 0 shift-p");
810 cx.assert_state(
811 indoc! {"
812 onˇefish•
813 twˇofish•
814 reˇdfish•
815 bluˇefish•
816 "},
817 Mode::Normal,
818 );
819 }
820
821 #[gpui::test]
822 async fn test_replace_with_register(cx: &mut gpui::TestAppContext) {
823 let mut cx = VimTestContext::new(cx, true).await;
824
825 cx.set_state(
826 indoc! {"
827 ˇfish one
828 two three
829 "},
830 Mode::Normal,
831 );
832 cx.simulate_keystrokes("y i w");
833 cx.simulate_keystrokes("w");
834 cx.simulate_keystrokes("g r i w");
835 cx.assert_state(
836 indoc! {"
837 fish fisˇh
838 two three
839 "},
840 Mode::Normal,
841 );
842 cx.simulate_keystrokes("j b g r e");
843 cx.assert_state(
844 indoc! {"
845 fish fish
846 two fisˇh
847 "},
848 Mode::Normal,
849 );
850 let clipboard: Register = cx.read_from_clipboard().unwrap().into();
851 assert_eq!(clipboard.text, "fish");
852
853 cx.set_state(
854 indoc! {"
855 ˇfish one
856 two three
857 "},
858 Mode::Normal,
859 );
860 cx.simulate_keystrokes("y i w");
861 cx.simulate_keystrokes("w");
862 cx.simulate_keystrokes("v i w g r");
863 cx.assert_state(
864 indoc! {"
865 fish fisˇh
866 two three
867 "},
868 Mode::Normal,
869 );
870 let clipboard: Register = cx.read_from_clipboard().unwrap().into();
871 assert_eq!(clipboard.text, "fish");
872 }
873
874 #[gpui::test]
875 async fn test_replace_with_register_dot_repeat(cx: &mut gpui::TestAppContext) {
876 let mut cx = VimTestContext::new(cx, true).await;
877
878 cx.set_state(
879 indoc! {"
880 ˇfish one
881 two three
882 "},
883 Mode::Normal,
884 );
885 cx.simulate_keystrokes("y i w");
886 cx.simulate_keystrokes("w");
887 cx.simulate_keystrokes("g r i w");
888 cx.assert_state(
889 indoc! {"
890 fish fisˇh
891 two three
892 "},
893 Mode::Normal,
894 );
895 cx.simulate_keystrokes("j .");
896 cx.assert_state(
897 indoc! {"
898 fish fish
899 two fisˇh
900 "},
901 Mode::Normal,
902 );
903 }
904}