nvim-fzf
An asynchronous Lua API for using fzf
in Neovim (>= 0.5). Allows for full asynchronicity for UI speed and usability.
Preview:
Note how in the example above, information is passed freely between
neovim and fzf
. Neovim is previewing the buffer in a split that you have
selected in fzf
. Using this library, you can perform anything in
response to fzf
events and keybindings.
Some handcrafted useful commands at
Tested on Linux, MacOS, and Windows.
Requirements
Usage
local fzf = require("fzf")
coroutine.wrap(function()
local result = fzf.fzf({"choice 1", "choice 2"}, "--ansi")
-- result is a list of lines that fzf returns, if the user has chosen
if result then
print(result[1])
end
end)()
Table of contents
Installation
Plug 'vijaymarupudi/nvim-fzf'
Important information
All fzf
functions should be run in a coroutine.
Example:
local fzf = require("fzf")
coroutine.wrap(function()
local result = fzf.fzf({"choice 1", "choice 2"})
if result then
print(result[1])
end
end)()
API Functions
Require this plugin using local fzf = require('fzf')
-
fzf.fzf(contents, [fzf_cli_args], [options])
An fzf function that opens a centered floating window and closes it
after the user has chosen.
Example:
local results = fzf.fzf({"Option 1", "Option 2"}, "--nth 1")
if results then
-- do something
end
options
: an optional table, taking optional
settings. You can use this to change the default floating window
behavior or the fzf binary.
options.title
(optional string): title of the window
options.title_pos
(string, default: 'center'): where the title should be placed
options.width
(number): width of the window
options.height
(number): height of the window
options.row
(number): row from top where window starts
options.col
(number): column from left where window starts
options.relative
('win', 'editor', 'cursor'): window position relative to
options.border
(boolean | string | table, default: true): whether to display a border
- if
border
is false
, a border won't be shown
- if
border
is true
, a rounded border will be shown
- if
border
is anything else, it is passed directly to
nvim_open_win
options.window_on_create
(function): a function that's
called after the window is created. Use this function to configure
the various properties of the window such as background highlight
group.
options.fzf_binary
(string): The name (or path) of the fzf
(or
skim
) executable.
options.fzf_cwd
(string): The path of the working directory to run
the fzf command in.
options.fzf_cli_args
(string): Additional fzf command line
arguments to prepend to the arguments supplied to the fzf functions.
This is only useful when used in conjunction with
fzf.default_options
.
NOTE: options
inherits its properties from
fzf.default_options
. If you'd like to change the defaults for
all nvim-fzf functions, modify this table e.g.
require("fzf").default_options = { border = false }
Example:
local results = fzf.fzf({"Option 1", "Option 2"},
"--nth 1",
{ width = 30, height = 10, border = false })
if results then
-- do something
end
-
fzf.fzf_relative(contents, [fzf_cli_args], [options])
An fzf function that opens a centered floating window relative to the
current split and closes it after the user has chosen.
(Same as setting options.relative = 'win'
)
Example:
local results = fzf.fzf_relative({"Option 1", "Option 2"}, "--nth 1")
if results then
-- do something
end
options
: an optional table taking optional
settings. See fzf.fzf
for information on settings.
-
fzf.provided_win_fzf(contents, [fzf_cli_args], [options])
Runs fzf in the current window, and closes it after the user has
chosen. Allows for the user to provide the fzf window.
-- for a vertical fzf
vim.cmd [[ vertical new ]]
fzf.provided_win_fzf(contents, fzf_cli_args)
-
fzf.raw_fzf(contents, [fzf_cli_args], [options])
An fzf function that runs fzf in the current window. See Main API
for more details about the general API.
NOTE: nvim-fzf inherits nvim's environmental variables. This means
that options in $FZF_DEFAULT_OPTS
and other environment variables are
respected. You can override them using command line switches or
:let-environment
.
Main API
fzf(contents, [fzf_cli_args])
-
contents
-
if string: a shell command
local result = fzf("fd")
-
if table: a list of strings or string convertibles
local result = fzf({1, 2, "item"})
-
if function: nvim-fzf
calls the function with a callback function to
write vals to the fzf pipe. This api is asynchronous, making it
possible to use fzf for long running applications and making the
user interface snappy. Callbacks can be concurrent.
-
cb(value, finished_cb)
value
: A value to write to fzf
finished_cb(err)
: A callback called with an err if there is an
error writing the value to fzf. This can occur if the user has
already picked a value in fzf.
local result = fzf(function(cb)
cb("value_1", function(err)
-- this error can happen if the user has already chosen a value
-- before the information was sent to fzf
if err then
return
end
cb("value_2", function(err)
if err then
return
end
cb(nil) -- to close the pipe to fzf, this removes the loading
-- indicator in fzf
end)
end)
end)
The function is also called with two other optional arguments for more
advanced usage.
-
The 2nd argument is a variant of the callback function (which is
passed as the 1st argument), but it does not add newlines to the
elements. This is useful to pass through information directly to
the pipe.
-
The 3rd argument is the vim.loop
/ luv
pipe to FZF. Use as you
see fit!
-
fzf_cli_args
: string, A list of command line arguments for fzf.
Can use to expect different key bindings (e.g. --expect ctrl-t,ctrl-v
), previews, and coloring.
-
return values
-
table, the lines that fzf returns in the shell
as a table. If not lines are returned by fzf, the function returns nil
for an easy conditional check.
local result = fzf("fd")
if result then
-- do something with result[1]
end
local result = fzf("fd", "--multi")
if result then
-- do something with result[1] to result[#result]
end
local result = fzf("fd", "--expect=ctrl-t")
if result then
if result[1] == "ctrl-t" then
-- do something with result[2]
else
-- do something with result[2]
end
end
-
number: Representing fzf's exit code.
Action API (fzf Previews, Bindings, Actions in Lua)
Sometimes you want to use neovim information in fzf (such as previews of
non file buffers, bindings to delete buffers, or change colorschemes).
fzf expects a shell command for these parameters. Making your own shell
command and setting up RPC can be cumbersome. This plugin provides an
easy API to run a lua function / closure in response to these actions.
local fzf = require "fzf".fzf
local action = require "fzf.actions".action
coroutine.wrap(function()
-- items is a table of selected or hovered fzf items
local shell = action(function(items, fzf_lines, fzf_cols)
-- only one item will be hovered at any time, so get the selection
-- out and convert it to a number
local buf = tonumber(items[1])
-- you can return either a string or a table to show in the preview
-- window
return vim.api.nvim_buf_get_lines(buf, 0, -1, false)
end)
fzf(vim.api.nvim_list_bufs(), "--preview " .. shell)
end)()
require("fzf.actions").action(fn, [fzf_field_expression])
-
fn(selections, fzf_lines, fzf_cols)
: A function that takes a
selection, performs an action, and optionally returns either a table
or string
to print to stdout. This command is shell-escaped, so
that you can easily append it to the --preview
fzf cli argument.
selections
: a table
of strings selected in fzf
fzf_lines
: number of lines in the preview window i.e.
$FZF_PREVIEW_LINES
fzf_cols
: number of cols in the preview window i.e.
$FZF_PREVIEW_COLS
-
fzf_field_expression
(string, optional, default: "{+}"
): This fzf
field expression determines what items are sent to the action
function.
-
return value: a shell-escaped string to append to the fzf
command
line arguments (fzf_cli_args
) for fzf
to run.
require("fzf.actions").raw_action(fn, [fzf_field_expression])
-
Same as above, except it is not shell-escaped, so you can use it for
complicated --bind
functions. Take care to escape the result of
this function before using it, as it contains spaces and quotes.
local fzf = require("fzf").fzf
local raw_action = require("fzf.actions").raw_action
local raw_act_string = raw_action(function(args)
-- do something with the args
end)
local bind_string =
vim.fn.shellescape(string.format("--bind=ctrl-r:reload(%s)",
raw_act_string))
coroutine.wrap(function()
fzf({1, 2, 3, 4}, "--multi " .. bind_string)
end)()
require("fzf.actions").async_action(fn, [fzf_field_expression])
-
fn(pipe, selections, fzf_lines, fzf_cols)
: Similar to action(...)
,
but fn
is passed an additional argument, the libuv
/ vim.loop
pipe to fzf
, as the first argument. Users can write to this pipe
using uv.write(pipe, data, callback)
and are expected to close the
pipe using uv.close(pipe)
.
This function can be used for previews that take a long time to render
and calculate from neovim.
require("fzf.actions").raw_async_action(fn, [fzf_field_expression])
- Same as above, except it is not shell-escaped, so you can use it for
complicated
--bind
functions. Take care to escape the result of
this function before using it, as it contains spaces and quotes.
Helpers
Asynchronous programming is hard. For the case when you want to accept a
shell command, and simply transform each line into another line,
nvim-fzf
has a helper function that returns a function that
asynchronously applies the transformation, which can be passed right
into fzf
.
require("fzf.helpers").cmd_line_transformer(cmd, fn)
cmd
- if string: the shell command to transform
- if table: a table taking the following properties
cmd.cmd
(string): the shell command to transform
cmd.cwd
(string, optional): the working directory to run the
shell script in.
cmd.pid_cb
(function, optional): a callback called with the pid
of the shell command when available.
fn
(function): a function that takes as input a line from the shell
command (string) and returns a new line to be sent to fzf
(string).
local fzf = require("fzf")
local fzf_helpers = require("fzf.helpers")
coroutine.wrap(function()
-- the transformation function runs for each line in the command
local fzf_fn = fzf_helpers.cmd_line_transformer("seq 1000", function(x)
local n = tonumber(x)
return tostring(n * n)
end)
local choices = fzf.fzf(fzf_fn)
end)()
require("fzf.helpers").choices_to_shell_cmd_previewer(fn, [fzf_field_expression])
-
fn(items, fzf_lines, fzf_cols)
: A function that is expected to
return a shell cmd string to run asynchronously and feed to fzf
.
This allows the user to use Lua to parse the input from fzf before
performantly using an external process to preview the output.
local fzf = require("fzf")
local helpers = require("fzf.helpers")
coroutine.wrap(function ()
local action = helpers.choices_to_shell_cmd_previewer(function(items)
return "seq " .. vim.fn.shellescape(tostring(items[1]))
end)
fzf.fzf("seq 1 1000", "--preview=" .. action)
end)()
-
fzf_field_expression
: See above
Examples
Filetype picker
local fts = {
"typescript",
"javascript",
"lua",
"python",
"vim",
"markdown",
"sh"
}
coroutine.wrap(function()
local choice = require "fzf".fzf(fts)
if choice then
vim.cmd(string.format("set ft=%s", choice[1]))
end
end)()
Colorscheme picker
This example provides a live preview of the colorscheme while the user
is choosing between them. An example showing the advantages of nvim-fzf
and the --preview
fzf cli arg.
local action = require("fzf.actions").action
local function get_colorschemes()
local colorscheme_vim_files = vim.fn.globpath(vim.o.rtp, "colors/*.vim", true, true)
local colorschemes = {}
for _, colorscheme_file in ipairs(colorscheme_vim_files) do
local colorscheme = vim.fn.fnamemodify(colorscheme_file, ":t:r")
table.insert(colorschemes, colorscheme)
end
return colorschemes
end
local function get_current_colorscheme()
if vim.g.colors_name then
return vim.g.colors_name
else
return 'default'
end
end
coroutine.wrap(function ()
local preview_function = action(function (args)
if args then
local colorscheme = args[1]
vim.cmd("colorscheme " .. colorscheme)
end
end)
local current_colorscheme = get_current_colorscheme()
local choices = fzf(get_colorschemes(), "--preview=" .. preview_function .. " --preview-window right:0")
if not choices then
vim.cmd("colorscheme " .. current_colorscheme)
else
vim.cmd("colorscheme " .. choices[1])
end
end)()
Helptags picker
This is a bit complex example that is completely asynchronous for
performance reasons. It also uses the fzf
--expect
command line flag.
local runtimepaths = vim.api.nvim_list_runtime_paths()
local uv = vim.loop
local fzf = require('fzf').fzf
local function readfilecb(path, callback)
uv.fs_open(path, "r", 438, function(err, fd)
if err then
callback(err)
return
end
uv.fs_fstat(fd, function(err, stat)
if err then
callback(err)
return
end
uv.fs_read(fd, stat.size, 0, function(err, data)
if err then
callback(err)
return
end
uv.fs_close(fd, function(err)
if err then
callback(err)
return
end
return callback(nil, data)
end)
end)
end)
end)
end
local function readfile(name)
local co = coroutine.running()
readfilecb(name, function (err, data)
coroutine.resume(co, err, data)
end)
local err, data = coroutine.yield()
if err then error(err) end
return data
end
local function deal_with_tags(tagfile, cb)
local co = coroutine.running()
coroutine.wrap(function ()
local success, data = pcall(readfile, tagfile)
if success then
for i, line in ipairs(vim.split(data, "\n")) do
local items = vim.split(line, "\t")
-- escape codes for grey
local tag = string.format("%s\t\27[0;37m%s\27[0m", items[1], items[2])
local co = coroutine.running()
cb(tag, function ()
coroutine.resume(co)
end)
coroutine.yield()
end
end
coroutine.resume(co)
end)()
coroutine.yield()
end
local fzf_function = function (cb)
local total_done = 0
for i, rtp in ipairs(runtimepaths) do
local tagfile = table.concat({rtp, "doc", "tags"}, "/")
-- wrapping to make all the file reading concurrent
coroutine.wrap(function ()
deal_with_tags(tagfile, cb)
total_done = total_done + 1
if total_done == #runtimepaths then
cb(nil)
end
end)()
end
end
coroutine.wrap(function ()
local result = fzf(fzf_function, "--nth 1 --ansi --expect=ctrl-t,ctrl-s,ctrl-v")
if not result then
return
end
local choice = vim.split(result[2], "\t")[1]
local key = result[1]
local windowcmd
if key == "" or key == "ctrl-s" then
windowcmd = ""
elseif key == "ctrl-v" then
windowcmd = "vertical"
elseif key == "ctrl-t" then
windowcmd = "tab"
else
print("Not implemented!")
error("Not implemented!")
end
vim.cmd(string.format("%s h %s", windowcmd, choice))
end)()
How it works
This plugin uses a temporary named pipe, and uses it to communicate to
fzf
.
FAQ
-
Does this conflict with fzf.vim
?
This library does not conflict with
fzf.vim
or the fzf vim API.
-
How do I change the color of the default floating window spawned by
fzf.fzf
?
You need to set the winhl
option for the default window. You can do
this for each command or globally by using the window_on_create
option.
This makes the background of the popup window the same color of the
backgrounds of normal windows. Example:
require("fzf").default_options = {
window_on_create = function()
vim.cmd("set winhl=Normal:Normal")
end
}