major performance improvement: process entries externally, READ BELOW:

Since LUA is single threaded I reached a limit to performance
optimization, both 'git_icons' and 'file_icons' require string
matching and manipulations which eventually hurt performance
when running on large amount of files.
In order to solve that this commit introduces the option to spawn
commands and process the entries in a separate neovim process which
prints to stdio as if it was a regular shell command. This speeds up
things significantly and also makes the UI super responsive as if fzf
was run in the shell. This required a few lua hacks to be able to load
nvim-web-devicons in a '--headless --clean' instance and sharing the
user configuration through the RPC interface from the running instance.
This is enabled by default for 'files' and 'grep' providers and can also
be enabled for 'git.files' if required, control using the 'multiprocess'
option.
main
bhagwan 3 years ago
parent fd4e94e7a4
commit 2d8a4e9afc

@ -380,6 +380,7 @@ require'fzf-lua'.setup {
files = {
-- previewer = "cat", -- uncomment to override previewer
prompt = 'Files ',
multiprocess = true, -- run command in a separator process
git_icons = true, -- show git icons?
file_icons = true, -- show file icons?
color_icons = true, -- colorize file|git icons
@ -410,6 +411,7 @@ require'fzf-lua'.setup {
git = {
files = {
prompt = 'GitFiles ',
multiprocess = false, -- run command in a separator process
cmd = 'git ls-files --exclude-standard',
git_icons = true, -- show git icons?
file_icons = true, -- show file icons?
@ -463,6 +465,7 @@ require'fzf-lua'.setup {
grep = {
prompt = 'Rg ',
input_prompt = 'Grep For ',
multiprocess = true, -- run command in a separator process
git_icons = true, -- show git icons?
file_icons = true, -- show file icons?
color_icons = true, -- colorize file|git icons

@ -414,6 +414,7 @@ Consult the list below for available settings:
files = {
-- previewer = "cat", -- uncomment to override previewer
prompt = 'Files ',
multiprocess = true, -- run command in a separator process
git_icons = true, -- show git icons?
file_icons = true, -- show file icons?
color_icons = true, -- colorize file|git icons
@ -444,6 +445,7 @@ Consult the list below for available settings:
git = {
files = {
prompt = 'GitFiles ',
multiprocess = false, -- run command in a separator process
cmd = 'git ls-files --exclude-standard',
git_icons = true, -- show git icons?
file_icons = true, -- show file icons?
@ -497,6 +499,7 @@ Consult the list below for available settings:
grep = {
prompt = 'Rg ',
input_prompt = 'Grep For ',
multiprocess = true, -- run command in a separator process
git_icons = true, -- show git icons?
file_icons = true, -- show file icons?
color_icons = true, -- colorize file|git icons

@ -12,13 +12,13 @@ local M = {}
M._has_devicons, M._devicons = pcall(require, "nvim-web-devicons")
-- if the caller has devicons lazy loaded
-- this will generate an error
-- nvim-web-devicons.lua:972: E5560:
-- nvim_command must not be called in a lua loop callback
if M._has_devicons and not M._devicons.has_loaded() then
M._devicons.setup()
end
M._devicons_path = (function()
for _, p in ipairs(vim.api.nvim_list_runtime_paths()) do
if path.tail(p) == "nvim-web-devicons" then
return path.join({p, "lua/nvim-web-devicons.lua"})
end
end
end)()
function M._default_previewer_fn()
return M.globals.default_previewer or M.globals.winopts.preview.default
@ -162,6 +162,7 @@ M.globals.files = {
previewer = M._default_previewer_fn,
prompt = '> ',
cmd = nil, -- default: auto detect find|fd
multiprocess = true,
file_icons = true and M._has_devicons,
color_icons = true,
git_icons = true,
@ -239,6 +240,7 @@ M.globals.grep = {
prompt = 'Rg> ',
input_prompt = 'Grep For> ',
cmd = nil, -- default: auto detect rg|grep
multiprocess = true,
file_icons = true and M._has_devicons,
color_icons = true,
git_icons = true,
@ -496,20 +498,7 @@ M.globals.nvim = {
M.globals.file_icon_padding = ''
if M._has_devicons then
M.globals.file_icon_colors = {}
local function hex(hex)
local r,g,b = hex:match('.(..)(..)(..)')
r, g, b = tonumber(r, 16), tonumber(g, 16), tonumber(b, 16)
return r, g, b
end
for ext, info in pairs(M._devicons.get_icons()) do
local r, g, b = hex(info.color)
utils.add_ansi_code('DevIcon' .. info.name, string.format('[38;2;%s;%s;%sm', r, g, b))
end
else
if not M._has_devicons then
M.globals.file_icon_colors = {
["lua"] = "blue",
["rockspec"] = "magenta",

@ -7,6 +7,7 @@ local actions = require "fzf-lua.actions"
local win = require "fzf-lua.win"
local libuv = require "fzf-lua.libuv"
local shell = require "fzf-lua.shell"
local make_entry = require "fzf-lua.make_entry"
local M = {}
@ -51,7 +52,7 @@ M.fzf = function(opts, contents)
fzf_win:create()
local selected, exit_code = fzf.raw_fzf(contents, M.build_fzf_cli(opts),
{ fzf_binary = opts.fzf_bin, fzf_cwd = opts.cwd })
utils.process_kill(opts._pid)
libuv.process_kill(opts._pid)
fzf_win:check_exit_status(exit_code)
if fzf_win:autoclose() == nil or fzf_win:autoclose() then
fzf_win:close()
@ -59,22 +60,6 @@ M.fzf = function(opts, contents)
return selected
end
M.get_devicon = function(file, ext)
local icon, hl
if config._has_devicons and config._devicons then
icon, hl = config._devicons.get_icon(file, ext:lower(), {default = true})
else
icon, hl = '', 'dark_grey'
end
-- allow user override of the color
local override = config.globals.file_icon_colors[ext]
if override then
hl = override
end
return icon..config.globals.file_icon_padding:gsub(" ", utils.nbsp), hl
end
M.preview_window = function(o)
local preview_args = ("%s:%s:%s:"):format(
@ -209,24 +194,72 @@ M.build_fzf_cli = function(opts)
return cli_args .. extra_args
end
local get_diff_files = function(opts)
local diff_files = {}
local cmd = opts.git_status_cmd or config.globals.files.git_status_cmd
if not cmd then return {} end
local status, err = utils.io_systemlist(path.git_cwd(cmd, opts.cwd))
if err == 0 then
for i = 1, #status do
local icon = status[i]:match("[MUDARC?]+")
local file = status[i]:match("[^ ]*$")
if icon and file then
diff_files[file] = icon
end
M.mt_cmd_wrapper = function(opts)
assert(opts and opts.cmd)
local str_to_str = function(s)
return "[[" .. s:gsub('[%]]', function(x) return "\\"..x end) .. "]]"
end
local opts_to_str = function(o)
local names = {
"debug",
"cmd",
"cwd",
"git_icons",
"file_icons",
"color_icons",
"strip_cwd_prefix",
}
local str = ""
for _, name in ipairs(names) do
if o[name] ~= nil then
if #str>0 then str = str..',' end
local val = o[name]
if type(val) == 'string' then
val = str_to_str(val)
end
if type(val) == 'table' then
val = vim.inspect(val)
end
str = str .. ("%s=%s"):format(name, val)
end
end
return '{'..str..'}'
end
return diff_files
if not opts.git_icons and not opts.file_icons then
-- command does not require any processing
return opts.cmd
elseif opts.multiprocess then
local fn_preprocess = [[return require("make_entry").preprocess]]
local fn_transform = [[return require("make_entry").file]]
if not opts.no_remote_config then
fn_transform = ([[_G._fzf_lua_server=%s; %s]]):format(
vim.fn.shellescape(vim.g.fzf_lua_server),
fn_transform)
end
if config._devicons_path then
fn_transform = ([[_G._devicons_path=%s; %s]]) :format(
vim.fn.shellescape(config._devicons_path),
fn_transform)
end
local cmd = libuv.wrap_spawn_stdio(opts_to_str(opts),
fn_transform, fn_preprocess)
if opts.debug then print(cmd) end
return cmd
else
return libuv.spawn_nvim_fzf_cmd(opts,
function(x)
return make_entry.file(opts, x)
end)
end
end
-- shortcuts to make_entry
M.get_devicon = make_entry.get_devicon
M.make_entry_file = make_entry.file
M.make_entry_lcol = function(_, entry)
if not entry then return nil end
local filename = entry.filename or vim.api.nvim_buf_get_name(entry.bufnr)
@ -238,58 +271,6 @@ M.make_entry_lcol = function(_, entry)
entry.text)
end
M.make_entry_file = function(opts, x)
local icon, hl
local ret = {}
local file = utils.strip_ansi_coloring(string.match(x, '[^:]*'))
if opts.cwd_only and path.starts_with_separator(file) then
local cwd = opts.cwd or vim.loop.cwd()
if not path.is_relative(file, cwd) then
return nil
end
end
-- fd v8.3 requires adding '--strip-cwd-prefix' to remove
-- the './' prefix, will not work with '--color=always'
-- https://github.com/sharkdp/fd/blob/master/CHANGELOG.md
if not (opts.strip_cwd_prefix == false) and path.starts_with_cwd(x) then
x = x:sub(3)
end
if opts.cwd and #opts.cwd > 0 then
-- TODO: does this work if there are ANSI escape codes in x?
x = path.relative(x, opts.cwd)
end
if opts.file_icons then
local filename = path.tail(file)
local ext = path.extension(filename)
icon, hl = M.get_devicon(filename, ext)
if opts.color_icons then
-- extra workaround for issue #119 (or similars)
-- use default if we can't find the highlight ansi
local fn = utils.ansi_codes[hl] or utils.ansi_codes['dark_grey']
icon = fn(icon)
end
ret[#ret+1] = icon
ret[#ret+1] = utils.nbsp
end
if opts.git_icons then
local indicators = opts.diff_files and opts.diff_files[file] or utils.nbsp
for i=1,#indicators do
icon = indicators:sub(i,i)
local git_icon = config.globals.git.icons[icon]
if git_icon then
icon = git_icon.icon
if opts.color_icons then
icon = utils.ansi_codes[git_icon.color or "dark_grey"](icon)
end
end
ret[#ret+1] = icon
end
ret[#ret+1] = utils.nbsp
end
ret[#ret+1] = x
return table.concat(ret)
end
M.set_fzf_line_args = function(opts)
opts._line_placeholder = 2
-- delimiters are ':' and <tab>
@ -336,6 +317,7 @@ M.set_header = function(opts, type)
return opts
end
M.fzf_files = function(opts, contents)
if not opts then return end
@ -345,13 +327,8 @@ M.fzf_files = function(opts, contents)
coroutine.wrap(function ()
if opts.cwd_only and not opts.cwd then
opts.cwd = vim.loop.cwd()
end
if opts.git_icons then
opts.diff_files = get_diff_files(opts)
end
-- setup opts.cwd and git diff files
make_entry.preprocess(opts)
local selected = M.fzf(opts, contents or opts.fzf_fn)
@ -385,7 +362,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 raw_async_act = libuv.spawn_reload_cmd_action(opts, placeholder)
local raw_async_act = shell.reload_action_cmd(opts, placeholder)
return M.set_fzf_interactive(opts, raw_async_act, placeholder)
end
@ -460,10 +437,8 @@ 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 = 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 })
opts.fzf_fn = act_cmd:gsub(placeholder,
#query>0 and utils.lua_escape(vim.fn.shellescape(query)) or "''")
end
opts.fzf_opts['--phony'] = ''
opts.fzf_opts['--query'] = vim.fn.shellescape(query)

@ -20,18 +20,14 @@ do
end
end
-- Since 'nvim_fzf.vim' doesn't get called we
-- have to manually set 'g:nvim_fzf_directory'
if not vim.g.nvim_fzf_directory then
local nvim_fzf_directory = path.join({
path.parent(path.parent(path.parent(path.parent(currFile)))),
"nvim-fzf"
})
if vim.loop.fs_stat(nvim_fzf_directory) then
vim.g.nvim_fzf_directory = nvim_fzf_directory
end
-- utils.info(("vim.g.nvim_fzf_directory = '%s'"):format(nvim_fzf_directory))
-- Create a new RPC server (tmp socket) to listen to messages (actions/headless)
-- this is safer than using $NVIM_LISTEN_ADDRESS. If the user is using a custom
-- fixed $NVIM_LISTEN_ADDRESS different neovim instances will use the same path
-- as their address and messages won't be recieved on older instances
if not vim.g.fzf_lua_server then
vim.g.fzf_lua_server = vim.fn.serverstart()
end
end
local M = {}

@ -1,9 +1,24 @@
local shell = require "fzf-lua.shell"
local utils = require "fzf-lua.utils"
local uv = vim.loop
local M = {}
-- path to current file
local __FILE__ = debug.getinfo(1, 'S').source:gsub("^@", "")
-- if loading this file as standalone ('--headless --clean')
-- add the current folder to package.path so we can 'require'
if not vim.g.fzf_lua_directory then
-- prepend this folder first so our modules always get first
-- priority over some unknown random module with the same name
package.path = (";%s/?.lua;"):format(vim.fn.fnamemodify(__FILE__, ':h'))
.. package.path
-- override require to remove the 'fzf-lua.' part
-- since all files are going to be loaded locally
local _require = require
require = function(s) return _require(s:gsub("^fzf%-lua%.", "")) end
end
-- save to upvalue for performance reasons
local string_byte = string.byte
local string_sub = string.sub
@ -24,6 +39,17 @@ local function find_next_newline(str, start_idx)
end
end
local function process_kill(pid, signal)
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
M.process_kill = process_kill
local function coroutine_callback(fn)
local co = coroutine.running()
local callback = function(...)
@ -31,7 +57,7 @@ local function coroutine_callback(fn)
coroutine.resume(co, ...)
else
local pid = unpack({...})
utils.process_kill(pid)
process_kill(pid)
end
end
fn(callback)
@ -60,11 +86,11 @@ M.spawn = function(opts, fn_transform, fn_done)
if opts.fn_transform then fn_transform = opts.fn_transform end
local finish = function(sig, pid)
local finish = function(code, sig, from, pid)
output_pipe:shutdown()
error_pipe:shutdown()
if opts.cb_finish then
opts.cb_finish(sig, pid)
opts.cb_finish(code, sig, from, pid)
end
-- coroutinify callback
if fn_done then
@ -74,7 +100,7 @@ M.spawn = function(opts, fn_transform, fn_done)
-- https://github.com/luvit/luv/blob/master/docs.md
-- uv.spawn returns tuple: handle, pid
local _, pid = uv.spawn(vim.env.SHELL or "sh", {
local handle, pid = uv.spawn(vim.env.SHELL or "sh", {
args = { "-c", opts.cmd },
stdio = { nil, output_pipe, error_pipe },
cwd = opts.cwd
@ -86,13 +112,14 @@ M.spawn = function(opts, fn_transform, fn_done)
if write_cb_count==0 then
-- only close if all our uv.write
-- calls are completed
finish(1)
finish(code, signal, 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
if opts._pid_cb then opts._pid_cb(pid) end
local function write_cb(data)
write_cb_count = write_cb_count + 1
@ -101,12 +128,12 @@ M.spawn = function(opts, fn_transform, fn_done)
if err then
-- can fail with premature process kill
-- assert(not err)
finish(2, pid)
finish(130, 0, 2, pid)
elseif 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)
finish(0, 0, 3, pid)
end
end)
end
@ -143,7 +170,7 @@ M.spawn = function(opts, fn_transform, fn_done)
if err then
assert(not err)
finish(4, pid)
finish(130, 0, 4, pid)
end
if not data then
return
@ -180,23 +207,37 @@ M.spawn = function(opts, fn_transform, fn_done)
local err_cb = function(err, data)
if err then
assert(not err)
finish(9, pid)
finish(130, 0, 9, pid)
end
if not data then
return
end
write_cb(data)
if opts.cb_err then
opts.cb_err(data)
else
write_cb(data)
end
end
output_pipe:read_start(read_cb)
error_pipe:read_start(err_cb)
if not handle then
-- uv.spawn failed, error will be in 'pid'
-- call once to output the error message
-- and second time to signal EOF (data=nil)
err_cb(nil, pid.."\n")
err_cb(pid, nil)
else
output_pipe:read_start(read_cb)
error_pipe:read_start(err_cb)
end
end
M.async_spawn = coroutinify(M.spawn)
M.spawn_nvim_fzf_cmd = function(opts, fn_transform)
assert(not fn_transform or type(fn_transform) == 'function')
return function(_, fzf_cb, _)
local function on_finish(_, _)
@ -225,78 +266,114 @@ M.spawn_nvim_fzf_cmd = function(opts, fn_transform)
end
end
M.spawn_stdio = function(opts, fn_transform, fn_preprocess)
M.spawn_nvim_fzf_action = function(fn, fzf_field_expression)
local function load_fn(fn_str)
if type(fn_str) ~= 'string' then return end
local fn_loaded = nil
local fn = loadstring(fn_str) or load(fn_str)
if fn then fn_loaded = fn() end
if type(fn_loaded) ~= 'function' then
fn_loaded = nil
end
return fn_loaded
end
return shell.async_action(function(pipe, ...)
fn_transform = load_fn(fn_transform)
fn_preprocess = load_fn(fn_preprocess)
local function on_finish(_, _)
if pipe and not uv.is_closing(pipe) then
uv.close(pipe)
pipe = nil
end
end
-- run the preprocessing fn
if fn_preprocess then fn_preprocess(opts) end
local function on_write(data, cb)
if not pipe then
cb(true)
else
uv.write(pipe, data, cb)
end
end
-- print(uv.os_getpid(), ":", opts.cmd, opts.cwd)
local stderr, stdout = nil, nil
return M.spawn({
cmd = fn(...),
cb_finish = on_finish,
cb_write = on_write,
}, false)
local function exit(exit_code, msg)
if msg then
-- prioritize writing errors to stderr
if stderr then stderr:write(msg)
else io.write(msg) end
end
os.exit(exit_code)
end
end, fzf_field_expression)
end
local function pipe_open(pipename)
local fd = uv.fs_open(pipename, "w", -1)
if type(fd) ~= 'number' then
exit(1, ("error opening '%s': %s\n"):format(pipename, fd))
end
local pipe = uv.new_pipe(false)
pipe:open(fd)
return pipe
end
M.spawn_reload_cmd_action = function(opts, fzf_field_expression)
local function pipe_close(pipe)
if pipe and not pipe:is_closing() then
pipe:close()
end
end
local _pid = nil
local function pipe_write(pipe, data, cb)
if not pipe or pipe:is_closing() then return end
pipe:write(data,
function(err)
-- if the user cancels the call prematurely with
-- <C-c> err will be either EPIPE or ECANCELED
-- don't really need to do anything since the
-- processs will be killed anyways with os.exit()
if err then print("write err:", err) end
if cb then cb(err) end
end)
end
return shell.raw_async_action(function(pipe, args)
stderr = pipe_open(opts.stderr or "/dev/stderr")
stdout = pipe_open(opts.stdout or "/dev/stdout")
assert(stderr and stdout)
local function on_pid(pid)
_pid = pid
if opts.pid_cb then
opts.pid_cb(pid)
end
local on_finish = opts.on_finish or
function(code)
pipe_close(stdout)
pipe_close(stderr)
exit(code)
end
local function on_finish(_, _)
if pipe and not uv.is_closing(pipe) then
uv.close(pipe)
pipe = nil
end
local on_write = opts.on_write or
function(data, cb)
pipe_write(stdout, data, cb)
end
local function on_write(data, cb)
if not pipe then
cb(true)
else
uv.write(pipe, data, cb)
end
local on_err = opts.on_err or
function(data)
pipe_write(stderr, data)
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,
-- must send false, 'coroutinify' adds callback as last argument
-- which will conflict with the 'fn_transform' argument
}, opts._fn_transform or false)
return M.spawn({
cwd = opts.cwd,
cmd = opts.cmd,
cb_finish = on_finish,
cb_write = on_write,
cb_err = on_err,
},
fn_transform and function(x)
return fn_transform(opts, x)
end)
end
end, fzf_field_expression)
M.wrap_spawn_stdio = function(opts, fn_transform, fn_preprocess)
assert(opts and type(opts) == 'string')
assert(not fn_transform or type(fn_transform) == 'string')
local nvim_bin = vim.v.argv[1]
local call_args = opts
for _, fn in ipairs({ fn_transform, fn_preprocess }) do
if type(fn) == 'string' then
call_args = ("%s,[[%s]]"):format(call_args, fn)
end
end
local cmd_str = ("%s -n --headless --clean --cmd %s"):format(
vim.fn.shellescape(nvim_bin),
vim.fn.shellescape(("lua loadfile([[%s]])().spawn_stdio(%s)")
:format(__FILE__, call_args)))
return cmd_str
end
return M

@ -0,0 +1,203 @@
local M = {}
local path = require "fzf-lua.path"
local utils = require "fzf-lua.utils"
local config = nil
-- attempt to load the current config
-- should fail if we're running headless
do
local ok, module = pcall(require, "fzf-lua.config")
if ok then config = module end
end
-- These globals are set by spawn.fn_transform loadstring
---@diagnostic disable-next-line: undefined-field
M._fzf_lua_server = _G._fzf_lua_server
---@diagnostic disable-next-line: undefined-field
M._devicons_path = _G._devicons_path
local function load_config_section(s)
if config then
local keys = utils.strsplit(s, '.')
local iter, sect = config, nil
for i=1,#keys do
iter = iter[keys[i]]
if not iter then break end
if i == #keys then sect = iter end
end
return sect
elseif M._fzf_lua_server then
-- load config from our running instance
local res = nil
local ok, errmsg = pcall(function()
local chan_id = vim.fn.sockconnect("pipe", M._fzf_lua_server, { rpc = true })
res = vim.rpcrequest(chan_id, "nvim_exec_lua", ([[
return require'fzf-lua'.config.%s
]]):format(s), {})
vim.fn.chanclose(chan_id)
end)
if not ok then
io.write(("Error loading remote config section '%s': %s\n")
:format(s, errmsg))
else
return res
end
end
end
-- Setup the terminal colors codes for nvim-web-devicons colors
local setup_devicon_term_hls = function()
local function hex(hexstr)
local r,g,b = hexstr:match('.(..)(..)(..)')
r, g, b = tonumber(r, 16), tonumber(g, 16), tonumber(b, 16)
return r, g, b
end
for _, info in pairs(M._devicons.get_icons()) do
local r, g, b = hex(info.color)
utils.add_ansi_code('DevIcon' .. info.name, string.format('[38;2;%s;%s;%sm', r, g, b))
end
end
local function load_devicons()
-- file was called from a headless instance
-- load nvim-web-devicons manually
if config and config._has_devicons then
M._devicons = config._devicons
elseif M._devicons_path and vim.loop.fs_stat(M._devicons_path) then
local file = loadfile(M._devicons_path)
M._devicons = file and file()
end
if M._devicons and M._devicons.setup and not M._devicons.has_loaded() then
-- if the caller has devicons lazy loaded
-- running without calling setup will generate an error:
-- nvim-web-devicons.lua:972: E5560:
-- nvim_command must not be called in a lua loop callback
M._devicons.setup()
end
-- Setup devicon terminal ansi color codes
setup_devicon_term_hls()
end
-- Load remote config and devicons
load_devicons()
if not config then
local _config = { globals = { git = {}, files = {} } }
_config.globals.git.icons = load_config_section('globals.git.icons')
_config.globals.file_icon_colors = load_config_section('globals.file_icon_colors')
_config.globals.file_icon_padding = load_config_section('globals.file_icon_padding')
_config.globals.files.git_status_cmd = load_config_section('globals.files.git_status_cmd')
-- _G.dump(_config)
config = _config
end
M.get_devicon = function(file, ext)
local icon, hl
if M._devicons then
icon, hl = M._devicons.get_icon(file, ext:lower(), {default = true})
else
icon, hl = '', 'dark_grey'
end
-- allow user override of the color
local override = config.globals.file_icon_colors[ext]
if override then
hl = override
end
return icon..config.globals.file_icon_padding:gsub(" ", utils.nbsp), hl
end
M.get_diff_files = function(opts)
local diff_files = {}
local cmd = opts.git_status_cmd or config.globals.files.git_status_cmd
if not cmd then return {} end
local status, err = utils.io_systemlist(path.git_cwd(cmd, opts.cwd))
if err == 0 then
for i = 1, #status do
local icon = status[i]:match("[MUDARC?]+")
local file = status[i]:match("[^ ]*$")
if icon and file then
diff_files[file] = icon
end
end
end
return diff_files
end
M.preprocess = function(opts)
if opts.cwd_only and not opts.cwd then
opts.cwd = vim.loop.cwd()
end
if opts.git_icons then
opts.diff_files = M.get_diff_files(opts)
end
return opts
end
M.file = function(opts, x)
local ret = {}
local icon, hl
local file = utils.strip_ansi_coloring(string.match(x, '[^:]*'))
-- TODO: this can cause issues with files/grep/live_grep
-- process_lines gsub will replace the entry with nil
-- **low priority as we never use 'cwd_only' with files/grep
if opts.cwd_only and path.starts_with_separator(file) then
local cwd = opts.cwd or vim.loop.cwd()
if not path.is_relative(file, cwd) then
return nil
end
end
-- fd v8.3 requires adding '--strip-cwd-prefix' to remove
-- the './' prefix, will not work with '--color=always'
-- https://github.com/sharkdp/fd/blob/master/CHANGELOG.md
if not (opts.strip_cwd_prefix == false) and path.starts_with_cwd(x) then
x = path.strip_cwd_prefix(x)
-- this is required to fix git icons not showing
-- since `git status -s` does not prepend './'
-- we can assume no ANSI coloring is present
-- since 'path.starts_with_cwd == true'
file = x
end
if opts.cwd and #opts.cwd > 0 then
-- TODO: does this work if there are ANSI escape codes in x?
x = path.relative(x, opts.cwd)
end
if opts.file_icons then
local filename = path.tail(file)
local ext = path.extension(filename)
icon, hl = M.get_devicon(filename, ext)
if opts.color_icons then
-- extra workaround for issue #119 (or similars)
-- use default if we can't find the highlight ansi
local fn = utils.ansi_codes[hl] or utils.ansi_codes['dark_grey']
icon = fn(icon)
end
ret[#ret+1] = icon
ret[#ret+1] = utils.nbsp
end
if opts.git_icons then
local indicators = opts.diff_files and opts.diff_files[file] or utils.nbsp
for i=1,#indicators do
icon = indicators:sub(i,i)
local git_icon = config.globals.git.icons[icon]
if git_icon then
icon = git_icon.icon
if opts.color_icons then
icon = utils.ansi_codes[git_icon.color or "dark_grey"](icon)
end
end
ret[#ret+1] = icon
end
ret[#ret+1] = utils.nbsp
end
ret[#ret+1] = x
return table.concat(ret)
end
return M

@ -7,16 +7,23 @@ M.separator = function()
return '/'
end
M.dot_byte = string_byte('.')
M.separator_byte = string_byte(M.separator())
M.starts_with_separator = function(path)
return path:find("^"..M.separator()) == 1
return string_byte(path, 1) == M.separator_byte
-- return path:find("^"..M.separator()) == 1
end
M.starts_with_cwd = function(path)
return path:match("^."..M.separator()) ~= nil
return #path>1
and string_byte(path, 1) == M.dot_byte
and string_byte(path, 2) == M.separator_byte
-- return path:match("^."..M.separator()) ~= nil
end
M.strip_cwd_prefix = function(path)
return #path>1 and path[1] == '.' and path[2] == M.separator()
return #path>2 and path:sub(3)
end
function M.tail(path)

@ -1,10 +1,8 @@
local path = require "fzf-lua.path"
local shell = require "fzf-lua.shell"
local utils = require "fzf-lua.utils"
local libuv = require "fzf-lua.libuv"
local Object = require "fzf-lua.class"
local Previewer = {}
Previewer.base = Object:extend()
@ -102,7 +100,7 @@ end
function Previewer.cmd_async:cmdline(o)
o = o or {}
local act = libuv.spawn_nvim_fzf_action(function(items)
local act = shell.preview_action_cmd(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
@ -126,7 +124,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 = libuv.spawn_nvim_fzf_action(function(items)
local act = shell.preview_action_cmd(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,
@ -151,7 +149,7 @@ end
function Previewer.git_diff:cmdline(o)
o = o or {}
local act = libuv.spawn_nvim_fzf_action(function(items)
local act = shell.preview_action_cmd(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)
@ -186,7 +184,7 @@ end
function Previewer.man_pages:cmdline(o)
o = o or {}
local act = libuv.spawn_nvim_fzf_action(function(items)
local act = shell.preview_action_cmd(function(items)
-- local manpage = require'fzf-lua.providers.manpages'.getmanpage(items[1])
local manpage = items[1]:match("[^[,( ]+")
local cmd = ("%s %s %s"):format(

@ -2,7 +2,6 @@ local core = require "fzf-lua.core"
local utils = require "fzf-lua.utils"
local shell = require "fzf-lua.shell"
local config = require "fzf-lua.config"
local libuv = require "fzf-lua.libuv"
local M = {}
@ -36,20 +35,10 @@ local get_files_cmd = function(opts)
end
M.files = function(opts)
opts = config.normalize_opts(opts, config.globals.files)
if not opts then return end
local command = get_files_cmd(opts)
local contents = (opts.git_icons or opts.file_icons) and
libuv.spawn_nvim_fzf_cmd(
{ cmd = command, cwd = opts.cwd, pid_cb = opts._pid_cb },
function(x)
return core.make_entry_file(opts, x)
end)
or command
opts.cmd = get_files_cmd(opts)
local contents = core.mt_cmd_wrapper(opts)
opts = core.set_header(opts, 2)
return core.fzf_files(opts, contents)
end

@ -4,6 +4,7 @@ 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 shell = require "fzf-lua.shell"
local M = {}
@ -12,13 +13,7 @@ M.files = function(opts)
if not opts then return end
opts.cwd = path.git_root(opts.cwd)
if not opts.cwd then return end
local contents = (opts.git_icons or opts.file_icons) and
libuv.spawn_nvim_fzf_cmd(
{ cmd = opts.cmd, cwd = opts.cwd, pid_cb = opts._pid_cb },
function(x)
return core.make_entry_file(opts, x)
end)
or opts.cmd
local contents = core.mt_cmd_wrapper(opts)
opts = core.set_header(opts, 2)
return core.fzf_files(opts, contents)
end
@ -87,7 +82,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 = libuv.spawn_nvim_fzf_action(function(items)
opts.preview = shell.preview_action_cmd(function(items)
local branch = items[1]:gsub("%*", "") -- remove the * from current branch
if branch:find("%)") ~= nil then
-- (HEAD detached at origin/master)

@ -2,7 +2,6 @@ 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 = {}
@ -83,16 +82,8 @@ M.grep = function(opts)
last_search.no_esc = no_esc or opts.no_esc
last_search.query = opts.search
local command = get_grep_cmd(opts, opts.search, no_esc)
local contents = (opts.git_icons or opts.file_icons) and
libuv.spawn_nvim_fzf_cmd(
{ cmd = command, cwd = opts.cwd, pid_cb = opts._pid_cb },
function(x)
return core.make_entry_file(opts, x)
end)
or command
opts.cmd = get_grep_cmd(opts, opts.search, no_esc)
local contents = core.mt_cmd_wrapper(opts)
opts = core.set_fzf_line_args(opts)
core.fzf_files(opts, contents)
opts.search = nil
@ -178,6 +169,14 @@ M.live_grep_native = function(opts)
-- search query in header line
opts = core.set_header(opts, 2)
-- we do not process any entries in the 'native' version
-- as fzf runs the command directly in the 'change:reload'
-- event, we could run the command through 'libuv.spawn_stdio'
-- wrapper but for that we have the non-native version
-- keep the native version "clean" for debugging/performance
opts.git_icons = false
opts.file_icons = false
-- 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}')
@ -199,12 +198,8 @@ 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 = libuv.spawn_nvim_fzf_cmd(
{cmd = initial_command:gsub(placeholder, vim.fn.shellescape(query)),
cwd = opts.cwd, pid_cb = opts._pid_cb },
function(x)
return core.make_entry_file(opts, x)
end)
opts.cmd = initial_command:gsub(placeholder, vim.fn.shellescape(query))
opts.fzf_fn = core.mt_cmd_wrapper(opts)
end
opts.fzf_opts['--phony'] = ''
opts.fzf_opts['--query'] = vim.fn.shellescape(query)
@ -213,12 +208,6 @@ M.live_grep_native = function(opts)
("%s || true"):format(reload_command))))
end
-- we cannot parse any entries as they're not getting called
-- past the initial command, until I can find a solution for
-- that icons must be disabled
opts.git_icons = false
opts.file_icons = false
opts = core.set_fzf_line_args(opts)
core.fzf_files(opts)
opts.search = nil

@ -2,6 +2,7 @@
-- 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 = {}
@ -41,20 +42,16 @@ function M.raw_async_action(fn, fzf_field_expression)
end)
end
if not action_server_address then
action_server_address = vim.fn.serverstart()
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_command = vim.v.argv[1]
local action_string = string.format("%s --headless --clean --cmd %s %s %s %s",
local action_string = string.format("%s -n --headless --clean --cmd %s %s %s %s",
vim.fn.shellescape(nvim_command),
vim.fn.shellescape("luafile " .. path.join{vim.g.fzf_lua_directory, "shell_helper.lua"}),
vim.fn.shellescape(action_server_address),
vim.fn.shellescape(vim.g.fzf_lua_server),
id,
fzf_field_expression)
return action_string, id
@ -109,4 +106,77 @@ if false then
M.raw_async_action = require("fzf.actions").raw_async_action
end
M.preview_action_cmd = function(fn, fzf_field_expression)
return M.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)
end
M.reload_action_cmd = function(opts, fzf_field_expression)
local _pid = nil
return M.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
libuv.process_kill(_pid)
-- return libuv.spawn({
return libuv.async_spawn({
cwd = opts.cwd,
cmd = opts._reload_command(args[1]),
cb_finish = on_finish,
cb_write = on_write,
cb_pid = on_pid,
-- must send false, 'coroutinify' adds callback as last argument
-- which will conflict with the 'fn_transform' argument
}, opts._fn_transform or false)
end, fzf_field_expression)
end
return M

@ -453,16 +453,6 @@ function M.io_system(cmd, use_lua_io)
end
end
local uv = vim.loop
function M.process_kill(pid, signal)
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)
local conv_map = {
['alt'] = 'A',

Loading…
Cancel
Save