Tool Composition - fzf ripgrep zoxide
Tool Composition: fzf, ripgrep, and zoxide
Understanding how command-line tools compose together is fundamental to Unix philosophy. fzf, ripgrep, and zoxide are independent tools that work beautifully together but don't directly depend on each other.
The Core Principle: Unix Tool Composition
Each tool does ONE thing well, and they communicate via standard input/output.
Tool A output β Tool B input β Tool C input β Final result
This is composition, not dependency.
The Three Tools
fzf - General-Purpose Fuzzy Finder
What it does:
- Takes ANY text input (from stdin or a command)
- Presents interactive fuzzy-finding interface
- Returns selected item(s) to stdout
- Has a built-in directory walker (doesn't need external tools by default)
What it does NOT do:
- Search file contents (it filters whatever you give it)
- Remember your navigation history
fzf is primarily UI, but includes basic file traversal.
fzf's Default Behavior (No External Tools Required!)
When you run fzf without piping input, it uses its built-in directory walker (since v0.47.0, March 2024):
bash# Default behavior (no ripgrep, find, or fd needed): fzf # Equivalent to: fzf --walker file,follow,hidden \ --walker-root . \ --walker-skip .git,node_modules
This is built into fzf itself - no external dependencies!
Historical Context: Why You Might Think fzf Uses Other Tools
Before fzf v0.47.0 (March 2024):
- fzf DID use an external
findcommand by default! - The default was:
find * -path '*/.*' -prune -o -type f -print -o -type l -print 2> /dev/null - This is probably why you remember a connection!
Why the change? From the v0.47.0 changelog:
"Replaced the default find command with a built-in directory walker to simplify the code and to achieve better performance and consistent behavior across platforms."
fzf now uses the charlievieth/fastwalk library, which:
- Outperforms external tools like
find - Works consistently across Windows/macOS/Linux
- Safely follows symlinks
- Parallelizes traversal for speed
Using fzf with External Tools (Optional)
You can pipe output from other tools for more control:
bashfind . -type f | fzf # File list from find rg --files | fzf # File list from ripgrep fd --type f | fzf # File list from fd ls | fzf # File list from ls history | fzf # Command history from shell
ripgrep (rg) - Fast Content Search
What it does:
- Searches file CONTENTS using regex patterns
- Can also list files (like
find) - Respects
.gitignoreby default - Extremely fast (written in Rust)
What it does NOT do:
- Provide fuzzy finding interface
- Interactive selection
- Remember directories you visit
ripgrep is data source, not UI.
bash# Search file contents rg "TODO" ~/Code # List files (similar to find) rg --files # Combined with fzf for interactive searching rg --files | fzf
zoxide - Smart Directory Jumper
What it does:
- Tracks directories you
cdinto - Ranks them by frecency (frequency + recency)
- Provides smart jumping to frequently-visited directories
What it does NOT do:
- Provide fuzzy finding interface
- Search file contents
- Generate file lists
zoxide is database + ranking algorithm, not UI.
bash# Jump to directory (zoxide provides the list) z maze # Jumps to ~/Code/.../midimaze # Interactive selection (zoxide + fzf) zi # Opens fzf with zoxide's directory list
How They Compose Together
Pattern 1: fzf + ripgrep
Interactive file content search:
bash# Search contents, select file to open rg --line-number . | fzf | cut -d: -f1 | xargs vim # Breakdown: # 1. rg --line-number . β Search all files, show line numbers # 2. | fzf β Fuzzy find through results # 3. | cut -d: -f1 β Extract filename only # 4. | xargs vim β Open in vim
Interactive ripgrep reload pattern:
bash# Live search: Every keystroke re-runs ripgrep 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' \ --ansi --disabled
In this pattern:
- ripgrep generates search results
- fzf presents them interactively
- Every change in fzf query β ripgrep re-runs with new pattern
Full-featured interactive code search (rfv function):
bash# ripgrep -> fzf -> nvim with quickfix support rfv() ( RELOAD='reload:rg --column --color=always --smart-case {q} || :' OPENER='if <span class="wikilink-broken" title="Page not found: $FZF_SELECT_COUNT -eq 0 "> $FZF_SELECT_COUNT -eq 0 </span>; then nvim {1} +{2} # No selection. Open the current line in Neovim. else nvim +cw -q {+f} # Build quickfix list for the selected items. fi' fzf --disabled --ansi --multi \ --bind "start:$RELOAD" --bind "change:$RELOAD" \ --bind "enter:become:$OPENER" \ --bind "ctrl-o:execute:$OPENER" \ --bind 'alt-a:select-all,alt-d:deselect-all,ctrl-/:toggle-preview' \ --delimiter : \ --preview 'bat --style=full --color=always --highlight-line {2} {1}' \ --preview-window '~4,+{2}+4/3,<80(up)' \ --query "$*" ) # Usage: rfv TODO # Search for "TODO" in current directory rfv "function.*main" # Regex search
Features:
- Live ripgrep search (updates as you type)
- Syntax-highlighted preview with
bat - Opens in Neovim at exact line
- Multi-select with TAB (builds Vim quickfix list)
- CTRL-O to open file and return to fzf
- Enter to open file and exit fzf
See fzf ripgrep integration guide for full walkthrough.
Pattern 2: fzf + zoxide
Interactive directory jumping:
bash# zoxide's interactive mode (zi) uses fzf internally zi # Under the hood: # 1. zoxide query --list β Get directory database # 2. | fzf β Fuzzy select directory # 3. cd (selected) β Jump to selection
In Yazi's Z command:
- zoxide provides ranked directory list
- fzf (or similar UI) presents interactive selection
- Yazi changes to selected directory
Pattern 3: fzf + Any Command
fzf doesn't depend on specific tools - it accepts ANY input:
bash# Process selection ps aux | fzf # Git commit selection git log --oneline | fzf # File preview with bat find . -type f | fzf --preview 'bat --color=always {}' # Command history (built-in shell integration) history | fzf
Environment Variable Configuration
fzf Configuration
Default Behavior (No Configuration)
bash# When FZF_DEFAULT_COMMAND is NOT set: fzf # Uses built-in walker (no external tools needed): # --walker file,follow,hidden # --walker-root . # --walker-skip .git,node_modules
fzf does NOT use ripgrep, find, or fd by default - it has its own file traversal built-in!
Overriding with External Tools (Optional)
bash# FZF_DEFAULT_COMMAND: Override built-in walker with external command # (Only for file/directory operations, NOT used by shell integration) export FZF_DEFAULT_COMMAND='rg --files --hidden --follow' # Now when you run bare `fzf`, it uses ripgrep instead: fzf # Same as: rg --files --hidden --follow | fzf # Or use fd: export FZF_DEFAULT_COMMAND='fd --type f --hidden --follow --exclude .git' # Or use find: export FZF_DEFAULT_COMMAND='find . -type f'
Why override?
- External tools might be faster for large directories
- More control over file filtering (e.g., respecting
.gitignorewith ripgrep) - Custom file selection logic
Important: FZF_DEFAULT_COMMAND is what fzf executes to GET input, not what fzf passes its output to.
Yazi Integration
Yazi's z command:
bash# Yazi runs this internally: find . -type f | fzf # Or uses fd/ripgrep if FZF_DEFAULT_COMMAND is set
Yazi's Z command:
bash# Yazi runs this internally: zoxide query --list | fzf # Then: cd (selected directory)
Dependency Chart
Independent Tools (No Dependencies):
βββ fzf β Interactive fuzzy finder (UI layer)
βββ ripgrep β Content search + file listing (data source)
βββ zoxide β Directory database (data source)
Composition Examples:
βββ ripgrep β fzf (search results β interactive selection)
βββ zoxide β fzf (directory list β interactive selection)
βββ find β fzf (file list β interactive selection)
βββ ANY β fzf (text input β interactive selection)
None of these tools "use" each other in their code - they compose via stdin/stdout.
Real-World Example: Yazi's z vs Z
z Command Architecture
bash# What Yazi does when you press 'z': if [ -n "$FZF_DEFAULT_COMMAND" ]; then eval "$FZF_DEFAULT_COMMAND" | fzf else find . -type f -o -type d | fzf fi # If result is directory β cd into it # If result is file β reveal it (cd to parent, highlight file)
Tools used: fzf + (find OR ripgrep OR fd - depending on config)
Z Command Architecture
bash# What Yazi does when you press 'Z': zoxide query --list | fzf-or-similar-ui # Then: cd (selected directory)
Tools used: zoxide + fzf (or built-in selector)
Common Misconceptions
β "fzf uses ripgrep"
Reality: fzf accepts ripgrep's output, but doesn't depend on it. You can pipe ANY text to fzf.
β "ripgrep needs fzf"
Reality: ripgrep is a standalone search tool. fzf just makes selection interactive.
β "zoxide is part of fzf"
Reality: Both are independent. zi (zoxide interactive) happens to use fzf for selection.
β "These tools compose together"
Reality: They communicate via stdin/stdout following Unix philosophy.
Configuration Examples
Using ripgrep as fzf's file source
bash# .zshrc or .bashrc export FZF_DEFAULT_COMMAND='rg --files --hidden --follow --glob "!.git/*"' # Now bare `fzf` uses ripgrep to generate file list fzf
Using fd instead
bashexport FZF_DEFAULT_COMMAND='fd --type f --hidden --follow --exclude .git'
CTRL-T in shell (uses different variable)
bash# For shell integration's CTRL-T export FZF_CTRL_T_COMMAND='rg --files --hidden' # Or use --walker options (fzf built-in) export FZF_CTRL_T_OPTS='--walker file,dir,follow,hidden --walker-skip .git,node_modules'
Interactive ripgrep (live search)
bash# Create alias for interactive content search alias irg='fzf --bind "start:reload:rg --column --line-number --no-heading --color=always --smart-case {q}" \ --bind "change:reload:rg --column --line-number --no-heading --color=always --smart-case {q} || true" \ --ansi --disabled \ --delimiter : \ --preview "bat --color=always {1} --highlight-line {2}"'
The Power of Composition
This architectural pattern is why Unix tools are so powerful:
- Modularity: Each tool has one job
- Flexibility: Compose in infinite combinations
- Interchangeability: Swap tools without breaking workflow
bash# Same fzf interface, different data sources: find . -type f | fzf # File list from find rg --files | fzf # File list from ripgrep fd --type f | fzf # File list from fd git ls-files | fzf # File list from git zoxide query --list | fzf # Directory list from zoxide
The UI (fzf) stays the same - only the data source changes!
Summary Table
| Tool | Role | Independence | Typical Usage |
|---|---|---|---|
| fzf | Interactive UI | Standalone fuzzy finder | ANYTHING | fzf |
| ripgrep | Search engine | Standalone content search | rg "pattern" | fzf |
| zoxide | Directory tracker | Standalone smart cd | zoxide query --list | fzf |
None depend on each other - they compose via stdin/stdout.
Related Articles
- Yazi Navigation with fzf and zoxide - How Yazi integrates these tools
- Unix Philosophy - Do one thing well, compose tools
- ripgrep - Fast content search
- fzf - Interactive fuzzy finder
Links
Official Documentation
- fzf: https://github.com/junegunn/fzf
- ripgrep: https://github.com/BurntSushi/ripgrep
- zoxide: https://github.com/ajeetdsouza/zoxide
- Summary: Independent tools that compose beautifully via Unix philosophy
Interactive ripgrep Examples
- URL: https://github.com/junegunn/fzf/blob/master/ADVANCED.md#using-fzf-as-interactive-ripgrep-launcher
- Summary: Advanced patterns for live content search with fzf + ripgrep