Simplify remote clipboard and share Neovim yanks over OSC 52 inside tmux
Sessions inside tmux used to skip the clipboard provider entirely, so yanks only reached the machine's own Wayland clipboard and never traveled over OSC 52 to clients attached via SSH. Now every copy also emits OSC 52, which tmux turns into a buffer and rebroadcasts to all attached clients. Paste prefers wl-paste when a display is available. This also drops the private-file fallback clipboard and its regtype sidecar: Neovim's built-in clipboard layer already restores the register type when pasted content matches its cache, and OSC 52 queries answered by tmux cover the display-less paste case. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
515b566cb9
commit
b4e9935951
@@ -1,6 +1,6 @@
|
|||||||
# Maintainer: Ryan Hughes <ryan@omarchy.org>
|
# Maintainer: Ryan Hughes <ryan@omarchy.org>
|
||||||
pkgname=omarchy-nvim
|
pkgname=omarchy-nvim
|
||||||
pkgver=2026.6.17
|
pkgver=2026.7.15
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="Pre-built LazyVim configuration with cached plugins"
|
pkgdesc="Pre-built LazyVim configuration with cached plugins"
|
||||||
arch=('any')
|
arch=('any')
|
||||||
|
|||||||
@@ -1,69 +1,10 @@
|
|||||||
|
-- Clipboard for sessions whose yanks may need to reach another machine:
|
||||||
|
-- every copy is emitted as OSC 52 (inside tmux this becomes a tmux buffer,
|
||||||
|
-- rebroadcast to every attached client, local or SSH). Paste prefers the
|
||||||
|
-- local Wayland clipboard when one is available, so content copied in other
|
||||||
|
-- apps remains pasteable; without a display, paste is an OSC 52 query that
|
||||||
|
-- tmux (or the terminal) answers.
|
||||||
local M = {}
|
local M = {}
|
||||||
local uv = vim.uv or vim.loop
|
|
||||||
|
|
||||||
local function secure_runtime_dir()
|
|
||||||
local uid = uv.getuid()
|
|
||||||
local candidates = {}
|
|
||||||
|
|
||||||
local function add(path)
|
|
||||||
if path and path ~= "" then
|
|
||||||
candidates[#candidates + 1] = path
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
add(vim.env.XDG_RUNTIME_DIR)
|
|
||||||
if uid then
|
|
||||||
add("/run/user/" .. uid)
|
|
||||||
add("/dev/shm/nvim-remote-clipboard-" .. uid)
|
|
||||||
end
|
|
||||||
|
|
||||||
for _, dir in ipairs(candidates) do
|
|
||||||
if dir:sub(1, 9) == "/dev/shm/" then
|
|
||||||
pcall(vim.fn.mkdir, dir, "p", 448)
|
|
||||||
pcall(vim.fn.setfperm, dir, "rwx------")
|
|
||||||
end
|
|
||||||
|
|
||||||
local stat = uv.fs_stat(dir)
|
|
||||||
if stat and stat.type == "directory" and stat.uid == uid and stat.mode % 512 == 448 then
|
|
||||||
return dir
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function wayland_connection(runtime_dir)
|
|
||||||
if vim.fn.executable("wl-copy") ~= 1 or vim.fn.executable("wl-paste") ~= 1 then
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
local function valid_socket(path)
|
|
||||||
local stat = uv.fs_stat(path)
|
|
||||||
return stat and stat.type == "socket"
|
|
||||||
end
|
|
||||||
|
|
||||||
if vim.env.WAYLAND_DISPLAY and vim.env.WAYLAND_DISPLAY ~= "" then
|
|
||||||
local display = vim.env.WAYLAND_DISPLAY
|
|
||||||
local socket_path = display:sub(1, 1) == "/" and display or (runtime_dir .. "/" .. display)
|
|
||||||
if valid_socket(socket_path) then
|
|
||||||
return display
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
for _, path in ipairs(vim.fn.glob(runtime_dir .. "/wayland-*", false, true)) do
|
|
||||||
local display = vim.fn.fnamemodify(path, ":t")
|
|
||||||
if not display:match("%.lock$") and valid_socket(path) then
|
|
||||||
return display
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function write_private_file(path, payload)
|
|
||||||
local tmp_path = path .. "." .. vim.fn.getpid() .. ".tmp"
|
|
||||||
|
|
||||||
vim.fn.writefile({ payload }, tmp_path, "b")
|
|
||||||
vim.fn.setfperm(tmp_path, "rw-------")
|
|
||||||
vim.fn.rename(tmp_path, path)
|
|
||||||
vim.fn.setfperm(path, "rw-------")
|
|
||||||
end
|
|
||||||
|
|
||||||
local function proc_lines(pid, file)
|
local function proc_lines(pid, file)
|
||||||
local ok, lines = pcall(vim.fn.readfile, "/proc/" .. pid .. "/" .. file)
|
local ok, lines = pcall(vim.fn.readfile, "/proc/" .. pid .. "/" .. file)
|
||||||
@@ -99,177 +40,60 @@ local function ancestor_process_named(name)
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
local function copy_to_client_clipboard(register, lines)
|
|
||||||
if vim.g.omarchy_remote_clipboard_osc52 == false then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local ok, osc52 = pcall(require, "vim.ui.clipboard.osc52")
|
|
||||||
if ok then
|
|
||||||
pcall(osc52.copy(register), lines)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.setup()
|
function M.setup()
|
||||||
local in_tmux = vim.env.TMUX ~= nil
|
local in_tmux = vim.env.TMUX ~= nil
|
||||||
local has_display = vim.env.WAYLAND_DISPLAY or vim.env.DISPLAY
|
local in_ssh = vim.env.SSH_TTY ~= nil or vim.env.SSH_CONNECTION ~= nil
|
||||||
local in_remote_session = vim.env.SSH_TTY or vim.env.SSH_CONNECTION
|
|
||||||
local in_herdr = vim.env.HERDR_PANE_ID ~= nil or ancestor_process_named("herdr")
|
local in_herdr = vim.env.HERDR_PANE_ID ~= nil or ancestor_process_named("herdr")
|
||||||
local needs_remote_clipboard = not in_tmux and (in_herdr or (in_remote_session and not has_display))
|
|
||||||
|
|
||||||
if not needs_remote_clipboard then
|
if not (in_tmux or in_ssh or in_herdr) then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local runtime_dir = secure_runtime_dir()
|
local osc52 = require("vim.ui.clipboard.osc52")
|
||||||
if not runtime_dir then
|
local has_wayland = vim.env.WAYLAND_DISPLAY ~= nil
|
||||||
return
|
and vim.fn.executable("wl-copy") == 1
|
||||||
end
|
and vim.fn.executable("wl-paste") == 1
|
||||||
|
|
||||||
local wayland_display = wayland_connection(runtime_dir)
|
local function copy(register)
|
||||||
local ttl_seconds = 10 * 60
|
local emit = osc52.copy(register)
|
||||||
|
|
||||||
local function empty_clipboard()
|
return function(lines)
|
||||||
return { {}, "v" }
|
if has_wayland then
|
||||||
end
|
local cmd = { "wl-copy", "--sensitive", "--type", "text/plain" }
|
||||||
|
|
||||||
local function read_regtype(path)
|
|
||||||
local stat = uv.fs_stat(path)
|
|
||||||
if not stat or stat.uid ~= uv.getuid() or stat.mode % 512 ~= 384 then
|
|
||||||
return "v"
|
|
||||||
end
|
|
||||||
|
|
||||||
local ok, file_lines = pcall(vim.fn.readfile, path)
|
|
||||||
if not ok or #file_lines == 0 then
|
|
||||||
return "v"
|
|
||||||
end
|
|
||||||
|
|
||||||
local ok_decode, data = pcall(vim.fn.json_decode, table.concat(file_lines, "\n"))
|
|
||||||
if not ok_decode or type(data) ~= "table" then
|
|
||||||
return "v"
|
|
||||||
end
|
|
||||||
|
|
||||||
if type(data.created_at) == "number" and os.time() - data.created_at > ttl_seconds then
|
|
||||||
vim.fn.delete(path)
|
|
||||||
return "v"
|
|
||||||
end
|
|
||||||
|
|
||||||
return type(data.regtype) == "string" and data.regtype or "v"
|
|
||||||
end
|
|
||||||
|
|
||||||
if wayland_display then
|
|
||||||
local regtype_path = runtime_dir .. "/nvim-remote-clipboard-regtype.json"
|
|
||||||
local env = {
|
|
||||||
"env",
|
|
||||||
"XDG_RUNTIME_DIR=" .. runtime_dir,
|
|
||||||
"WAYLAND_DISPLAY=" .. wayland_display,
|
|
||||||
}
|
|
||||||
|
|
||||||
local function write_regtype(regtype)
|
|
||||||
write_private_file(regtype_path, vim.fn.json_encode({
|
|
||||||
regtype = regtype,
|
|
||||||
created_at = os.time(),
|
|
||||||
}))
|
|
||||||
end
|
|
||||||
|
|
||||||
local function copy(register)
|
|
||||||
return function(lines, regtype)
|
|
||||||
write_regtype(regtype)
|
|
||||||
|
|
||||||
local cmd = vim.list_extend(vim.deepcopy(env), {
|
|
||||||
"wl-copy",
|
|
||||||
"--sensitive",
|
|
||||||
"--type",
|
|
||||||
"text/plain",
|
|
||||||
})
|
|
||||||
if register == "*" then
|
if register == "*" then
|
||||||
cmd[#cmd + 1] = "--primary"
|
cmd[#cmd + 1] = "--primary"
|
||||||
end
|
end
|
||||||
|
|
||||||
vim.fn.system(cmd, lines)
|
vim.fn.system(cmd, lines)
|
||||||
copy_to_client_clipboard(register, lines)
|
end
|
||||||
|
|
||||||
|
if vim.g.omarchy_remote_clipboard_osc52 ~= false then
|
||||||
|
emit(lines)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function paste(register)
|
|
||||||
return function()
|
|
||||||
local cmd = vim.list_extend(vim.deepcopy(env), { "wl-paste", "--no-newline" })
|
|
||||||
if register == "*" then
|
|
||||||
cmd[#cmd + 1] = "--primary"
|
|
||||||
end
|
|
||||||
|
|
||||||
local lines = vim.fn.systemlist(cmd, "", 1)
|
|
||||||
if vim.v.shell_error ~= 0 then
|
|
||||||
return empty_clipboard()
|
|
||||||
end
|
|
||||||
|
|
||||||
return { lines, read_regtype(regtype_path) }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
vim.g.clipboard = {
|
|
||||||
name = "OmarchyWaylandClipboard",
|
|
||||||
copy = {
|
|
||||||
["+"] = copy("+"),
|
|
||||||
["*"] = copy("*"),
|
|
||||||
},
|
|
||||||
paste = {
|
|
||||||
["+"] = paste("+"),
|
|
||||||
["*"] = paste("*"),
|
|
||||||
},
|
|
||||||
cache_enabled = 0,
|
|
||||||
}
|
|
||||||
else
|
|
||||||
local clipboard_path = runtime_dir .. "/nvim-remote-clipboard.json"
|
|
||||||
|
|
||||||
local function copy(register)
|
|
||||||
return function(lines, regtype)
|
|
||||||
write_private_file(clipboard_path, vim.fn.json_encode({
|
|
||||||
lines = lines,
|
|
||||||
regtype = regtype,
|
|
||||||
created_at = os.time(),
|
|
||||||
}))
|
|
||||||
copy_to_client_clipboard(register, lines)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function read_clipboard()
|
|
||||||
local stat = uv.fs_stat(clipboard_path)
|
|
||||||
if not stat or stat.uid ~= uv.getuid() or stat.mode % 512 ~= 384 then
|
|
||||||
return empty_clipboard()
|
|
||||||
end
|
|
||||||
|
|
||||||
local ok, file_lines = pcall(vim.fn.readfile, clipboard_path)
|
|
||||||
if not ok or #file_lines == 0 then
|
|
||||||
return empty_clipboard()
|
|
||||||
end
|
|
||||||
|
|
||||||
local ok_decode, data = pcall(vim.fn.json_decode, table.concat(file_lines, "\n"))
|
|
||||||
if not ok_decode or type(data) ~= "table" or type(data.lines) ~= "table" then
|
|
||||||
return empty_clipboard()
|
|
||||||
end
|
|
||||||
|
|
||||||
if type(data.created_at) == "number" and os.time() - data.created_at > ttl_seconds then
|
|
||||||
vim.fn.delete(clipboard_path)
|
|
||||||
return empty_clipboard()
|
|
||||||
end
|
|
||||||
|
|
||||||
return { data.lines, data.regtype or "v" }
|
|
||||||
end
|
|
||||||
|
|
||||||
vim.g.clipboard = {
|
|
||||||
name = "RemoteRuntimeClipboard",
|
|
||||||
copy = {
|
|
||||||
["+"] = copy("+"),
|
|
||||||
["*"] = copy("*"),
|
|
||||||
},
|
|
||||||
paste = {
|
|
||||||
["+"] = read_clipboard,
|
|
||||||
["*"] = read_clipboard,
|
|
||||||
},
|
|
||||||
cache_enabled = 0,
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function paste(register)
|
||||||
|
if not has_wayland then
|
||||||
|
return osc52.paste(register)
|
||||||
|
end
|
||||||
|
|
||||||
|
return function()
|
||||||
|
local cmd = { "wl-paste", "--no-newline" }
|
||||||
|
if register == "*" then
|
||||||
|
cmd[#cmd + 1] = "--primary"
|
||||||
|
end
|
||||||
|
|
||||||
|
local lines = vim.fn.systemlist(cmd, "", 1)
|
||||||
|
return vim.v.shell_error == 0 and lines or {}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
vim.g.clipboard = {
|
||||||
|
name = "OmarchyRemoteClipboard",
|
||||||
|
copy = { ["+"] = copy("+"), ["*"] = copy("*") },
|
||||||
|
paste = { ["+"] = paste("+"), ["*"] = paste("*") },
|
||||||
|
cache_enabled = 0,
|
||||||
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|||||||
Reference in New Issue
Block a user