180 lines
5.4 KiB
Lua
180 lines
5.4 KiB
Lua
-- Clipboard for sessions whose yanks may need to reach another machine, so that
|
|
-- yanking, deleting and putting behave the same whether Neovim is local, in
|
|
-- tmux, over SSH, or any combination of those. LazyVim empties 'clipboard'
|
|
-- whenever SSH_CONNECTION is set, which leaves a plain `y` in the unnamed
|
|
-- register; this restores 'unnamedplus' and backs it with a provider that has a
|
|
-- fast path in every one of those environments.
|
|
--
|
|
-- Writes go to whichever transports the session has: the local Wayland
|
|
-- clipboard when there is a display, and the terminal on the other end of the
|
|
-- connection via OSC 52 -- emitted by tmux itself where possible, since `tmux
|
|
-- load-buffer -w` keeps the payload out of tmux's own input parser and its size
|
|
-- limit. Reads prefer whichever clipboard the person is actually sitting in
|
|
-- front of, and fall back to the last value this Neovim copied rather than
|
|
-- blocking on an OSC 52 query that most terminals refuse to answer.
|
|
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
|
|
|
|
local function empty(lines)
|
|
return lines == nil or #lines == 0 or (#lines == 1 and lines[1] == "")
|
|
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
|
|
|
|
-- The last value this Neovim put on the clipboard, per register, as
|
|
-- { lines, regtype }. A session with no readable clipboard still has to
|
|
-- answer a put, and answering it with our own last copy keeps `"+y` `"+p`
|
|
-- working instead of failing with E353.
|
|
local last_copy = {}
|
|
|
|
local function copy(register)
|
|
local emit = osc52.copy(register)
|
|
|
|
return function(lines, regtype)
|
|
last_copy[register] = { lines, regtype or "" }
|
|
|
|
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
|
|
return
|
|
end
|
|
|
|
-- tmux has no notion of a primary selection, so `*` keeps using the
|
|
-- escape sequence directly.
|
|
if has_tmux and register == "+" then
|
|
vim.fn.system({ "tmux", "load-buffer", "-w", "-" }, lines)
|
|
else
|
|
emit(lines)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function read_wayland(register)
|
|
local cmd = { "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 lines
|
|
end
|
|
end
|
|
|
|
-- `refresh-client -l` asks the attached client for its clipboard; the short
|
|
-- sleep gives the reply time to land before the buffer is read. A terminal
|
|
-- that refuses simply leaves tmux's newest buffer in place, so this returns
|
|
-- something useful either way and never blocks the way an OSC 52 query does.
|
|
local function read_tmux()
|
|
local lines = vim.fn.systemlist(
|
|
{ "sh", "-c", "tmux refresh-client -l 2>/dev/null; sleep 0.05; tmux save-buffer -" },
|
|
"",
|
|
1
|
|
)
|
|
if vim.v.shell_error == 0 then
|
|
return lines
|
|
end
|
|
end
|
|
|
|
local function read_osc52(register)
|
|
if not (vim.g.termfeatures or {}).osc52 then
|
|
return
|
|
end
|
|
|
|
local result = osc52.paste(register)()
|
|
if type(result) == "table" then
|
|
return result
|
|
end
|
|
end
|
|
|
|
local function paste(register)
|
|
return function()
|
|
local lines
|
|
|
|
-- Over SSH the clipboard worth reading is the one on the machine the
|
|
-- person is sitting at, which is the far end of the connection, not the
|
|
-- display this Neovim happens to have.
|
|
if in_ssh and has_tmux then
|
|
lines = read_tmux()
|
|
elseif has_wayland then
|
|
lines = read_wayland(register)
|
|
elseif has_tmux then
|
|
lines = read_tmux()
|
|
else
|
|
lines = read_osc52(register)
|
|
end
|
|
|
|
if not empty(lines) then
|
|
return lines
|
|
end
|
|
|
|
return last_copy[register] or { vim.fn.getreg('"', 1, true), vim.fn.getregtype('"') }
|
|
end
|
|
end
|
|
|
|
vim.g.clipboard = {
|
|
name = "OmarchyRemoteClipboard",
|
|
copy = { ["+"] = copy("+"), ["*"] = copy("*") },
|
|
paste = { ["+"] = paste("+"), ["*"] = paste("*") },
|
|
cache_enabled = 0,
|
|
}
|
|
|
|
-- LazyVim empties this whenever SSH_CONNECTION is set. Restore it so yanking,
|
|
-- deleting and putting reach the clipboard exactly as they do locally.
|
|
vim.opt.clipboard = "unnamedplus"
|
|
end
|
|
|
|
return M
|