From 239f62dc3fb2069213926125bb27e24f3672bf6c Mon Sep 17 00:00:00 2001 From: Zsolt Simon Date: Fri, 31 Jul 2026 15:03:18 +1000 Subject: [PATCH] fix(kitty): translate paths for a Windows-side terminal under WSL When Neovim runs inside WSL but the terminal emulator is a Windows program -- in practice WezTerm, the one Windows terminal implementing this protocol -- transmit_medium=file hands it a path the two processes don't agree on: Neovim sends /tmp/foo.png and the terminal resolves it against the Windows filesystem, where it doesn't exist. The transmit silently does nothing, and the placement that follows draws whatever still occupies that image id -- so the symptom is usually a stale or unrelated image, not a blank. Verified with a bare protocol probe: t=f with /tmp/x.png renders nothing, while the same command with \\wsl.localhost\\tmp\x.png renders correctly, as does t=d with inlined bytes. So the path is the variable, not the payload. Translate via wslpath -w, cached per path since the same file is retransmitted across renders. Gate on the terminal actually being Windows-side rather than on WSL alone: a Linux-native terminal under WSLg shares our filesystem and cannot open a UNC path, so translating there would break a working setup. Linux-side terminals set their own env vars in our process (WEZTERM_PANE, KITTY_WINDOW_ID, ...) and a Windows-side one cannot, so their absence is the signal. Verified both ways: WezTerm on Windows gets the UNC path, kitty under WSLg keeps /tmp/... and both render. Co-Authored-By: Claude Opus 5 --- lua/image/backends/kitty/helpers.lua | 40 ++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/lua/image/backends/kitty/helpers.lua b/lua/image/backends/kitty/helpers.lua index 7a49b4a..fa102a4 100644 --- a/lua/image/backends/kitty/helpers.lua +++ b/lua/image/backends/kitty/helpers.lua @@ -11,6 +11,43 @@ if not stdout then error("failed to open stdout") end local is_SSH = (vim.env.SSH_CLIENT ~= nil) or (vim.env.SSH_TTY ~= nil) +-- A Windows-side terminal (in practice WezTerm) resolves the path we send it for +-- transmit_medium=file against the Windows filesystem, where /tmp/... does not +-- exist -- so the transmit silently does nothing. Translate to a UNC path +-- (\\wsl.localhost\\tmp\...) so the terminal can find it. +-- +-- Only when the terminal really is on the Windows side: a Linux-native terminal +-- under WSLg shares our filesystem and cannot open a UNC path, so translating +-- there would break a setup that works. Terminals running Linux-side set their +-- own env vars in our process; a Windows-side one cannot, so their absence is +-- the signal. +local is_WSL = (vim.env.WSL_DISTRO_NAME ~= nil) or (vim.env.WSL_INTEROP ~= nil) +local is_linux_side_terminal = (vim.env.WEZTERM_PANE ~= nil) + or (vim.env.WEZTERM_EXECUTABLE ~= nil) + or (vim.env.KITTY_WINDOW_ID ~= nil) + or (vim.env.KITTY_INSTALLATION_DIR ~= nil) + or (vim.env.GHOSTTY_RESOURCES_DIR ~= nil) +local needs_windows_path = is_WSL and not is_linux_side_terminal + +---@type table +local win_path_cache = {} + +---@param path string +---@return string +local to_terminal_path = function(path) + if not needs_windows_path or vim.fn.executable("wslpath") == 0 then return path end + local cached = win_path_cache[path] + if cached then return cached end + + local out = vim.fn.system({ "wslpath", "-w", path }) + if vim.v.shell_error ~= 0 then return path end + out = vim.fn.trim(out) + if out == "" then return path end + + win_path_cache[path] = out + return out +end + -- https://github.com/edluffy/hologram.nvim/blob/main/lua/hologram/terminal.lua#L77 local DEFAULT_DIRECT_CHUNK_SIZE = 4096 @@ -123,6 +160,9 @@ local write_graphics = function(config, data, direct_chunk_size) if not ok then error(result) end if not close_ok then error(close_err) end data = result + else + -- transmit_medium=file: `data` is a path the terminal will open itself. + data = to_terminal_path(data) end data = vim.base64.encode(data):gsub("%-", "/") local chunks = get_chunked(data, direct_chunk_size)