Skip to content

Commit 2d8a4e9

Browse files
committed
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.
1 parent fd4e94e commit 2d8a4e9

14 files changed

Lines changed: 549 additions & 265 deletions

File tree

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,7 @@ require'fzf-lua'.setup {
380380
files = {
381381
-- previewer = "cat", -- uncomment to override previewer
382382
prompt = 'Files❯ ',
383+
multiprocess = true, -- run command in a separator process
383384
git_icons = true, -- show git icons?
384385
file_icons = true, -- show file icons?
385386
color_icons = true, -- colorize file|git icons
@@ -410,6 +411,7 @@ require'fzf-lua'.setup {
410411
git = {
411412
files = {
412413
prompt = 'GitFiles❯ ',
414+
multiprocess = false, -- run command in a separator process
413415
cmd = 'git ls-files --exclude-standard',
414416
git_icons = true, -- show git icons?
415417
file_icons = true, -- show file icons?
@@ -463,6 +465,7 @@ require'fzf-lua'.setup {
463465
grep = {
464466
prompt = 'Rg❯ ',
465467
input_prompt = 'Grep For❯ ',
468+
multiprocess = true, -- run command in a separator process
466469
git_icons = true, -- show git icons?
467470
file_icons = true, -- show file icons?
468471
color_icons = true, -- colorize file|git icons

doc/fzf-lua.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,7 @@ Consult the list below for available settings:
414414
files = {
415415
-- previewer = "cat", -- uncomment to override previewer
416416
prompt = 'Files❯ ',
417+
multiprocess = true, -- run command in a separator process
417418
git_icons = true, -- show git icons?
418419
file_icons = true, -- show file icons?
419420
color_icons = true, -- colorize file|git icons
@@ -444,6 +445,7 @@ Consult the list below for available settings:
444445
git = {
445446
files = {
446447
prompt = 'GitFiles❯ ',
448+
multiprocess = false, -- run command in a separator process
447449
cmd = 'git ls-files --exclude-standard',
448450
git_icons = true, -- show git icons?
449451
file_icons = true, -- show file icons?
@@ -497,6 +499,7 @@ Consult the list below for available settings:
497499
grep = {
498500
prompt = 'Rg❯ ',
499501
input_prompt = 'Grep For❯ ',
502+
multiprocess = true, -- run command in a separator process
500503
git_icons = true, -- show git icons?
501504
file_icons = true, -- show file icons?
502505
color_icons = true, -- colorize file|git icons

lua/fzf-lua/config.lua

Lines changed: 10 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,13 @@ local M = {}
1212

1313
M._has_devicons, M._devicons = pcall(require, "nvim-web-devicons")
1414

15-
-- if the caller has devicons lazy loaded
16-
-- this will generate an error
17-
-- nvim-web-devicons.lua:972: E5560:
18-
-- nvim_command must not be called in a lua loop callback
19-
if M._has_devicons and not M._devicons.has_loaded() then
20-
M._devicons.setup()
21-
end
15+
M._devicons_path = (function()
16+
for _, p in ipairs(vim.api.nvim_list_runtime_paths()) do
17+
if path.tail(p) == "nvim-web-devicons" then
18+
return path.join({p, "lua/nvim-web-devicons.lua"})
19+
end
20+
end
21+
end)()
2222

2323
function M._default_previewer_fn()
2424
return M.globals.default_previewer or M.globals.winopts.preview.default
@@ -162,6 +162,7 @@ M.globals.files = {
162162
previewer = M._default_previewer_fn,
163163
prompt = '> ',
164164
cmd = nil, -- default: auto detect find|fd
165+
multiprocess = true,
165166
file_icons = true and M._has_devicons,
166167
color_icons = true,
167168
git_icons = true,
@@ -239,6 +240,7 @@ M.globals.grep = {
239240
prompt = 'Rg> ',
240241
input_prompt = 'Grep For> ',
241242
cmd = nil, -- default: auto detect rg|grep
243+
multiprocess = true,
242244
file_icons = true and M._has_devicons,
243245
color_icons = true,
244246
git_icons = true,
@@ -496,20 +498,7 @@ M.globals.nvim = {
496498

497499
M.globals.file_icon_padding = ''
498500

499-
if M._has_devicons then
500-
M.globals.file_icon_colors = {}
501-
502-
local function hex(hex)
503-
local r,g,b = hex:match('.(..)(..)(..)')
504-
r, g, b = tonumber(r, 16), tonumber(g, 16), tonumber(b, 16)
505-
return r, g, b
506-
end
507-
508-
for ext, info in pairs(M._devicons.get_icons()) do
509-
local r, g, b = hex(info.color)
510-
utils.add_ansi_code('DevIcon' .. info.name, string.format('[38;2;%s;%s;%sm', r, g, b))
511-
end
512-
else
501+
if not M._has_devicons then
513502
M.globals.file_icon_colors = {
514503
["lua"] = "blue",
515504
["rockspec"] = "magenta",

lua/fzf-lua/core.lua

Lines changed: 69 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ local actions = require "fzf-lua.actions"
77
local win = require "fzf-lua.win"
88
local libuv = require "fzf-lua.libuv"
99
local shell = require "fzf-lua.shell"
10+
local make_entry = require "fzf-lua.make_entry"
1011

1112
local M = {}
1213

@@ -51,30 +52,14 @@ M.fzf = function(opts, contents)
5152
fzf_win:create()
5253
local selected, exit_code = fzf.raw_fzf(contents, M.build_fzf_cli(opts),
5354
{ fzf_binary = opts.fzf_bin, fzf_cwd = opts.cwd })
54-
utils.process_kill(opts._pid)
55+
libuv.process_kill(opts._pid)
5556
fzf_win:check_exit_status(exit_code)
5657
if fzf_win:autoclose() == nil or fzf_win:autoclose() then
5758
fzf_win:close()
5859
end
5960
return selected
6061
end
6162

62-
M.get_devicon = function(file, ext)
63-
local icon, hl
64-
if config._has_devicons and config._devicons then
65-
icon, hl = config._devicons.get_icon(file, ext:lower(), {default = true})
66-
else
67-
icon, hl = '', 'dark_grey'
68-
end
69-
70-
-- allow user override of the color
71-
local override = config.globals.file_icon_colors[ext]
72-
if override then
73-
hl = override
74-
end
75-
76-
return icon..config.globals.file_icon_padding:gsub(" ", utils.nbsp), hl
77-
end
7863

7964
M.preview_window = function(o)
8065
local preview_args = ("%s:%s:%s:"):format(
@@ -209,24 +194,72 @@ M.build_fzf_cli = function(opts)
209194
return cli_args .. extra_args
210195
end
211196

212-
local get_diff_files = function(opts)
213-
local diff_files = {}
214-
local cmd = opts.git_status_cmd or config.globals.files.git_status_cmd
215-
if not cmd then return {} end
216-
local status, err = utils.io_systemlist(path.git_cwd(cmd, opts.cwd))
217-
if err == 0 then
218-
for i = 1, #status do
219-
local icon = status[i]:match("[MUDARC?]+")
220-
local file = status[i]:match("[^ ]*$")
221-
if icon and file then
222-
diff_files[file] = icon
223-
end
197+
M.mt_cmd_wrapper = function(opts)
198+
assert(opts and opts.cmd)
199+
200+
local str_to_str = function(s)
201+
return "[[" .. s:gsub('[%]]', function(x) return "\\"..x end) .. "]]"
202+
end
203+
204+
local opts_to_str = function(o)
205+
local names = {
206+
"debug",
207+
"cmd",
208+
"cwd",
209+
"git_icons",
210+
"file_icons",
211+
"color_icons",
212+
"strip_cwd_prefix",
213+
}
214+
local str = ""
215+
for _, name in ipairs(names) do
216+
if o[name] ~= nil then
217+
if #str>0 then str = str..',' end
218+
local val = o[name]
219+
if type(val) == 'string' then
220+
val = str_to_str(val)
224221
end
222+
if type(val) == 'table' then
223+
val = vim.inspect(val)
224+
end
225+
str = str .. ("%s=%s"):format(name, val)
226+
end
225227
end
228+
return '{'..str..'}'
229+
end
226230

227-
return diff_files
231+
if not opts.git_icons and not opts.file_icons then
232+
-- command does not require any processing
233+
return opts.cmd
234+
elseif opts.multiprocess then
235+
local fn_preprocess = [[return require("make_entry").preprocess]]
236+
local fn_transform = [[return require("make_entry").file]]
237+
if not opts.no_remote_config then
238+
fn_transform = ([[_G._fzf_lua_server=%s; %s]]):format(
239+
vim.fn.shellescape(vim.g.fzf_lua_server),
240+
fn_transform)
241+
end
242+
if config._devicons_path then
243+
fn_transform = ([[_G._devicons_path=%s; %s]]) :format(
244+
vim.fn.shellescape(config._devicons_path),
245+
fn_transform)
246+
end
247+
local cmd = libuv.wrap_spawn_stdio(opts_to_str(opts),
248+
fn_transform, fn_preprocess)
249+
if opts.debug then print(cmd) end
250+
return cmd
251+
else
252+
return libuv.spawn_nvim_fzf_cmd(opts,
253+
function(x)
254+
return make_entry.file(opts, x)
255+
end)
256+
end
228257
end
229258

259+
-- shortcuts to make_entry
260+
M.get_devicon = make_entry.get_devicon
261+
M.make_entry_file = make_entry.file
262+
230263
M.make_entry_lcol = function(_, entry)
231264
if not entry then return nil end
232265
local filename = entry.filename or vim.api.nvim_buf_get_name(entry.bufnr)
@@ -238,58 +271,6 @@ M.make_entry_lcol = function(_, entry)
238271
entry.text)
239272
end
240273

241-
M.make_entry_file = function(opts, x)
242-
local icon, hl
243-
local ret = {}
244-
local file = utils.strip_ansi_coloring(string.match(x, '[^:]*'))
245-
if opts.cwd_only and path.starts_with_separator(file) then
246-
local cwd = opts.cwd or vim.loop.cwd()
247-
if not path.is_relative(file, cwd) then
248-
return nil
249-
end
250-
end
251-
-- fd v8.3 requires adding '--strip-cwd-prefix' to remove
252-
-- the './' prefix, will not work with '--color=always'
253-
-- https://github.com/sharkdp/fd/blob/master/CHANGELOG.md
254-
if not (opts.strip_cwd_prefix == false) and path.starts_with_cwd(x) then
255-
x = x:sub(3)
256-
end
257-
if opts.cwd and #opts.cwd > 0 then
258-
-- TODO: does this work if there are ANSI escape codes in x?
259-
x = path.relative(x, opts.cwd)
260-
end
261-
if opts.file_icons then
262-
local filename = path.tail(file)
263-
local ext = path.extension(filename)
264-
icon, hl = M.get_devicon(filename, ext)
265-
if opts.color_icons then
266-
-- extra workaround for issue #119 (or similars)
267-
-- use default if we can't find the highlight ansi
268-
local fn = utils.ansi_codes[hl] or utils.ansi_codes['dark_grey']
269-
icon = fn(icon)
270-
end
271-
ret[#ret+1] = icon
272-
ret[#ret+1] = utils.nbsp
273-
end
274-
if opts.git_icons then
275-
local indicators = opts.diff_files and opts.diff_files[file] or utils.nbsp
276-
for i=1,#indicators do
277-
icon = indicators:sub(i,i)
278-
local git_icon = config.globals.git.icons[icon]
279-
if git_icon then
280-
icon = git_icon.icon
281-
if opts.color_icons then
282-
icon = utils.ansi_codes[git_icon.color or "dark_grey"](icon)
283-
end
284-
end
285-
ret[#ret+1] = icon
286-
end
287-
ret[#ret+1] = utils.nbsp
288-
end
289-
ret[#ret+1] = x
290-
return table.concat(ret)
291-
end
292-
293274
M.set_fzf_line_args = function(opts)
294275
opts._line_placeholder = 2
295276
-- delimiters are ':' and <tab>
@@ -336,6 +317,7 @@ M.set_header = function(opts, type)
336317
return opts
337318
end
338319

320+
339321
M.fzf_files = function(opts, contents)
340322

341323
if not opts then return end
@@ -345,13 +327,8 @@ M.fzf_files = function(opts, contents)
345327

346328
coroutine.wrap(function ()
347329

348-
if opts.cwd_only and not opts.cwd then
349-
opts.cwd = vim.loop.cwd()
350-
end
351-
352-
if opts.git_icons then
353-
opts.diff_files = get_diff_files(opts)
354-
end
330+
-- setup opts.cwd and git diff files
331+
make_entry.preprocess(opts)
355332

356333
local selected = M.fzf(opts, contents or opts.fzf_fn)
357334

@@ -385,7 +362,7 @@ M.set_fzf_interactive_cmd = function(opts)
385362
-- fzf already adds single quotes around the placeholder when expanding
386363
-- for skim we surround it with double quotes or single quote searches fail
387364
local placeholder = utils._if(opts._is_skim, '"{}"', '{q}')
388-
local raw_async_act = libuv.spawn_reload_cmd_action(opts, placeholder)
365+
local raw_async_act = shell.reload_action_cmd(opts, placeholder)
389366
return M.set_fzf_interactive(opts, raw_async_act, placeholder)
390367
end
391368

@@ -460,10 +437,8 @@ M.set_fzf_interactive = function(opts, act_cmd, placeholder)
460437
-- around the place holder
461438
opts.fzf_fn = {}
462439
if opts.exec_empty_query or (query and #query>0) then
463-
opts.fzf_fn = libuv.spawn_nvim_fzf_cmd(
464-
{ cmd = act_cmd:gsub(placeholder,
465-
#query>0 and utils.lua_escape(vim.fn.shellescape(query)) or "''"),
466-
cwd = opts.cwd, pid_cb = opts._pid_cb })
440+
opts.fzf_fn = act_cmd:gsub(placeholder,
441+
#query>0 and utils.lua_escape(vim.fn.shellescape(query)) or "''")
467442
end
468443
opts.fzf_opts['--phony'] = ''
469444
opts.fzf_opts['--query'] = vim.fn.shellescape(query)

lua/fzf-lua/init.lua

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,14 @@ do
2020
end
2121
end
2222

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

3733
local M = {}

0 commit comments

Comments
 (0)