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