Skip to content
Guille edited this page Dec 24, 2024 · 32 revisions

Welcome to the fzf ❤️ lua wiki!

Click here for ADVANCED CUSTOMIZATION / API DOCS

FAQ

General

Fzf-lua is a neovim (>0.5) plugin written in lua integrating fzf into the neovim ecosystem.

OK, but what does that even mean?

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 searching 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 or coc-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 pane No 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'

There are two ways of testing fzf-lua with minimal init:

Using scripts/mini.sh (recommended)

❯ sh -c "$(curl -s https://raw.githubusercontent.com/ibhagwan/fzf-lua/main/scripts/mini.sh)"

NOTES:

  • Creates a sandbox environment for neovim under the OS temp dir (i.e. /tmp/fzf-lua.tmp/)
  • Also downloads nvim-web-devicons so devicons integration can be tested
  • Default binds:
    • <C-k>: :FzfLua builtin
    • <C-p>: :FzfLua files
    • <F1> : :FzfLua help_tags

If you wish to modify the default init.lua and test your own version, download fzf-lua and run the script locally:

❯ git clone https://github.com/ibhagwan/fzf-lua.git
❯ cd fzf-lua
# optional: edit 'scripts/init.lua'
# make sure to delete the previous sandbox (OSX will
# be a different folder, use `mktemp -d` to discover)
❯ rm -rf /tmp/fzf-lua.tmp
❯ ./scripts/mini.sh

Using scripts/minimal_init.lua (the "old" method)

Download (+modify setup) and run:

❯ curl -LO https://raw.githubusercontent.com/ibhagwan/fzf-lua/main/scripts/minimal_init.lua
❯ nvim -u minimal_init.lua

Or run directly from github:

❯ nvim -u <((echo "lua << EOF") && (curl -s https://raw.githubusercontent.com/ibhagwan/fzf-lua/main/scripts/minimal_init.lua) && (echo "EOF"))

NOTES:

  • nvim-web-devicons is not used here, to test fzf-lua with devicons use scripts/mini.sh
  • The script uses packer.nvim but does not remove your currently downloaded plugins, it does not load optional plugins or run setup for auto-loaded plugins, therefore plugin conflicts (although rare) can still happen
  • Default binds:
    • <C-p>: :FzfLua files
require('fzf-lua').setup {
  fzf_opts = { ['--cycle'] = true }
}

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:

  1. Apply a global height setting:
require("fzf-lua").setup({
  winopts = { height = 0.4 }
})
  1. Apply height under the files provider:
require("fzf-lua").setup({
  files = {
    prompt = 'Files❯ ', -- example, not required
    winopts = { height = 0.4 },
  }
})
  1. Apply height to a single call:
:lua require("fzf-lua").files({ winopts = { height = 0.4 } })
  1. For dynamically generated arguments, options can also be defined as a function:
:lua require("fzf-lua").files(function() return {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}))

Appearance

The window look and feel is controlled by 4 parameters, winopts.height, winopts.width, winopts.row and winopts.col, their values can either contain a range from 0.0 - 1.0 and correspond as a percentage of available screen space based on nvim width and height (vim.o.columns, vim.o.lines) or they can also accept a normal integer value indicating exact size.

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) is controlled 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 split above current window
    split = "belowright new"  -- open in split below current window
    split = "aboveleft vnew"  -- open in split left of current window
    split = "belowright vnew" -- open in split right of current window
    split = "topleft new"   -- open in a full-width split on top
    split = "botright new"  -- open in a full-width split on the bottom
    split = "topleft vnew"  -- open in a full-height split on the far left
    split = "botright vnew" -- open in a full-height split on the far right
  }
})

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 = ' ',
})

FzfLua will automatically redraw when the neovim window size changes and the VimResized event is triggered.

If for some reason you still need manually redraw the fzf-lua window you can call :FzfLua redraw or :lua require'fzf-lua'.redraw() to manually trigger the redraw.

NOTE: fzf-lua is a terminal window, you need to first press <C-\>C-n> to get to Normal mode before you can issue the :FzfLua ... command.

You can also use an autocmd to trigger an automatic redraw once certain events happen, below is an equivalent of the buffer local autocmd setup by fzf-lua:

vim.api.nvim_create_autocmd("VimResized", {
  pattern = '*',   
  command = 'lua require("fzf-lua").redraw()'
})
-- using the default prompt
:lua require("fzf-lua").files({ cwd_prompt = false })
-- using a custom prompt
:lua require("fzf-lua").files({ cwd_prompt = false, prompt = 'Files❯ ' })

See #793 for more info

require("fzf-lua").register_ui_select(function(_, items)
  local min_h, max_h = 0.15, 0.70
  local h = (#items + 4) / vim.o.lines
  if h < min_h then
    h = min_h
  elseif h > max_h then
    h = max_h
  end
  return { winopts = { height = h, width = 0.60, row = 0.40 } }
end)

Keybinds

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.keymap.set("t", "<M-BS>",
        "<cmd>lua vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes('<C-u>', true, false, true), 'n', true)<CR>",
        {nowait = true, buffer = 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,
    },
  },
})

Discussions in #279 and #331

Fzf supports saving input history into a history file using --history flag. You can configure a history file globally or per provider. Once the --history flag is supplied fzf will automatically map ctrl-{n|p} to {next|previous}-history, you can change the default binds under keymap.fzf.

Example #1: saving global input history under ~/.local/share/nvim/fzf-lua-history:

require('fzf-lua').setup{
  fzf_opts = {
    ['--history'] = vim.fn.stdpath("data") .. '/fzf-lua-history',
  },
}

NOTE: the directory must exist, so if you're using a custom folder make sure the directory exists using mkdir -p.

Example #2: saving a separate history file for files|grep under ~/.local/share/nvim/:

require('fzf-lua').setup{
  files = {
    fzf_opts = {
      ['--history'] = vim.fn.stdpath("data") .. '/fzf-lua-files-history',
    },
  },
  grep = {
    fzf_opts = {
      ['--history'] = vim.fn.stdpath("data") .. '/fzf-lua-grep-history',
    },
  }
}

To replicate Telescope's ctrl-q behavior:

require('fzf-lua').setup({
  grep = {
    actions = {
      ['ctrl-q'] = {
        fn = actions.file_edit_or_qf, prefix = 'select-all+'
      },
    },
  },
})

Discussions in #324, #546, #1109, #1196

Note: Prefix requires at least fzf 0.53. If you're running Ubuntu 22.04 or similar, you need to install a newer release from github.

Misc

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 prioritizes fd over rg --files over find, default options can be modified by setting files.{fd|rg|find}_opts respectively.
  • grep prioritizes rg over grep, default options can be modified by setting grep.{rg|grep|_opts respectively.
-- Note the omission of `fd`:
:lua require'fzf-lua'.files({ fd_opts = '--type f --exclude node_modules' })

We can also exclude some more complex regex patterns by either piping a filter command to files.cmd or using grep.filter. The below example excludes .lua files:

-- use `grep -P` for perl regex syntax
:lua require'fzf-lua'.files({ cmd = "fd --type f | rg -v '\\.lua$'" })
:lua require'fzf-lua'.files({ cmd = "fd --type f | grep -v -P '\\.lua$'" })

However, we cannot do this with grep because the search string is appended after the cmd argument, we use the filter option to achieve the same result:

Note the omission of the $ sign as the regex is performed on the entire line including the matching text so the filename is in not at the end of the line

:lua require'fzf-lua'.live_grep({ filter = "rg -v '\\.lua'", debug=true})

Excluding paths as explained above is the preferred and most performant way as it reduces the amount of lines fzf-lua needs to process but it won't work on file based providers where a command is not used (e.g. oldfiles, lsp_xxx, etc).

In addition, sometimes a different approach is needed and you might want to ignore ceratin file patterns globally, for this we have file_ignore_patterns, a simple array of lua pattern matching strings which can be set globally, per provider or sent directly in the call arguments.

Note: file_ignore_patterns affects all file based providers: files, grep|live_grep, git_files, git_status, diagnostics, lsp_XXX, args, oldfiles, quickfix, loclist, dap_breakpoints

As a global setting:

require'fzf-lua'.setup {
  -- ignore all '.lua' and '.vim' files
  file_ignore_patterns = { "%.lua$", "%.vim$" }
}

As a provider setting:

require'fzf-lua'.setup {
  files = {
    prompt = "Files> ", 
    file_ignore_patterns = { "%.lua$", "%.vim$" },
  }
}

Directly in a call:

-- ignore all files that start with a '/'
:lua require'fzf-lua'.lsp_live_workspace_symbols({ file_ignore_patterns={ "^/" } })

All of the above can be combined, you can have a global setting, provider setting and call argument all at the same time, all 3 settings will be merged at runtime into a single file_ignore_patterns array.

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 between grep and live_grep?

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:

Glob

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({ resume = 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 = {
    rg_glob         = true        -- enable glob parsing by default to all
                                  -- grep providers? (default:false)
    glob_flag       = "--iglob",  -- for case sensitive globs use '--glob'
    glob_separator  = "%s%-%-"    -- query separator pattern (lua): ' --'
  }
})

Setting rg_glob=true instructs fzf-lua to parse the search query (regex) and look for the separator, anything after the separator will be converted to a separate glob flag, few examples:

Search for all lines starting with foo within *.lua files:

*Rg> ^foo -- *.lua

Generates the below underlying command:

rg ${rg_opts} --iglob *.lua -e "^foo"

Search for all lines starting with foo within *.lua files, exclude *spec* files:

*Rg> ^foo -- *.lua !*spec*

Generates the below underlying command:

rg ${rg_opts} --iglob *.lua --iglob !*spec* -e "^foo"

Once rg_glob=true we aren't limited to live_grep, any search regex is parsed for a separator and uses the same logic so we can use globs in grep directly:

:lua require("fzf-lua").grep({ no_esc=true, search="foo -- *.lua" })

Another example, from issue #167, shows how the underlying rg command (below the status line) changes with each keystroke and the generated glob arguments once the separator is detected:

167

Using a custom rg_glob_fn we can build our own live_grep_glob, one such example would be sending any argument to rg similar to telescope-live-grep-args.nvim:

require("fzf-lua").setup({
  grep = {
    rg_glob = true,
    -- first returned string is the new search query
    -- second returned string are (optional) additional rg flags
    -- @return string, string?
    rg_glob_fn = function(query, opts)
      local regex, flags = query:match("^(.-)%s%-%-(.*)$")
      -- If no separator is detected will return the original query
      return (regex or query), flags
    end
  }
})

For example, sending the "word boundary" flag:

*Rg> ^foo -- --word-regexp --glob="*.lua"

generates the below underlying command

rg ${rg_opts} --word-regexp --glob="*.lua" -e "^foo"

Similar to the default glob search, once rg_glob is enabled we can use the new parser directly from grep:

:lua require("fzf-lua").grep({ no_esc=true, search="foo -- --word-regexp" })

For other examples of custom rg_glob_fn see #373.

Note restricting searches to file globs requires installing rg.

Building on the above, either enable rg_glob to all providers an or use live_grep_glob and use the default separator -- (space dash dash) to separate the search query from the file glob specification:

  1. Open glob enabled live grep by executing :FzfLua live_grep_glob
  2. Type your search, separated by the glob_separator, e.g: Rg> <search_term> -- <glob_spec1> <glob_spec2> ...

More specific examples:

  • Searching for all test files that import the react-hooks library assuming test files are named <name>.spec.<ext>: Rg> @testing-library/react-hooks --*.spec.*

  • Searching for all occurrences of the Partial utility in javascript and typescript files: Rg> Partial --*.ts* *.js

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.async = false (globally) or send send async = false with your LSP call, e.g.:

:lua require("fzf-lua").lsp_code_actions({ async = false })

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,
})

When using lsp_references some users prefer to see "other references" only and ignore the current line, this is possible via:

:lua require("fzf-lua").lsp_references({ ignore_current_line = true })

lsp_references can be configured to exclude declaration of current symbol from results:

:lua require("fzf-lua").lsp_references({ includeDeclaration = false })

Combined with ignore_current_line = true, it achieves "show other usages" behavior.

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 for FIELD 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:

MediaDemo

List of command line utilities for terminal image previews

Type Name Linux Mac Comments
Images viu ✔️ ✔️
Images ueberzugpp ✔️ 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

List of command line utilities for image generation

For more information see vifmimg, ranger Wiki

Type Name Linux Mac
Videos ffmpegthumbnailer ✔️ ✔️
ePub epub-thumbnailer ✔️ ✔️
PDF pdftoppm ✔️ ✔️
Font fontpreview ✔️ ✔️
djvu ddjvu ✔️ ✔️

CtrlGWorkflow

Clone this wiki locally