brook.nvim brings seamless ripgrep search to Neovim. It's a precision tool for code exploration and refactoring.
Brook integrates ripgrep directly with Neovim's quickfix list and search register, producing persistent results that compose with native commands rather than replacing them.
Brook is a Neovim-native search-and-replace tool for users who already know Vim and ripgrep, and want those tools to work together at scale.
Brook is built around Neovim's existing primitives – the quickfix list and the search register – and avoids introducing new modes, buffers, or workflows.
The goal is composability. Results are persistent and immediately available to native commands and other plugins. Once a search completes, Brook steps aside: navigation, editing, and transformation are handled by Vim itself.
This design targets systematic exploration and transformation of codebases: ordered navigation, reusable result sets, and batch operations across files.
Quickfix-centric – Results are streamed into the quickfix list, where they remain available for navigation, batch operations, and reuse. The quickfix list has persisted for decades because it solves a simple problem well: given a list of locations, navigate and act on them.
Native search integration – Ripgrep patterns are translated into Neovim's
search register, enabling highlighting and n / N navigation within buffers,
as well as project-wide replacement with :cfdo %s//replacement/, without
repeating the pattern.
Transparency – Brook adds no abstraction layer over ripgrep. The command you type is the command that runs; flags behave exactly as documented.
Safe and shell-agnostic – Argument handling is independent of the user's
shell, immune to injection, and unaffected by escaping quirks. If a command
works with rg, it works with Brook.
Fast and responsive – Results are streamed using carefully tuned cooperative scheduling, keeping the UI responsive even in demanding scenarios.
Understated, refined UX – Operates quietly and discreetly, with a small number of direct, intuitive keymaps. Brook respects the user's workflow, produces helpful warnings and errors, and integrates cleanly with search and command history.
Minimal footprint – Brook is small with no dependencies and no required configuration.
Requirements:
Using lazy.nvim:
{
'bravoecho/brook.nvim',
dependencies = {
-- Recommended for context preview and result filtering.
-- See: https://github.com/kevinhwang91/nvim-bqf
{ 'kevinhwang91/nvim-bqf', optional = true },
},
-- Lazy loading
cmd = {
'Rg',
'RgStop',
'RgRepeat',
},
keys = {
{ '<leader>g', mode = 'n', desc = 'Search for current word with ripgrep' },
{ '<leader>g', mode = 'x', desc = 'Search for visual selection with ripgrep' },
{ '<leader>/', mode = 'n', desc = 'Open ripgrep prompt' },
{ '<leader>G', mode = 'n', desc = 'Stop ripgrep search' },
{ '<leader>r', mode = 'n', desc = 'Repeat last ripgrep search' },
},
-- Defaults: only specify the fields you want to customise; if you are happy
-- with the defaults, you only need to set `opts = {}`
opts = {
-- Keymaps (set to false to disable)
keymap_cword = '<leader>g',
keymap_visual = '<leader>g',
keymap_prompt = '<leader>/',
keymap_stop = '<leader>G',
keymap_repeat = '<leader>r',
-- Result limits
max_results = 1000, -- default 1,000; see note below
max_preview_chars = 200, -- 100-500
-- Performance tuning
-- (larger batches and lower throttling increase speed but can cause UI stutter)
max_batch_size = 100, -- results per quickfix update, 10-200
flush_throttle_ms = 10, -- delay between updates (0 to disable)
-- Advanced performance tuning
-- (for ultra-large number of results, emitted by ripgrep at exceptionally high rate)
drain_phase_max_batch_size = 500, -- defaults to 5x max_batch_size
drain_phase_flush_throttle_ms = 5, -- defaults to 1/2 of flush_throttle_ms
-- Quickfix window
qf_open = true,
qf_auto_resize = true,
qf_win_height = 10,
-- 'one-line-per-match' (default) or 'unique-lines'
output_format = 'one-line-per-match',
-- Populate search register for n/N navigation and :cfdo substitutions
set_search_register = true,
},
}
[!CAUTION] About
max_results— The default of 1,000 is appropriate for most workflows. You can raise it as high as you like, but values above 10,000 carry real trade-offs you should understand before committing to them.Neovim resolves filenames in quickfix entries via a linear scan of its internal buffer list. Each search creates unlisted buffers as a side effect, and these accumulate across the session. At high result counts,
setqflist()becomes progressively slower with each search, and Neovim's memory usage grows without bound. Brook mitigates this with an internal buffer-number cache, but the underlying cost is in Neovim's core and cannot be fully eliminated by a plugin.Why not just wipe the unlisted buffers? Because Neovim maintains a stack of up to 10 quickfix lists, navigable with
:colderand:cnewer. These older lists reference the same buffers. Wiping them would break navigation in every previous search result — exactly the kind of semi-persistent history the quickfix stack is meant to provide.If you set
max_resultsvery high and run searches that produce tens of thousands of matches, expect increasing UI stutter and memory pressure over the course of a session. The only recovery is restarting Neovim.
[!TIP] For consistent results, configure both ripgrep and Neovim with smart case.
init.lua:vim.o.ignorecase = true vim.o.smartcase = true
~/.ripgreprc:--smart-case
Brook is designed for a simple "Search-Navigate-Edit" loop powered by the quickfix list.
Search – Brook provides a small set of direct entry points into ripgrep searches:
<leader>g – search word under cursor (normal mode) or selection (visual mode)<leader>/ – open prompt for manual search<leader>G – stop current search if any<leader>r – repeat last search:Rg your_query – manual search:Rg --hidden "function handle_click" – with ripgrep flags:RgStop – stop current search if any:RgRepeat – repeat last searchExplore – Browse with :cnext/:cprev (map these to ]q/[q).
Navigate – Use n/N to jump between matches within each file.
Refactor – Search and replace with :cfdo %s//replacement/gc: the search
register is already populated, so empty // targets exactly what ripgrep
found.
:Rg --<any-options> will use the current word:Rg -t lua config or :Rg -T js bug:Rg -F "($[0].item)" for special characters:Rg -e TODO -e FIXME searches and navigates both, and
combines with other flags like -w (whole-word) and -F (literal):Rg -s MyClass (sensitive) or :Rg -i error (insensitive)-n flag to enable it for a single search<Tab> in the command to autocomplete relative paths:Rg mypattern -n -m1:cfdo edit:Rg mypattern %<leader>G or :RgStop, results so far remain in quickfix: then arrow-up (or q:) to recall, edit, and
re-run them~/.ripgreprc and specify
which paths to exclude in ~/.rgignoreBrook by design is restricted to ripgrep's defaults in two important ways:
No multiline search – Brook aborts with a warning if you use -U or
search a multiline selection. The quickfix format is inherently line-based.
No PCRE2 – only ripgrep's default regexp engine is supported. Commands setting it to PCRE2 will result in an error. This guarantees predictable performance and more accurate search pattern translation.
MIT