internal: libuv helpers, perf enhancements

main
bhagwan 3 years ago
parent fd497f313d
commit f947669dec

@ -234,7 +234,7 @@ M.globals.grep = {
color_icons = true,
git_icons = true,
grep_opts = "--binary-files=without-match --line-number --recursive --color=auto --perl-regexp",
rg_opts = "--column --line-number --no-heading --color=always --smart-case",
rg_opts = "--column --line-number --no-heading --color=always --smart-case --max-columns=512",
actions = M.globals.files.actions,
-- live_grep_glob options
glob_flag = "--iglob", -- for case sensitive globs use '--glob'
@ -637,7 +637,7 @@ function M.normalize_opts(opts, defaults)
-- are we using skim?
opts._is_skim = opts.fzf_bin:find('sk') ~= nil
-- cmd_line_transformer pid callback
-- libuv.spawn_nvim_fzf_cmd() pid callback
opts._pid_cb = function(pid) opts._pid = pid end
-- mark as normalized

@ -4,6 +4,7 @@ local utils = require "fzf-lua.utils"
local config = require "fzf-lua.config"
local actions = require "fzf-lua.actions"
local win = require "fzf-lua.win"
local libuv = require "fzf-lua.libuv"
local M = {}
@ -346,48 +347,6 @@ M.fzf_files = function(opts)
end
-- save to upvalue for performance reasons
local string_byte = string.byte
local string_sub = string.sub
local function find_last_newline(str)
for i=#str,1,-1 do
if string_byte(str, i) == 10 then
return i
end
end
end
-- https://github.com/luvit/luv/blob/master/docs.md
-- uv.spawn returns tuple: handle, pid
local _, _pid
local function coroutine_callback(fn)
local co = coroutine.running()
local callback = function(...)
if coroutine.status(co) == 'suspended' then
coroutine.resume(co, ...)
else
local pid = unpack({...})
utils.process_kill(pid)
end
end
fn(callback)
return coroutine.yield()
end
local function coroutinify(fn)
return function(...)
local args = {...}
return coroutine.wrap(function()
return coroutine_callback(function(cb)
table.insert(args, cb)
fn(unpack(args))
end)
end)()
end
end
M.set_fzf_interactive_cmd = function(opts)
if not opts then return end
@ -395,133 +354,7 @@ M.set_fzf_interactive_cmd = function(opts)
-- fzf already adds single quotes around the placeholder when expanding
-- for skim we surround it with double quotes or single quote searches fail
local placeholder = utils._if(opts._is_skim, '"{}"', '{q}')
local uv = vim.loop
local function spawn(pipe, args, cb)
local shell_cmd = opts._reload_command(args[1])
local output_pipe = uv.new_pipe(false)
local error_pipe = uv.new_pipe(false)
local write_cb_count = 0
local prev_line_content = nil
local shell = vim.env.SHELL or "sh"
local finish = function(_)
if pipe and not uv.is_closing(pipe) then
uv.close(pipe)
pipe = nil
end
output_pipe:shutdown()
error_pipe:shutdown()
if cb then cb(_pid) end
end
-- terminate previously running commands
utils.process_kill(_pid)
_, _pid = uv.spawn(shell, {
args = { "-c", shell_cmd },
stdio = { nil, output_pipe, error_pipe },
cwd = opts.cwd
}, function(code, signal)
output_pipe:read_stop()
error_pipe:read_stop()
output_pipe:close()
error_pipe :close()
if write_cb_count==0 then
-- only close if all our uv.write
-- calls are completed
finish(1)
end
end)
-- save current process pid
local pid = _pid
opts._pid = _pid
local function write_cb(data)
if not pipe then return end
if pid ~= _pid then
-- safety, I never saw this get called
-- will set `pipe = nill`
finish(2)
return
end
write_cb_count = write_cb_count + 1
uv.write(pipe, data, function(err)
if err then
-- sometime fails?
-- assert(not err)
finish(3)
end
write_cb_count = write_cb_count - 1
if write_cb_count == 0 and uv.is_closing(output_pipe) then
-- spawn callback 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(4)
end
end)
end
local function process_lines(str)
write_cb(str:gsub("[^\n]+",
function(x)
return opts._transform_command(x)
end))
end
local read_cb = function(err, data)
if err then
assert(not err)
finish(5)
end
if not data then
return
end
if prev_line_content then
data = prev_line_content .. data
prev_line_content = nil
end
if not opts._transform_command then
write_cb(data)
elseif string_byte(data, #data) == 10 then
process_lines(data)
else
local nl_index = find_last_newline(data)
if not nl_index then
prev_line_content = data
else
prev_line_content = string_sub(data, nl_index + 1)
local stripped_with_newline = string_sub(data, 1, nl_index)
process_lines(stripped_with_newline)
end
end
end
local err_cb = function(err, data)
if err then
assert(not err)
finish(6)
end
if not data then
return
end
write_cb(data)
end
output_pipe:read_start(read_cb)
error_pipe:read_start(err_cb)
end
-- local spawn_fn = spawn
local spawn_fn = coroutinify(spawn)
local raw_async_act = require("fzf.actions").raw_async_action(spawn_fn, placeholder)
local raw_async_act = libuv.spawn_reload_cmd_action(opts, placeholder)
return M.set_fzf_interactive(opts, raw_async_act, placeholder)
end
@ -536,26 +369,32 @@ M.set_fzf_interactive_cb = function(opts)
local uv = vim.loop
local raw_async_act = require("fzf.actions").raw_async_action(function(pipe, args)
local results = opts._reload_action(args[1])
coroutine.wrap(function()
local close_pipe = function()
if pipe and not uv.is_closing(pipe) then
uv.close(pipe)
pipe = nil
local co = coroutine.running()
local results = opts._reload_action(args[1])
local close_pipe = function()
if pipe and not uv.is_closing(pipe) then
uv.close(pipe)
pipe = nil
end
coroutine.resume(co)
end
end
if type(results) == 'table' then
for _, entry in ipairs(results) do
uv.write(pipe, entry .. "\n", function(err)
if err then
if type(results) == 'table' and not vim.tbl_isempty(results) then
uv.write(pipe,
vim.tbl_map(function(x) return x.."\n" end, results),
function(_)
close_pipe()
end
end)
end)
-- wait for write to finish
coroutine.yield()
end
end
-- does nothing if write finished successfully
close_pipe()
close_pipe()
end)()
end, placeholder)
return M.set_fzf_interactive(opts, raw_async_act, placeholder)
@ -590,10 +429,10 @@ M.set_fzf_interactive = function(opts, act_cmd, placeholder)
-- around the place holder
opts.fzf_fn = {}
if opts.exec_empty_query or (query and #query>0) then
opts.fzf_fn = require("fzf.helpers").cmd_line_transformer(
{ cmd = act_cmd:gsub(placeholder, vim.fn.shellescape(query)),
cwd = opts.cwd, pid_cb = opts._pid_cb },
function(x) return x end)
opts.fzf_fn = libuv.spawn_nvim_fzf_cmd(
{ cmd = act_cmd:gsub(placeholder,
#query>0 and utils.lua_escape(vim.fn.shellescape(query)) or "''"),
cwd = opts.cwd, pid_cb = opts._pid_cb })
end
opts.fzf_opts['--phony'] = ''
opts.fzf_opts['--query'] = vim.fn.shellescape(query)

@ -0,0 +1,270 @@
local utils = require "fzf-lua.utils"
local async_action = require("fzf.actions").async_action
local raw_async_action = require("fzf.actions").raw_async_action
local uv = vim.loop
local M = {}
-- save to upvalue for performance reasons
local string_byte = string.byte
local string_sub = string.sub
local function find_last_newline(str)
for i=#str,1,-1 do
if string_byte(str, i) == 10 then
return i
end
end
end
local function coroutine_callback(fn)
local co = coroutine.running()
local callback = function(...)
if coroutine.status(co) == 'suspended' then
coroutine.resume(co, ...)
else
local pid = unpack({...})
utils.process_kill(pid)
end
end
fn(callback)
return coroutine.yield()
end
local function coroutinify(fn)
return function(...)
local args = {...}
return coroutine.wrap(function()
return coroutine_callback(function(cb)
table.insert(args, cb)
fn(unpack(args))
end)
end)()
end
end
M.spawn = function(opts, fn_transform, fn_done)
local output_pipe = uv.new_pipe(false)
local error_pipe = uv.new_pipe(false)
local write_cb_count = 0
local prev_line_content = nil
if opts.fn_transform then fn_transform = opts.fn_transform end
local shell = vim.env.SHELL or "sh"
local finish = function(sig, pid)
output_pipe:shutdown()
error_pipe:shutdown()
if opts.cb_finish then
opts.cb_finish(sig, pid)
end
-- coroutinify callback
if fn_done then
fn_done(pid)
end
end
-- https://github.com/luvit/luv/blob/master/docs.md
-- uv.spawn returns tuple: handle, pid
local _, pid = uv.spawn(shell, {
args = { "-c", opts.cmd },
stdio = { nil, output_pipe, error_pipe },
cwd = opts.cwd
}, function(code, signal)
output_pipe:read_stop()
error_pipe:read_stop()
output_pipe:close()
error_pipe :close()
if write_cb_count==0 then
-- only close if all our uv.write
-- calls are completed
finish(1)
end
end)
-- save current process pid
if opts.cb_pid then opts.cb_pid(pid) end
if opts.pid_cb then opts.pid_cb(pid) end
local function write_cb(data)
write_cb_count = write_cb_count + 1
opts.cb_write(data, function(err)
if err then
-- can fail with premature process kill
-- assert(not err)
finish(2, pid)
end
write_cb_count = write_cb_count - 1
if write_cb_count == 0 and uv.is_closing(output_pipe) then
-- spawn callback 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, pid)
end
end)
end
local function process_lines(str)
write_cb(str:gsub("[^\n]+",
function(x)
return fn_transform(x)
end))
end
local read_cb = function(err, data)
if err then
assert(not err)
finish(4, pid)
end
if not data then
return
end
if prev_line_content then
data = prev_line_content .. data
prev_line_content = nil
end
if not fn_transform then
write_cb(data)
elseif string_byte(data, #data) == 10 then
process_lines(data)
else
local nl_index = find_last_newline(data)
if not nl_index then
prev_line_content = data
else
prev_line_content = string_sub(data, nl_index + 1)
local stripped_with_newline = string_sub(data, 1, nl_index)
process_lines(stripped_with_newline)
end
end
end
local err_cb = function(err, data)
if err then
assert(not err)
finish(9, pid)
end
if not data then
return
end
write_cb(data)
end
output_pipe:read_start(read_cb)
error_pipe:read_start(err_cb)
end
M.async_spawn = coroutinify(M.spawn)
M.spawn_nvim_fzf_cmd = function(opts, fn_transform)
return function(fzf_cb)
local function on_finish(_, _)
fzf_cb(nil)
end
local function on_write(data, cb)
-- nvim-fzf adds "\n" at the end
-- so we have to remove ours
assert(string_byte(data, #data) == 10)
fzf_cb(string_sub(data, 1, #data-1), cb)
end
if not fn_transform then
-- must add a dummy function here, without it
-- spawn() wouldn't parse EOLs and send partial
-- data via nvim-fzf's fzf_cb() which adds "\n"
-- each call, resulting in mangled data
fn_transform = function(x) return x end
end
return M.spawn({
cwd = opts.cwd,
cmd = opts.cmd,
cb_finish = on_finish,
cb_write = on_write,
cb_pid = opts.pid_cb,
}, fn_transform)
end
end
M.spawn_nvim_fzf_action = function(fn, fzf_field_expression)
return 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 M.spawn({
cmd = fn(...),
cb_finish = on_finish,
cb_write = on_write,
}, false)
end, fzf_field_expression)
end
M.spawn_reload_cmd_action = function(opts, fzf_field_expression)
local _pid = nil
return raw_async_action(function(pipe, args)
local function on_pid(pid)
_pid = pid
if opts.pid_cb then
opts.pid_cb(pid)
end
end
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
-- terminate previously running commands
utils.process_kill(_pid)
-- return M.spawn({
return M.async_spawn({
cwd = opts.cwd,
cmd = opts._reload_command(args[1]),
cb_finish = on_finish,
cb_write = on_write,
cb_pid = on_pid,
}, opts._fn_transform)
end, fzf_field_expression)
end
return M

@ -138,6 +138,7 @@ function M.entry_to_file(entry, cwd)
entry = utils.strip_ansi_coloring(entry)
local sep = ":"
local s = strsplit(entry, sep)
if not s[1] then return {} end
local file = s[1]:match("[^"..utils.nbsp.."]*$")
-- entries from 'buffers'
local bufnr = s[1]:match("%[(%d+)")

@ -250,6 +250,7 @@ end
function Previewer.buffer_or_file:populate_preview_buf(entry_str)
if not self.win or not self.win:validate_preview() then return end
local entry = self:parse_entry(entry_str)
if vim.tbl_isempty(entry) then return end
if entry.bufnr and api.nvim_buf_is_loaded(entry.bufnr) then
-- must convert to number or our backup will have conflicting keys
local bufnr = tonumber(entry.bufnr)

@ -1,6 +1,6 @@
local path = require "fzf-lua.path"
local utils = require "fzf-lua.utils"
local helpers = require("fzf.helpers")
local libuv = require "fzf-lua.libuv"
local previewer_base = require "fzf-lua.previewer"
local raw_action = require("fzf.actions").raw_action
@ -121,7 +121,7 @@ end
function Previewer.cmd_async:cmdline(o)
o = o or {}
local act = helpers.choices_to_shell_cmd_previewer(function(items)
local act = libuv.spawn_nvim_fzf_action(function(items)
local file = path.entry_to_file(items[1], not self.relative and self.opts.cwd)
local cmd = string.format('%s %s %s', self.cmd, self.args, vim.fn.shellescape(file.path))
-- uncomment to see the command in the preview window
@ -146,7 +146,7 @@ function Previewer.bat_async:cmdline(o)
if self.opts._line_placeholder then
highlight_line = string.format("--highlight-line=", self.opts._line_placeholder)
end
local act = helpers.choices_to_shell_cmd_previewer(function(items)
local act = libuv.spawn_nvim_fzf_action(function(items)
local file = path.entry_to_file(items[1], not self.relative and self.opts.cwd)
local cmd = string.format('%s %s %s%s "%s"',
self.cmd, self.args,
@ -171,7 +171,7 @@ end
function Previewer.git_diff:cmdline(o)
o = o or {}
local act = helpers.choices_to_shell_cmd_previewer(function(items)
local act = libuv.spawn_nvim_fzf_action(function(items)
local is_deleted = items[1]:match("D"..utils.nbsp) ~= nil
local is_untracked = items[1]:match("[?RAC]"..utils.nbsp) ~= nil
local file = path.entry_to_file(items[1], not self.relative and self.opts.cwd)
@ -207,7 +207,7 @@ end
function Previewer.man_pages:cmdline(o)
o = o or {}
local act = helpers.choices_to_shell_cmd_previewer(function(items)
local act = libuv.spawn_nvim_fzf_action(function(items)
-- local manpage = require'fzf-lua.providers.manpages'.getmanpage(items[1])
local manpage = items[1]:match("[^[,( ]+")
local cmd = ("%s %s %s"):format(

@ -2,10 +2,10 @@ if not pcall(require, "fzf") then
return
end
local fzf_helpers = require("fzf.helpers")
local core = require "fzf-lua.core"
local utils = require "fzf-lua.utils"
local config = require "fzf-lua.config"
local libuv = require "fzf-lua.libuv"
local M = {}
@ -32,7 +32,7 @@ M.files = function(opts)
local command = get_files_cmd(opts)
opts.fzf_fn = fzf_helpers.cmd_line_transformer(
opts.fzf_fn = libuv.spawn_nvim_fzf_cmd(
{ cmd = command, cwd = opts.cwd, pid_cb = opts._pid_cb },
function(x)
return core.make_entry_file(opts, x)
@ -62,7 +62,7 @@ M.files_resume = function(opts)
opts._fzf_cli_args = ('--bind=change:execute-silent:%s'):
format(vim.fn.shellescape(raw_act))
opts.fzf_fn = fzf_helpers.cmd_line_transformer(
opts.fzf_fn = libuv.spawn_nvim_fzf_cmd(
{cmd = command, cwd = opts.cwd},
function(x)
return core.make_entry_file(opts, x)

@ -2,12 +2,11 @@ if not pcall(require, "fzf") then
return
end
local fzf_helpers = require("fzf.helpers")
local core = require "fzf-lua.core"
local path = require "fzf-lua.path"
local utils = require "fzf-lua.utils"
local config = require "fzf-lua.config"
local actions = require "fzf-lua.actions"
local libuv = require "fzf-lua.libuv"
local M = {}
@ -22,7 +21,7 @@ M.files = function(opts)
opts.cwd = path.git_root(opts.cwd)
if not opts.cwd then return end
local make_entry_file = core.make_entry_file
opts.fzf_fn = fzf_helpers.cmd_line_transformer(
opts.fzf_fn = libuv.spawn_nvim_fzf_cmd(
{ cmd = opts.cmd, cwd = opts.cwd, pid_cb = opts._pid_cb },
function(x)
return make_entry_file(opts, x)
@ -38,7 +37,7 @@ M.status = function(opts)
if opts.preview then
opts.preview = vim.fn.shellescape(path.git_cwd(opts.preview, opts.cwd))
end
opts.fzf_fn = fzf_helpers.cmd_line_transformer(
opts.fzf_fn = libuv.spawn_nvim_fzf_cmd(
{ cmd = opts.cmd, cwd = opts.cwd, pid_cb = opts._pid_cb },
function(x)
-- greedy match anything after last space
@ -52,7 +51,7 @@ local function git_cmd(opts)
opts.cwd = path.git_root(opts.cwd)
if not opts.cwd then return end
coroutine.wrap(function ()
opts.fzf_fn = fzf_helpers.cmd_line_transformer(
opts.fzf_fn = libuv.spawn_nvim_fzf_cmd(
{ cmd = opts.cmd, cwd = opts.cwd, pid_cb = opts._pid_cb },
function(x) return x end)
local selected = core.fzf(opts, opts.fzf_fn)
@ -89,7 +88,7 @@ M.branches = function(opts)
if not opts then return end
opts.fzf_opts["--no-multi"] = ''
opts._preview = path.git_cwd(opts.preview, opts.cwd)
opts.preview = fzf_helpers.choices_to_shell_cmd_previewer(function(items)
opts.preview = libuv.spawn_nvim_fzf_action(function(items)
local branch = items[1]:gsub("%*", "") -- remove the * from current branch
if branch:find("%)") ~= nil then
-- (HEAD detached at origin/master)

@ -2,11 +2,11 @@ if not pcall(require, "fzf") then
return
end
local fzf_helpers = require("fzf.helpers")
local path = require "fzf-lua.path"
local core = require "fzf-lua.core"
local utils = require "fzf-lua.utils"
local config = require "fzf-lua.config"
local libuv = require "fzf-lua.libuv"
local last_search = {}
@ -110,7 +110,7 @@ M.grep = function(opts)
local command = get_grep_cmd(opts, opts.search, no_esc)
opts.fzf_fn = fzf_helpers.cmd_line_transformer(
opts.fzf_fn = libuv.spawn_nvim_fzf_cmd(
{ cmd = command, cwd = opts.cwd, pid_cb = opts._pid_cb },
function(x)
return core.make_entry_file(opts, x)
@ -168,7 +168,7 @@ M.live_grep = function(opts)
end
if opts.experimental then
opts._transform_command = function(x)
opts._fn_transform = function(x)
return core.make_entry_file(opts, x)
end
end
@ -226,7 +226,7 @@ M.live_grep_native = function(opts)
else
opts.fzf_fn = {}
if opts.exec_empty_query or (opts.search and #opts.search > 0) then
opts.fzf_fn = fzf_helpers.cmd_line_transformer(
opts.fzf_fn = libuv.spawn_nvim_fzf_cmd(
{cmd = initial_command:gsub(placeholder, vim.fn.shellescape(query)),
cwd = opts.cwd, pid_cb = opts._pid_cb },
function(x)

@ -2,11 +2,11 @@ if not pcall(require, "fzf") then
return
end
local fzf_helpers = require("fzf.helpers")
local core = require "fzf-lua.core"
local utils = require "fzf-lua.utils"
local config = require "fzf-lua.config"
local actions = require "fzf-lua.actions"
local libuv = require "fzf-lua.libuv"
local M = {}
@ -33,9 +33,7 @@ M.manpages = function(opts)
coroutine.wrap(function ()
-- local prev_act = action(function (args) end)
local fzf_fn = fzf_helpers.cmd_line_transformer(
local fzf_fn = libuv.spawn_nvim_fzf_cmd(
{ cmd = opts.cmd, cwd = opts.cwd, pid_cb = opts._pid_cb },
function(x)
-- split by first occurence of ' - ' (spaced hyphen)

@ -86,6 +86,13 @@ function M.sk_escape(str)
end)
end
function M.lua_escape(str)
if not str then return str end
return str:gsub('[%%]', function(x)
return '%' .. x
end)
end
-- TODO: why does `file --dereference --mime` return
-- wrong result for some lua files ('charset=binary')?
M.file_is_binary = function(filepath)
@ -412,9 +419,12 @@ end
local uv = vim.loop
function M.process_kill(pid, signal)
if pid and type(uv.os_getpriority(pid)) == 'number' then
if not pid or not tonumber(pid) then return false end
if type(uv.os_getpriority(pid)) == 'number' then
uv.kill(pid, signal or 9)
return true
end
return false
end
function M.fzf_bind_to_neovim(key)

Loading…
Cancel
Save