You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

182 lines
5.3 KiB
Lua

-- slimmed down version of nvim-fzf's 'raw_fzf', changes include:
-- DOES NOT SUPPORT WINDOWS
-- does not close the pipe before all writes are complete
-- option to not add '\n' on content function callbacks
-- https://github.com/vijaymarupudi/nvim-fzf/blob/master/lua/fzf.lua
local uv = vim.loop
local M = {}
local function get_lines_from_file(file)
local t = {}
for v in file:lines() do
table.insert(t, v)
end
return t
end
-- workaround to a potential 'tempname' bug? (#222)
-- neovim doesn't guarantee the existence of the
-- parent temp dir potentially failing `mkfifo`
-- https://github.com/neovim/neovim/issues/1432
-- https://github.com/neovim/neovim/pull/11284
local function tempname()
local tmpname = vim.fn.tempname()
local parent = vim.fn.fnamemodify(tmpname, ':h')
-- parent must exist for `mkfifo` to succeed
-- if the neovim temp dir was deleted or the
-- tempname already exists we use 'os.tmpname'
if not uv.fs_stat(parent) or uv.fs_stat(tmpname) then
tmpname = os.tmpname()
-- 'os.tmpname' touches the file which
-- will also fail `mkfifo`, delete it
vim.fn.delete(tmpname)
end
return tmpname
end
-- contents can be either a table with tostring()able items, or a function that
-- can be called repeatedly for values. the latter can use coroutines for async
-- behavior.
function M.raw_fzf(contents, fzf_cli_args, opts)
if not coroutine.running() then
error("please run function in a coroutine")
end
if not opts then opts = {} end
local cwd = opts.fzf_cwd or opts.cwd
local cmd = opts.fzf_binary or opts.fzf_bin or 'fzf'
local fifotmpname = tempname()
local outputtmpname = tempname()
if fzf_cli_args then cmd = cmd .. " " .. fzf_cli_args end
if opts.fzf_cli_args then cmd = cmd .. " " .. opts.fzf_cli_args end
if contents then
if type(contents) == "string" and #contents>0 then
cmd = ("%s | %s"):format(contents, cmd)
else
cmd = ("%s < %s"):format(cmd, vim.fn.shellescape(fifotmpname))
end
end
cmd = ("%s > %s"):format(cmd, vim.fn.shellescape(outputtmpname))
local fd, output_pipe = nil, nil
local finish_called = false
local write_cb_count = 0
-- Create the output pipe
-- We use tbl for perf reasons, from ':help system':
-- If {cmd} is a List it runs directly (no 'shell')
-- If {cmd} is a String it runs in the 'shell'
vim.fn.system({"mkfifo", fifotmpname})
local function finish(_)
-- mark finish if once called
finish_called = true
-- close pipe if there are no outstanding writes
if output_pipe and write_cb_count == 0 then
output_pipe:close()
output_pipe = nil
end
end
local function write_cb(data, cb)
if not output_pipe then return end
write_cb_count = write_cb_count + 1
output_pipe:write(data, function(err)
-- decrement write call count
write_cb_count = write_cb_count - 1
-- this will call the user's cb
if cb then cb(err) end
if err then
-- can fail with premature process kill
finish(2)
elseif finish_called and write_cb_count == 0 then
-- 'termopen.on_exit' already called and did not close the
-- pipe due to write_cb_count>0, since this is the last call
-- we can close the fzf pipe
finish(3)
end
end)
end
-- nvim-fzf compatibility, builds the user callback functions
-- 1st argument: callback function that adds newline to each write
-- 2nd argument: callback function thhat writes the data as is
-- 3rd argument: direct access to the pipe object
local function usr_write_cb(nl)
local function end_of_data(usrdata, cb)
if usrdata == nil then
if cb then cb(nil) end
finish(5)
return true
end
return false
end
if nl then
return function(usrdata, cb)
if not end_of_data(usrdata, cb) then
write_cb(tostring(usrdata).."\n", cb)
end
end
else
return function(usrdata, cb)
if not end_of_data(usrdata, cb) then
write_cb(usrdata, cb)
end
end
end
end
local co = coroutine.running()
vim.fn.termopen({"sh", "-c", cmd}, {
cwd = cwd,
env = { ['SHELL'] = 'sh' },
on_exit = function(_, rc, _)
local f = io.open(outputtmpname)
local output = get_lines_from_file(f)
f:close()
finish(1)
vim.fn.delete(fifotmpname)
vim.fn.delete(outputtmpname)
if #output == 0 then output = nil end
coroutine.resume(co, output, rc)
end
})
vim.cmd[[set ft=fzf]]
vim.cmd[[startinsert]]
if not contents or type(contents) == "string" then
goto wait_for_fzf
end
-- have to open this after there is a reader (termopen)
-- otherwise this will block
fd = uv.fs_open(fifotmpname, "w", -1)
output_pipe = uv.new_pipe(false)
output_pipe:open(fd)
-- print(output_pipe:getpeername())
-- this part runs in the background, when the user has selected, it will
-- error out, but that doesn't matter so we just break out of the loop.
if contents then
if type(contents) == "table" then
if not vim.tbl_isempty(contents) then
write_cb(vim.tbl_map(function(x) return x.."\n" end, contents))
end
finish(4)
else
contents(usr_write_cb(true), usr_write_cb(false), output_pipe)
end
end
::wait_for_fzf::
return coroutine.yield()
end
return M