261020.22 · c8198e52b0c261dd1fe788962037c4910237a2c5
fix(win): stop the mediator's default pipe (serverlist pollution)
Every Windows nvim listens by default (`\\.\pipe\nvim.<pid>.<n>`), so the headless `win_spawn.lua` mediator - alive for the whole picker - was listed as a server by the `serverlist` picker (the discovery globs the pipe namespace, `providers/nvim.lua`). Its sibling scripts (`spawn.lua`, `rpc.lua`, `scripts/cli.lua`) already stop their default listener at startup; `win_spawn.lua` (added in 25d9e551) missed that guard.
- stop the default listener in `win_spawn.lua` (same `serverstop` guard)
- update the `providers/nvim.lua` discovery comment: our headless spawns
never register a pipe; third-party UI-less instances (`--embed`) are
still deliberately listed (filtering them emptied the picker for
embed-based workflows, see the note there)
- new `win_spawn_spec` case spawns the real mediator (kept alive via a
`ping` spec) and asserts no `\\.\pipe\nvim*` appears while it runs;
verified failing before the fix ("mediator left a listener:
//./pipe/nvim.8476.0") and passing after
The transient `previewer/fzf.lua` `--remote-ui` screenshot client is left as is: it lives only for a preview dump and a `--cmd` guard proved fragile (argument splitting); not observed in the picker.
Verification: full nightly 315/0 fails/19 notes; full stable 315/0/19; focused {win_spawn} 6/0, {win} 65/0, {serverlist,headless,fzf} 33/0; emmylua_check clean.
reading declarations
lua/fzf-lua/providers/nvim.lua
@@ -807,7 +807,10 @@ end ---no peer candidates; every address it reports is instead excluded below in ---all slash forms, so none of our own sockets/pipes can be listed. Entries ---failing an RPC probe, ourselves and our own `fzf-lua*` infra are skipped----everywhere; every other responsive listener is listed, including+---everywhere; our headless spawns (`scripts/cli.lua`, `spawn.lua`,+---`rpc.lua`, `win_spawn.lua`) additionally stop their default listener+---(`serverstop`), so they never register a pipe at all (every Windows nvim+---listens by default); every other responsive listener is listed, including ---UI-less `--embed` backends (indistinguishable from detached user ---sessions). Addresses stay in native ---form; `utils.serverlist_parse_entry` repairs the single backslash
@@ -8,6 +8,15 @@ -- the terminal job's exit code (see `fzf.raw_fzf`). assert(#vim.api.nvim_list_uis() == 0) +-- every Windows nvim listens by default (`\\.\pipe\nvim.<pid>.<n>`); the+-- mediator doesn't need the server and its pipe would be listed as a+-- server in the `serverlist` picker (`providers/nvim.lua` discovery globs+-- that namespace), stopping the server removes the pipe (same as+-- `spawn.lua`/`rpc.lua`/`scripts/cli.lua`)+if vim.v.servername and #vim.v.servername > 0 then+ pcall(vim.fn.serverstop, vim.v.servername)+end+ -- path to this file local _info = debug.getinfo(1, "S") ---@cast _info {source: string}
@@ -200,4 +200,51 @@ describe("Testing win_spawn (Windows one route)", function() eq(out, "") vim.fn.delete(infile) end)++ it("mediator does not leave a default listener (serverlist pollution)", function()+ helpers.SKIP_IF_NOT_WIN()+ ---@diagnostic disable-next-line: deprecated+ local _info = debug.getinfo(1, "S")+ ---@cast _info {source: string}+ local root = vim.fn.fnamemodify(_info.source:gsub("^@", ""), ":h:h")+ local script = (root .. "/lua/fzf-lua/win_spawn.lua"):gsub("\\", "/")++ local outfile = utils.tempname()+ local spec_path = utils.tempname()+ -- `ping` keeps the mediator alive for a few seconds (fzf exits too fast+ -- to sample the pipe namespace reliably)+ write_file(spec_path, libuv.serialize({+ bin = vim.fn.exepath("ping"),+ args = { "-n", "6", "127.0.0.1" },+ outfile = outfile,+ env = { SHELL = "cmd.exe" },+ }, true))++ local seen = utils.list_to_map(vim.fn.glob([[\\.\pipe\nvim*]], false, true))+ local rc ---@type integer?+ local handle = uv.spawn((vim.v.progpath):gsub("\\", "/"), {+ args = { "-u", "NONE", "-l", script, (spec_path:gsub("\\", "/")) },+ stdio = { nil, nil, nil },+ }, function(code, _)+ rc = code+ end)+ assert.is.True(handle ~= nil)+ -- sample while the mediator runs: every Windows nvim listens by default+ -- on `\\.\pipe\nvim.<pid>.<n>`, so without a `serverstop` guard the+ -- mediator registers a listener and the `serverlist` picker lists it+ -- (`providers/nvim.lua` discovery globs this namespace)+ local leaked = {}+ for _ = 1, 3 do+ vim.wait(900)+ for _, p in ipairs(vim.fn.glob([[\\.\pipe\nvim*]], false, true)) do+ if not seen[p] then leaked[#leaked + 1] = p end+ end+ end+ vim.wait(30000, function() return rc ~= nil end)+ vim.fn.delete(outfile)+ eq(rc, 0)+ if #leaked > 0 then+ error("mediator left a listener: " .. table.concat(leaked, ", "))+ end+ end) end)