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 the reload and reload-sync action keywords when processing --bind expressions. The parser recognizes these as valid actions that can be attached to events like change, start, or key combinations.

  • src/terminal.go (lines 7477-7490) – When a reload action triggers, the terminal controller builds a searchRequest containing 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 reload to change – Use --bind 'change:reload:rg ...' to execute ripgrep on every keystroke.
  • Disable fuzzy filtering – Add --disabled to let ripgrep control the matching logic exclusively.
  • Preserve colors – Include --ansi to 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.1 to reduce process spam during rapid typing.
  • Toggle modes – Use unbind(change) and enable-search to 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →