Skip to content

Give remote Neovim the same clipboard behavior as local - #125

Open
dhh wants to merge 1 commit into
masterfrom
nvim-clipboard-parity
Open

Give remote Neovim the same clipboard behavior as local#125
dhh wants to merge 1 commit into
masterfrom
nvim-clipboard-parity

Conversation

@dhh

@dhh dhh commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

The bug

LazyVim empties 'clipboard' whenever SSH_CONNECTION is set:

opt.clipboard = vim.env.SSH_CONNECTION and "" or "unnamedplus"

So a plain y stayed in the unnamed register and never reached the OSC 52
provider this package ships. In an SSH or tmux session yanks never left the
machine — only an explicit "+y did.

Upstream leaves the option empty so Neovim's own OSC 52 autodetection can win,
gated on &clipboard ==# '' in provider/clipboard.vim. That gate is
irrelevant here, because remote_clipboard.lua assigns g:clipboard directly
and takes the explicit branch well before it — and inside tmux Neovim picks its
tmux provider instead, never reaching the OSC 52 branch at all. Two mechanisms
solving the same problem, each unaware of the other.

The fix

Set 'unnamedplus' so yanking, deleting and putting behave exactly as they do
locally. That puts the provider's paste path on every p, which the old one
could not carry, so it grew two fixes:

Reads no longer stall. The old path answered every read with an OSC 52
query. Terminals and tmux mostly refuse those, and Neovim blocks 1s, prints
Waiting for OSC 52 response..., then blocks 9s more — a 10 second stall per
put. Reads now use whatever transport the session has: tmux's own clipboard
request, wl-paste where there is a display, and an OSC 52 query only when
Neovim itself flagged termfeatures.osc52.

"+y then "+p no longer fails with E353. Nothing kept what was copied,
so a read that came back empty lost the text. Each register now retains its
last { lines, regtype }, which answers a put when no clipboard is readable
and keeps linewise yanks linewise.

Copies inside tmux go through tmux load-buffer -w -, so tmux emits the escape
sequence itself and the payload never crosses tmux's input parser, which
discards any sequence over input-buffer-size (1 MiB default).

Over SSH a read prefers the far end of the connection, since that is the machine
the person is sitting at. A box with its own display still receives every copy
via wl-copy, so both clipboards stay usable.

Behavior change worth calling out

'unnamedplus' routes deletes and changes through the clipboard too, so dd
and x now overwrite the host clipboard and, in shared tmux, reach every
attached client. That is what local behavior is, but it is new for remote
sessions and means ordinary editing exports text off the machine.

One parity gap remains: wl-copy --sensitive keeps clipboard managers from
recording these values, while a purely local Neovim uses the built-in provider
without that flag. Dropping it would complete the parity but reverses a
deliberate choice, so it is left alone here.

Existing installs are updated by a migration in basecamp/omarchy, which
reinstalls the provider from /usr/share/omarchy-nvim/config. That migration
needs to ship alongside this package bump — omarchy update runs packages
first, then migrations.

— 🤖 Claude, posting on behalf of @dhh

LazyVim empties 'clipboard' whenever SSH_CONNECTION is set, so a plain `y`
stayed in the unnamed register and never reached the OSC 52 provider this
package ships. Yanks in an SSH or tmux session simply never left the machine;
only an explicit `"+y` did. Upstream leaves the option empty so Neovim's own
OSC 52 autodetection can win, but that autodetection is irrelevant here since
this file assigns g:clipboard directly, and it is skipped inside tmux anyway.

Set 'unnamedplus' so yanking, deleting and putting reach the clipboard exactly
as they do locally. That puts the provider's paste path on every `p`, which the
old one could not carry:

  - It answered a read with an OSC 52 query. Terminals and tmux mostly refuse
    those, and Neovim blocks 1s, prints "Waiting for OSC 52 response...", then
    blocks 9s more before giving up -- a 10s stall per put. Reads now use the
    transport the session actually has: tmux's own clipboard request, wl-paste
    where there is a display, and an OSC 52 query only when Neovim itself
    detected support.

  - `"+y` followed by `"+p` failed with E353 once a read could come back empty,
    because nothing kept what was copied. Each register now retains its last
    { lines, regtype }, which answers a put when no clipboard can be read and
    keeps linewise yanks linewise.

Copies inside tmux go through `tmux load-buffer -w -` so tmux emits the escape
sequence itself. The payload never crosses tmux's input parser, which discards
any sequence over input-buffer-size, 1 MiB by default.

Over SSH a read prefers the far end of the connection, since that is the
machine the person is sitting at. A box with its own display still gets every
copy through wl-copy, so both clipboards stay usable.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@dhh
dhh force-pushed the nvim-clipboard-parity branch from e1078f9 to 47d31f2 Compare August 7, 2026 22:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant