How to Use fzf as an Interactive Selector for ripgrep: Complete Integration Guide
Use fzf's --bind option with the reload action attached to the change event to trigger ripgrep on every keystroke, disable fzf's internal filtering with --disabled, and parse ripgrep's file:line:column output to open selections directly in your editor.
The junegunn/fzf repository provides a general-purpose command-line fuzzy finder that functions as a powerful interactive selector for ripgrep. By leveraging fzf's event-action engine, you can build a live search interface where ripgrep executes on every query change, returning structured results that fzf displays while preserving syntax highlighting and exact positional information.
Understanding the fzf Event-Action Engine
The reload Action and change Event
According to the source code in src/options.go, fzf parses the reload keyword as part of its action regex system. When you bind reload to the change event, fzf creates a searchRequest that is queued in src/terminal.go (lines 7477-7490). This request executes your ripgrep command as an external process and streams its output back into fzf's item list without blocking the user interface.
Disabling Internal Filtering
The --disabled flag is critical for ripgrep integration. It prevents fzf from applying its fuzzy matching algorithm to the incoming data, ensuring that ripgrep remains the sole source of search results and filtering logic. This transforms fzf from a fuzzy finder into a pure interactive selector for ripgrep's output.
Implementing the ripgrep-fzf Workflow
Basic Configuration with reload Binding
Start fzf with bindings that trigger ripgrep when the query changes. The {q} placeholder expands to the current query string, while {1} and {2} capture the file path and line number from ripgrep's colon-delimited output:
: | rg_prefix='rg --column --line-number --no-heading --color=always --smart-case' \
fzf --bind 'start:reload:$rg_prefix ""' \
--bind 'change:reload:$rg_prefix {q} || true' \
--bind 'enter:become(vim {1} +{2})' \
--ansi --disabled \
--height=50% --layout=reverse
Adding Preview and Debouncing
For a richer interface, add a preview window showing the file content and debounce rapid keystrokes to prevent spawning excessive ripgrep processes:
RG='rg --column --line-number --no-heading --color=always --smart-case'
fzf --ansi --disabled --height=50% --layout=reverse \
--bind "start:reload:$RG ''" \
--bind "change:reload:sleep 0.1; $RG {q} || true" \
--bind "enter:become(vim {1} +{2})" \
--preview 'bat --style=numbers --color=always --highlight-line {2} {1}' \
--preview-window 'right:70%,border-left'
The sleep 0.1 command waits 100 milliseconds after the last keystroke before executing the reload, reducing CPU load during rapid typing.
Toggling Between ripgrep and fzf Filtering
Switch between ripgrep mode and fuzzy filtering mid-session to refine results after an initial search:
RG='rg --column --line-number --no-heading --color=always --smart-case '
fzf --ansi --disabled \
--bind "start:reload:$RG {q}" \
--bind "change:reload:sleep 0.1; $RG {q} || true" \
--bind "alt-enter:unbind(change)+change-prompt('2. fzf> ')+enable-search+clear-query" \
--bind "enter:become(vim {1} +{2})" \
--prompt '1. ripgrep> ' \
--preview 'bat --color=always {1} --highlight-line {2}'
Pressing Alt+Enter unbinds the change event, enables fzf's native fuzzy search, and switches the prompt, allowing you to filter the existing ripgrep results without re-executing the search.
How the Reload Mechanism Works Internally
According to the junegunn/fzf source code, the reload integration relies on two core components:
-
src/options.go– Parses thereloadandreload-syncaction keywords when processing--bindexpressions. The parser recognizes these as valid actions that can be attached to events likechange,start, or key combinations. -
src/terminal.go(lines 7477-7490) – When a reload action triggers, the terminal controller builds asearchRequestcontaining the external command. This request enters the search queue, executing the ripgrep process and streaming its output back into fzf's item list without blocking the UI.
The --ansi flag ensures that ripgrep's color escape sequences pass through to the terminal, while --disabled bypasses fzf's internal matcher entirely, delegating all filtering logic to the external command.
Summary
- Bind
reloadtochange– Use--bind 'change:reload:rg ...'to execute ripgrep on every keystroke. - Disable fuzzy filtering – Add
--disabledto let ripgrep control the matching logic exclusively. - Preserve colors – Include
--ansito display ripgrep's syntax highlighting within fzf. - Handle selection – Use
enter:become(vim {1} +{2})to open files at specific lines and columns. - Debounce input – Prefix reload commands with
sleep 0.1to reduce process spam during rapid typing. - Toggle modes – Use
unbind(change)andenable-searchto switch between ripgrep and fzf filtering.
Frequently Asked Questions
How do I prevent fzf from filtering ripgrep results?
Pass the --disabled flag when starting fzf. This disables fzf's internal fuzzy matcher, ensuring that ripgrep remains the sole source of search results and filtering logic. Without this flag, fzf would apply its fuzzy algorithm to ripgrep's output, potentially hiding relevant matches.
What is the purpose of the reload action in fzf?
The reload action, parsed in src/options.go and executed via searchRequest in src/terminal.go, tells fzf to replace its current item list with the output of an external command. When bound to the change event, it enables live updating of results as the user types, creating a responsive search interface.
How can I add a file preview when using fzf with ripgrep?
Add the --preview flag with a command that accepts fzf's placeholders. For example: --preview 'bat --style=numbers --color=always --highlight-line {2} {1}'. The {1} and {2} placeholders extract the file path and line number from ripgrep's colon-delimited output, allowing the preview to highlight the specific match line.
Why does my ripgrep integration feel slow when typing?
Rapid keystrokes spawn multiple ripgrep processes simultaneously, overwhelming the CPU. Add a debounce delay by prefixing the reload command with sleep: --bind 'change:reload:sleep 0.1; rg ...'. This waits 100 milliseconds after the last keystroke before executing ripgrep, reducing process spam and improving responsiveness.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →