-
Notifications
You must be signed in to change notification settings - Fork 162
Home
Click here for ADVANCED CUSTOMIZATION / API DOCS
- General
- Customization
-
Appearance
- How do I change the window size and position?
- Can I set the window size dynamically?
- How do I change the layout of fzf and the preview window?
- How do I set the preview to start hidden?
- How do I set the preview syntax highlight theme?
- How do I change the colors of the fzf popup window?
- Can I use fzf-lua in a split instead of a window?
- Icon padding for terminals with double width icon support
- Keybinds
-
Misc
- Set current working directory for providers
- How do I exclude paths (e.g. 'node_modules')?
- What does
multiprocess
do? - How can I use custom devicons with
multiprocess
- What's the difference between
grep
andlive_grep
? - How can I simulate fzf.vim's
Rg
command? - Can I continue from the last search?
live_grep
shows no results before I start typing- Can I use ripgrep's
--iglob
option withlive_grep
? - Search results do not appear in the same order
- LSP: prevent window flashing when no results
- LSP: jump to location for single result
- Disable or hide filename fuzzy search
- How do I get maximum performance out of fzf-lua?
- Can fzf-lua preview media files?
- A recommended workflow for big projects
Fzf-lua is a neovim (>0.5) plugin written in lua integrating
fzf
into the neovim ecosystem.
fzf
is described as a general-purpose command line fuzzy finder meaning
you can search any output using the fuzzy search algorithm. This opaque
description hides an endless wealth of functionality from serching files,
processes, command-line history and much more. fzf-lua aims to bring all this
goodness to neovim.
Wait, but isn't there already a vim/neovim plugin for that,
fzf.vim
?
fzf.vim
is a great plugin, it worked
flawlessly for me for a long time and until I recently I wouldn't have
considered replacing it.
So what changed? Neovim 0.5 was released with lua programming language as a first class citizen and with it the opportunity to extend neovim for anyone who has been avoiding it due to the vimL scripting language. Don't get me wrong, there's nothing wrong with vimL but I personally found it not very intuitive and although I've been using vim for years now I avoided writing anything but the absolute basics in vimL.
Pros over fzf.vim
:
- Written in pure lua especially for neovim 0.5
- Customizable but has sane defaults
- Easily extendible in lua
- Builtin providers for LSP and quickfix lists (references, definitions,
diagnostics, symbols, etc) without requiring additional plugins like
nvim-lspfuzzy
orcoc-fzf
- Builtin support for file icons and git indicators
Cons vs fzf.vim
:
- Does not work with vim or any neovim version lower than 0.5
Fzf-lua only requirements are the fzf
binary, it does not
require nor conflict with fzf.vim
.
Fzf-lua utilizes a separate neovim --headless --clean
instance to do it's
processing which is then piped into fzf in a neovim terminal windows, it does
not require any of the vim functions exposed by the fzf
or fzf.vim
plugins.
telescope.nvim
is a
great fuzzy finding ecosystem written by the core maintainer of neovim,
@tjdevries, it has a thriving ecosystem of
extensions and a big community, some of its advantages over fzf-lua
are:
- Tightly integrated within neovim, does not require an external program or process
- Vast amount of extensions and vast support for pretty much anything
- Big community
That said, after using telescope for a while it had a few shortcomings which pushed me towards developing fzf-lua:
-
Inability to cycle through the results, you are only able to see one page of the results and narrow them down using fuzzy search, you can't page up/down (ctrl-b|f) the results paneNo longer relevant since Telescope#1232 - Performance issue when operating on large files and code bases, that is partly due to telescope being mostly synchronous. Recently telescope merged plenary PR #83: async await using libuv and telescope PR #709: feat: asyncify pickers in an effort to solve these issues.
- A rather large code base and community naturally introduces more complexity which has greater potential for bugs and issues down the road.
In addition, I really liked fzf
, it had worked flawlessly for me for a long
time and I enjoy having uniformity and similar experience/interface throughout
my shell, tmux and neovim.
To get the best out of fzf-lua I highly recommend installing the below:
-
fd: a better version of the
find
utility -
ripgrep(rg): a better version of
the
grep
utility
In addition, in neovim, use your favorite plugin manager to install
kyazdani42/nvim-web-devicons
in order to be able to display file icons in
the interface.
If you're using packer.nvim
add
the below to packer.startup({})
:
use { 'ibhagwan/fzf-lua',
requires = { 'kyazdani42/nvim-web-devicons' },
}
If you're using vim-plug
:
Plug 'ibhagwan/fzf-lua'
Plug 'kyazdani42/nvim-web-devicons'
It's important to understand how customization works in order to understand the following sections better as all options are specified in the same manner and adhere to the same order:
- Global options
- Provider options
- Function call options
For example, if we wanted to change the height of the files
window, we could
do so in 3 different ways:
- Apply a global
height
setting:
require("fzf-lua").setup({
winopts = { height = 0.4 }
})
- Apply
height
under thefiles
provider:
require("fzf-lua").setup({
files = {
prompt = 'Files❯ ', -- example, not required
winopts = { height = 0.4 },
}
})
- Apply
height
to a single call:
:lua require("fzf-lua").files({ winopts = { height = 0.4 } })
- For dynamically generated arguments, options can also be defined as a function:
:lua require("fzf-lua").files(function() return {show_cwd_header=true, cwd=vim.loop.cwd()} end)
Most options defaults can be found on the project's main page, unfortunately, some options are not yet properly documented, searching the project's issues is your best bet of finding an esoteric option (e.g.
live_grep({exec_empty_query=true})
)
The window look and feel is controlled by 4 parameters, winopts.height
,
winopts.width
, winopts.row
and winopts.col
, their values range from
0.0 - 1.0
and correspond with the available screen space based on nvim width
(vim.o.columns
)
If you wish to set some (or all) winopts
parameters dynamically, you can
define winopts_fn
as a function that returns the parameters you wish to
override:
require("fzf-lua").setup({
winopts_fn = function()
-- smaller width if neovim win has over 80 columns
return { width = vim.o.columns>80 and 0.65 or 0.85 }
end,
})
The appearance of fzf (input box location, colors, etc) by the fzf binary.
Fzf-lua exposes all fzf options through the config, fzf_opts
can accept any
fzf flag, fzf_colors
controls the colors and keymap.fzf
controls all binds
that are native to fzf and the preview when using the native fzf previewer.
To change the location of the fuzzy search input field set 'fzf_opts[--layout']
option which is equal to the fzf --layout=
command line flag, valid values are
default
, reverse
and reverse-list
.
Fundamentally fzf-lua has two different preview window options, the first preview option is controlled by the fzf binary and the second is a neovim floating window, some providers support both options (mostly files-based providers) and some support only the native fzf preview window.
The default previewer for files uses neovim's floating win, if you wish to use
bat
as a previewer set winopts.preview.default = 'bat'
.
To change the location of the preview window, set the winopts.preview.layout
option, valid values are vertical
, horizontal
and flex
, when set to flex
the layout will dynamically switch between vertical and horizontal based on
the number of available columns in conjunction with the flip_columns
option.
E.g. when flip_columns = 120
the layout will be set to vertical
(i.e.
preview on the bottom) when screen size is less than 120 columns or horizontal
otherwise.
To control location of the preview of the preview window
(up|down|left|right
) look into winopts.preview.horizontal
and winopts.preview.vertical
respectively.
Set winopts.preview.hidden
option to control the preview visibility (works
for both neovim floating preview and native fzf's --preview-window=
), can be
set to hidden
or nohidden
.
Note: The above also applies to the preview
wrap|nowrap
option which controls word wrapping in the preview window.
When using the default neovim float win previewer the syntax highlight will match the current neovim colorscheme.
To enable preview syntax highlighting when using fzf native previewer, you need
to install bat
which is the fancy version of
cat
(used as the fzf previewer).
Fzf-lua will auto-detect you have bat installed and use it automatically, run
bat --list-themes
in your shell to preview all available theme and set
previewers.bat.theme
to your preferred theme.
Fzf colors can be set under fzf_colors
, fzf-lua will then translate the
neovim highlight to a RGB color and pass it to fzf using the --color=
flag.
The popup colors of fzf (bg/background) as well as the neovim floating window
preview colors are defined under winopts.hl
.
Yes, this requires setting the winopts.split
command, the command will then
run right before fzf-lua starts:
Once
split
option is set all other layout options for the window (col
,row
,border
, etc) are ignored.
require("fzf-lua").setup({
winopts = {
-- Use **only one** of the below options
split = "aboveleft new" -- open in a bottom split
split = "belowright new" -- open in a top split
split = "aboveleft vnew" -- open in a left split
split = "belowright vnew" -- open in a right split
}
})
Some terminals (such as kitty
) support rendering double-width icons, this
can cause the edge of wide icons to be cut off, this can be solved with the
file_icon_padding
option:
require("fzf-lua").setup({
file_icon_padding = ' ',
})
All keybinds for both the neovim floating win previewer and fzf are defined
under the keymap.builtin
and keymap.fzf
tables respectively.
When defining keymap.builtin
binds you must use neovim-style keymaps
(i.e. <C-c>
for Control-C).
When defining keymap.fzf
binds you must use fzf-style keymas (i.e. ctrl-c
for Control-c), man fzf
for all available fzf mappings.
If you wish to define custom logic or define fzf binds that are otherwise not
available you can do so inside the winopts.on_create
callback event.
For example, the Super/Meta key is not available to use with fzf but you can map
<Super-Backspace>
to <ctrl-u>
with:
fzf_lua.setup({
winopts = {
on_create = function()
-- creates a local buffer mapping translating <M-BS> to <C-u>
vim.api.nvim_buf_set_keymap(0, 't', '<M-BS>',
"<cmd>lua vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes('<C-u>', true, false, true), 'n', true)<CR>",
{nowait = true, noremap = true})
end
}
})
Actions are essentially fzf keybinds which control what to do with selected entries.
All file actions inherit from actions.files
and all buffer actions inherit
from actions.buffers
which then gets merged with the provider actions table
(if such table exists), for example the actions for files
are defined as
actions.files
table merged with files.actions
table.
It's important to note not all providers inherit from the global actions
table:
-
actions.files
is inherited by:files
,git_files
,git_status
,grep
,lsp
,oldfiles
,quickfix
,loclist
,tags
,btags
args
,tagstack
-
actions.buffers
is inherited by:buffers
,tabs
,lines
,blines
For example, the default action for file edit is to open a single file or send multiple selection to the quickfix list, if you wish to change the default behavior to open multiple selection you need to define:
local actions = require'fzf-lua.actions'
require 'fzf-lua'.setup({
actions = {
files = {
-- instead of the default action 'actions.file_edit_or_qf'
-- it's important to define all other actions here as this
-- table does not get merged with the global defaults
["default"] = actions.file_edit,
["ctrl-s"] = actions.file_split,
["ctrl-v"] = actions.file_vsplit,
["ctrl-t"] = actions.file_tabedit,
["alt-q"] = actions.file_sel_to_qf,
},
},
})
Most providers support setting the working directory using the cwd
option:
:lua require("fzf-lua").files({ cwd = "~/dots" })
Some providers (buffers
, oldfiles
) support showing files from the current
directory only using the cwd_only
option.
An example by @gegoune, show all file history when in ~
, otherwise show
current directory only:
:lua require('fzf-lua').oldfiles({
cwd_only = function()
return vim.api.nvim_command('pwd') ~= vim.env.HOME
end
})
Excluding paths can be done by adding flags to the underlying executed command for the provider.
Setting the cmd
option will override the underlying command:
files
: excluding a folder using fd
:
:lua require'fzf-lua'.files({ cmd = 'fd --type f --exclude node_modules' })
files
: excluding a folder using find
:
:lua require'fzf-lua'.files({ cmd = "find -type f -not -path '*/node_modules/*' -printf '%P\n'" })
grep
: excluding a folder using rg
:
:lua require'fzf-lua'.grep({ cmd = "rg --color=always --smart-case -g '!{.git,node_modules}/'" })
Alternatively, you can also modify the default auto-detect options:
files
prioritizesfd
overfind
, default options can be modified by settingfiles.fd_opts
orfiles.find_opts
respectively.*grep
prioritizesrg
overgrep
, default options can be modified by settinggrep.rg_opts
orgrep.grep_opts
respectively.-- Note the omission of `fd`: :lua require'fzf-lua'.files({ fd_opts = '--type f --exclude node_modules' })
Best explained by reading the commit message and the discussion in #248:
Since LUA is single threaded I reached a limit to performance
optimization, both 'git_icons' and 'file_icons' require string
matching and manipulations which eventually hurt performance
when running on large amount of files.
In order to solve that this commit introduces the option to spawn
commands and process the entries in a separate neovim process which
prints to stdio as if it was a regular shell command. This speeds up
things significantly and also makes the UI super responsive as if fzf
was run in the shell. This required a few lua hacks to be able to load
nvim-web-devicons in a '--headless --clean' instance and sharing the
user configuration through the RPC interface from the running instance.
This is enabled by default for 'files' and 'grep' providers and can also
be enabled for 'git.files' if required, control using the 'multiprocess'
option.
Unless you're having issues I highly recommend keeping the default
multiprocess = true
. Also see the discussion how to debug multiprocess in What's the difference betweengrep
andlive_grep
?
As explained above, multiprocess
requires loading devicons manually thus
skipping any customization in your neovim init files, the solution for this as
explained in #311 is
to let fzf-lua know where your custom devicons setup function resides:
require'fzf-lua.config'._devicons_setup = "~/.config/nvim/lua/plugins/devicons.lua"
Note that this requires to separate your devicons setup into it's own file with no other
require
statements other thanrequire('nvim-web-devicons')
grep
uses rg
(or grep
if rg
is not installed) to search for a pattern,
the results are then fed into fzf
for further refinement using fuzzy search.
live_grep
on the other hand uses fzf
as a UI selector only, the fuzzy
search functionality is disabled and each keystroke generates a new rg
query
with the input prompt text.
Both methods have their pros and cons, in large codebases live_grep
can be
faster at the cost of not having the fuzzy search, it's about using the right
tool for the job.
In order to better understand how this works I recommend experimenting with
both grep
and live_grep
with both multiprocess
and debug
:
:lua require'fzf-lua'.grep({ multiprocess=true, debug=true })
:lua require'fzf-lua'.live_grep({ multiprocess=true, debug=true })
With debug=true
the underlying rg
command will be printed as the first
match so you'll be able to see that with grep
the command never changes, all
results are already indexed inside fzf
and every keystroke shows the fuzzy
mathced results, on the other hand with live_grep
you'll notice that each
keystroke generates a new underlying rg
command. I find this very helpful to
also understand how live_grep_glob
query parsing works, for example you can
see that the typed query now -- *which*
gets translated to searching for the
word now
limited to files which contain the word which
:
Unless a search term is specified, Rg
feeds all lines of the project into fzf,
the equivalent in fzf-lua would be running grep
with an empty search query:
:lua require("fzf-lua").grep({ search = "" })
fzf-lua
comes with a convinience shortcut grep_project
that combines both
the empty string search as well as excluding file names from fuzzy matching:
-- both commands are equal:
:lua require("fzf-lua").grep_project()
:lua require("fzf-lua").grep({ search = "", fzf_opts = { ['--nth'] = '2..' } })
Yes, both grep
and live_grep
can resume the last search:
-- `live_grep` is also supported
:lua require("fzf-lua").grep({ continue_last_search = true })
Alternatively you can also use :FzfLua resume
.
By default live_grep
does not run an empty query unless:
:lua require("fzf-lua").live_grep({ exec_empty_query = true })
Both --glob
and --iglob
are supported, the default options are under the
grep
provider:
require("fzf-lua").setup({
grep = {
glob_flag = "--iglob", -- for case sensitive globs use '--glob'
glob_separator = "%s%-%-" -- query separator pattern (lua): ' --'
}
})
For usage please refer to man rg
as well as read
#167
For performance reasons the default rg
command does not sort the results, to
sort the results add --sort-files
to the default grep.rg_opts
:
require("fzf-lua").setup({
grep = {
rg_opts = "--sort-files --hidden --column --line-number --no-heading " ..
"--color=always --smart-case -g '!{.git,node_modules}/*'",
}
})
By default all LSP calls are asynchronous (better UI responsiveness), when running async calls fzf-lua can't tell in advance if there will be any results hence when there are no results the window is flashing.
It's possible to prevent the window from flashing by running in sync
mode,
to do so either define lsp.sync = true
(globally) or send send sync = true
with your LSP call, e.g.:
:lua require("fzf-lua").lsp_code_actions({ sync = true })
When running refernces|definitions
the LSP can sometimes return a single
result, if you wish to jump there immediately instead of opening the window
run:
:lua require("fzf-lua").lsp_definitions({ jump_to_single_result = true })
It's also possible to control how the single result action will behave:
:lua require('fzf-lua').lsp_definitions({
sync = true,
jump_to_single_result = true,
jump_to_single_result_action = require('fzf-lua.actions').file_vsplit,
})
By default, fuzzy searching will match the entire line which includes
filenames, it is possible to hide certain fields or leave them visible but
exclude them from the fuzzy search using fzf's --nth
and --with-nth
options:
For example when using lsp_document_symbols
matching the filename doesn't
add much value, to disable filename matching:
:lua require'fzf-lua'.lsp_document_symbols({ fzf_cli_args = '--nth 2..' })
To hide the filename altogether use --with-nth
:
:lua require'fzf-lua'.lsp_document_symbols({ fzf_cli_args = '--with-nth 2..' })
man fzf
and search forFIELD INDEX EXPRESSION
for more info
Having to extract extensions from filenames, git status
indicators and
drawing icons all have a performance impact as well as having to deal with
ANSI coloring (fzf's --ansi
option).
If you do not care about any of these and just want maximum speed you need to
disable both file_icons
and git_icons
and disable fzf's --ansi
:
The below does this for files
but it can obviously be done for grep
,
lsp
, etc and all file-based providers:
require("fzf-lua").setup({
winopts = {
preview = { default = 'bat_native' }
},
fzf_opts = { ['--ansi'] = false },
files = {
git_icons = false,
file_icons = false,
}
})
-- Alternatively, can be called be called directly or mapped to a bind
:lua require("fzf-lua").files({fzf_opts = {['--ansi']=false}, file_icons=false, git_icons=false})
In addition it is worth noting that the default previewer (neovim's floating
win aka "builtin") is slower than bat
(or cat
) previewers, to switch to
bat
previewer set winopts.default.preview = 'bat'
or
winopts.default.preview = 'bat_native'
, the difference between the two is
that the former will use neovim as a proxy to the bat
command and
bat_native
will defer the execution of bat
to fzf
(hence "native").
Due to the same reasoning as above live_grep_native
may be a bit faster than
live_grep
as the latter needs to call neovim for each keystroke in order to
save the search term for the continue_last_search
option. live_grep_native
is the closest as possible to a pure shell/terminal version.
Yes! it's possible to preview media files with the builtin
previewer. To do
so you will need to configure previewer.builtin.extensions
with a shell
command per extension, the shell command will then run in a neovim terminal
buffer inside the preview window.
For example, the below configures png
files to open with
viu
and jpg
files to open with
ueberzug
:
require("fzf-lua").setup({
previewers = {
builtin = {
extensions = {
-- neovim terminal only supports `viu` block output
["png"] = { "viu", "-b" },
["jpg"] = { "ueberzug" },
}
-- When using 'ueberzug' we can also control the way images
-- fill the preview area with ueberzug's image scaler, set to:
-- false (no scaling), "crop", "distort", "fit_contain",
-- "contain", "forced_cover", "cover"
-- For more details see:
-- https://github.com/seebye/ueberzug
ueberzug_scaler = "cover",
}
},
})
Below is an example of using ueberzug
with cover
scaling:
Type | Name | Linux | Mac | Comments |
---|---|---|---|---|
Images | viu |
✔️ | ✔️ | |
Images | ueberzug |
✔️ | ❌ | Requires X11 |
Images | termpix |
✔️ | ✔️ | |
Images | kitty imgcat |
❌ | ❌ | Does not work inside neovim terminal, see https://github.com/kovidgoyal/kitty/issues/413 |
Images | iTerm2 imgcat |
❌ | ❌ | Does not work inside neovim terminal, see https://github.com/neovim/neovim/issues/4349 |
For more information see
vifmimg
,ranger
Wiki
Type | Name | Linux | Mac |
---|---|---|---|
Videos | ffmpegthumbnailer |
✔️ | ✔️ |
ePub | epub-thumbnailer |
✔️ | ✔️ |
pdftoppm |
✔️ | ✔️ | |
Font | fontpreview |
✔️ | ✔️ |
djvu | ddjvu |
✔️ | ✔️ |