Skip to content

feat(vim): yank to system clipboard (#5463)#9733

Open
LioSergent wants to merge 1 commit into
marimo-team:mainfrom
LioSergent:vim-sync-clipboard
Open

feat(vim): yank to system clipboard (#5463)#9733
LioSergent wants to merge 1 commit into
marimo-team:mainfrom
LioSergent:vim-sync-clipboard

Conversation

@LioSergent
Copy link
Copy Markdown
Contributor

@LioSergent LioSergent commented May 30, 2026

Add support for set clipboard=unnamedplus (and clipboard=unnamed) in vimrc. Yanking in vim normal mode now writes to the system clipboard; p/P reads from the system clipboard before pasting.

📝 Summary

Closes #5463

📋 Pre-Review Checklist

  • For large changes, or changes that affect the public API: this change was discussed or approved through an issue, on Discord, or the community discussions (Please provide a link if applicable).
  • Any AI generated code has been reviewed line-by-line by the human PR author, who stands by it.
  • Video or media evidence is provided for any visual changes (optional).

✅ Merge Checklist

  • I have read the contributor guidelines.
  • Documentation has been updated where applicable, including docstrings for API changes.
  • Tests have been added for the changes made.

Add support for `set clipboard=unnamedplus` (and `clipboard=unnamed`) in vimrc.
Yanking in vim normal mode now writes to the system clipboard; `p`/`P` reads
from the system clipboard before pasting.
@vercel
Copy link
Copy Markdown

vercel Bot commented May 30, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
marimo-docs Ready Ready Preview, Comment May 30, 2026 3:52pm

Request Review

@LioSergent LioSergent marked this pull request as draft May 30, 2026 15:51
@LioSergent LioSergent marked this pull request as ready for review May 30, 2026 15:58
@Light2Dark Light2Dark added the enhancement New feature or request label Jun 1, 2026
@kirangadhave
Copy link
Copy Markdown
Member

@cubic-dev-ai

@cubic-dev-ai
Copy link
Copy Markdown
Contributor

cubic-dev-ai Bot commented Jun 1, 2026

@cubic-dev-ai

@kirangadhave I have started the AI code review. It will take a few minutes to complete.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds limited support for :set clipboard=unnamedplus / clipboard=unnamed in the frontend vim keymap integration so that yanks sync into the system clipboard and p/P pulls from the system clipboard before pasting.

Changes:

  • Extend vimrc parsing to recognize the set command with an option argument.
  • Apply set clipboard=unnamedplus|unnamed at runtime by enabling clipboard synchronization with the vim unnamed register.
  • Intercept p/P to read the system clipboard into the unnamed register before delegating to the vim key handler.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.

File Description
frontend/src/core/codemirror/keymaps/vimrc.ts Adds set as a known vimrc command with an option argument.
frontend/src/core/codemirror/keymaps/vim.ts Enables clipboard sync via register patching and intercepts p/P to paste from system clipboard.
frontend/src/core/codemirror/keymaps/tests/vimrc.test.ts Adds tests for parsing set clipboard=unnamedplus and erroring on set with no args.

Comment on lines +332 to +337
unnamedRegister.setText = (text, linewise, blockwise) => {
origSetText(text, linewise, blockwise);
if (text) {
navigator.clipboard?.writeText(text).catch(() => {});
}
};
Comment on lines +338 to +345
unnamedRegister.pushText = (text, linewise) => {
origPushText(text, linewise);
if (text) {
navigator.clipboard
?.writeText(unnamedRegister.toString())
.catch(() => {});
}
};
Comment on lines +373 to +386
const origSet = _origSetTextFn;
navigator.clipboard
?.readText()
.then((text) => {
if (text) {
origSet(text, false, false);
}
Vim.handleKey(cm, key, "mapping");
})
.catch(() => {
// Clipboard unavailable — fall back to whatever is in the register.
Vim.handleKey(cm, key, "mapping");
});
return true;
Comment on lines +348 to +351
const isFirefox = /firefox/i.test(navigator.userAgent);
Logger.log(
`[vim] clipboard sync (unnamedplus) enabled${isFirefox ? " — Firefox will show a paste-permission popup on each p/P; workaround: set dom.events.testing.asyncClipboard=true in about:config (at your own risk)" : ""}`,
);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with this comment. We should just say that Firefox may ask for permissions. Users can figure out how to workaround it if they want. We don't want to prompt them on bypassing clipboard security.

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 3 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="frontend/src/core/codemirror/keymaps/vim.ts">

<violation number="1" location="frontend/src/core/codemirror/keymaps/vim.ts:334">
P2: `p`/`P` can paste stale register contents when system clipboard text is empty because empty clipboard values are ignored.</violation>
</file>
Architecture diagram
sequenceDiagram
    participant User as User
    participant VimRC as .vimrc File
    participant VimParser as vimrc Parser
    participant VimExt as Vim Extension
    participant CM as CodeMirror
    participant Register as CodeMirror Register
    participant Clipboard as System Clipboard (navigator.clipboard)

    User->>VimRC: writes "set clipboard=unnamedplus"
    VimExt->>VimParser: parseVimrc()
    VimParser-->>VimExt: commands [{name:"set", args:{option:"clipboard=unnamedplus"}}]
    VimExt->>VimExt: enableVimClipboardSync()
    Note over VimExt: Patches registerController.unnamedRegister.setText() and .pushText()
    VimExt->>Register: replaces setText/pushText with wrapper
    Register-->>VimExt: patched

    Note over User,VimExt: Yank in normal mode

    User->>Vim: executes y (yank) in normal mode
    Vim->>Register: setText("selected-text")
    Register->>Register: origSetText("selected-text")
    Register->>Clipboard: navigator.clipboard.writeText("selected-text")
    Clipboard-->>Register: ok (async)

    Note over User,VimExt: Paste in normal mode (p/P)

    User->>Vim: presses p (paste)
    Vim->>CM: interceptVimPaste(view, "p")
    alt clipboard sync enabled AND in normal mode AND no explicit register
        CM->>Clipboard: navigator.clipboard.readText()
        Clipboard-->>CM: "clipboard-text" (async)
        CM->>Register: origSetText("clipboard-text", false, false)
        CM->>Vim: Vim.handleKey(cm, "p", "mapping")
        Vim->>Register: reads unnamedRegister
        Register-->>Vim: "clipboard-text"
        Vim-->>User: pastes content from system clipboard
    else clipboard sync disabled OR explicit register OR not normal mode
        CM-->>Vim: returns false (fallback to normal vim paste)
        Vim->>Register: reads unnamedRegister (unchanged)
        Register-->>Vim: stored text
        Vim-->>User: pastes register content (no clipboard sync)
    else clipboard read fails
        CM->>Vim: Vim.handleKey(cm, key, "mapping")
        Vim->>Register: reads unnamedRegister (unchanged)
        Register-->>Vim: stored text
        Vim-->>User: pastes register content (clipboard read failed)
    end
Loading

Reply with feedback, questions, or to request a fix.

Re-trigger cubic


unnamedRegister.setText = (text, linewise, blockwise) => {
origSetText(text, linewise, blockwise);
if (text) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: p/P can paste stale register contents when system clipboard text is empty because empty clipboard values are ignored.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/src/core/codemirror/keymaps/vim.ts, line 334:

<comment>`p`/`P` can paste stale register contents when system clipboard text is empty because empty clipboard values are ignored.</comment>

<file context>
@@ -273,6 +305,87 @@ function isMacroActive() {
+
+  unnamedRegister.setText = (text, linewise, blockwise) => {
+    origSetText(text, linewise, blockwise);
+    if (text) {
+      navigator.clipboard?.writeText(text).catch(() => {});
+    }
</file context>

Copy link
Copy Markdown
Member

@kirangadhave kirangadhave left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @LioSergent for the PR. The PR is handrolling features codemirror-vim already supports in some way.

The library already syncs the system clipboard with the + register in both directions:

  • RegisterController.pushText calls navigator.clipboard.writeText when the register is +
  • the paste action reads from the system clipboard when the register is +

So "+y and "+p work as-is today.

The things in this PR that reimplement existing functionality:

  • enableVimClipboardSync monkey-patches Vim.getVimGlobalState_().registerController.unnamedRegister. This is a no-go, because we're patching a codemirror internal. It can break in future updates, and there's no test catching it, so a codemirror update would give us a silent failure with just a warning log.
  • interceptVimPaste reads the clipboard and re-dispatches Vim.handleKey outside the paste pipeline. I'm not sure it behaves correctly with count prefixes (3p) and dot-repeat. And "+p already does the async read internally, so this isn't needed.

The only thing actually missing is the aliasing: making a bare y/p use + without the prefix. The library has no option for that, but we shouldn't patch it via internals. Two ways forward:

  1. Add a clipboard option upstream in codemirror-vim. The plumbing (+ register, defineOption, RegisterController) is already there, so it belongs in the library. There's an open issue for this: replit/codemirror-vim#142

  2. Remap the operators onto the + register via the public Vim.noremap API (the same one vimrc noremap lines already use), e.g.:

    for (const ctx of ["normal", "visual"] as const) {
      for (const op of ["y", "d", "c", "s", "x"]) {
        Vim.noremap(op, `"+${op}`, ctx);
      }
    }
    Vim.noremap("p", '"+p', "normal");
    Vim.noremap("P", '"+P', "normal");

    This lets the library do all the clipboard I/O and drops the internal patching and the p/P interceptor entirely. The one tradeoff is that an explicit register prefix on a mapped key ("ay) would still route to +, which is acceptable for an opt-in setting.

Either way, I'd drop the internal function patching approach.

return;
}
const unnamedRegister =
Vim.getVimGlobalState_()?.registerController?.unnamedRegister;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is an internal codemirror function. it can break with future updates, we shouldn't rely on internals.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

vim yank to system clipboard

4 participants