159 lines
5.2 KiB
Lua
159 lines
5.2 KiB
Lua
-- Copies reach the local Wayland clipboard and attached terminals. Reads use
|
|
-- Wayland or tmux's buffer, falling back to this Neovim's last copy. Never query
|
|
-- the terminal with OSC 52: support for writes does not imply permission to read,
|
|
-- and an unanswered query blocks ordinary paste for ten seconds. To insert new
|
|
-- text from the host when no readable clipboard exists, use terminal paste.
|
|
local M = {}
|
|
|
|
local function proc_lines(pid, file)
|
|
local ok, lines = pcall(vim.fn.readfile, "/proc/" .. pid .. "/" .. file)
|
|
return ok and lines or {}
|
|
end
|
|
|
|
local function proc_ppid(pid)
|
|
for _, line in ipairs(proc_lines(pid, "status")) do
|
|
local ppid = line:match("^PPid:%s+(%d+)")
|
|
if ppid then
|
|
return tonumber(ppid)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function ancestor_process_named(name)
|
|
local pid = vim.fn.getpid()
|
|
|
|
for _ = 1, 16 do
|
|
local ppid = proc_ppid(pid)
|
|
if not ppid or ppid <= 1 then
|
|
return false
|
|
end
|
|
|
|
local comm = proc_lines(ppid, "comm")[1] or ""
|
|
if comm:find(name, 1, true) then
|
|
return true
|
|
end
|
|
|
|
pid = ppid
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
function M.setup()
|
|
local in_tmux = vim.env.TMUX ~= nil
|
|
local in_ssh = vim.env.SSH_TTY ~= nil or vim.env.SSH_CONNECTION ~= nil
|
|
local in_herdr = vim.env.HERDR_PANE_ID ~= nil or ancestor_process_named("herdr")
|
|
|
|
if not (in_tmux or in_ssh or in_herdr) then
|
|
return
|
|
end
|
|
|
|
local osc52 = require("vim.ui.clipboard.osc52")
|
|
local has_wayland = vim.env.WAYLAND_DISPLAY ~= nil
|
|
and vim.fn.executable("wl-copy") == 1
|
|
and vim.fn.executable("wl-paste") == 1
|
|
|
|
local has_tmux = in_tmux and vim.fn.executable("tmux") == 1
|
|
local last_copy = {}
|
|
|
|
local function tmux_export_enabled()
|
|
local setting = vim.fn.systemlist({ "tmux", "show-options", "-sv", "set-clipboard" })
|
|
return vim.v.shell_error == 0 and (setting[1] == "on" or setting[1] == "external")
|
|
end
|
|
|
|
local function copy(register)
|
|
local emit = osc52.copy(register)
|
|
|
|
return function(lines, regtype)
|
|
last_copy[register] = { vim.deepcopy(lines), regtype }
|
|
|
|
if has_wayland then
|
|
local cmd = { "wl-copy", "--sensitive", "--type", "text/plain" }
|
|
if register == "*" then
|
|
cmd[#cmd + 1] = "--primary"
|
|
end
|
|
vim.fn.system(cmd, lines)
|
|
end
|
|
|
|
if vim.g.omarchy_remote_clipboard_osc52 ~= false then
|
|
if has_tmux and register == "+" then
|
|
-- tmux ignores zero-byte loads. Record the current buffer's identity
|
|
-- instead: all Neovim instances see empty until a new buffer arrives,
|
|
-- without deleting the user's shared tmux clipboard history.
|
|
if #lines == 0 or (#lines == 1 and lines[1] == "") then
|
|
vim.fn.system({ "tmux", "set-option", "-sF", "@omarchy-nvim-cleared-buffer", "#{buffer_name}" })
|
|
return
|
|
end
|
|
-- Let tmux emit OSC 52, avoiding its input parser's payload size limit.
|
|
-- Explicit -w bypasses set-clipboard, so honor the user's policy first.
|
|
local cmd = { "tmux", "load-buffer" }
|
|
if tmux_export_enabled() then
|
|
cmd[#cmd + 1] = "-w"
|
|
end
|
|
cmd[#cmd + 1] = "-"
|
|
vim.fn.system(cmd, lines)
|
|
if vim.v.shell_error == 0 then
|
|
return
|
|
end
|
|
end
|
|
emit(lines)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function paste(register)
|
|
return function()
|
|
local cmd
|
|
-- tmux's buffer is shared between Neovim instances on the server. Over
|
|
-- SSH, prefer it to the remote machine's unrelated graphical clipboard.
|
|
if has_tmux and register == "+" and (in_ssh or not has_wayland)
|
|
and vim.g.omarchy_remote_clipboard_osc52 ~= false then
|
|
local buffers = vim.fn.systemlist({ "tmux", "display-message", "-p",
|
|
"#{buffer_name}\n#{@omarchy-nvim-cleared-buffer}" })
|
|
if vim.v.shell_error ~= 0 then
|
|
return vim.deepcopy(last_copy[register] or { {}, "v" })
|
|
end
|
|
if not buffers[1] or buffers[1] == "" or buffers[1] == buffers[2] then
|
|
return { {}, "v" }
|
|
end
|
|
-- Pin the buffer selected above: a concurrent copy must not change
|
|
-- which payload we read after checking its identity against the marker.
|
|
cmd = { "tmux", "save-buffer", "-b", buffers[1], "-" }
|
|
elseif has_wayland then
|
|
cmd = { "wl-paste", "--no-newline" }
|
|
if register == "*" then
|
|
cmd[#cmd + 1] = "--primary"
|
|
end
|
|
end
|
|
|
|
if cmd then
|
|
local lines = vim.fn.systemlist(cmd, "", 1)
|
|
if vim.v.shell_error == 0 then
|
|
-- An empty clipboard is valid; only failed reads use the fallback.
|
|
if last_copy[register] and vim.deep_equal(lines, last_copy[register][1]) then
|
|
return vim.deepcopy(last_copy[register])
|
|
end
|
|
return lines
|
|
end
|
|
end
|
|
|
|
return vim.deepcopy(last_copy[register] or { {}, "v" })
|
|
end
|
|
end
|
|
|
|
vim.g.clipboard = {
|
|
name = "OmarchyRemoteClipboard",
|
|
copy = { ["+"] = copy("+"), ["*"] = copy("*") },
|
|
paste = { ["+"] = paste("+"), ["*"] = paste("*") },
|
|
cache_enabled = 0,
|
|
}
|
|
|
|
-- LazyVim clears this over SSH. Users can opt out before setup or override
|
|
-- the option afterward in config/options.lua.
|
|
if vim.g.omarchy_remote_clipboard_sync ~= false then
|
|
vim.opt.clipboard:append("unnamedplus")
|
|
end
|
|
end
|
|
|
|
return M
|