From 179af67c0fa13d2e3bb981a59c99151120295768 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Soares?= <37777652+Dnreikronos@users.noreply.github.com> Date: Tue, 21 Apr 2026 17:41:42 -0300 Subject: [PATCH] vim: Preserve system clipboard when pasting over visual selection (#52948) Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [ ] Unsafe blocks (if any) have justifying comments - [x] The content is consistent with the UI/UX checklist - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Closes #52352 Release Notes: - Fixed system clipboard being overwritten when pasting over a visual selection with Cmd-V/Ctrl-V in vim mode --- crates/vim/src/normal/paste.rs | 37 +++++++++++++++++++++++++++++++++- crates/vim/src/vim.rs | 4 +++- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/crates/vim/src/normal/paste.rs b/crates/vim/src/normal/paste.rs index fab9b353e3e9bb5b5d00d9d415783b4a5a31ae95..f3df2ce1c07d94619933a03cf5989022daedd4de 100644 --- a/crates/vim/src/normal/paste.rs +++ b/crates/vim/src/normal/paste.rs @@ -25,7 +25,7 @@ pub struct Paste { #[serde(default)] before: bool, #[serde(default)] - preserve_clipboard: bool, + pub(crate) preserve_clipboard: bool, } impl Vim { @@ -835,6 +835,41 @@ mod test { ); } + #[gpui::test] + async fn test_editor_paste_visual_preserves_system_clipboard(cx: &mut gpui::TestAppContext) { + let mut cx = VimTestContext::new(cx, true).await; + + cx.set_state( + indoc! {" + The quick brown + fox ˇjumps over + the lazy dog"}, + Mode::Normal, + ); + + // Put known content on the system clipboard + cx.write_to_clipboard(ClipboardItem::new_string("from clipboard".to_string())); + + // Select "jumps" in visual mode, then editor::Paste (Cmd-V / Ctrl-V) + cx.simulate_keystrokes("v i w"); + cx.dispatch_action(editor::actions::Paste); + + // The selected text should be replaced with clipboard content + cx.assert_state( + indoc! {" + The quick brown + fox from clipboarˇd over + the lazy dog"}, + Mode::Normal, + ); + + // System clipboard must still hold the original value, not "jumps" + assert_eq!( + cx.read_from_clipboard().map(|item| item.text().unwrap()), + Some("from clipboard".into()), + ); + } + #[gpui::test] async fn test_numbered_registers(cx: &mut gpui::TestAppContext) { let mut cx = NeovimBackedTestContext::new(cx).await; diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index 987def8a1ce63ffc0cf58dd63e4a1eb3cd4ddb60..d9617d34ecfaba5dd64a76c75ca379e5a08b0717 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -965,7 +965,9 @@ impl Vim { Mode::Replace => vim.paste_replace(window, cx), Mode::Visual | Mode::VisualLine | Mode::VisualBlock => { vim.selected_register.replace('+'); - vim.paste(&VimPaste::default(), window, cx); + let mut action = VimPaste::default(); + action.preserve_clipboard = true; + vim.paste(&action, window, cx); } _ => { vim.update_editor(cx, |_, editor, cx| editor.paste(&Paste, window, cx));