How the fzf `--bind` Action System Works for Custom Key Bindings
The fzf --bind flag maps keys to actions through a three-stage pipeline that parses the command line, extracts key chords, and decodes action lists into executable handlers.
The junegunn/fzf repository implements a sophisticated event-driven architecture that transforms raw strings like ctrl-r:reload:git status into runtime behavior. Understanding this --bind action system reveals how fzf converts user input into concrete tui.Event mappings and executes action chains.
The Three-Stage Binding Pipeline
When you pass --bind "key:action", fzf processes the string through three distinct phases before the interactive session begins.
Stage 1: Option Parsing with parseKeymap
The entry point resides in src/options.go. When the command-line parser encounters --bind, it delegates the raw binding string to parseKeymap (lines 1972–1990). This function initializes the parsing process and prepares the string for structural analysis.
Stage 2: Key Extraction and Masking
Before splitting the string on delimiters, fzf must protect action arguments that contain commas, colons, or plus signs. The maskActionContents function (lines 1630–1660 in src/options.go) implements this protection by:
- Scanning the bind string for action content (text following colons)
- Replacing
,,:, and+inside actions with sentinel runes (escapedColon,escapedComma,escapedPlus) - Preserving these characters in the key name portion for accurate splitting
This masking ensures that a binding like ctrl-r:reload:git log,ctrl-y:accept splits correctly into two separate bindings rather than breaking on the comma inside git log.
Stage 3: Action Decoding
With the string masked, parseActionList (lines 1695–1780 in src/options.go) executes the final transformation:
- Key chord resolution:
parseKeyChords(insrc/keychord.go) converts symbolic names likectrl-x,alt-Shift-A, orf5into concretetui.Eventstructures. - Action tokenization: The right-hand side splits on
+to support multi-action chains (e.g.,reload+accept). - Action type mapping: A comprehensive switch statement maps textual tokens (
accept,reload,toggle-preview,unbind) to constants defined insrc/actiontype_string.go(e.g.,actAccept,actReload). - Command-action handling:
isExecuteAction(lines 1820–1890) identifies actions requiring arguments (reload:,execute:,preview:) and stores the post-colon suffix in the action's argument field.
Data Structures and Runtime Execution
The parsed bindings populate the Options struct defined in src/options.go:
type Options struct {
// ... other fields ...
Keymap map[tui.Event][]*action
}
This map associates each tui.Event (representing a physical key press or mouse event) with a slice of *action pointers. When fzf initializes, src/terminal.go establishes the defaultKeymap and merges user-defined bindings using last-one-wins semantics—later --bind arguments override earlier definitions for the same key.
During the interactive session, the terminal's event loop in src/terminal.go performs the following sequence:
- Captures raw input and translates it into a
tui.Event - Looks up the event in
opts.Keymap - If found, iterates over the
[]*actionslice and invokes each action's handler (e.g.,actAccept,actReload,actTogglePreview) - Executes handlers immediately for synchronous actions or delegates to goroutines for asynchronous operations like
reloadorexecute
Special Action Types and Meta-Actions
The --bind system supports several categories of actions that modify runtime behavior beyond simple selection:
Meta-Actions (Keymap Modification)
unbind: Removes a binding from the active keymap (implemented asactUnbindinsrc/terminal.go)rebind: Restores a previously unbound keytoggle-bind: Switches a binding on or off dynamically
These actions mutate opts.Keymap during execution, allowing interactive reconfiguration without restarting fzf.
Source Regeneration
reloadandreload-sync: Execute a shell command (stored inaction.a) to regenerate the candidate list.reloadruns asynchronously whilereload-syncblocks the UI until completion. Handlers reside insrc/terminal.gowith command execution logic insrc/reload.go.
Preview and Transformation
preview,change-preview,transform, andbg-transform: These actions trigger preview rendering or modify preview content. The presence of any preview-related action forcesmayTriggerPreviewto initialize a preview event box even when--previewwas not explicitly provided on the command line. Implementation details spansrc/preview.goandsrc/terminal.go.
Practical Examples of fzf Custom Key Bindings
The following examples demonstrate real-world usage patterns, each processed by the parseKeymap pipeline described above:
# Bind Ctrl-P to accept the current selection (overrides default behavior)
fzf --bind "ctrl-p:accept"
# Reload git status on Enter, useful for dynamic lists
fzf --bind "enter:reload:git status"
# Combine preview toggling with list reloading
fzf \
--preview 'bat --style=numbers {}' \
--bind "space:toggle-preview" \
--bind "ctrl-r:reload"
# Chain multiple actions: reload then accept first result
fzf --bind "f5:reload:git log|accept"
# Runtime keymap modification: toggle Ctrl-L binding for clearing query
fzf --bind "f2:toggle-bind+ctrl-l:clear-query"
Each binding string undergoes masking, splitting, and action resolution before the interactive session begins. Multiple --bind flags are concatenated and processed sequentially, with later bindings overriding earlier ones for the same key chord.
Summary
- Three-stage parsing:
parseKeymapinitiates processing,maskActionContentsprotects action arguments from delimiter splitting, andparseActionListresolves keys totui.Eventobjects and action tokens toactionTypeconstants. - Core data structure: The
Options.Keymap(defined insrc/options.go) stores amap[tui.Event][]*actionthat links physical input events to executable action chains. - Runtime execution: The terminal event loop in
src/terminal.godispatches actions by looking up events inopts.Keymapand invoking handlers likeactAccept,actReload, oractTogglePreview. - Meta-actions: Special actions including
unbind,rebind, andtoggle-bindmutate the keymap at runtime, whilereloadand preview actions integrate with external commands viasrc/reload.goandsrc/preview.go.
Frequently Asked Questions
How does fzf handle commas and colons inside action arguments?
fzf uses a masking mechanism in maskActionContents (located in src/options.go) to protect delimiters within action arguments. Before splitting the bind string, the parser replaces commas, colons, and plus signs inside action content (such as reload:git log --oneline) with sentinel runes (escapedComma, escapedColon, escapedPlus). This ensures that structural parsing occurs only on delimiters outside action arguments, preserving complex shell commands intact.
What is the difference between reload and reload-sync actions?
Both actions trigger source list regeneration, but they differ in execution concurrency. The reload action (mapped to actReload) executes the specified command asynchronously, allowing the UI to remain responsive while the command runs. In contrast, reload-sync (mapped to actReloadSync) blocks the terminal event loop until the command completes, preventing user interaction during execution. The implementation resides in src/terminal.go with command execution logic in src/reload.go.
Can I modify key bindings while fzf is running?
Yes, fzf supports runtime keymap modification through meta-actions. The unbind action removes a key binding from the active opts.Keymap, while rebind restores previously removed bindings. The toggle-bind action switches a binding on or off dynamically. These actions (defined as actUnbind, actRebind, and actToggleBind in src/actiontype_string.go) mutate the keymap during execution in src/terminal.go, allowing interactive reconfiguration without restarting the fzf process.
Why do later --bind arguments override earlier ones?
fzf implements a "last-one-wins" semantics for duplicate key bindings to allow incremental configuration. When parseKeymap processes the concatenated bind strings (from multiple --bind flags), it inserts entries directly into opts.Keymap. Because Go maps overwrite existing keys, subsequent bindings for the same tui.Event replace earlier definitions. This behavior enables users to define default bindings in shell aliases or environment variables while overriding specific keys via command-line arguments.
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 →