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
Changes to tests/win_spawn_spec.lua. All files
@@ -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)