local NVIM_BIN = os.getenv("FZF_LUA_NVIM_BIN") or vim.v.progpath
local NVIM_RUNTIME = os.getenv("FZF_LUA_NVIM_RUNTIME") or os.getenv("VIMRUNTIME")
if NVIM_RUNTIME then vim.env.VIMRUNTIME = NVIM_RUNTIME end
---@diagnostic disable-next-line: deprecated
local api, uv, fn = vim.api, vim.uv or vim.loop, vim.fn
local _is_win = fn.has("win32") == 1 or fn.has("win64") == 1
-- POSIX-only ffi: `ioctl`, `fcntl`, `fork`, `execl`, etc. do not exist on
-- Windows (calling them errors with "cannot resolve symbol"). Keep `ffi`
-- nil on Windows so all helpers below take their portable fallbacks.
local ffi
if not _is_win and package.preload.ffi then
-- `require` caches the module and shares the CType state across modules;
-- calling the constructor directly would re-initialize it and drop every
-- declaration made by other modules (see `win_ffi_init` in libuv.lua)
ffi = require("ffi")
end
---@diagnostic disable-next-line: call-non-callable, need-check-nil
pcall(function()
---@diagnostic disable-next-line: call-non-callable, need-check-nil
ffi.cdef [[
struct winsize {
unsigned short ws_row;
unsigned short ws_col;
unsigned short ws_xpixel;
unsigned short ws_ypixel;
};
int ioctl(int fd, unsigned long request, ...);
int fcntl(int fd, int cmd, ...);
int execl(const char *, const char *, ...);
int fork(void);
int isatty(int fd);
int fileno(void *stream);
void _exit(int status);
]]
end)
local function quit() vim.cmd.quit() end
local function parse_entries(s, o)
return vim.tbl_map(function(e)
e = FzfLua.path.entry_to_file(e --[[@as string]], o)
e.path = FzfLua.path.relative_to(assert(e.path), FzfLua.utils.cwd())
return e
end, s)
end
local function isatty(file)
if not ffi then return false end
local fd = ffi.C.fileno(file)
return ffi.C.isatty(fd) ~= 0
end
local function fork(cmd, ...)
if not ffi then return false end
local pid = ffi.C.fork()
if pid < 0 then return end -- fork failed
if pid > 0 then return end -- parent process, do nothing
-- pid == 0, child process, build the shell command
-- tiny delay to let parent (fzf-lua) exit
-- then exec nvim conneced to /dev/tty
local shell_cmd = string.format(
"sleep 0.05; %s %s </dev/tty >/dev/tty 2>/dev/tty", cmd,
table.concat(vim.tbl_map(function(x) return FzfLua.libuv.shellescape(x) end, { ... }), " ")
)
-- Replace the post-fork child with `/bin/sh` immediately: continuing to
-- run Neovim code (and especially Neovim's teardown via `os.exit()`) on
-- the libuv state copied by `fork()` is undefined without
-- `uv_loop_fork()` and can leave a 100%-CPU spinning process behind
-- (observed on macOS with nvim 0.13-dev, #2823). `os.execute()` uses
-- `/bin/sh -c` as well, so this is behavior-preserving.
---@diagnostic disable-next-line: call-non-callable, need-check-nil
ffi.C.execl("/bin/sh", "sh", "-c", shell_cmd, nil)
-- `execl` only returns on failure, exit raw (no atexit handlers/Lua GC)
---@diagnostic disable-next-line: call-non-callable, need-check-nil
ffi.C._exit(127)
end
local function posix_exec(cmd, ...)
if type(cmd) ~= "string" or _is_win or not ffi then return end
if not isatty(io.stdout) then return fork(cmd, ...) end
local args = { ... }
-- NOTE: must add NULL to mark end of the vararg
table.insert(args, string.byte("\0"))
ffi.C.execl(cmd, cmd, unpack(args))
-- if `execl` succeeds we should never get here
---@diagnostic disable-next-line: undefined-field
error(string.format([[execl("%s",...) failed with error %d]], cmd, ffi.errno()))
end
-- Windows replacement for `posix_exec`: Windows has no `fork`/`execl`, so
-- start the new nvim as a child on the same console (inherited stdio) and
-- wait for it to exit before returning; quitting first would kill the
-- child since Neovim terminates its spawned children on exit. The caller
-- then quits the headless parent (same-window takeover like `execl`).
-- Unlike the `cmd.exe /c` launcher the binary starts directly (no shell),
-- so a plain argv list with libuv's default quoting is correct, even with
-- spaces or quotes in paths. Optional trailing args may be nil (no lnum
-- or col) and are filtered out since `uv.spawn` needs a dense argv.
-- Spawn failures are reported via `utils.warn` and returned as `false`; the
-- wrapper never raises. Callers quit afterwards either way: a failed launch
-- only means nothing takes the console over, and leaving the dead picker
-- process around would hang the console instead of returning to the shell.
-- When the parent's stdout is not a console (e.g. PowerShell captures the
-- picker output through a pipe, as the shell widgets do), `{ 0, 1, 2 }`
-- would give the child a stdout it cannot render a TUI on and the CLI
-- would hang waiting for it to exit. stdin/stderr usually remain attached
-- to the console in that case, so inherit stderr for both. When stderr is
-- piped as well (PowerShell's PSReadLine key handler captures both), no
-- console handle is left to inherit at all: reopen `CONIN$`/`CONOUT$` as
-- inheritable CRT fds and pass those to the child.
local function win_stdio()
local okout, hout = pcall(uv.guess_handle, 1)
if okout and hout == "tty" then return { 0, 1, 2 } end
local okerr, herr = pcall(uv.guess_handle, 2)
if okerr and herr == "tty" then return { 0, 2, 2 } end
return FzfLua.libuv.win_console_stdio() or { 0, 1, 2 }
end
---@return boolean success
local function win_exec(cmd, ...)
if type(cmd) ~= "string" or not _is_win then return false end
local args = vim.tbl_filter(function(a) return a ~= nil end, { ... })
local done = false
local stdio = win_stdio()
-- libuv (<= 1.52) leaks every inheritable handle of this process into the
-- child (`CreateProcess(bInheritHandles=TRUE)`, no handle list), even the
-- ones not part of `stdio`. When the shell integration captures the
-- picker output through pipes, the spawned nvim inherits them and the
-- `--embed` server left running by a later `:detach` keeps the pipes'
-- write end open forever, hanging the shell's pipeline reader. Hide the
-- unused stdio fds for the duration of the spawn.
local restore = FzfLua.libuv.win_stdio_no_inherit(stdio)
local ok, err = pcall(FzfLua.libuv.uv_spawn, cmd, {
args = args,
stdio = stdio,
},
function() done = true end)
if restore then restore() end
if not ok then
-- a non-throwing report: `utils.error` re-raises ERROR notifies when the
-- CLI runs headless (see `utils.notify`), which would abort the action
-- instead of letting the caller react to the failed launch
FzfLua.utils.warn("failed to launch %s: %s", cmd, err)
return false
end
vim.wait(2147483647, function() return done end)
return true
end
_G.fzf_tty_get_width = require("fzf-lua.utils").fzf_tty_get_width
-- https://github.com/libuv/libuv/blob/04fc1580d48c1f7aa339b6ccf91f1f815bc08b45/src/unix/core.c#L800
local enable_stdio_inheritance = function()
-- No-op on Windows (no `fcntl`/CLOEXEC): `uv.spawn` stdio inheritance
-- is handled by libuv/Neovim, and `posix_exec` is a no-op on Windows.
if _is_win or not ffi then return end
local function clear_cloexec(fd)
local F_SETFD = 2
local EINTR = 4
local res
repeat
---@diagnostic disable-next-line: need-check-nil
res = ffi.C.fcntl(fd, F_SETFD, 1)
---@diagnostic disable-next-line: need-check-nil
until not (res == -1 and ffi.errno() == EINTR)
return res ~= -1
end
for i = 0, 15 do
if not clear_cloexec(i) then break end
end
end
local HAS_TMUX = os.getenv("TMUX")
return {
-- always use fzf-tmux profile for fzf native border labels
-- nullify "--tmux" in `fzf_opts` if tmux isn't detected
{ "fzf-tmux" },
desc = "run in shell cmdline",
-- NOTE: on Windows pipe items into fzf (`(cmd) | fzf`) instead of
-- $FZF_DEFAULT_COMMAND: fzf only honors the env when stdin is a tty and
-- headless-spawned fzf isn't (IsTty()=false), so it ignores the env and
-- blocks on stdin EOF forever (same reason tests force `pipe_cmd`).
-- `defaults` (not top-level) is what `normalize_opts` merges per-picker.
defaults = { pipe_cmd = _is_win or nil },
fzf_opts = {
["--height"] = "50%",
["--border"] = HAS_TMUX and "rounded" or "top",
["--tmux"] = (function() return not HAS_TMUX and false or nil end)(),
},
hls = {
title = "diffAdd",
title_flags = "Visual",
header_bind = "Directory",
header_text = "WarningMsg",
live_prompt = "ErrorMsg",
},
actions = {
files = {
true,
["esc"] = quit,
["ctrl-c"] = quit,
["enter"] = function(s, o)
local entries = parse_entries(s, o)
vim.tbl_map(function(e) io.stdout:write(e.path .. "\n") end, entries)
quit()
end,
["ctrl-q"] = function(s, o)
local entries = parse_entries(s, o)
local exec = _is_win and win_exec or posix_exec
if (_is_win or ffi) and #entries == 1 then
local lnum = entries[1].line > 0 and entries[1].line or nil
local col = entries[1].col > 0 and entries[1].col or nil
exec(NVIM_BIN, entries[1].path,
lnum and ("+" .. entries[1].line) or nil,
col and ("+norm! %s|"):format(col) or nil)
elseif (_is_win or ffi) and #entries > 1 then
local qf_items = {}
for _, e in ipairs(entries) do
local text = e.stripped:match(":%d+:%d?%d?%d?%d?:?(.*)$") or ""
table.insert(qf_items, {
filename = e.path,
lnum = e.line or 0,
col = e.col or 0,
valid = 1,
text = text,
})
end
local qf_str = vim.inspect(qf_items):gsub("\n%s*", " ")
exec(NVIM_BIN, "-c", string.format(
"lua vim.o.hidden=false; vim.fn.setqflist(%s); vim.cmd('cfirst | set hidden&')",
qf_str))
end
quit()
end,
["ctrl-x"] = function(_, o)
FzfLua.builtin(vim.tbl_deep_extend("force", o.__call_opts, {
actions = {
enter = function(s)
if not s[1] then quit() end
FzfLua[s[1]](o.__call_opts)
end
},
}))
end
}
},
serverlist = {
previewer = { _ctor = require("fzf-lua.previewer").fzf.nvim_server },
actions = {
["enter"] = function(s)
local remote = FzfLua.utils.serverlist_parse_entry(assert(s[1]))
for _, chan in ipairs(api.nvim_list_chans()) do
if chan.stream ~= "stderr" then
fn.chanclose(chan.id)
end
end
for _, pid in ipairs(api.nvim_get_proc_children(fn.getpid())) do
vim.uv.kill(pid, vim.uv.constants.SIGTERM)
end
enable_stdio_inheritance()
if _is_win then
-- no `execl` replacement on Windows: `win_exec` returns and the
-- picker must quit explicitly to hand the console over. Quit even
-- on a failed launch (reported by `win_exec`): the channels and
-- children above are already torn down, the picker cannot continue
-- and a leftover CLI would hang the console
win_exec(NVIM_BIN, "--remote-ui", "--server", remote)
quit()
else
posix_exec(NVIM_BIN, "--remote-ui", "--server", remote)
end
end
}
}
}