Major refactor, read below if you're having issues

This patch is quite massive, hopefully I got everything right in
testing, changes include:
- New API interface for "live" queries, for usage refer to:
  https://github.com/ibhagwan/fzf-lua/wiki/Advanced
- All providers now use 'fzf_exec' API (previously 'fzf_wrap')
- All "live" queries now use 'fzf_live' API
- Better resume support for "live" queries
- Fzf initial command now uses $FZF_DEFAULT_COMMAND instead of piping the
  command, this delegates the responsiblity to fzf which kills the
  command on exit resulting in better responsibness when exiting fzf
- Added 'silent_fail' option (default:'true') to prevent fzf from
  displaying [Command failed:...] when commands exit with error code
- Exposed 'config.globals' as 'require'fzf-lua'.defaults'
- Fix: 'libuv.shellescape' with special chars in fish shells
- Manpages: moved fzf option `--tiebreak=begin' to config
- Buffer actions: navigate to line if exists
- Lsp_diagnostics: properly use a coroutine
- make_entry signatures changed (entry before opts)
- Removed make_entry shortcuts from 'core'
- Removed 'coroutine.yield' where unecessary
- Fix: 'git_icons' with 'live_grep({multiprocess=false})'
- tagstack: use relative paths and replace '$HOME' with '~'
- Deprecated 'core.fzf_files'
- Fix: resume query if cancelled while loading indicator is shown
- Fix: resume query when command failed with 'silent_fail=false'
main
bhagwan 2 years ago
parent 341f0641ea
commit 817df87a8e

@ -8,9 +8,10 @@
![Demo](https://raw.githubusercontent.com/wiki/ibhagwan/fzf-lua/demo.gif) ![Demo](https://raw.githubusercontent.com/wiki/ibhagwan/fzf-lua/demo.gif)
[fzf](https://github.com/junegunn/fzf) changed my life, it can change yours too, if you allow it. [fzf](https://github.com/junegunn/fzf) changed my command life, it can change
yours too, if you allow it.
</div> </div>
## Rationale ## Rationale
@ -30,9 +31,9 @@ the new shiny fuzzy finders for neovim.
## Why Fzf-Lua ## Why Fzf-Lua
... and not, to name a few, ... and not
[telescope](https://github.com/nvim-telescope/telescope.nvim) or [telescope](https://github.com/nvim-telescope/telescope.nvim)
[vim-clap](https://github.com/liuchengxu/vim-clap)? or any other vim/neovim household name?
As [@junegunn](https://github.com/junegunn) himself put it, “because you can As [@junegunn](https://github.com/junegunn) himself put it, “because you can
and you love `fzf`”. and you love `fzf`”.
@ -55,7 +56,7 @@ at it. That, **and colorful file icons and git indicators!**.
(optional) (optional)
> `fzf` version > `0.27` is recommended but it's still possible to use `fzf` > `fzf` version > `0.27` is recommended but it's still possible to use `fzf`
> version > `0.24` by setting `fzf_opts = { ['--border'] = false }`, see > version > `0.25` by setting `fzf_opts = { ['--border'] = false }`, see
> [Customization](#customization). > [Customization](#customization).
### Optional dependencies ### Optional dependencies
@ -249,9 +250,14 @@ vim.api.nvim_set_keymap('n', '<c-P>',
## Customization ## Customization
I tried to make it as customizable as possible, if you find you need to change something that isnt below, open an issue and Ill do my best to add it. > **[ADVANCED CUSTOMIZATION](https://github.com/ibhagwan/fzf-lua/wiki/Advanced)
: to create your own fzf-lua commands see
[Wiki/ADVANCED](https://github.com/ibhagwan/fzf-lua/wiki/Advanced)**
customization can be achieved by calling the `setup()` function or individually sending parameters to a builtin command, for example: I tried to make this plugin as customizable as possible, if you find you need to
change something that isnt below, open an issue and Ill do my best to add it.
Customization can be achieved by calling the `setup()` function or individually sending parameters to a builtin command, for example:
```lua ```lua
:lua require('fzf-lua').files({ fzf_opts = {['--layout'] = 'reverse-list'} }) :lua require('fzf-lua').files({ fzf_opts = {['--layout'] = 'reverse-list'} })
``` ```
@ -603,7 +609,7 @@ require'fzf-lua'.setup {
-- 'rg_glob_fn' to return a pair: -- 'rg_glob_fn' to return a pair:
-- first returned argument is the new search query -- first returned argument is the new search query
-- second returned argument are addtional rg flags -- second returned argument are addtional rg flags
-- rg_glob_fn = function(opts, query) -- rg_glob_fn = function(query, opts)
-- ... -- ...
-- return new_query, flags -- return new_query, flags
-- end, -- end,
@ -810,7 +816,7 @@ and plugin codes that I probably forgot where I found some samples from so if
I missed your name feel free to contact me and I'll add it below: I missed your name feel free to contact me and I'll add it below:
- [@vijaymarupudi](https://github.com/vijaymarupudi/) for his wonderful - [@vijaymarupudi](https://github.com/vijaymarupudi/) for his wonderful
[nvim-fzf](https://github.com/vijaymarupudi/nvim-fzf) plugin which is in the [nvim-fzf](https://github.com/vijaymarupudi/nvim-fzf) plugin which is at the
core of this plugin core of this plugin
- [@tjdevries](https://github.com/tjdevries/) for too many great things to - [@tjdevries](https://github.com/tjdevries/) for too many great things to
list here and for borrowing some of his list here and for borrowing some of his

@ -66,7 +66,7 @@ DEPENDENCIES *fzf-lua-dependencies*
- nvim-web-devicons <https://github.com/kyazdani42/nvim-web-devicons> - nvim-web-devicons <https://github.com/kyazdani42/nvim-web-devicons>
(optional) (optional)
`fzf` version > `0.27` is recommended but it's still possible to use `fzf` `fzf` version > `0.27` is recommended but it's still possible to use `fzf`
version > `0.24` by setting `fzf_opts = { ['--border'] = false }`, see version > `0.25` by setting `fzf_opts = { ['--border'] = false }`, see
Customization <#customization>. Customization <#customization>.
@ -648,7 +648,7 @@ Consult the list below for available settings:
-- 'rg_glob_fn' to return a pair: -- 'rg_glob_fn' to return a pair:
-- first returned argument is the new search query -- first returned argument is the new search query
-- second returned argument are addtional rg flags -- second returned argument are addtional rg flags
-- rg_glob_fn = function(opts, query) -- rg_glob_fn = function(query, opts)
-- ... -- ...
-- return new_query, flags -- return new_query, flags
-- end, -- end,
@ -874,4 +874,4 @@ I missed your name feel free to contact me and I'll add it below:
as baseline for the builtin previewer and his must have plugin nvim-bqf as baseline for the builtin previewer and his must have plugin nvim-bqf
<https://github.com/kevinhwang91/nvim-bqf> <https://github.com/kevinhwang91/nvim-bqf>
vim:tw=78:ts=8:ft=help:norl: vim:tw=78:ts=8:ft=help:norl:

@ -213,27 +213,37 @@ M.file_switch_or_edit = function(...)
end end
-- buffer actions -- buffer actions
M.vimcmd_buf = function(vimcmd, selected, _) M.vimcmd_buf = function(vimcmd, selected, opts)
local curbuf = vim.api.nvim_get_current_buf() local curbuf = vim.api.nvim_get_current_buf()
local lnum = vim.api.nvim_win_get_cursor(0)[1]
local is_term = utils.is_term_buffer(0)
for i = 1, #selected do for i = 1, #selected do
local bufnr = string.match(selected[i], "%[(%d+)") local entry = path.entry_to_file(selected[i], opts)
if bufnr then if not entry.bufnr then return end
if vimcmd == 'b' assert(type(entry.bufnr) == 'number')
and curbuf ~= tonumber(bufnr) if vimcmd == 'b'
and not vim.o.hidden and and curbuf ~= entry.bufnr
utils.buffer_is_dirty(nil, true) then and not vim.o.hidden and
-- warn the user when trying to switch from a dirty buffer utils.buffer_is_dirty(nil, true) then
-- when `:set nohidden` -- warn the user when trying to switch from a dirty buffer
return -- when `:set nohidden`
end return
if vimcmd ~= "b" or curbuf ~= tonumber(bufnr) then end
local cmd = vimcmd .. " " .. bufnr -- add current location to jumplist
local ok, res = pcall(vim.cmd, cmd) if not is_term then vim.cmd("normal! m`") end
if not ok then if vimcmd ~= "b" or curbuf ~= entry.bufnr then
utils.warn(("':%s' failed: %s"):format(cmd, res)) local cmd = vimcmd .. " " .. entry.bufnr
end local ok, res = pcall(vim.cmd, cmd)
if not ok then
utils.warn(("':%s' failed: %s"):format(cmd, res))
end end
end end
if curbuf ~= entry.bufnr or lnum ~= entry.line then
-- make sure we have valid column
entry.col = entry.col and entry.col>0 and entry.col or 1
vim.api.nvim_win_set_cursor(0, {tonumber(entry.line), tonumber(entry.col)-1})
end
if not is_term then vim.cmd("norm! zvzz") end
end end
end end
@ -431,19 +441,25 @@ M.help_tab = function(selected)
M.vimcmd(vimcmd, helptags(selected), true) M.vimcmd(vimcmd, helptags(selected), true)
end end
local function mantags(s)
return vim.tbl_map(function(x)
return x:match("[^[,( ]+")
end, s)
end
M.man = function(selected) M.man = function(selected)
local vimcmd = "Man" local vimcmd = "Man"
M.vimcmd(vimcmd, selected) M.vimcmd(vimcmd, mantags(selected))
end end
M.man_vert = function(selected) M.man_vert = function(selected)
local vimcmd = "vert Man" local vimcmd = "vert Man"
M.vimcmd(vimcmd, selected) M.vimcmd(vimcmd, mantags(selected))
end end
M.man_tab = function(selected) M.man_tab = function(selected)
local vimcmd = "tab Man" local vimcmd = "tab Man"
M.vimcmd(vimcmd, selected) M.vimcmd(vimcmd, mantags(selected))
end end
@ -586,8 +602,7 @@ end
M.grep_lgrep = function(_, opts) M.grep_lgrep = function(_, opts)
-- 'FNCREF' is set only on 'M.live_grep' calls -- 'MODULE' is set on 'grep' and 'live_grep' calls
-- 'MODULE' is set on 'M.grep' and 'live_grep' calls
assert(opts.__MODULE__ assert(opts.__MODULE__
and type(opts.__MODULE__.grep) == 'function' and type(opts.__MODULE__.grep) == 'function'
or type(opts.__MODULE__.live_grep) == 'function') or type(opts.__MODULE__.live_grep) == 'function')
@ -601,7 +616,8 @@ M.grep_lgrep = function(_, opts)
requires_processing = opts.rg_glob or opts.__call_opts.rg_glob, requires_processing = opts.rg_glob or opts.__call_opts.rg_glob,
}, opts.__call_opts or {}) }, opts.__call_opts or {})
if opts.__FNCREF__ then -- 'fn_reload' is set only on 'live_grep' calls
if opts.fn_reload then
opts.__MODULE__.grep(o) opts.__MODULE__.grep(o)
else else
opts.__MODULE__.live_grep(o) opts.__MODULE__.live_grep(o)
@ -609,3 +625,4 @@ M.grep_lgrep = function(_, opts)
end end
return M return M

@ -484,6 +484,7 @@ M.globals.manpages = {
["ctrl-v"] = actions.man_vert, ["ctrl-v"] = actions.man_vert,
["ctrl-t"] = actions.man_tab, ["ctrl-t"] = actions.man_tab,
}, },
fzf_opts = { ['--tiebreak'] = 'begin' },
previewer = "man", previewer = "man",
} }
M.globals.lsp = { M.globals.lsp = {

@ -10,6 +10,73 @@ local make_entry = require "fzf-lua.make_entry"
local M = {} local M = {}
-- Main API, see:
-- https://github.com/ibhagwan/fzf-lua/wiki/Advanced
M.fzf_exec = function(contents, opts)
if not opts or not opts._normalized then
opts = config.normalize_opts(opts or {}, {})
if not opts then return end
end
opts.fn_selected = opts.fn_selected or function(selected)
if not selected then return end
actions.act(opts.actions, selected, opts)
end
-- wrapper for command transformer
if type(contents) == 'string' and
(opts.fn_transform or opts.fn_preprocess) then
contents = libuv.spawn_nvim_fzf_cmd({
cmd = contents,
cwd = opts.cwd,
pid_cb = opts._pid_cb,
},
opts.fn_transform or function(x) return x end,
opts.fn_preprocess)
end
-- setup as "live": disables fuzzy matching and reload the content
-- every keystroke (query changed), utlizes fzf's 'change:reload'
-- event trigger or skim's "interactive" mode
if type(opts.fn_reload) == 'string' then
if not opts.fn_transform then
-- TODO: add support for 'fn_transform' using 'mt_cmd_wrapper'
-- functions can be stored using 'config.bytecode' which uses
-- 'string.dump' to convert to function code to bytes
opts = M.setup_fzf_interactive_native(opts.fn_reload, opts)
contents = opts.__fzf_init_cmd
else
-- the caller requested to transform, we need to convert
-- to a function that returns string so that libuv.spawn
-- is called
local cmd = opts.fn_reload
opts.fn_reload = function(q)
if cmd:match(M.fzf_query_placeholder) then
return cmd:gsub(M.fzf_query_placeholder, q or '')
else
return string.format("%s %s", cmd, q or '')
end
end
end
end
if type(opts.fn_reload) == 'function' then
opts.__fn_transform = opts.fn_transform
opts.__fn_reload = function(query)
if config.__resume_data then
config.__resume_data.last_query = query
end
return opts.fn_reload(query)
end
opts = M.setup_fzf_interactive_wrap(opts)
contents = opts.__fzf_init_cmd
end
return M.fzf_wrap(opts, contents)()
end
M.fzf_live = function(contents, opts)
assert(contents)
opts = opts or {}
opts.fn_reload = contents
return M.fzf_exec(nil, opts)
end
M.fzf_resume = function(opts) M.fzf_resume = function(opts)
if not config.__resume_data or not config.__resume_data.opts then if not config.__resume_data or not config.__resume_data.opts then
utils.info("No resume data available, is 'global_resume' enabled?") utils.info("No resume data available, is 'global_resume' enabled?")
@ -17,26 +84,16 @@ M.fzf_resume = function(opts)
end end
opts = vim.tbl_deep_extend("force", config.__resume_data.opts, opts or {}) opts = vim.tbl_deep_extend("force", config.__resume_data.opts, opts or {})
local last_query = config.__resume_data.last_query local last_query = config.__resume_data.last_query
if last_query and #last_query>0 then if not last_query or #last_query==0 then
last_query = vim.fn.shellescape(last_query)
else
-- in case we continue from another resume -- in case we continue from another resume
-- reset the previous query which was saved -- reset the previous query which was saved
-- inside "fzf_opts['--query']" argument -- inside "fzf_opts['--query']" argument
last_query = false last_query = false
end end
opts.__resume = true opts.__resume = true
if opts.__FNCREF__ then opts.query = last_query
-- HACK for 'live_grep' and 'lsp_live_workspace_symbols' opts.fzf_opts['--query'] = last_query and vim.fn.shellescape(last_query)
opts.cmd = nil M.fzf_exec(config.__resume_data.contents, opts)
opts.query = nil
opts.search = nil
opts.resume = true
opts.__FNCREF__(opts)
else
opts.fzf_opts['--query'] = last_query
M.fzf_wrap(opts, config.__resume_data.contents)()
end
end end
M.fzf_wrap = function(opts, contents, fn_selected) M.fzf_wrap = function(opts, contents, fn_selected)
@ -50,26 +107,10 @@ M.fzf_wrap = function(opts, contents, fn_selected)
end) end)
end end
M.fzf_exec = function(contents, opts)
opts = config.normalize_opts(opts or {}, {})
opts.fn_selected = opts.fn_selected or function(selected)
if not selected then return end
actions.act(opts.actions, selected, opts)
end
-- wrapper for command transformer
if type(contents) == 'string' and opts.fn_transform then
contents = libuv.spawn_nvim_fzf_cmd({
cmd = contents,
cwd = opts.cwd,
}, opts.fn_transform)
end
return M.fzf_wrap(opts, contents)()
end
M.fzf = function(opts, contents) M.fzf = function(opts, contents)
-- normalize with globals if not already normalized -- normalize with globals if not already normalized
if not opts._normalized then if not opts or not opts._normalized then
opts = config.normalize_opts(opts, {}) opts = config.normalize_opts(opts or {}, {})
if not opts then return end if not opts then return end
end end
if opts.fn_pre_win then if opts.fn_pre_win then
@ -163,9 +204,12 @@ M.fzf = function(opts, contents)
opts.fzf_opts['--preview-window'] = 'hidden:right:0' opts.fzf_opts['--preview-window'] = 'hidden:right:0'
end end
-- some functions such as buffers|tabs
-- need to reacquire current buffer|tab state
if opts._fn_pre_fzf then
opts._fn_pre_fzf(opts)
end
if opts.fn_pre_fzf then if opts.fn_pre_fzf then
-- some functions such as buffers|tabs
-- need to reacquire current buffer|tab state
opts.fn_pre_fzf(opts) opts.fn_pre_fzf(opts)
end end
@ -175,7 +219,7 @@ M.fzf = function(opts, contents)
-- lose overrides by 'winopts_fn|winopts_raw' -- lose overrides by 'winopts_fn|winopts_raw'
opts.winopts.preview = fzf_win.winopts.preview opts.winopts.preview = fzf_win.winopts.preview
local selected, exit_code = fzf.raw_fzf(contents, M.build_fzf_cli(opts), local selected, exit_code = fzf.raw_fzf(contents, M.build_fzf_cli(opts),
{ fzf_binary = opts.fzf_bin, fzf_cwd = opts.cwd }) { fzf_bin = opts.fzf_bin, cwd = opts.cwd, silent_fail = opts.silent_fail })
-- This was added by 'resume': -- This was added by 'resume':
-- when '--print-query' is specified -- when '--print-query' is specified
-- we are guaranteed to have the query -- we are guaranteed to have the query
@ -191,6 +235,9 @@ M.fzf = function(opts, contents)
end end
table.remove(selected, 1) table.remove(selected, 1)
end end
if opts._fn_post_fzf then
opts._fn_post_fzf(opts, selected)
end
if opts.fn_post_fzf then if opts.fn_post_fzf then
opts.fn_post_fzf(opts, selected) opts.fn_post_fzf(opts, selected)
end end
@ -227,10 +274,12 @@ M.get_color = function(hl_group, what)
end end
-- Create fzf --color arguments from a table of vim highlight groups. -- Create fzf --color arguments from a table of vim highlight groups.
M.create_fzf_colors = function(colors) M.create_fzf_colors = function(opts)
if not colors then local colors = opts and opts.fzf_colors
return "" if type(colors) == 'function' then
colors = colors(opts)
end end
if not colors then return end
local tbl = {} local tbl = {}
for highlight, list in pairs(colors) do for highlight, list in pairs(colors) do
@ -247,7 +296,7 @@ M.create_fzf_colors = function(colors)
end end
end end
return string.format("--color=%s", table.concat(tbl, ",")) return not vim.tbl_isempty(tbl) and table.concat(tbl, ",")
end end
M.create_fzf_binds = function(binds) M.create_fzf_binds = function(binds)
@ -282,7 +331,7 @@ M.build_fzf_cli = function(opts)
end end
opts.fzf_opts["--bind"] = M.create_fzf_binds(opts.keymap.fzf) opts.fzf_opts["--bind"] = M.create_fzf_binds(opts.keymap.fzf)
if opts.fzf_colors then if opts.fzf_colors then
opts.fzf_opts["--color"] = M.create_fzf_colors(opts.fzf_colors) opts.fzf_opts["--color"] = M.create_fzf_colors(opts)
end end
opts.fzf_opts["--expect"] = actions.expect(opts.actions) opts.fzf_opts["--expect"] = actions.expect(opts.actions)
opts.fzf_opts["--preview"] = opts.preview or opts.fzf_opts["--preview"] opts.fzf_opts["--preview"] = opts.preview or opts.fzf_opts["--preview"]
@ -399,8 +448,10 @@ M.mt_cmd_wrapper = function(opts)
-- command does not require any processing -- command does not require any processing
return opts.cmd return opts.cmd
elseif opts.multiprocess then elseif opts.multiprocess then
local fn_preprocess = opts._fn_preprocess_str or [[return require("make_entry").preprocess]] assert(not opts.__mt_transform or type(opts.__mt_transform) == 'string')
local fn_transform = opts._fn_transform_str or [[return require("make_entry").file]] assert(not opts.__mt_preprocess or type(opts.__mt_preprocess) == 'string')
local fn_preprocess = opts.__mt_preprocess or [[return require("make_entry").preprocess]]
local fn_transform = opts.__mt_transform or [[return require("make_entry").file]]
-- replace all below 'fn.shellescape' with our version -- replace all below 'fn.shellescape' with our version
-- replacing the surrounding single quotes with double -- replacing the surrounding single quotes with double
-- as this was causing resume to fail with fish shell -- as this was causing resume to fail with fish shell
@ -427,40 +478,23 @@ M.mt_cmd_wrapper = function(opts)
end end
return cmd return cmd
else else
assert(not opts.__mt_transform or type(opts.__mt_transform) == 'function')
assert(not opts.__mt_preprocess or type(opts.__mt_preprocess) == 'function')
return libuv.spawn_nvim_fzf_cmd(opts, return libuv.spawn_nvim_fzf_cmd(opts,
function(x) function(x)
return opts._fn_transform return opts.__mt_transform
and opts._fn_transform(opts, x) and opts.__mt_transform(x, opts)
or make_entry.file(opts, x) or make_entry.file(x, opts)
end, end,
function(o) function(o)
-- setup opts.cwd and git diff files -- setup opts.cwd and git diff files
return opts._fn_preprocess return opts.__mt_preprocess
and opts._fn_preprocess(o) and opts.__mt_preprocess(o)
or make_entry.preprocess(o) or make_entry.preprocess(o)
end) end)
end end
end end
-- shortcuts to make_entry
M.get_devicon = make_entry.get_devicon
M.make_entry_file = make_entry.file
M.make_entry_preprocess = make_entry.preprocess
M.make_entry_lcol = function(opts, entry)
if not entry then return nil end
local filename = entry.filename or vim.api.nvim_buf_get_name(entry.bufnr)
return string.format("%s:%s:%s:%s%s",
-- uncomment to test URIs
-- "file://" .. filename,
filename, --utils.ansi_codes.magenta(filename),
utils.ansi_codes.green(tostring(entry.lnum)),
utils.ansi_codes.blue(tostring(entry.col)),
entry.text and #entry.text>0 and " " or "",
not entry.text and "" or
(opts.trim_entry and vim.trim(entry.text)) or entry.text)
end
-- given the default delimiter ':' this is the -- given the default delimiter ':' this is the
-- fzf experssion field index for the line number -- fzf experssion field index for the line number
-- when entry format is 'file:line:col: text' -- when entry format is 'file:line:col: text'
@ -542,7 +576,7 @@ M.set_header = function(opts, hdr_tbl)
if opts.no_header_i then return end if opts.no_header_i then return end
for k, v in pairs(opts.actions) do for k, v in pairs(opts.actions) do
if type(v) == 'table' and v[1] == actions.grep_lgrep then if type(v) == 'table' and v[1] == actions.grep_lgrep then
local to = opts.__FNCREF__ and 'Grep' or 'Live Grep' local to = opts.fn_reload and 'Grep' or 'Live Grep'
return (':: <%s> to %s'):format( return (':: <%s> to %s'):format(
utils.ansi_codes.yellow(k), utils.ansi_codes.yellow(k),
utils.ansi_codes.red(to)) utils.ansi_codes.red(to))
@ -583,127 +617,108 @@ M.set_header = function(opts, hdr_tbl)
return opts return opts
end end
-- NOT IN USE, here for backward compat
M.fzf_files = function(opts, contents) M.fzf_files = function(opts, contents)
utils.warn("'core.fzf_files' is deprecated, use 'fzf_exec' instead,"
.. " see github@fzf-lua/wiki/Advanced.")
M.fzf_exec(contents or opts and opts.fzf_fn and opts.fzf_fn, opts)
end
if not opts then return end M.setup_fzf_interactive_flags = function(command, fzf_field_expression, opts)
-- query cannot be 'nil'
opts.query = opts.query or ''
M.fzf_wrap(opts, contents or opts.fzf_fn, function(selected) -- by redirecting the error stream to stdout
-- we make sure a clear error message is displayed
-- when the user enters bad regex expressions
local initial_command = command
if (opts.stderr_to_stdout ~= false) and
not initial_command:match("2>") then
initial_command = command .. " 2>&1"
end
if opts.post_select_cb then local reload_command = initial_command
opts.post_select_cb() if not opts.exec_empty_query then
reload_command = ('[ -z %s ] || %s'):format(fzf_field_expression, reload_command)
end
if opts._is_skim then
-- skim interactive mode does not need a piped command
opts.__fzf_init_cmd = nil
opts.prompt = opts.prompt or opts.fzf_opts['--prompt']
if opts.prompt then
opts.fzf_opts['--prompt'] = opts.prompt:match("[^%*]+")
opts.fzf_opts['--cmd-prompt'] = libuv.shellescape(opts.prompt)
opts.prompt = nil
end end
-- '--query' was set by 'resume()', skim has the option to switch back and
if not selected then return end -- forth between interactive command and fuzzy matching (using 'ctrl-q')
-- setting both '--query' and '--cmd-query' will use <query> to fuzzy match
if #selected > 1 then -- on top of our result set double filtering our results (undesierable)
local idx = utils.tbl_length(opts.actions)>1 and 2 or 1 opts.fzf_opts['--query'] = nil
for i = idx, #selected do -- since we surrounded the skim placeholder with quotes
selected[i] = path.entry_to_file(selected[i], opts).stripped -- we need to escape them in the initial query
end opts.fzf_opts['--cmd-query'] = libuv.shellescape(utils.sk_escape(opts.query))
opts._fzf_cli_args = string.format("--interactive --cmd %s",
vim.fn.shellescape(reload_command))
else
-- send an empty table to avoid running $FZF_DEFAULT_COMMAND
opts.__fzf_init_cmd = {}
if opts.exec_empty_query or (opts.query and #opts.query > 0) then
opts.__fzf_init_cmd = initial_command:gsub(fzf_field_expression,
libuv.shellescape(opts.query:gsub("%%", "%%%%")))
end end
opts.fzf_opts['--disabled'] = ''
opts.fzf_opts['--query'] = libuv.shellescape(opts.query)
-- OR with true to avoid fzf's "Command failed:" message
if opts.silent_fail ~= false then
reload_command = ("%s || true"):format(reload_command)
end
opts._fzf_cli_args = string.format('--bind=%s',
vim.fn.shellescape(("change:reload:%s"):format(
("%s"):format(reload_command))))
end
actions.act(opts.actions, selected, opts) return opts
end)()
end end
M.set_fzf_interactive_cmd = function(opts) -- query placeholder for "live" queries
M.fzf_query_placeholder = "<query>"
if not opts then return end
M.fzf_field_expression = function(opts)
-- fzf already adds single quotes around the placeholder when expanding -- fzf already adds single quotes around the placeholder when expanding
-- for skim we surround it with double quotes or single quote searches fail -- for skim we surround it with double quotes or single quote searches fail
local placeholder = utils._if(opts._is_skim, '"{}"', '{q}') return opts and opts._is_skim and '"{}"' or '{q}'
local raw_async_act = shell.reload_action_cmd(opts, placeholder)
return M.set_fzf_interactive(opts, raw_async_act, placeholder)
end end
M.set_fzf_interactive_cb = function(opts) -- Sets up the flags and commands require for running a "live" interface
-- @param fn_reload :function called for reloading contents
if not opts then return end -- @param fn_transform :function to transform entries when using shell cmd
M.setup_fzf_interactive_wrap = 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 raw_async_act = shell.raw_async_action(function(pipe, args)
coroutine.wrap(function() assert(opts and opts.__fn_reload)
local co = coroutine.running() -- neovim shell wrapper for parsing the query and loading contents
local results = opts._reload_action(args[1]) local fzf_field_expression = M.fzf_field_expression(opts)
local command = shell.reload_action_cmd(opts, fzf_field_expression)
local close_pipe = function() return M.setup_fzf_interactive_flags(command, fzf_field_expression, opts)
if pipe and not uv.is_closing(pipe) then
uv.close(pipe)
pipe = nil
end
coroutine.resume(co)
end
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)
-- wait for write to finish
coroutine.yield()
end
-- does nothing if write finished successfully
close_pipe()
end)()
end, placeholder, opts.debug)
return M.set_fzf_interactive(opts, raw_async_act, placeholder)
end end
M.set_fzf_interactive = function(opts, act_cmd, placeholder) M.setup_fzf_interactive_native = function(command, opts)
if not opts or not act_cmd or not placeholder then return end local fzf_field_expression = M.fzf_field_expression(opts)
-- cannot be nil -- replace placeholder with the field index expression
local query = opts.query or '' -- if the command doesn't contain our placeholder append
-- the field index expression instead
if opts._is_skim then if command:match(M.fzf_query_placeholder) then
-- do not run an empty string query unless the user requested command = opts.fn_reload:gsub(M.fzf_query_placeholder, fzf_field_expression)
if not opts.exec_empty_query then
act_cmd = "sh -c " .. vim.fn.shellescape(
("[ -z %s ] || %s"):format(placeholder, act_cmd))
else
act_cmd = vim.fn.shellescape(act_cmd)
end
-- skim interactive mode does not need a piped command
opts.fzf_fn = nil
opts.fzf_opts['--prompt'] = opts.prompt:match("[^%*]+")
opts.fzf_opts['--cmd-prompt'] = libuv.shellescape(opts.prompt)
opts.prompt = nil
-- since we surrounded the skim placeholder with quotes
-- we need to escape them in the initial query
opts.fzf_opts['--cmd-query'] = libuv.shellescape(utils.sk_escape(query))
opts._fzf_cli_args = string.format( "-i -c %s", act_cmd)
else else
-- fzf already adds single quotes command = ("%s %s"):format(command, fzf_field_expression)
-- around the place holder
opts.fzf_fn = {}
if opts.exec_empty_query or (query and #query>0) then
opts.fzf_fn = act_cmd:gsub(placeholder,
#query>0 and utils.lua_escape(libuv.shellescape(query)) or "''")
end
opts.fzf_opts['--phony'] = ''
opts.fzf_opts['--query'] = libuv.shellescape(query)
opts._fzf_cli_args = string.format('--bind=%s',
vim.fn.shellescape(string.format("change:reload:%s || true", act_cmd)))
end end
return opts return M.setup_fzf_interactive_flags(command, fzf_field_expression, opts)
end end
return M return M

@ -7,15 +7,6 @@ local uv = vim.loop
local M = {} local M = {}
local function get_lines_from_file(file)
local t = {}
for v in file:lines() do
table.insert(t, v)
end
return t
end
-- workaround to a potential 'tempname' bug? (#222) -- workaround to a potential 'tempname' bug? (#222)
-- neovim doesn't guarantee the existence of the -- neovim doesn't guarantee the existence of the
-- parent temp dir potentially failing `mkfifo` -- parent temp dir potentially failing `mkfifo`
@ -41,21 +32,31 @@ end
-- behavior. -- behavior.
function M.raw_fzf(contents, fzf_cli_args, opts) function M.raw_fzf(contents, fzf_cli_args, opts)
if not coroutine.running() then if not coroutine.running() then
error("please run function in a coroutine") error("[Fzf-lua] function must be called inside a coroutine.")
end end
if not opts then opts = {} end if not opts then opts = {} end
local cwd = opts.fzf_cwd or opts.cwd local cwd = opts.fzf_cwd or opts.cwd
local cmd = opts.fzf_binary or opts.fzf_bin or 'fzf' local cmd = opts.fzf_bin or 'fzf'
local fifotmpname = tempname() local fifotmpname = tempname()
local outputtmpname = 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 fzf_cli_args then cmd = cmd .. " " .. fzf_cli_args end
if opts.fzf_cli_args then cmd = cmd .. " " .. opts.fzf_cli_args end if opts.fzf_cli_args then cmd = cmd .. " " .. opts.fzf_cli_args end
if contents then if contents then
if type(contents) == "string" and #contents>0 then if type(contents) == "string" and #contents>0 then
cmd = ("%s | %s"):format(contents, cmd) if opts.silent_fail ~= false then
contents = ("%s || true"):format(contents)
end
FZF_DEFAULT_COMMAND = contents
else else
cmd = ("%s < %s"):format(cmd, vim.fn.shellescape(fifotmpname)) cmd = ("%s < %s"):format(cmd, vim.fn.shellescape(fifotmpname))
end end
@ -134,11 +135,20 @@ function M.raw_fzf(contents, fzf_cli_args, opts)
local co = coroutine.running() local co = coroutine.running()
vim.fn.termopen({"sh", "-c", cmd}, { vim.fn.termopen({"sh", "-c", cmd}, {
cwd = cwd, cwd = cwd,
env = { ['SHELL'] = 'sh' }, env = {
['SHELL'] = 'sh',
['FZF_DEFAULT_COMMAND'] = FZF_DEFAULT_COMMAND,
['SKIM_DEFAULT_COMMAND'] = FZF_DEFAULT_COMMAND,
},
on_exit = function(_, rc, _) on_exit = function(_, rc, _)
local output = {}
local f = io.open(outputtmpname) local f = io.open(outputtmpname)
local output = get_lines_from_file(f) if f then
f:close() for v in f:lines() do
table.insert(output, v)
end
f:close()
end
finish(1) finish(1)
vim.fn.delete(fifotmpname) vim.fn.delete(fifotmpname)
vim.fn.delete(outputtmpname) vim.fn.delete(outputtmpname)

@ -155,9 +155,11 @@ M.dap_frames = require'fzf-lua.providers.dap'.frames
-- API shortcuts -- API shortcuts
M.fzf = require'fzf-lua.core'.fzf M.fzf = require'fzf-lua.core'.fzf
M.fzf_raw = require'fzf-lua.fzf'.raw_fzf
M.fzf_wrap = require'fzf-lua.core'.fzf_wrap M.fzf_wrap = require'fzf-lua.core'.fzf_wrap
M.fzf_exec = require'fzf-lua.core'.fzf_exec M.fzf_exec = require'fzf-lua.core'.fzf_exec
M.raw_fzf = require'fzf-lua.fzf'.raw_fzf M.fzf_live = require'fzf-lua.core'.fzf_live
M.defaults = config.globals
-- exported modules -- exported modules
M._exported_modules = { M._exported_modules = {
@ -169,15 +171,18 @@ M._exported_modules = {
'shell', 'shell',
'config', 'config',
'actions', 'actions',
'make_entry',
} }
-- excluded from builtin / auto-complete -- excluded from builtin / auto-complete
M._excluded_meta = { M._excluded_meta = {
'setup', 'setup',
'fzf', 'fzf',
'fzf_raw',
'fzf_wrap', 'fzf_wrap',
'fzf_exec', 'fzf_exec',
'raw_fzf', 'fzf_live',
'defaults',
'_excluded_meta', '_excluded_meta',
'_excluded_metamap', '_excluded_metamap',
'_exported_modules', '_exported_modules',

@ -418,7 +418,7 @@ M.spawn_stdio = function(opts, fn_transform, fn_preprocess)
cb_err = on_err, cb_err = on_err,
}, },
fn_transform and function(x) fn_transform and function(x)
return fn_transform(opts, x) return fn_transform(x, opts)
end) end)
end end
@ -443,7 +443,11 @@ M.shellescape = function(s)
-- replace surrounding single quote with double quotes -- replace surrounding single quote with double quotes
-- temporarily replace all single quotes with double -- temporarily replace all single quotes with double
-- quotes and restore after the call to shellescape -- quotes and restore after the call to shellescape
ret = vim.fn.shellescape(s:gsub([[']], [["]])) -- NOTE: we use '({s:gsub(...)})[1]' to extract the
-- modified string without the multival # of changes
-- otherwise the number will be sent to shellescape
-- as {special} triggering an escape for ! % and #
ret = vim.fn.shellescape(({s:gsub([[']], [["]])})[1])
ret = [["]] .. ret:gsub([["]], [[']]):sub(2, #ret-1) .. [["]] ret = [["]] .. ret:gsub([["]], [[']]):sub(2, #ret-1) .. [["]]
else else
ret = vim.fn.shellescape(s) ret = vim.fn.shellescape(s)

@ -206,12 +206,12 @@ M.get_diff_files = function(opts)
return diff_files return diff_files
end end
M.glob_parse = function(opts, query) M.glob_parse = function(query, opts)
if not query or not query:find(opts.glob_separator) then if not query or not query:find(opts.glob_separator) then
return query, nil return query, nil
end end
if config.globals.grep.rg_glob_fn then if config.globals.grep.rg_glob_fn then
return config.globals.grep.rg_glob_fn(opts, query) return config.globals.grep.rg_glob_fn(query, opts)
end end
local glob_args = "" local glob_args = ""
local search_query, glob_str = query:match("(.*)"..opts.glob_separator.."(.*)") local search_query, glob_str = query:match("(.*)"..opts.glob_separator.."(.*)")
@ -245,7 +245,7 @@ M.preprocess = function(opts)
-- live_grep replace pattern with last argument -- live_grep replace pattern with last argument
local argvz = "{argvz}" local argvz = "{argvz}"
local has_argvz = opts.cmd:match(argvz) local has_argvz = opts.cmd and opts.cmd:match(argvz)
-- save our last search argument for resume -- save our last search argument for resume
if opts.argv_expr and has_argvz then if opts.argv_expr and has_argvz then
@ -261,7 +261,7 @@ M.preprocess = function(opts)
-- mannipulation needs to be done before the argv hack -- mannipulation needs to be done before the argv hack
if opts.rg_glob and has_argvz then if opts.rg_glob and has_argvz then
local query = argv() local query = argv()
local search_query, glob_args = M.glob_parse(opts, query) local search_query, glob_args = M.glob_parse(query, opts)
if glob_args then if glob_args then
-- gsub doesn't like single % on rhs -- gsub doesn't like single % on rhs
search_query = search_query:gsub("%%", "%%%%") search_query = search_query:gsub("%%", "%%%%")
@ -284,7 +284,22 @@ M.preprocess = function(opts)
return opts return opts
end end
M.file = function(opts, x) M.lcol = function(entry, opts)
if not entry then return nil end
local filename = entry.filename or vim.api.nvim_buf_get_name(entry.bufnr)
return string.format("%s:%s:%s:%s%s",
-- uncomment to test URIs
-- "file://" .. filename,
filename, --utils.ansi_codes.magenta(filename),
utils.ansi_codes.green(tostring(entry.lnum)),
utils.ansi_codes.blue(tostring(entry.col)),
entry.text and #entry.text>0 and " " or "",
not entry.text and "" or
(opts and opts.trim_entry and vim.trim(entry.text)) or entry.text)
end
M.file = function(x, opts)
opts = opts or {}
local ret = {} local ret = {}
local icon, hl local icon, hl
local file = utils.strip_ansi_coloring(string.match(x, '[^:]*')) local file = utils.strip_ansi_coloring(string.match(x, '[^:]*'))
@ -351,7 +366,7 @@ M.file = function(opts, x)
return table.concat(ret) return table.concat(ret)
end end
M.tag = function(opts, x) M.tag = function(x, opts)
local name, file, text = x:match("([^\t]+)\t([^\t]+)\t(.*)") local name, file, text = x:match("([^\t]+)\t([^\t]+)\t(.*)")
if not file or not name or not text then return x end if not file or not name or not text then return x end
text = text:match('(.*);"') or text -- remove ctag comments text = text:match('(.*);"') or text -- remove ctag comments
@ -364,7 +379,7 @@ M.tag = function(opts, x)
local line, tag = text:match("(%d-);?(/.*/)") local line, tag = text:match("(%d-);?(/.*/)")
line = line and #line>0 and tonumber(line) line = line and #line>0 and tonumber(line)
return ("%s%s: %s %s"):format( return ("%s%s: %s %s"):format(
M.file(opts, file), M.file(file, opts),
not line and "" or ":"..utils.ansi_codes.green(tostring(line)), not line and "" or ":"..utils.ansi_codes.green(tostring(line)),
utils.ansi_codes.magenta(name), utils.ansi_codes.magenta(name),
utils.ansi_codes.green(tag)) utils.ansi_codes.green(tag))

@ -164,7 +164,6 @@ local grep_tag = function(file, tag)
else else
utils.warn(("previewer: unable to find pattern '%s' in file '%s'"):format(pattern, file)) utils.warn(("previewer: unable to find pattern '%s' in file '%s'"):format(pattern, file))
end end
-- if line == 1 then print(cmd) end
return line return line
end end

@ -2,7 +2,7 @@ local core = require "fzf-lua.core"
local path = require "fzf-lua.path" local path = require "fzf-lua.path"
local utils = require "fzf-lua.utils" local utils = require "fzf-lua.utils"
local config = require "fzf-lua.config" local config = require "fzf-lua.config"
local actions = require "fzf-lua.actions" local make_entry = require "fzf-lua.make_entry"
local M = {} local M = {}
@ -143,11 +143,11 @@ local function gen_buffer_entry(opts, buf, hl_curbuf)
if opts.file_icons then if opts.file_icons then
if utils.is_term_bufname(buf.info.name) then if utils.is_term_bufname(buf.info.name) then
-- get shell-like icon for terminal buffers -- get shell-like icon for terminal buffers
buficon, hl = core.get_devicon(buf.info.name, "sh") buficon, hl = make_entry.get_devicon(buf.info.name, "sh")
else else
local filename = path.tail(buf.info.name) local filename = path.tail(buf.info.name)
local extension = path.extension(filename) local extension = path.extension(filename)
buficon, hl = core.get_devicon(filename, extension) buficon, hl = make_entry.get_devicon(filename, extension)
end end
if opts.color_icons then if opts.color_icons then
buficon = utils.ansi_codes[hl](buficon) buficon = utils.ansi_codes[hl](buficon)
@ -193,13 +193,7 @@ M.buffers = function(opts)
opts = core.set_fzf_field_index(opts) opts = core.set_fzf_field_index(opts)
core.fzf_wrap(opts, contents, function(selected) core.fzf_exec(contents, opts)
if not selected then return end
actions.act(opts.actions, selected, opts)
end)()
end end
M.lines = function(opts) M.lines = function(opts)
@ -239,7 +233,7 @@ M.buffer_lines = function(opts)
if opts.file_icons then if opts.file_icons then
local filename = path.tail(bufname) local filename = path.tail(bufname)
local extension = path.extension(filename) local extension = path.extension(filename)
buficon, hl = core.get_devicon(filename, extension) buficon, hl = make_entry.get_devicon(filename, extension)
if opts.color_icons then if opts.color_icons then
buficon = utils.ansi_codes[hl](buficon) buficon = utils.ansi_codes[hl](buficon)
end end
@ -267,23 +261,7 @@ M.buffer_lines = function(opts)
opts = core.set_fzf_field_index(opts, 3, opts._is_skim and "{}" or "{..-2}") opts = core.set_fzf_field_index(opts, 3, opts._is_skim and "{}" or "{..-2}")
core.fzf_wrap(opts, items, function(selected) core.fzf_exec(items, opts)
if not selected then return end
-- get the line number
local line = tonumber(selected[2]:match(":(%d+):"))
actions.act(opts.actions, selected, opts)
if line then
-- add current location to jumplist
local is_term = utils.is_term_buffer(0)
if not is_term then vim.cmd("normal! m`") end
vim.api.nvim_win_set_cursor(0, {line, 0})
if not is_term then vim.cmd("norm! zz") end
end
end)()
end end
M.tabs = function(opts) M.tabs = function(opts)
@ -351,13 +329,7 @@ M.tabs = function(opts)
opts = core.set_fzf_field_index(opts, 3, "{}") opts = core.set_fzf_field_index(opts, 3, "{}")
core.fzf_wrap(opts, contents, function(selected) core.fzf_exec(contents, opts)
if not selected then return end
actions.act(opts.actions, selected, opts)
end)()
end end
return M return M

@ -115,12 +115,7 @@ M.highlights = function(opts)
opts.fzf_opts['--no-multi'] = '' opts.fzf_opts['--no-multi'] = ''
core.fzf_wrap(opts, contents, function(selected) core.fzf_exec(contents, opts)
if not selected then return end
actions.act(opts.actions, selected)
end)()
end end
return M return M

@ -3,6 +3,7 @@ local path = require "fzf-lua.path"
local utils = require "fzf-lua.utils" local utils = require "fzf-lua.utils"
local config = require "fzf-lua.config" local config = require "fzf-lua.config"
local actions = require "fzf-lua.actions" local actions = require "fzf-lua.actions"
local make_entry = require "fzf-lua.make_entry"
local _has_dap, _dap = nil, nil local _has_dap, _dap = nil, nil
@ -42,12 +43,7 @@ M.commands = function(opts)
opts.fzf_opts['--no-multi'] = '' opts.fzf_opts['--no-multi'] = ''
core.fzf_wrap(opts, entries, function(selected) core.fzf_exec(entries, opts)
if not selected then return end
actions.act(opts.actions, selected)
end)()
end end
M.configurations = function(opts) M.configurations = function(opts)
@ -83,12 +79,7 @@ M.configurations = function(opts)
opts.fzf_opts['--no-multi'] = '' opts.fzf_opts['--no-multi'] = ''
core.fzf_wrap(opts, entries, function(selected) core.fzf_exec(entries, opts)
if not selected then return end
actions.act(opts.actions, selected)
end)()
end end
M.breakpoints = function(opts) M.breakpoints = function(opts)
@ -133,14 +124,14 @@ M.breakpoints = function(opts)
local contents = function (cb) local contents = function (cb)
local entries = {} local entries = {}
for _, entry in ipairs(opts._locations) do for _, entry in ipairs(opts._locations) do
table.insert(entries, core.make_entry_lcol(opts, entry)) table.insert(entries, make_entry.lcol(entry, opts))
end end
for i, x in ipairs(entries) do for i, x in ipairs(entries) do
x = ("[%s] %s"):format( x = ("[%s] %s"):format(
-- tostring(opts._locations[i].bufnr), -- tostring(opts._locations[i].bufnr),
utils.ansi_codes.yellow(tostring(opts._locations[i].bufnr)), utils.ansi_codes.yellow(tostring(opts._locations[i].bufnr)),
core.make_entry_file(opts, x)) make_entry.file(x, opts))
if x then if x then
cb(x, function(err) cb(x, function(err)
if err then return end if err then return end
@ -160,12 +151,7 @@ M.breakpoints = function(opts)
opts = core.set_fzf_field_index(opts, 3, opts._is_skim and "{}" or "{..-2}") opts = core.set_fzf_field_index(opts, 3, opts._is_skim and "{}" or "{..-2}")
core.fzf_wrap(opts, contents, function(selected) core.fzf_exec(contents, opts)
if not selected then return end
actions.act(opts.actions, selected, opts)
end)()
end end
@ -197,12 +183,7 @@ M.variables = function(opts)
end end
end end
core.fzf_wrap(opts, entries, function(selected) core.fzf_exec(entries, opts)
if not selected then return end
actions.act(opts.actions, selected)
end)()
end end
@ -249,12 +230,7 @@ M.frames = function(opts)
opts.fzf_opts['--no-multi'] = '' opts.fzf_opts['--no-multi'] = ''
core.fzf_wrap(opts, entries, function(selected) core.fzf_exec(entries, opts)
if not selected then return end
actions.act(opts.actions, selected, opts)
end)()
end end

@ -1,6 +1,7 @@
local core = require "fzf-lua.core" local core = require "fzf-lua.core"
local utils = require "fzf-lua.utils" local utils = require "fzf-lua.utils"
local config = require "fzf-lua.config" local config = require "fzf-lua.config"
local make_entry = require "fzf-lua.make_entry"
local M = {} local M = {}
@ -39,7 +40,7 @@ M.files = function(opts)
opts.cmd = get_files_cmd(opts) opts.cmd = get_files_cmd(opts)
local contents = core.mt_cmd_wrapper(opts) local contents = core.mt_cmd_wrapper(opts)
opts = core.set_header(opts, opts.headers or {"cwd"}) opts = core.set_header(opts, opts.headers or {"cwd"})
return core.fzf_files(opts, contents) return core.fzf_exec(contents, opts)
end end
M.args = function(opts) M.args = function(opts)
@ -54,7 +55,7 @@ M.args = function(opts)
local contents = function (cb) local contents = function (cb)
local function add_entry(x, co) local function add_entry(x, co)
x = core.make_entry_file(opts, x) x = make_entry.file(x, opts)
if not x then return end if not x then return end
cb(x, function(err) cb(x, function(err)
coroutine.resume(co) coroutine.resume(co)
@ -89,14 +90,13 @@ M.args = function(opts)
-- end; print("took", os.time()-start, "seconds.") -- end; print("took", os.time()-start, "seconds.")
-- done -- done
cb(nil, function() coroutine.resume(co) end) cb(nil)
coroutine.yield()
end)() end)()
end end
opts = core.set_header(opts, opts.headers or {"cwd"}) opts = core.set_header(opts, opts.headers or {"cwd"})
return core.fzf_files(opts, contents) return core.fzf_exec(contents, opts)
end end
return M return M

@ -2,9 +2,9 @@ local core = require "fzf-lua.core"
local path = require "fzf-lua.path" local path = require "fzf-lua.path"
local utils = require "fzf-lua.utils" local utils = require "fzf-lua.utils"
local config = require "fzf-lua.config" local config = require "fzf-lua.config"
local actions = require "fzf-lua.actions"
local libuv = require "fzf-lua.libuv" local libuv = require "fzf-lua.libuv"
local shell = require "fzf-lua.shell" local shell = require "fzf-lua.shell"
local make_entry = require "fzf-lua.make_entry"
local M = {} local M = {}
@ -23,7 +23,7 @@ M.files = function(opts)
if not opts.cwd then return end if not opts.cwd then return end
local contents = core.mt_cmd_wrapper(opts) local contents = core.mt_cmd_wrapper(opts)
opts = core.set_header(opts, opts.headers or {"cwd"}) opts = core.set_header(opts, opts.headers or {"cwd"})
return core.fzf_files(opts, contents) return core.fzf_exec(contents, opts)
end end
M.status = function(opts) M.status = function(opts)
@ -67,8 +67,8 @@ M.status = function(opts)
if f1:match("%s%->%s") then if f1:match("%s%->%s") then
f1, f2 = f1:match("(.*)%s%->%s(.*)") f1, f2 = f1:match("(.*)%s%->%s(.*)")
end end
f1 = f1 and core.make_entry_file(opts, f1) f1 = f1 and make_entry.file(f1, opts)
f2 = f2 and core.make_entry_file(opts, f2) f2 = f2 and make_entry.file(f2, opts)
local staged = git_iconify(x:sub(1,1):gsub("?", " ")) local staged = git_iconify(x:sub(1,1):gsub("?", " "))
local unstaged = git_iconify(x:sub(2,2)) local unstaged = git_iconify(x:sub(2,2))
local entry = ("%s%s%s%s%s"):format( local entry = ("%s%s%s%s%s"):format(
@ -77,20 +77,17 @@ M.status = function(opts)
return entry return entry
end, end,
function(o) function(o)
return core.make_entry_preprocess(o) return make_entry.preprocess(o)
end) end)
opts = core.set_header(opts, opts.headers or {"cwd"}) opts = core.set_header(opts, opts.headers or {"cwd"})
return core.fzf_files(opts, contents) return core.fzf_exec(contents, opts)
end end
local function git_cmd(opts) local function git_cmd(opts)
opts = set_git_cwd_args(opts) opts = set_git_cwd_args(opts)
if not opts.cwd then return end if not opts.cwd then return end
opts = core.set_header(opts, opts.headers or {"cwd"}) opts = core.set_header(opts, opts.headers or {"cwd"})
core.fzf_wrap(opts, opts.cmd, function(selected) core.fzf_exec(opts.cmd, opts)
if not selected then return end
actions.act(opts.actions, selected, opts)
end)()
end end
M.commits = function(opts) M.commits = function(opts)

@ -53,7 +53,7 @@ local get_grep_cmd = function(opts, search_query, no_esc)
end end
if opts.rg_glob then if opts.rg_glob then
local new_query, glob_args = make_entry.glob_parse(opts, search_query) local new_query, glob_args = make_entry.glob_parse(search_query, opts)
if glob_args then if glob_args then
-- since the search string mixes both the query and -- since the search string mixes both the query and
-- glob separators we cannot used unescaped strings -- glob separators we cannot used unescaped strings
@ -126,9 +126,6 @@ M.grep = function(opts)
opts.search = utils.input(opts.input_prompt) or '' opts.search = utils.input(opts.input_prompt) or ''
end end
-- search query in header line
opts = core.set_header(opts, opts.headers or {"actions","cwd","search"})
-- get the grep command before saving the last search -- get the grep command before saving the last search
-- incase the search string is overwritten by 'rg_glob' -- incase the search string is overwritten by 'rg_glob'
opts.cmd = get_grep_cmd(opts, opts.search, no_esc) opts.cmd = get_grep_cmd(opts, opts.search, no_esc)
@ -163,8 +160,10 @@ M.grep = function(opts)
end end
end end
-- search query in header line
opts = core.set_header(opts, opts.headers or {"actions","cwd","search"})
opts = core.set_fzf_field_index(opts) opts = core.set_fzf_field_index(opts)
core.fzf_files(opts, contents) core.fzf_exec(contents, opts)
end end
-- single threaded version -- single threaded version
@ -195,10 +194,7 @@ M.live_grep_st = function(opts)
set_last_search(opts, opts.query, true) set_last_search(opts, opts.query, true)
end end
-- search query in header line opts.fn_reload = function(query)
opts = core.set_header(opts, opts.headers or {"actions","cwd"})
opts._reload_command = function(query)
if query and not (opts.save_last_search == false) then if query and not (opts.save_last_search == false) then
set_last_search(opts, query, true) set_last_search(opts, query, true)
end end
@ -209,9 +205,13 @@ M.live_grep_st = function(opts)
end end
if opts.requires_processing or opts.git_icons or opts.file_icons then if opts.requires_processing or opts.git_icons or opts.file_icons then
opts._fn_transform = opts._fn_transform opts.fn_transform = opts.fn_transform or
or function(x) function(x)
return core.make_entry_file(opts, x) return make_entry.file(x, opts)
end
opts.fn_preprocess = opts.fn_preprocess or
function(o)
return make_entry.preprocess(o)
end end
end end
@ -228,13 +228,10 @@ M.live_grep_st = function(opts)
end end
end end
-- disable global resume -- search query in header line
-- conflicts with 'change:reload' event opts = core.set_header(opts, opts.headers or {"actions","cwd"})
opts.global_resume_query = false
opts.__FNCREF__ = opts.__FNCREF__ or utils.__FNCREF__()
opts = core.set_fzf_field_index(opts) opts = core.set_fzf_field_index(opts)
opts = core.set_fzf_interactive_cmd(opts) core.fzf_exec(nil, opts)
core.fzf_files(opts)
end end
@ -264,29 +261,26 @@ M.live_grep_mt = function(opts)
opts.search, no_esc = get_last_search(opts) opts.search, no_esc = get_last_search(opts)
end end
local query = opts.search or '' -- interactive interface uses 'query' parameter
opts.query = opts.search or ''
if opts.search and #opts.search>0 then if opts.search and #opts.search>0 then
-- escape unless the user requested not to -- escape unless the user requested not to
if not (no_esc or opts.no_esc) then if not (no_esc or opts.no_esc) then
query = utils.rg_escape(opts.search) opts.query = utils.rg_escape(opts.search)
end end
-- save the search query so the use can -- save the search query so the use can
-- call the same search again -- call the same search again
set_last_search(opts, query, true) set_last_search(opts, opts.query, true)
end end
-- search query in header line
opts = core.set_header(opts, opts.headers or {"actions","cwd"})
-- signal to preprocess we are looking to replace {argvz} -- signal to preprocess we are looking to replace {argvz}
opts.argv_expr = true opts.argv_expr = true
-- fzf already adds single quotes around the placeholder when expanding -- this will be replaced by the approperiate fzf
-- for skim we surround it with double quotes or single quote searches fail -- FIELD INDEX EXPRESSION by 'fzf_exec'
local placeholder = utils._if(opts._is_skim, '"{}"', '{q}') opts.cmd = get_grep_cmd(opts , core.fzf_query_placeholder, 2)
opts.cmd = get_grep_cmd(opts , placeholder, 2) local command = core.mt_cmd_wrapper(opts)
local initial_command = core.mt_cmd_wrapper(opts) if command ~= opts.cmd then
if initial_command ~= opts.cmd then
-- this means mt_cmd_wrapper wrapped the command -- this means mt_cmd_wrapper wrapped the command
-- since now the `rg` command is wrapped inside -- since now the `rg` command is wrapped inside
-- the shell escaped '--headless .. --cmd' we won't -- the shell escaped '--headless .. --cmd' we won't
@ -297,41 +291,14 @@ M.live_grep_mt = function(opts)
-- * preprocess then relaces it with vim.fn.argv(1) -- * preprocess then relaces it with vim.fn.argv(1)
-- NOTE: since we cannot guarantee the positional index -- NOTE: since we cannot guarantee the positional index
-- of arguments (#291) we use the last argument instead -- of arguments (#291) we use the last argument instead
initial_command = initial_command:gsub(placeholder, "{argvz}") command = command:gsub(core.fzf_query_placeholder, "{argvz}")
.. " " .. placeholder .. " " .. core.fzf_query_placeholder
end
-- by redirecting the error stream to stdout
-- we make sure a clear error message is displayed
-- when the user enters bad regex expressions
initial_command = initial_command .. " 2>&1"
local reload_command = initial_command
if not opts.exec_empty_query then
reload_command = ('[ -z %s ] || %s'):format(placeholder, reload_command)
end
if opts._is_skim then
-- skim interactive mode does not need a piped command
opts.fzf_fn = nil
opts.fzf_opts['--prompt'] = opts.prompt:match("[^%*]+")
opts.fzf_opts['--cmd-prompt'] = vim.fn.shellescape(opts.prompt)
opts.prompt = nil
-- since we surrounded the skim placeholder with quotes
-- we need to escape them in the initial query
opts.fzf_opts['--cmd-query'] = libuv.shellescape(utils.sk_escape(query))
opts._fzf_cli_args = string.format("-i -c %s",
vim.fn.shellescape(reload_command))
else
opts.fzf_fn = {}
if opts.exec_empty_query or (opts.search and #opts.search > 0) then
opts.fzf_fn = initial_command:gsub(placeholder,
libuv.shellescape(query:gsub("%%", "%%%%")))
end
opts.fzf_opts['--phony'] = ''
opts.fzf_opts['--query'] = libuv.shellescape(query)
opts._fzf_cli_args = string.format('--bind=%s',
vim.fn.shellescape(("change:reload:%s"):format(
("%s || true"):format(reload_command))))
end end
-- signal 'fzf_exec' to set 'change:reload' parameters
-- or skim's "interactive" mode (AKA "live query")
opts.fn_reload = command
-- when running 'live_grep' with 'exec_empty_query=false' (default) -- when running 'live_grep' with 'exec_empty_query=false' (default)
-- an empty typed query will not be saved as the 'neovim --headless' -- an empty typed query will not be saved as the 'neovim --headless'
-- command isn't executed resulting in '_last_search.query' never -- command isn't executed resulting in '_last_search.query' never
@ -359,13 +326,10 @@ M.live_grep_mt = function(opts)
end end
end end
-- disable global resume -- search query in header line
-- conflicts with 'change:reload' event opts = core.set_header(opts, opts.headers or {"actions","cwd"})
opts.global_resume_query = false
opts.__FNCREF__ = opts.__FNCREF__ or utils.__FNCREF__()
opts = core.set_fzf_field_index(opts) opts = core.set_fzf_field_index(opts)
core.fzf_files(opts) core.fzf_exec(nil, opts)
opts.search = nil
end end
M.live_grep_glob_st = function(opts) M.live_grep_glob_st = function(opts)
@ -399,18 +363,12 @@ end
M.live_grep_native = function(opts) M.live_grep_native = function(opts)
-- backward compatibility, by setting git|files icons to false -- backward compatibility, by setting git|files icons to false
-- we forces mt_cmd_wrapper to pipe the command as is so fzf -- we force 'mt_cmd_wrapper' to pipe the command as is so fzf
-- runs the command directly in the 'change:reload' event -- runs the command directly in the 'change:reload' event
opts = opts or {} opts = opts or {}
opts.git_icons = false opts.git_icons = false
opts.file_icons = false opts.file_icons = false
opts.rg_glob = false opts.rg_glob = false
-- disable ctrl-g switch by default
if not opts.actions or not opts.actions["ctrl-g"] then
opts.actions = opts.actions or {}
opts.actions["ctrl-g"] = false
end
opts.__FNCREF__ = utils.__FNCREF__()
return M.live_grep_mt(opts) return M.live_grep_mt(opts)
end end
@ -418,8 +376,6 @@ M.live_grep = function(opts)
opts = config.normalize_opts(opts, config.globals.grep) opts = config.normalize_opts(opts, config.globals.grep)
if not opts then return end if not opts then return end
opts.__FNCREF__ = opts.__FNCREF__ or utils.__FNCREF__()
if opts.multiprocess then if opts.multiprocess then
return M.live_grep_mt(opts) return M.live_grep_mt(opts)
else else
@ -431,8 +387,6 @@ M.live_grep_glob = function(opts)
opts = config.normalize_opts(opts, config.globals.grep) opts = config.normalize_opts(opts, config.globals.grep)
if not opts then return end if not opts then return end
opts.__FNCREF__ = opts.__FNCREF__ or utils.__FNCREF__()
if opts.multiprocess then if opts.multiprocess then
return M.live_grep_glob_mt(opts) return M.live_grep_glob_mt(opts)
else else

@ -2,12 +2,11 @@ local path = require "fzf-lua.path"
local core = require "fzf-lua.core" local core = require "fzf-lua.core"
local utils = require "fzf-lua.utils" local utils = require "fzf-lua.utils"
local config = require "fzf-lua.config" local config = require "fzf-lua.config"
local actions = require "fzf-lua.actions"
local M = {} local M = {}
local fzf_function = function (cb) local fzf_fn = function (cb)
local opts = {} local opts = {}
opts.lang = config.globals.helptags.lang or vim.o.helplang opts.lang = config.globals.helptags.lang or vim.o.helplang
opts.fallback = utils._if(config.globals.helptags.fallback ~= nil, config.globals.helptags.fallback, true) opts.fallback = utils._if(config.globals.helptags.fallback ~= nil, config.globals.helptags.fallback, true)
@ -82,11 +81,7 @@ local fzf_function = function (cb)
end end
end end
end end
-- done, we can't call utils.delayed_cb here cb(nil)
-- because sleep() messes up the coroutine
-- cb(nil, function() coroutine.resume(co) end)
utils.delayed_cb(cb, function() coroutine.resume(co) end)
coroutine.yield()
end)() end)()
end end
@ -96,18 +91,10 @@ M.helptags = function(opts)
opts = config.normalize_opts(opts, config.globals.helptags) opts = config.normalize_opts(opts, config.globals.helptags)
if not opts then return end if not opts then return end
-- local prev_act = action(function (args) end)
opts.fzf_opts['--no-multi'] = '' opts.fzf_opts['--no-multi'] = ''
opts.fzf_opts['--preview-window'] = 'hidden:right:0' opts.fzf_opts['--preview-window'] = 'hidden:right:0'
core.fzf_wrap(opts, fzf_function, function(selected) core.fzf_exec(fzf_fn, opts)
if not selected then return end
actions.act(opts.actions, selected)
end)()
end end

@ -1,7 +1,7 @@
local core = require "fzf-lua.core" local core = require "fzf-lua.core"
local utils = require "fzf-lua.utils" local utils = require "fzf-lua.utils"
local config = require "fzf-lua.config" local config = require "fzf-lua.config"
local actions = require "fzf-lua.actions" local make_entry = require "fzf-lua.make_entry"
local M = {} local M = {}
@ -39,8 +39,8 @@ local function location_handler(opts, cb, _, result, ctx, _)
for _, entry in ipairs(items) do for _, entry in ipairs(items) do
if not opts.current_buffer_only or if not opts.current_buffer_only or
vim.api.nvim_buf_get_name(opts.bufnr) == entry.filename then vim.api.nvim_buf_get_name(opts.bufnr) == entry.filename then
entry = core.make_entry_lcol(opts, entry) entry = make_entry.lcol(entry, opts)
entry = core.make_entry_file(opts, entry) entry = make_entry.file(entry, opts)
if entry then if entry then
cb(entry, function(err) cb(entry, function(err)
if err then return end if err then return end
@ -61,8 +61,8 @@ local function call_hierarchy_handler(opts, cb, _, result, _, _)
lnum = range.start.line + 1, lnum = range.start.line + 1,
col = range.start.character + 1, col = range.start.character + 1,
} }
entry = core.make_entry_lcol(opts, entry) entry = make_entry.lcol(entry, opts)
entry = core.make_entry_file(opts, entry) entry = make_entry.file(entry, opts)
if entry then if entry then
cb(entry, function(err) cb(entry, function(err)
if err then return end if err then return end
@ -88,8 +88,8 @@ local function symbol_handler(opts, cb, _, result, _, _)
entry.text = entry.text:gsub("%[.-%]", M._sym2style[kind]) entry.text = entry.text:gsub("%[.-%]", M._sym2style[kind])
end end
end end
entry = core.make_entry_lcol(opts, entry) entry = make_entry.lcol(entry, opts)
entry = core.make_entry_file(opts, entry) entry = make_entry.file(entry, opts)
if entry then if entry then
cb(entry, function(err) cb(entry, function(err)
if err then return end if err then return end
@ -121,10 +121,10 @@ local function code_action_handler(opts, cb, _, code_actions, context, _)
end end
end end
local function diagnostics_handler(opts, cb, _, entry) local function diagnostics_handler(opts, cb, co, entry)
local type = entry.type local type = entry.type
entry = core.make_entry_lcol(opts, entry) entry = make_entry.lcol(entry, opts)
entry = core.make_entry_file(opts, entry) entry = make_entry.file(entry, opts)
if not entry then return end if not entry then return end
if opts.lsp_icons and opts._severity_icons[type] then if opts.lsp_icons and opts._severity_icons[type] then
local severity = opts._severity_icons[type] local severity = opts._severity_icons[type]
@ -134,9 +134,7 @@ local function diagnostics_handler(opts, cb, _, entry)
end end
entry = icon .. utils.nbsp .. utils.nbsp .. entry entry = icon .. utils.nbsp .. utils.nbsp .. entry
end end
cb(entry, function(err) cb(entry, function() coroutine.resume(co) end)
if err then return end
end)
end end
-- see neovim #15504 -- see neovim #15504
@ -181,7 +179,7 @@ local function wrap_handler(handler, opts, cb, co)
if opts.num_callbacks == opts.num_clients then if opts.num_callbacks == opts.num_clients then
-- close the pipe to fzf, this -- close the pipe to fzf, this
-- removes the loading indicator in fzf -- removes the loading indicator in fzf
utils.delayed_cb(cb) cb(nil)
end end
end end
return ret return ret
@ -275,15 +273,13 @@ local function set_lsp_fzf_fn(opts)
-- cancel all remaining LSP requests -- cancel all remaining LSP requests
-- once the user made their selection -- once the user made their selection
-- or closed the fzf popup -- or closed the fzf popup
opts.post_select_cb = function() opts._fn_post_fzf = function()
if opts._cancel_all then if opts._cancel_all then
opts._cancel_all() opts._cancel_all()
opts._cancel_all = nil opts._cancel_all = nil
end end
end end
-- coroutine.yield()
end)() end)()
end end
@ -303,7 +299,6 @@ local normalize_lsp_opts = function(opts, cfg)
opts = config.normalize_opts(opts, cfg) opts = config.normalize_opts(opts, cfg)
if not opts then return end if not opts then return end
if not opts.cwd then opts.cwd = vim.loop.cwd() end
if not opts.prompt and opts.prompt_postfix then if not opts.prompt and opts.prompt_postfix then
opts.prompt = opts.lsp_handler.label .. (opts.prompt_postfix or '') opts.prompt = opts.lsp_handler.label .. (opts.prompt_postfix or '')
end end
@ -327,7 +322,7 @@ local function fzf_lsp_locations(opts)
if opts.force_uri == nil then opts.force_uri = true end if opts.force_uri == nil then opts.force_uri = true end
opts = set_lsp_fzf_fn(opts) opts = set_lsp_fzf_fn(opts)
if not opts.fzf_fn then return end if not opts.fzf_fn then return end
return core.fzf_files(opts) return core.fzf_exec(opts.fzf_fn, opts)
end end
-- define the functions for wrap_module_fncs -- define the functions for wrap_module_fncs
@ -441,7 +436,7 @@ M.document_symbols = function(opts)
opts.fn_pre_fzf = function() gen_sym2style_map(opts) end opts.fn_pre_fzf = function() gen_sym2style_map(opts) end
opts.fn_post_fzf = function() M._sym2style = nil end opts.fn_post_fzf = function() M._sym2style = nil end
end end
return core.fzf_files(opts) return core.fzf_exec(opts.fzf_fn, opts)
end end
M.workspace_symbols = function(opts) M.workspace_symbols = function(opts)
@ -458,7 +453,7 @@ M.workspace_symbols = function(opts)
opts.fn_pre_fzf = function() gen_sym2style_map(opts) end opts.fn_pre_fzf = function() gen_sym2style_map(opts) end
opts.fn_post_fzf = function() M._sym2style = nil end opts.fn_post_fzf = function() M._sym2style = nil end
end end
return core.fzf_files(opts) return core.fzf_exec(opts.fzf_fn, opts)
end end
-- Converts 'vim.diagnostic.get' to legacy style 'get_line_diagnostics()' -- Converts 'vim.diagnostic.get' to legacy style 'get_line_diagnostics()'
@ -627,17 +622,7 @@ M.code_actions = function(opts)
opts.fzf_opts["--no-multi"] = '' opts.fzf_opts["--no-multi"] = ''
opts.fzf_opts["--preview-window"] = 'right:0' opts.fzf_opts["--preview-window"] = 'right:0'
core.fzf_wrap(opts, opts.fzf_fn, function(selected) core.fzf_exec(opts.fzf_fn, opts)
if opts.post_select_cb then
opts.post_select_cb()
end
if not selected then return end
actions.act(opts.actions, selected, opts)
end)()
end end
@ -761,7 +746,7 @@ M.diagnostics = function(opts)
return buffer_diag return buffer_diag
end end
opts.fzf_fn = function (cb) opts.fzf_fn = function (fzf_cb)
coroutine.wrap(function () coroutine.wrap(function ()
local co = coroutine.running() local co = coroutine.running()
@ -771,8 +756,13 @@ M.diagnostics = function(opts)
-- empty tables for unused buffers -- empty tables for unused buffers
if not vim.tbl_isempty(diag) then if not vim.tbl_isempty(diag) then
if filter_diag_severity(opts, diag.severity) then if filter_diag_severity(opts, diag.severity) then
diagnostics_handler(opts, cb, co, -- wrap with 'vim.scheudle' or calls to vim.{fn|api} fail:
preprocess_diag(diag, bufnr)) -- E5560: vimL function must not be called in a lua loop callback
vim.schedule(function()
diagnostics_handler(opts, fzf_cb, co, preprocess_diag(diag, bufnr))
end)
-- wait here for 'diagnostics_handler' to return
coroutine.yield()
end end
end end
end end
@ -787,14 +777,14 @@ M.diagnostics = function(opts)
end end
-- close the pipe to fzf, this -- close the pipe to fzf, this
-- removes the loading indicator -- removes the loading indicator
cb(nil) fzf_cb(nil)
end)() end)()
end end
opts = core.set_header(opts, opts.headers or {"cwd"}) opts = core.set_header(opts, opts.headers or {"cwd"})
opts = core.set_fzf_field_index(opts) opts = core.set_fzf_field_index(opts)
if opts.force_uri == nil then opts.force_uri = true end if opts.force_uri == nil then opts.force_uri = true end
return core.fzf_files(opts) return core.fzf_exec(opts.fzf_fn, opts)
end end
M.workspace_diagnostics = function(opts) M.workspace_diagnostics = function(opts)
@ -826,7 +816,7 @@ M.live_workspace_symbols = function(opts)
opts.bufnr = vim.api.nvim_get_current_buf() opts.bufnr = vim.api.nvim_get_current_buf()
opts.winid = vim.api.nvim_get_current_win() opts.winid = vim.api.nvim_get_current_win()
opts._reload_action = function(query) opts.fn_reload = function(query)
if query and not (opts.save_last_query == false) then if query and not (opts.save_last_query == false) then
last_search = { query = query } last_search = { query = query }
config.__resume_data.last_query = query config.__resume_data.last_query = query
@ -838,19 +828,13 @@ M.live_workspace_symbols = function(opts)
return opts.fzf_fn return opts.fzf_fn
end end
-- disable global resume
-- conflicts with 'change:reload' event
opts.global_resume_query = false
opts.__FNCREF__ = M.live_workspace_symbols
opts = core.set_fzf_interactive_cb(opts)
opts = core.set_fzf_field_index(opts) opts = core.set_fzf_field_index(opts)
if opts.force_uri == nil then opts.force_uri = true end if opts.force_uri == nil then opts.force_uri = true end
if opts.symbol_style or opts.symbol_fmt then if opts.symbol_style or opts.symbol_fmt then
opts.fn_pre_fzf = function() gen_sym2style_map(opts) end opts.fn_pre_fzf = function() gen_sym2style_map(opts) end
opts.fn_post_fzf = function() M._sym2style = nil end opts.fn_post_fzf = function() M._sym2style = nil end
end end
core.fzf_files(opts) core.fzf_exec(nil, opts)
opts.search = nil
end end
local function check_capabilities(feature) local function check_capabilities(feature)

@ -1,60 +1,25 @@
local core = require "fzf-lua.core" local core = require "fzf-lua.core"
local utils = require "fzf-lua.utils" local utils = require "fzf-lua.utils"
local config = require "fzf-lua.config" local config = require "fzf-lua.config"
local actions = require "fzf-lua.actions"
local libuv = require "fzf-lua.libuv"
local M = {} local M = {}
M.getmanpage = function(line)
--[[ -- extract section from the last pair of parentheses
local name, section = line:match("^(.*)%((.-)%)[^()]-$")
if name:sub(-1) == " " then
-- man-db
name = name:sub(1, -2)
else
-- mandoc
name = name:match("^[^, ]+")
section = section:match("^[^, ]+")
end
return name .. "(" .. section .. ")" ]]
return line:match("[^[,( ]+")
end
M.manpages = function(opts) M.manpages = function(opts)
opts = config.normalize_opts(opts, config.globals.manpages) opts = config.normalize_opts(opts, config.globals.manpages)
if not opts then return end if not opts then return end
local fzf_fn = libuv.spawn_nvim_fzf_cmd( opts.fn_transform = function(x)
{ cmd = opts.cmd, cwd = opts.cwd, pid_cb = opts._pid_cb }, -- split by first occurence of ' - ' (spaced hyphen)
function(x) local man, desc = x:match("^(.-) %- (.*)$")
-- split by first occurence of ' - ' (spaced hyphen) return string.format("%-45s %s",
local man, desc = x:match("^(.-) %- (.*)$") utils.ansi_codes.magenta(man), desc)
return string.format("%-45s %s", end
utils.ansi_codes.magenta(man), desc)
end)
opts.fzf_opts['--no-multi'] = '' opts.fzf_opts['--no-multi'] = ''
opts.fzf_opts['--preview-window'] = 'hidden:right:0' opts.fzf_opts['--preview-window'] = 'hidden:right:0'
opts.fzf_opts['--tiebreak'] = 'begin'
opts.fzf_opts['--nth'] = '1,2'
core.fzf_wrap(opts, fzf_fn, function(selected)
if not selected then return end
if #selected > 1 then
for i = 2, #selected do
selected[i] = M.getmanpage(selected[i])
end
end
actions.act(opts.actions, selected)
end)()
core.fzf_exec(opts.cmd, opts)
end end
return M return M

@ -1,7 +1,6 @@
local core = require "fzf-lua.core" local core = require "fzf-lua.core"
local shell = require "fzf-lua.shell" local shell = require "fzf-lua.shell"
local config = require "fzf-lua.config" local config = require "fzf-lua.config"
local actions = require "fzf-lua.actions"
local M = {} local M = {}
@ -35,13 +34,7 @@ M.metatable = function(opts)
-- as the behavior might confuse users (#267) -- as the behavior might confuse users (#267)
opts.global_resume = false opts.global_resume = false
core.fzf_wrap(opts, methods, function(selected) core.fzf_exec(methods, opts)
if not selected then return end
actions.act(opts.actions, selected)
end)()
end end

@ -3,7 +3,7 @@ local path = require "fzf-lua.path"
local utils = require "fzf-lua.utils" local utils = require "fzf-lua.utils"
local shell = require "fzf-lua.shell" local shell = require "fzf-lua.shell"
local config = require "fzf-lua.config" local config = require "fzf-lua.config"
local actions = require "fzf-lua.actions" local make_entry = require "fzf-lua.make_entry"
local M = {} local M = {}
@ -40,12 +40,7 @@ M.commands = function(opts)
opts.fzf_opts['--no-multi'] = '' opts.fzf_opts['--no-multi'] = ''
opts.fzf_opts['--preview'] = prev_act opts.fzf_opts['--preview'] = prev_act
core.fzf_wrap(opts, entries, function(selected) core.fzf_exec(entries, opts)
if not selected then return end
actions.act(opts.actions, selected)
end)()
end end
@ -64,12 +59,7 @@ local history = function(opts, str)
opts.fzf_opts['--no-multi'] = '' opts.fzf_opts['--no-multi'] = ''
opts.fzf_opts['--preview-window'] = 'hidden:right:0' opts.fzf_opts['--preview-window'] = 'hidden:right:0'
core.fzf_wrap(opts, entries, function(selected) core.fzf_exec(entries, opts)
if not selected then return end
actions.act(opts.actions, selected)
end)()
end end
local arg_header = function(sel_key, edit_key, text) local arg_header = function(sel_key, edit_key, text)
@ -119,12 +109,7 @@ M.jumps = function(opts)
opts.fzf_opts['--no-multi'] = '' opts.fzf_opts['--no-multi'] = ''
core.fzf_wrap(opts, entries, function(selected) core.fzf_exec(entries, opts)
if not selected then return end
actions.act(opts.actions, selected, opts)
end)()
end end
M.tagstack = function(opts) M.tagstack = function(opts)
@ -154,12 +139,13 @@ M.tagstack = function(opts)
local entries = {} local entries = {}
for i, tag in ipairs(tags) do for i, tag in ipairs(tags) do
local bufname = tag.filename local bufname = path.HOME_to_tilde(
path.relative(tag.filename, vim.loop.cwd()))
local buficon, hl local buficon, hl
if opts.file_icons then if opts.file_icons then
local filename = path.tail(bufname) local filename = path.tail(bufname)
local extension = path.extension(filename) local extension = path.extension(filename)
buficon, hl = core.get_devicon(filename, extension) buficon, hl = make_entry.get_devicon(filename, extension)
if opts.color_icons then if opts.color_icons then
buficon = utils.ansi_codes[hl](buficon) buficon = utils.ansi_codes[hl](buficon)
end end
@ -181,12 +167,7 @@ M.tagstack = function(opts)
opts.fzf_opts['--no-multi'] = '' opts.fzf_opts['--no-multi'] = ''
core.fzf_wrap(opts, entries, function(selected) core.fzf_exec(entries, opts)
if not selected then return end
actions.act(opts.actions, selected, opts)
end)()
end end
@ -226,12 +207,7 @@ M.marks = function(opts)
-- opts.fzf_opts['--preview'] = prev_act -- opts.fzf_opts['--preview'] = prev_act
opts.fzf_opts['--no-multi'] = '' opts.fzf_opts['--no-multi'] = ''
core.fzf_wrap(opts, entries, function(selected) core.fzf_exec(entries, opts)
if not selected then return end
actions.act(opts.actions, selected)
end)()
end end
M.registers = function(opts) M.registers = function(opts)
@ -285,12 +261,7 @@ M.registers = function(opts)
opts.fzf_opts['--no-multi'] = '' opts.fzf_opts['--no-multi'] = ''
opts.fzf_opts['--preview'] = prev_act opts.fzf_opts['--preview'] = prev_act
core.fzf_wrap(opts, entries, function(selected) core.fzf_exec(entries, opts)
if not selected then return end
actions.act(opts.actions, selected)
end)()
end end
M.keymaps = function(opts) M.keymaps = function(opts)
@ -343,12 +314,7 @@ M.keymaps = function(opts)
opts.fzf_opts['--no-multi'] = '' opts.fzf_opts['--no-multi'] = ''
opts.fzf_opts['--preview'] = prev_act opts.fzf_opts['--preview'] = prev_act
core.fzf_wrap(opts, entries, function(selected) core.fzf_exec(entries, opts)
if not selected then return end
actions.act(opts.actions, selected)
end)()
end end
M.spell_suggest = function(opts) M.spell_suggest = function(opts)
@ -365,12 +331,7 @@ M.spell_suggest = function(opts)
opts.fzf_opts['--no-multi'] = '' opts.fzf_opts['--no-multi'] = ''
opts.fzf_opts['--preview-window'] = 'hidden:right:0' opts.fzf_opts['--preview-window'] = 'hidden:right:0'
core.fzf_wrap(opts, entries, function(selected) core.fzf_exec(entries, opts)
if not selected then return end
actions.act(opts.actions, selected)
end)()
end end
@ -385,12 +346,7 @@ M.filetypes = function(opts)
opts.fzf_opts['--no-multi'] = '' opts.fzf_opts['--no-multi'] = ''
opts.fzf_opts['--preview-window'] = 'hidden:right:0' opts.fzf_opts['--preview-window'] = 'hidden:right:0'
core.fzf_wrap(opts, entries, function(selected) core.fzf_exec(entries, opts)
if not selected then return end
actions.act(opts.actions, selected)
end)()
end end
@ -406,12 +362,7 @@ M.packadd = function(opts)
opts.fzf_opts['--no-multi'] = '' opts.fzf_opts['--no-multi'] = ''
opts.fzf_opts['--preview-window'] = 'hidden:right:0' opts.fzf_opts['--preview-window'] = 'hidden:right:0'
core.fzf_wrap(opts, entries, function(selected) core.fzf_exec(entries, opts)
if not selected then return end
actions.act(opts.actions, selected)
end)()
end end
@ -448,12 +399,7 @@ M.menus = function(opts)
opts.fzf_opts['--no-multi'] = '' opts.fzf_opts['--no-multi'] = ''
opts.fzf_opts['--preview-window'] = 'hidden:right:0' opts.fzf_opts['--preview-window'] = 'hidden:right:0'
core.fzf_wrap(opts, entries, function(selected) core.fzf_exec(entries, opts)
if not selected then return end
actions.act(opts.actions, selected)
end)()
end end

@ -1,5 +1,6 @@
local core = require "fzf-lua.core" local core = require "fzf-lua.core"
local config = require "fzf-lua.config" local config = require "fzf-lua.config"
local make_entry = require "fzf-lua.make_entry"
local M = {} local M = {}
@ -29,14 +30,14 @@ M.oldfiles = function(opts)
local contents = function (cb) local contents = function (cb)
local function add_entry(x, co) local function add_entry(x, co)
x = core.make_entry_file(opts, x) x = make_entry.file(x, opts)
if not x then return end if not x then return end
cb(x, function(err) cb(x, function(err)
coroutine.resume(co) coroutine.resume(co)
if err then if err then
-- close the pipe to fzf, this -- close the pipe to fzf, this
-- removes the loading indicator in fzf -- removes the loading indicator in fzf
cb(nil, function() end) cb(nil)
end end
end) end)
coroutine.yield() coroutine.yield()
@ -60,14 +61,13 @@ M.oldfiles = function(opts)
-- end; print("took", os.time()-start, "seconds.") -- end; print("took", os.time()-start, "seconds.")
-- done -- done
cb(nil, function() coroutine.resume(co) end) cb(nil)
coroutine.yield()
end)() end)()
end end
opts = core.set_header(opts, opts.headers or {"cwd"}) opts = core.set_header(opts, opts.headers or {"cwd"})
return core.fzf_files(opts, contents) return core.fzf_exec(contents, opts)
end end
return M return M

@ -1,6 +1,7 @@
local core = require "fzf-lua.core" local core = require "fzf-lua.core"
local utils = require "fzf-lua.utils" local utils = require "fzf-lua.utils"
local config = require "fzf-lua.config" local config = require "fzf-lua.config"
local make_entry = require "fzf-lua.make_entry"
local M = {} local M = {}
@ -14,12 +15,12 @@ local quickfix_run = function(opts, cfg, locations)
if not opts.cwd then opts.cwd = vim.loop.cwd() end if not opts.cwd then opts.cwd = vim.loop.cwd() end
for _, entry in ipairs(locations) do for _, entry in ipairs(locations) do
table.insert(results, core.make_entry_lcol(opts, entry)) table.insert(results, make_entry.lcol(entry, opts))
end end
local contents = function(cb) local contents = function(cb)
for _, x in ipairs(results) do for _, x in ipairs(results) do
x = core.make_entry_file(opts, x) x = make_entry.file(x, opts)
if x then if x then
cb(x, function(err) cb(x, function(err)
if err then return end if err then return end
@ -29,11 +30,11 @@ local quickfix_run = function(opts, cfg, locations)
end) end)
end end
end end
utils.delayed_cb(cb) cb(nil)
end end
opts = core.set_fzf_field_index(opts) opts = core.set_fzf_field_index(opts)
return core.fzf_files(opts, contents) return core.fzf_exec(contents, opts)
end end
M.quickfix = function(opts) M.quickfix = function(opts)

@ -76,7 +76,7 @@ local function tags(opts)
}) })
local ok, lines, err = pcall(utils.io_systemlist, cmd) local ok, lines, err = pcall(utils.io_systemlist, cmd)
if ok and err == 0 and lines and not vim.tbl_isempty(lines) then if ok and err == 0 and lines and not vim.tbl_isempty(lines) then
local tag, line = make_entry.tag(opts, lines[1]) local tag, line = make_entry.tag(lines[1], opts)
if tag and not line then if tag and not line then
-- tags file does not contain lines -- tags file does not contain lines
-- remove preview offset field index -- remove preview offset field index
@ -87,8 +87,11 @@ local function tags(opts)
-- prevents 'file|git_icons=false' from overriding processing -- prevents 'file|git_icons=false' from overriding processing
opts.requires_processing = true opts.requires_processing = true
opts._fn_transform = make_entry.tag -- multiprocess=false if opts.multiprocess then
opts._fn_transform_str = [[return require("make_entry").tag]] -- multiprocess=true opts.__mt_transform = [[return require("make_entry").tag]]
else
opts.__mt_transform = make_entry.tag
end
if opts.lgrep then if opts.lgrep then
-- live_grep requested by caller ('tags_live_grep') -- live_grep requested by caller ('tags_live_grep')
@ -101,9 +104,9 @@ local function tags(opts)
if opts.multiprocess then if opts.multiprocess then
return require'fzf-lua.providers.grep'.live_grep_mt(opts) return require'fzf-lua.providers.grep'.live_grep_mt(opts)
else else
-- 'live_grep_st' uses different signature '_fn_transform' -- 'live_grep_st' uses different signature 'fn_transform'
opts._fn_transform = function(x) opts.fn_transform = function(x)
return make_entry.tag(opts, x) return make_entry.tag(x, opts)
end end
return require'fzf-lua.providers.grep'.live_grep_st(opts) return require'fzf-lua.providers.grep'.live_grep_st(opts)
end end
@ -159,7 +162,6 @@ M.live_grep = function(opts)
opts = config.normalize_opts(opts, config.globals.tags) opts = config.normalize_opts(opts, config.globals.tags)
if not opts then return end if not opts then return end
opts.lgrep = true opts.lgrep = true
opts.__FNCREF__ = utils.__FNCREF__()
return tags(opts) return tags(opts)
end end

@ -37,9 +37,14 @@ function M.raw_async_action(fn, fzf_field_expression, debug)
local pipe = uv.new_pipe(false) local pipe = uv.new_pipe(false)
local args = {...} local args = {...}
uv.pipe_connect(pipe, pipe_path, function(err) uv.pipe_connect(pipe, pipe_path, function(err)
vim.schedule(function () if err then
fn(pipe, unpack(args)) error(string.format("pipe_connect(%s) failed with error: %s",
end) pipe_path, err))
else
vim.schedule(function ()
fn(pipe, unpack(args))
end)
end
end) end)
end end
@ -131,45 +136,134 @@ end
M.reload_action_cmd = function(opts, fzf_field_expression) M.reload_action_cmd = function(opts, fzf_field_expression)
local _pid = nil 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) return M.raw_async_action(function(pipe, args)
local function on_pid(pid) -- get the type of contents from the caller
_pid = pid local reload_contents = opts.__fn_reload(args[1])
if opts.pid_cb then local write_cb_count = 0
opts.pid_cb(pid) local pipe_want_close = false
end
end -- local on_finish = function(code, sig, from, pid)
-- print("finish", pipe, pipe_want_close, code, sig, from, pid)
local function on_finish(_, _) local on_finish = function(_, _, _, _)
if pipe and not uv.is_closing(pipe) then 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) uv.close(pipe)
pipe = nil pipe = nil
end end
end end
local function on_write(data, cb) local on_write = function(data, cb, co)
if not pipe then -- pipe can be nil when using a shell command with spawn
cb(true) -- 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 else
uv.write(pipe, data, cb) 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
end end
-- terminate previously running commands if type(reload_contents) == 'string' then
libuv.process_kill(_pid) -- string will be used as a shell command
-- terminate previously running commands
libuv.process_kill(opts.__pid)
opts.__pid = nil
-- return libuv.spawn({ -- spawn/async_spawn already async, no need to send opts.__co
return libuv.async_spawn({ -- 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, cwd = opts.cwd,
cmd = opts._reload_command(args[1]), cmd = reload_contents,
cb_finish = on_finish, cb_finish = on_finish,
cb_write = on_write, cb_write = on_write,
cb_pid = on_pid, cb_pid = function(pid) opts.__pid = pid end,
-- must send false, 'coroutinify' adds callback as last argument -- must send false, 'coroutinify' adds callback as last argument
-- which will conflict with the 'fn_transform' argument -- which will conflict with the 'fn_transform' argument
}, opts._fn_transform or false) }, 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, fzf_field_expression, opts.debug)
end end

@ -413,17 +413,6 @@ function M.feed_keys_termcodes(key)
vim.api.nvim_replace_termcodes(key, true, false, true), 'n', true) vim.api.nvim_replace_termcodes(key, true, false, true), 'n', true)
end end
function M.delayed_cb(cb, fn)
-- HACK: slight delay to prevent missing results
-- otherwise the input stream closes too fast
-- sleep was causing all sorts of issues
-- vim.cmd("sleep! 10m")
if fn == nil then fn = function() end end
vim.defer_fn(function()
cb(nil, fn)
end, 20)
end
function M.is_term_bufname(bufname) function M.is_term_bufname(bufname)
if bufname and bufname:match("term://") then return true end if bufname and bufname:match("term://") then return true end
return false return false
@ -590,8 +579,8 @@ function M.io_systemlist(cmd, use_lua_io)
-- last line contains the exit status -- last line contains the exit status
rc = tonumber(stdout[#stdout]) rc = tonumber(stdout[#stdout])
stdout[#stdout] = nil stdout[#stdout] = nil
handle:close()
end end
handle:close()
return stdout, rc return stdout, rc
else else
return vim.fn.systemlist(cmd), vim.v.shell_error return vim.fn.systemlist(cmd), vim.v.shell_error

@ -29,7 +29,9 @@ function FzfWin.save_query(key)
local query = lines[1]:gsub("^%*+", "") local query = lines[1]:gsub("^%*+", "")
:gsub("^"..utils.lua_escape(self.prompt:match("[^%*]+")), "") :gsub("^"..utils.lua_escape(self.prompt:match("[^%*]+")), "")
-- remove '--info=inline' -- remove '--info=inline'
query = query and query:gsub("<%s%d+/%d+.*$", "") query = query and query:gsub("[<%-]%s%d+/%d+.*$", "")
-- remove '< [Command failed: ...]
query = query and query:gsub("<%s%[Command failed:.*$", "")
-- trim whitespaces at the end -- trim whitespaces at the end
query = query and query:gsub("%s*$", "") query = query and query:gsub("%s*$", "")
if self.fn_save_query then if self.fn_save_query then

Loading…
Cancel
Save