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.
214 lines
6.8 KiB
Lua
214 lines
6.8 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 = {}
|
|
|
|
-- 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("[Fzf-lua] function must be called inside a coroutine.")
|
|
end
|
|
|
|
if not opts then opts = {} end
|
|
local cwd = opts.fzf_cwd or opts.cwd
|
|
local cmd = opts.fzf_bin or 'fzf'
|
|
local fifotmpname = tempname()
|
|
local outputtmpname = tempname()
|
|
|
|
-- we use a temporary env $FZF_DEFAULT_COMMAND instead of piping
|
|
-- the command to fzf, this way fzf kills the command when it exits
|
|
-- this is especially important with our shell helper as io.write fails
|
|
-- to delect when the pipe is broken (EPIPE) so the neovim headless
|
|
-- instance never terminates which hangs fzf on exit
|
|
local FZF_DEFAULT_COMMAND = nil
|
|
|
|
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
|
|
if opts.silent_fail ~= false then
|
|
contents = ("%s || true"):format(contents)
|
|
end
|
|
FZF_DEFAULT_COMMAND = contents
|
|
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',
|
|
['FZF_DEFAULT_COMMAND'] = FZF_DEFAULT_COMMAND,
|
|
['SKIM_DEFAULT_COMMAND'] = FZF_DEFAULT_COMMAND,
|
|
},
|
|
on_exit = function(_, rc, _)
|
|
local output = {}
|
|
local f = io.open(outputtmpname)
|
|
if f then
|
|
for v in f:lines() do
|
|
table.insert(output, v)
|
|
end
|
|
f:close()
|
|
end
|
|
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]]
|
|
|
|
-- terminal behavior seem to have changed after the introduction
|
|
-- of 'nt' mode (terminal-normal mode) which is included in 0.6
|
|
-- https://github.com/neovim/neovim/pull/15878
|
|
-- preferably I'd like to check if the vim patch is included using
|
|
-- vim.fn.has('patch-8.2.3461')
|
|
-- but this doesn't work for vim patches > 8.1 as explained in:
|
|
-- https://github.com/neovim/neovim/issues/9635
|
|
-- however, since this patch was included in 0.6 we can test
|
|
-- for neovim version 0.6
|
|
-- beats me why 'nvim_get_mode().mode' still returns 'nt' even
|
|
-- after we're clearly in insert mode or why `:startinsert`
|
|
-- won't change the mode from 'nt' to 't' so we use feedkeys()
|
|
-- instead.
|
|
-- this "retires" 'actions.ensure_insert_mode' and solves the
|
|
-- issue of calling an fzf-lua mapping from insert mode (#429)
|
|
if vim.fn.has('nvim-0.6') == 1 then
|
|
vim.cmd([[noautocmd lua vim.api.nvim_feedkeys(]]
|
|
.. [[vim.api.nvim_replace_termcodes("<Esc>i", true, false, true)]]
|
|
.. [[, 'n', true)]])
|
|
else
|
|
vim.cmd[[startinsert]]
|
|
end
|
|
|
|
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
|