/fzfx.nvim

E(x)tended commands missing in fzf.vim.

Primary LanguageLuaMIT LicenseMIT

fzfx.nvim

Neovim-v0.5 Top Language License ci.yml codecov

E(x)tended commands missing in fzf.vim.

FzfxLiveGrep-v2.mp4

This is the next generation of fzfx.vim, a brand new fzf plugin for Neovim, build from scratch, focused on user friendly, customization and performance.

✨ Feature

  • Icons & colors.
  • Windows support.
  • Lua support: preview lua function defined commands and key mappings (todo).
  • Fully dynamic parsing user query and selection, a typical use case is passing raw rg options via -- flag (see demo).
  • Multiple variants to avoid manual input:
    • Search by visual select.
    • Search by cursor word.
    • Search by yank text.
  • Easily switch on data sources:
    • Whether to search hidden or ignored files.
    • Local or remote git branches.
    • All diagnostics on workspace or only on current buffer (todo).
  • Maximized configuration.
  • ...

Actually all above features are built on an engine that support fully dynamic runtime & pipeline control, it allows you to do almost anything you want, please see Configuration and Wiki.

✅ Requirement

  • Neovim ≥ 0.5.
  • Nerd fonts (optional for icons).
  • rg (optional for live grep, by default use grep).
  • fd (optional for files, by default use find).
  • bat (optional for preview files, e.g. the right side of live grep, files, by default use cat).
  • git (optional for git commands).
  • Neovim ≥ 0.6 (optional for lsp diagnostics commands).

Note: grep, find and cat are unix/linux builtin commands, while on Windows we don't have a builtin shell environment, so install rg, fd and bat should be a better choice. Also see Windows for how to install linux commands on Windows.

Windows

Click here to see how to install linux commands on Windows

There're many ways to install portable linux shell and builtin commands on Windows, but personally I would recommend below two methods.

Install with the below 3 options:

  • In Select Components, select Associate .sh files to be run with Bash.

    install-windows-git1.png
  • In Adjusting your PATH environment, select Use Git and optional Unix tools from the Command Prompt.

    install-windows-git2.png
  • In Configuring the terminal emulator to use with Git Bash, select Use Windows's default console window.

    install-windows-git3.png

After this step, git.exe and builtin linux commands(such as sh.exe, grep.exe, find.exe, sleep.exe, cd.exe, ls.exe) will be available in %PATH%.

Run below powershell commands:

# scoop
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser
irm get.scoop.sh | iex

scoop bucket add extras
scoop install git
scoop install mingw
scoop install coreutils
scoop install sleep
scoop install grep
scoop install findutils

Fix conflicts between embeded commands in C:\Windows\System32 and portable linux commands

Windows actually already provide some commands (find.exe, bash.exe) in C:\Windows\System32 (or %SystemRoot%\system32), which could override our installations. To fix this issue, we could prioritize the git or scoop environment variables in %PATH%.

windows-path

Path containing whitespace & Escaping issue

Click here to see how whitespace affect escaping characters on path

This plugin internally extends nvim, fzf and lua scripts to full path when launching command.

But when there're whitespaces on the path, launching correct shell command becomes quite difficult, since it will seriously affected escaping characters. Here're two typical cases:

  1. C:\Program Files\Neovim\bin\nvim.exe - nvim.exe installed in C:\Program Files directory.

    Please add executables (nvim.exe, fzf.exe) to %PATH% ($env:PATH in PowerShell), and set the env configuration:

    require("fzfx").setup({
        env = {
            nvim = 'nvim',
            fzf = 'fzf',
        }
    })

    This will help fzfx.nvim avoid the shell command issue.

  2. C:\Users\Lin Rongbin\opt\Neovim\bin\nvim.exe or /Users/linrongbin/Library/Application\ Support/Neovim/bin/nvim - Lin Rongbin (user name) or Application Support (macOS application) contains whitespace.

    We still cannot handle the 2nd case for now, please always try to avoid whitespaces in path.

    Here's an example of searching files command (macOS):

    • /opt/homebrew/bin/nvim -n --clean --headless -l /Users/linrongbin/.local/share/nvim/lazy/fzfx.nvim/bin/files/provider.lua /tmp/nvim.linrongbin/3NXwys/0

    Here's an example of launching fzf command (Windows 10):

    • C:/Users/linrongbin/github/junegunn/fzf/bin/fzf --query "" --header ":: Press \27[38;2;255;121;198mCTRL-U\27[0m to unrestricted mode" --prompt "~/g/l/fzfx.nvim > " --bind "start:unbind(ctrl-r)" --bind "ctrl-u:unbind(ctrl-u)+execute-silent(C:\\Users\\linrongbin\\scoop\\apps\\neovim\\current\\bin\\nvim.exe -n --clean --headless -l C:\\Users\\linrongbin\\github\\linrongbin16\\fzfx.nvim\\bin\\rpc\\client.lua 1)+change-header(:: Press \27[38;2;255;121;198mCTRL-R\27[0m to restricted mode)+rebind(ctrl-r)+reload(C:\\Users\\linrongbin\\scoop\\apps\\neovim\\current\\bin\\nvim.exe -n --clean --headless -l C:\\Users\\linrongbin\\github\\linrongbin16\\fzfx.nvim\\bin\\files\\provider.lua C:\\Users\\linrongbin\\AppData\\Local\\nvim-data\\fzfx.nvim\\switch_files_provider)" --bind "ctrl-r:unbind(ctrl-r)+execute-silent(C:\\Users\\linrongbin\\scoop\\apps\\neovim\\current\\bin\\nvim.exe -n --clean --headless -l C:\\Users\\linrongbin\\github\\linrongbin16\\fzfx.nvim\\bin\\rpc\\client.lua 1)+change-header(:: Press \27[38;2;255;121;198mCTRL-U\27[0m to unrestricted mode)+rebind(ctrl-u)+reload(C:\\Users\\linrongbin\\scoop\\apps\\neovim\\current\\bin\\nvim.exe -n --clean --headless -l C:\\Users\\linrongbin\\github\\linrongbin16\\fzfx.nvim\\bin\\files\\provider.lua C:\\Users\\linrongbin\\AppData\\Local\\nvim-data\\fzfx.nvim\\switch_files_provider)" --preview "C:\\Users\\linrongbin\\scoop\\apps\\neovim\\current\\bin\\nvim.exe -n --clean --headless -l C:\\Users\\linrongbin\\github\\linrongbin16\\fzfx.nvim\\bin\\files\\previewer.lua {}" --bind "ctrl-l:toggle-preview" --expect "enter" --expect "double-click" >C:\\Users\\LINRON~1\\AppData\\Local\\Temp\\nvim.0\\JSmP06\\2

    If the path contains whitespace, that will make all lua scripts contains whitespace, thus the shell command cannot being correctly evaluated.

📦 Install

call plug#begin()

" optional for icons
Plug 'nvim-tree/nvim-web-devicons'

" mandatory
Plug 'junegunn/fzf', { 'do': { -> fzf#install() } }
Plug 'linrongbin16/fzfx.nvim'

call plug#end()

lua require('fzfx').setup()
return require('packer').startup(function(use)
    -- optional for icons
    use { "nvim-tree/nvim-web-devicons" }

    -- mandatory
    use { "junegunn/fzf", run = ":call fzf#install()" }
    use {
        "linrongbin16/fzfx.nvim",
        config = function()
            require("fzfx").setup()
        end
    }
end)
require("lazy").setup({
    -- optional for icons
    { "nvim-tree/nvim-web-devicons" },

    -- mandatory
    { "junegunn/fzf", build = ":call fzf#install()" },
    {
        "linrongbin16/fzfx.nvim",
        dependencies = { "junegunn/fzf" },
        config = function()
            require("fzfx").setup()
        end
    },

})

🚀 Commands

Commands are named following below rules:

  • All commands are named with prefix Fzfx.
  • The main command name has no suffix.
  • The unrestricted variant is named with U suffix.
  • The visual select variant is named with V suffix.
  • The cursor word variant is named with W suffix.
  • The yank text variant is named with P suffix (just like press the p key).
  • The current buffer only variant is named with B suffix.

Especially for git commands:

  • The remote git branch variant is named with R suffix.
  • The git files in current directory variant is named with C suffix.

Note: command names can be configured, see Configuration.

Bind Keys

  • Preview keys
    • alt-p: toggle preview.
    • ctrl-f: preview half page down.
    • ctrl-b: preview half page up.
  • Multi keys
    • ctrl-e: toggle select.
    • ctrl-a: toggle select all.

Note: builtin keys can be configured, see Configuration.

Group Command Mode Multi Key? Preview Key? Special Feature
Files FzfxFiles(U) N Yes Yes
FzfxFiles(U)V V
FzfxFiles(U)W N
FzfxFiles(U)P N
Live Grep FzfxLiveGrep(U) N Yes Yes 1. Use `--` to pass raw options to search command (grep, rg)
FzfxLiveGrep(U)V V
FzfxLiveGrep(U)W N
FzfxLiveGrep(U)P N
Buffers FzfxBuffers N Yes Yes
FzfxBuffersV V
FzfxBuffersW N
FzfxBuffersP N
Git Files FzfxGFiles N Yes Yes
FzfxGFilesV V
FzfxGFilesW N
FzfxGFilesP N
Git Branches FzfxGBranches(R) N No Yes 1. Use `enter` to checkout branch
FzfxGBranches(R)V V
FzfxGBranches(R)W N
FzfxGBranches(R)P N
Git Commits FzfxGCommits(B) N No Yes 1. Use `enter` to copy commit SHA
FzfxGCommits(B)V V
FzfxGCommits(B)W N
FzfxGCommits(B)P N
Git Blame FzfxGBlame N No Yes 1. Use `enter` to copy commit SHA
FzfxGBlameV V
FzfxGBlameW N
FzfxGBlameP N
Lsp Diagnostics FzfxLspDiagnostics(B) N Yes Yes
FzfxLspDiagnostics(B)V V
FzfxLspDiagnostics(B)W N
FzfxLspDiagnostics(B)P N
Lsp Definitions FzfxLspDefinitions N Yes Yes

📌 Recommended Key Mappings

Vimscript

Click here to see vimscripts
" ======== files ========

" find files
nnoremap <space>f :\<C-U>FzfxFiles<CR>
" by visual select
xnoremap <space>f :\<C-U>FzfxFilesV<CR>
" by cursor word
nnoremap <space>wf :\<C-U>FzfxFilesW<CR>
" by yank text
nnoremap <space>pf :\<C-U>FzfxFilesP<CR>

" ======== live grep ========

" live grep
nnoremap <space>l :\<C-U>FzfxLiveGrep<CR>
" by visual select
xnoremap <space>l :\<C-U>FzfxLiveGrepV<CR>
" by cursor word
nnoremap <space>wl :\<C-U>FzfxLiveGrepW<CR>
" by yank text
nnoremap <space>pl :\<C-U>FzfxLiveGrepP<CR>

" ======== buffers ========

" buffers
nnoremap <space>bf :\<C-U>FzfxBuffers<CR>
" by visual select
xnoremap <space>bf :\<C-U>FzfxBuffersV<CR>
" by cursor word
nnoremap <space>wbf :\<C-U>FzfxBuffersW<CR>
" by yank text
nnoremap <space>pbf :\<C-U>FzfxBuffersP<CR>

" ======== git files ========

" git files
nnoremap <space>gf :\<C-U>FzfxGFiles<CR>
" by visual select
xnoremap <space>gf :\<C-U>FzfxGFilesV<CR>
" by cursor word
nnoremap <space>wgf :\<C-U>FzfxGFilesW<CR>
" by yank text
nnoremap <space>pgf :\<C-U>FzfxGFilesP<CR>

" ======== git branches ========

" git branches
nnoremap <space>br :\<C-U>FzfxGBranches<CR>
" by visual select
xnoremap <space>br :\<C-U>FzfxGBranchesV<CR>
" by cursor word
nnoremap <space>wbr :\<C-U>FzfxGBranchesW<CR>
" by yank text
nnoremap <space>pbr :\<C-U>FzfxGBranchesP<CR>

" ======== git commits ========

" git commits
nnoremap <space>gc :\<C-U>FzfxGCommits<CR>
" by visual select
xnoremap <space>gc :\<C-U>FzfxGCommitsV<CR>
" by cursor word
nnoremap <space>wgc :\<C-U>FzfxGCommitsW<CR>
" by yank text
nnoremap <space>pgc :\<C-U>FzfxGCommitsP<CR>

" ======== git blame ========

" git blame
nnoremap <space>gb :\<C-U>FzfxGBlame<CR>
" by visual select
xnoremap <space>gb :\<C-U>FzfxGBlameV<CR>
" by cursor word
nnoremap <space>wgb :\<C-U>FzfxGBlameW<CR>
" by yank text
nnoremap <space>pgb :\<C-U>FzfxGBlameP<CR>

" ======== lsp diagnostics ========

" lsp diagnostics
nnoremap <space>dg :\<C-U>FzfxLspDiagnostics<CR>
" by visual select
xnoremap <space>dg :\<C-U>FzfxLspDiagnosticsV<CR>
" by cursor word
nnoremap <space>wdg :\<C-U>FzfxLspDiagnosticsW<CR>
" by yank text
nnoremap <space>pdg :\<C-U>FzfxLspDiagnosticsP<CR>


" ======== lsp definitions ========

" lsp definitions
nnoremap gd :\<C-U>FzfxLspDefinitions<CR>

Lua

Click here to see lua scripts
-- ======== files ========

-- find files
vim.keymap.set('n', '<space>f', '<cmd>FzfxFiles<cr>',
        {silent=true, noremap=true, desc="Find files"})
-- by visual select
vim.keymap.set('x', '<space>f', '<cmd>FzfxFilesV<CR>',
        {silent=true, noremap=true, desc="Find files"})
-- by cursor word
vim.keymap.set('n', '<space>wf', '<cmd>FzfxFilesW<cr>',
        {silent=true, noremap=true, desc="Find files by cursor word"})
-- by yank text
vim.keymap.set('n', '<space>pf', '<cmd>FzfxFilesP<cr>',
        {silent=true, noremap=true, desc="Find files by yank text"})

-- ======== live grep ========

-- live grep
vim.keymap.set('n', '<space>l',
        '<cmd>FzfxLiveGrep<cr>',
        {silent=true, noremap=true, desc="Live grep"})
-- by visual select
vim.keymap.set('x', '<space>l',
        "<cmd>FzfxLiveGrepV<cr>",
        {silent=true, noremap=true, desc="Live grep"})
-- by cursor word
vim.keymap.set('n', '<space>wl',
        '<cmd>FzfxLiveGrepW<cr>',
        {silent=true, noremap=true, desc="Live grep by cursor word"})
-- by yank text
vim.keymap.set('n', '<space>pl',
        '<cmd>FzfxLiveGrepP<cr>',
        {silent=true, noremap=true, desc="Live grep by cursor word"})

-- ======== buffers ========

-- buffers
vim.keymap.set('n', '<space>bf',
        '<cmd>FzfxBuffers<cr>',
        {silent=true, noremap=true, desc="Find buffers"})
-- by visual select
vim.keymap.set('x', '<space>bf',
        "<cmd>FzfxBuffersV<cr>",
        {silent=true, noremap=true, desc="Find buffers"})
-- by cursor word
vim.keymap.set('n', '<space>wbf',
        '<cmd>FzfxBuffersW<cr>',
        {silent=true, noremap=true, desc="Find buffers by cursor word"})
-- by yank text
vim.keymap.set('n', '<space>pbf',
        '<cmd>FzfxBuffersP<cr>',
        {silent=true, noremap=true, desc="Find buffers by yank text"})

-- ======== git files ========

-- git files
vim.keymap.set('n', '<space>gf',
        '<cmd>FzfxGFiles<cr>',
        {silent=true, noremap=true, desc="Find git files"})
-- by visual select
vim.keymap.set('x', '<space>gf',
        "<cmd>FzfxGFilesV<cr>",
        {silent=true, noremap=true, desc="Find git files"})
-- by cursor word
vim.keymap.set('n', '<space>wgf',
        '<cmd>FzfxGFilesW<cr>',
        {silent=true, noremap=true, desc="Find git files by cursor word"})
-- by yank text
vim.keymap.set('n', '<space>pgf',
        '<cmd>FzfxGFilesP<cr>',
        {silent=true, noremap=true, desc="Find git files by yank text"})

-- ======== git branches ========

-- git branches
vim.keymap.set('n', '<space>br', '<cmd>FzfxGBranches<cr>',
        {silent=true, noremap=true, desc="Search git branches"})
-- by visual select
vim.keymap.set('x', '<space>br', '<cmd>FzfxGBranchesV<CR>',
        {silent=true, noremap=true, desc="Search git branches"})
-- by cursor word
vim.keymap.set('n', '<space>wbr', '<cmd>FzfxGBranchesW<cr>',
        {silent=true, noremap=true, desc="Search git branches by cursor word"})
-- by yank text
vim.keymap.set('n', '<space>pbr', '<cmd>FzfxGBranchesP<cr>',
        {silent=true, noremap=true, desc="Search git branches by yank text"})

-- ======== git commits ========

-- git commits
vim.keymap.set('n', '<space>gc', '<cmd>FzfxGCommits<cr>',
        {silent=true, noremap=true, desc="Search git commits"})
-- by visual select
vim.keymap.set('x', '<space>gc', '<cmd>FzfxGCommitsV<CR>',
        {silent=true, noremap=true, desc="Search git commits"})
-- by cursor word
vim.keymap.set('n', '<space>wgc', '<cmd>FzfxGCommitsW<cr>',
        {silent=true, noremap=true, desc="Search git commits by cursor word"})
-- by yank text
vim.keymap.set('n', '<space>pgc', '<cmd>FzfxGCommitsP<cr>',
        {silent=true, noremap=true, desc="Search git commits by yank text"})

-- ======== git blame ========

-- git blame
vim.keymap.set('n', '<space>gb',
        '<cmd>FzfxGBlame<cr>',
        {silent=true, noremap=true, desc="Search git blame"})
-- by visual select
vim.keymap.set('x', '<space>gb',
        "<cmd>FzfxGBlameV<cr>",
        {silent=true, noremap=true, desc="Search git blame"})
-- by cursor word
vim.keymap.set('n', '<space>wgb',
        '<cmd>FzfxGBlameW<cr>',
        {silent=true, noremap=true, desc="Search git blame by cursor word"})
-- by yank text
vim.keymap.set('n', '<space>pgb',
        '<cmd>FzfxGBlameP<cr>',
        {silent=true, noremap=true, desc="Search git blame by yank text"})

-- ======== lsp diagnostics ========

-- lsp diagnostics
vim.keymap.set('n', '<space>dg', '<cmd>FzfxLspDiagnostics<cr>',
        {silent=true, noremap=true, desc="Search lsp diagnostics"})
-- by visual select
vim.keymap.set('x', '<space>dg', '<cmd>FzfxLspDiagnosticsV<CR>',
        {silent=true, noremap=true, desc="Search lsp diagnostics"})
-- by cursor word
vim.keymap.set('n', '<space>wdg', '<cmd>FzfxLspDiagnosticsW<cr>',
        {silent=true, noremap=true, desc="Search lsp diagnostics by cursor word"})
-- by yank text
vim.keymap.set('n', '<space>pdg', '<cmd>FzfxLspDiagnosticsP<cr>',
        {silent=true, noremap=true, desc="Search lsp diagnostics by yank text"})

-- ======== lsp definitions ========

-- lsp definitions
vim.keymap.set('n', 'gd', '<cmd>FzfxLspDefinitions<cr>',
        {silent=true, noremap=true, desc="Search lsp definitions"})

🔧 Configuration

For complete options and default configurations, please check config.lua.

If you have encounter some breaks on configuration, please see Break Changes.

Create your own commands

To create your own commands, please see A General Schema for Creating FZF Command and schema.lua.

💩 Break Changes

  • 2023-08-17
    • Re-bind keys 'ctrl-e'(select), 'ctrl-a'(select-all) to 'toggle', 'toggle-all'.
    • Remove default bind keys 'ctrl-d'(deselect), 'alt-a'(deselect-all).
    • Re-bind key 'ctrl-x' (delete buffer on FzfxBuffers) to 'ctrl-d'.
    • Re-bind key 'ctrl-l' (toggle-preview) to 'alt-p'.
  • 2023-08-19
    • Refactor configs schema for better structure.
  • 2023-08-28
    • Deprecate 'git_commits' (FzfxGCommits) configs, notify user migrate to new schema.
  • 2023-08-30
    • Deprecate 'buffers' (FzfxBuffers) configs, notify user migrate to new schema.
  • 2023-09-11
    • Deprecate 'git_branches' (FzfxGBranches) configs, notify user migrate to new schema.
  • 2023-09-12
    • Deprecate 'git_files' (FzfxGFiles) configs, notify user migrate to new schema.

🍀 Credit

  • fzf.vim: Things you can do with fzf and Vim.
  • fzf-lua: Improved fzf.vim written in lua.

✏️ Development

To develop the project and make PR, please setup with:

To run unit tests, please install below dependencies:

Then test with vusted --shuffle ./test.

🎁 Contribute

Please open issue/PR for anything about fzfx.nvim.

Like fzfx.nvim? Consider

Github Sponsor Wechat Pay Alipay