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.
278 lines
8.4 KiB
Lua
278 lines
8.4 KiB
Lua
-- modified version of:
|
|
-- https://github.com/vijaymarupudi/nvim-fzf/blob/master/lua/fzf/actions.lua
|
|
local uv = vim.loop
|
|
local path = require "fzf-lua.path"
|
|
local libuv = require "fzf-lua.libuv"
|
|
|
|
local M = {}
|
|
|
|
local _counter = 0
|
|
local _registry = {}
|
|
|
|
function M.register_func(fn)
|
|
_counter = _counter + 1
|
|
_registry[_counter] = fn
|
|
return _counter
|
|
end
|
|
|
|
function M.get_func(counter)
|
|
return _registry[counter]
|
|
end
|
|
|
|
-- creates a new address to listen to messages from actions. This is important,
|
|
-- if the user is using a custom fixed $NVIM_LISTEN_ADDRESS. Different neovim
|
|
-- instances will then use the same path as the address and it causes a mess,
|
|
-- i.e. actions stop working on the old instance. So we create our own (random
|
|
-- path) RPC server for this instance if it hasn't been started already.
|
|
-- NOT USED ANYMORE, we use `vim.g.fzf_lua_server` instead
|
|
-- local action_server_address = nil
|
|
|
|
function M.raw_async_action(fn, fzf_field_expression, debug)
|
|
|
|
if not fzf_field_expression then
|
|
fzf_field_expression = "{+}"
|
|
end
|
|
|
|
local receiving_function = function(pipe_path, ...)
|
|
local pipe = uv.new_pipe(false)
|
|
local args = {...}
|
|
uv.pipe_connect(pipe, pipe_path, function(err)
|
|
if err then
|
|
error(string.format("pipe_connect(%s) failed with error: %s",
|
|
pipe_path, err))
|
|
else
|
|
vim.schedule(function ()
|
|
fn(pipe, unpack(args))
|
|
end)
|
|
end
|
|
end)
|
|
end
|
|
|
|
local id = M.register_func(receiving_function)
|
|
|
|
-- this is for windows WSL and AppImage users, their nvim path isn't just
|
|
-- 'nvim', it can be something else
|
|
local nvim_bin = vim.v.progpath
|
|
|
|
local call_args = ("fzf_lua_server=[[%s]], fnc_id=%d %s"):format(
|
|
vim.g.fzf_lua_server, id, debug and ", debug=true" or "")
|
|
|
|
local action_cmd = ("%s -n --headless --clean --cmd %s %s"):format(
|
|
libuv.shellescape(nvim_bin),
|
|
libuv.shellescape(("lua loadfile([[%s]])().rpc_nvim_exec_lua({%s})")
|
|
:format(path.join{vim.g.fzf_lua_directory, "shell_helper.lua"}, call_args)),
|
|
fzf_field_expression)
|
|
|
|
return action_cmd, id
|
|
end
|
|
|
|
function M.async_action(fn, fzf_field_expression, debug)
|
|
local action_string, id = M.raw_async_action(fn, fzf_field_expression, debug)
|
|
return vim.fn.shellescape(action_string), id
|
|
end
|
|
|
|
function M.raw_action(fn, fzf_field_expression, debug)
|
|
|
|
local receiving_function = function(pipe, ...)
|
|
local ret = fn(...)
|
|
|
|
local on_complete = function(_)
|
|
-- We are NOT asserting, in case fzf closes
|
|
-- the pipe before we can send the preview
|
|
-- assert(not err)
|
|
uv.close(pipe)
|
|
end
|
|
|
|
if type(ret) == "string" then
|
|
uv.write(pipe, ret, on_complete)
|
|
elseif type(ret) == nil then
|
|
on_complete()
|
|
elseif type(ret) == "table" then
|
|
if not vim.tbl_isempty(ret) then
|
|
uv.write(pipe, vim.tbl_map(function(x) return x.."\n" end, ret), on_complete)
|
|
else
|
|
on_complete()
|
|
end
|
|
else
|
|
uv.write(pipe, tostring(ret) .. "\n", on_complete)
|
|
end
|
|
end
|
|
|
|
return M.raw_async_action(receiving_function, fzf_field_expression, debug)
|
|
end
|
|
|
|
function M.action(fn, fzf_field_expression ,debug)
|
|
local action_string, id = M.raw_action(fn, fzf_field_expression, debug)
|
|
return vim.fn.shellescape(action_string), id
|
|
end
|
|
|
|
M.preview_action_cmd = function(fn, fzf_field_expression, debug)
|
|
local action_string, id =
|
|
M.raw_preview_action_cmd(fn, fzf_field_expression, debug)
|
|
return vim.fn.shellescape(action_string), id
|
|
end
|
|
|
|
M.raw_preview_action_cmd = function(fn, fzf_field_expression, debug)
|
|
|
|
return M.raw_async_action(function(pipe, ...)
|
|
|
|
local function on_finish(_, _)
|
|
if pipe and not uv.is_closing(pipe) then
|
|
uv.close(pipe)
|
|
pipe = nil
|
|
end
|
|
end
|
|
|
|
local function on_write(data, cb)
|
|
if not pipe then
|
|
cb(true)
|
|
else
|
|
uv.write(pipe, data, cb)
|
|
end
|
|
end
|
|
|
|
return libuv.spawn({
|
|
cmd = fn(...),
|
|
cb_finish = on_finish,
|
|
cb_write = on_write,
|
|
}, false)
|
|
|
|
end, fzf_field_expression, debug)
|
|
end
|
|
|
|
M.reload_action_cmd = function(opts, fzf_field_expression)
|
|
|
|
if opts.fn_preprocess and type(opts.fn_preprocess) == 'function' then
|
|
-- run the preprocessing fn
|
|
opts = vim.tbl_deep_extend("keep", opts, opts.fn_preprocess(opts))
|
|
end
|
|
|
|
return M.raw_async_action(function(pipe, args)
|
|
|
|
-- get the type of contents from the caller
|
|
local reload_contents = opts.__fn_reload(args[1])
|
|
local write_cb_count = 0
|
|
local pipe_want_close = false
|
|
|
|
-- local on_finish = function(code, sig, from, pid)
|
|
-- print("finish", pipe, pipe_want_close, code, sig, from, pid)
|
|
local on_finish = function(_, _, _, _)
|
|
if not pipe then return end
|
|
pipe_want_close = true
|
|
if write_cb_count==0 then
|
|
-- only close if all our uv.write calls are completed
|
|
uv.close(pipe)
|
|
pipe = nil
|
|
end
|
|
end
|
|
|
|
local on_write = function(data, cb, co)
|
|
-- pipe can be nil when using a shell command with spawn
|
|
-- and typing quickly, the process will terminate and
|
|
assert(not co or (co and pipe and not uv.is_closing(pipe)))
|
|
if not pipe then return end
|
|
if not data then
|
|
on_finish(nil, nil, 5)
|
|
if cb then cb(nil) end
|
|
else
|
|
write_cb_count = write_cb_count + 1
|
|
uv.write(pipe, tostring(data), function(err)
|
|
write_cb_count = write_cb_count - 1
|
|
if co then coroutine.resume(co) end
|
|
if cb then cb(err) end
|
|
if err then
|
|
-- force close on error
|
|
write_cb_count = 0
|
|
on_finish(nil, nil, 2)
|
|
end
|
|
if write_cb_count == 0 and pipe_want_close then
|
|
on_finish(nil, nil, 3)
|
|
end
|
|
end)
|
|
-- yield returns when uv.write compeletes
|
|
-- or when a new coroutine calls resume(1)
|
|
if co and coroutine.yield() == 1 then
|
|
-- we have a new routine in opts.__co, this
|
|
-- routine is no longer relevant so kill it
|
|
write_cb_count = 0
|
|
on_finish(nil, nil, 4)
|
|
end
|
|
end
|
|
end
|
|
|
|
if type(reload_contents) == 'string' then
|
|
-- string will be used as a shell command
|
|
-- terminate previously running commands
|
|
libuv.process_kill(opts.__pid)
|
|
opts.__pid = nil
|
|
|
|
-- spawn/async_spawn already async, no need to send opts.__co
|
|
-- also, we can't call coroutine.yield inside a libuv callback
|
|
-- due to: "attempt to yield across C-call boundary"
|
|
libuv.async_spawn({
|
|
cwd = opts.cwd,
|
|
cmd = reload_contents,
|
|
cb_finish = on_finish,
|
|
cb_write = on_write,
|
|
cb_pid = function(pid) opts.__pid = pid end,
|
|
-- must send false, 'coroutinify' adds callback as last argument
|
|
-- which will conflict with the 'fn_transform' argument
|
|
}, opts.__fn_transform or false)
|
|
else
|
|
-- table or function runs in a coroutine
|
|
-- which isn't required for 'libuv.spawn'
|
|
coroutine.wrap(function()
|
|
|
|
if opts.__co then
|
|
local costatus = coroutine.status(opts.__co)
|
|
if costatus ~= 'dead' then
|
|
-- the previous routine is either 'running' or 'suspended'
|
|
-- return 1 from yield to signal abort to 'on_write'
|
|
coroutine.resume(opts.__co, 1)
|
|
end
|
|
assert(coroutine.status(opts.__co) == 'dead')
|
|
end
|
|
-- reset var to current running routine
|
|
opts.__co = coroutine.running()
|
|
|
|
-- callback with newline
|
|
local on_write_nl = function(data, cb)
|
|
data = data and tostring(data) .. "\n" or nil
|
|
return on_write(data, cb)
|
|
end
|
|
|
|
-- callback with newline and coroutine
|
|
local on_write_nl_co = function(data, cb)
|
|
data = data and tostring(data) .. "\n" or nil
|
|
return on_write(data, cb, opts.__co)
|
|
end
|
|
|
|
-- callback with coroutine (no NL)
|
|
local on_write_co = function(data, cb)
|
|
return on_write(data, cb, opts.__co)
|
|
end
|
|
|
|
|
|
if type(reload_contents) == 'table' then
|
|
for _, l in ipairs(reload_contents) do
|
|
on_write_nl_co(l)
|
|
end
|
|
on_finish()
|
|
elseif type(reload_contents) == 'function' then
|
|
-- by default we use the async callbacks
|
|
if opts.func_async_callback ~= false then
|
|
reload_contents(on_write_nl_co, on_write_co)
|
|
else
|
|
reload_contents(on_write_nl, on_write)
|
|
end
|
|
else
|
|
end
|
|
|
|
end)()
|
|
end
|
|
|
|
end, fzf_field_expression, opts.debug)
|
|
end
|
|
|
|
return M
|