@ 261105.2

261020.15 · ee1d77b5e7d5ac3b2661ffddb6243a7b477370fa

feat(win): spawn fzf via uv.spawn on a single route (CLI + TUI)

Windows fzf launches ran through `cmd.exe /c` with a single assembled command line, so shell escaping had to survive two layers (nvim -> cmd), user fragments needed caret escaping, and command lines over cmd.exe's 8191-char limit fell back to a PowerShell `ProcessStartInfo` script with its own quoting rules. The fallback also owned the producer pump and the result-file redirect, so quoting and lifecycle bugs only reproduced on one of the two paths.

Replace both with one managed route: `libuv.win_spawn` runs fzf with the raw argv (`_cli_segments`; no shell, no escaping, no command line limit), redirects stdout through the result file descriptor and pumps an optional producer (`cmd.exe /c <cmd>`) into fzf's stdin -- every chunk forwarded as-is, immediately, with backpressure (4MB/1MB watermarks). When fzf exits, the producer's process tree is reaped via `taskkill /PID /T /F`, which also covers producers blocked writing into a dead stdin.

TUI hosts (`utils.termopen`) need fzf to own the terminal buffer's pty: `win_spawn.lua`, a mediator nvim attached to that pty (`-u NONE -l`, like the existing `spawn.lua` producers), reads the serialized spec from a temp file (deleted right away), runs the same spawn route and exits with fzf's code. The CLI host (`scripts/cli.lua`) calls `libuv.win_spawn` in-process with a scheduled `on_exit`, keeping stdio like a bare `fzf`.

User fragments are cmd-ready strings (`shellescape` caret style), so `libuv.cmd_argv` reproduces what `cmd.exe /c` plus the Win32 argv parser would deliver (caret consumption, then quoting rules) and replaces `shellparse` on Windows -- existing configs keep working, including `--bind=load:...` payloads built by `stringify_data` (regression tests).

Removed: the PowerShell fallback and its BOM strip, `libuv.ps_escape`, the `escape_cli` "powershell" variant and the fzf_spec tests for the fallback. Note: fragments are now data, exactly like on posix -- `cmd` operators in user options are no longer interpreted by an outer shell (caret-escaped fragments are unaffected; values are still expanded by any shell fzf itself invokes).

Tests: `win_spawn_spec` (byte-exact pump/argv quoting, exit codes, 4.8MB backpressure, mediator round-trip, producer tree-kill on early exit), `libuv_spec` (`cmd_argv` round-trips, callback payload), `fzf_spec` (route wiring for both hosts), `api_spec` repro case.

reading declarations

Changes to scripts/cli.lua. All files

scripts/cli.lua

@@ -54,12 +54,26 @@ end  _G.fzf_jobstart = function(cmd, opts)   if fn.has("win32") == 1 or fn.has("win64") == 1 then-    -- Inherit stdio like bare `fzf` (works on any terminal: conhost,-    -- Windows Terminal, tmux pane). Join argv verbatim for the `cmd.exe`-    -- launcher: the caret-escaped args from `fzf.raw_fzf` are built for-    -- raw `cmd /c` parsing and libuv quoting corrupts them ("...syntax-    -- is incorrect"). The PowerShell fallback (>8000 chars) keeps-    -- default quoting.+    -- One managed Windows route: `fzf.raw_fzf` hands us a `win_spawn` spec+    -- (raw argv, result file, optional item command); `libuv.win_spawn`+    -- runs fzf directly via `uv.spawn` -- no shell, no escaping, no+    -- PowerShell fallback, results via file descriptor and the item+    -- command piped into fzf's stdin (see `libuv.win_spawn`).+    if cmd.__win_spawn then+      FzfLua.libuv.win_spawn(cmd.__win_spawn, {+        on_exit = vim.schedule_wrap(function(rc)+          opts.on_exit(nil, rc, nil)+          if not opts.no_quit and #api.nvim_get_proc_children(fn.getpid()) == 0 then+            vim.cmd.cquit { count = rc, bang = true }+          end+        end),+      })+      return+    end+    -- Legacy `cmd.exe` launcher for callers without raw segments (direct+    -- `raw_fzf` invocations). Join argv verbatim for `cmd.exe`: the+    -- caret-escaped args from `fzf.raw_fzf` are built for raw `cmd /c`+    -- parsing and libuv quoting corrupts them ("...syntax is incorrect").     -- stderr: fzf's light renderer (`--height`) requires a console on     -- stderr (it draws the UI there and falls back to fullscreen without     -- one, see `IsLightRendererSupported` in src/tui/light_windows.go).