junegunn/fzf
Architecture
fzf is a single Go process that pipes items from a Reader, through a Matcher, to a Terminal, with options parsed up front and an optional HTTP server for remote control. Everything coordinates through an in-process event bus (util.EventBox).
High-level component map
graph LR
CLI[main.go] -->|ParseOptions| OPT[Options<br/>src/options.go]
OPT -->|Run| CORE[Run loop<br/>src/core.go]
CORE --> READER[Reader<br/>src/reader.go]
CORE --> MATCHER[Matcher<br/>src/matcher.go]
CORE --> TERM[Terminal<br/>src/terminal.go]
READER -->|push items| CHUNKS[ChunkList<br/>src/chunklist.go]
MATCHER -->|read chunks| CHUNKS
MATCHER -->|MatchResult| TERM
TERM -->|key/mouse| ACTIONS[Action engine<br/>src/terminal.go]
ACTIONS -->|reload/transform| READER
ACTIONS -->|change-* / search| MATCHER
SERVER[HTTP server<br/>src/server.go] -->|actions| TERM
TERM --> RENDERER[Renderer<br/>src/tui/light.go<br/>src/tui/tcell.go]Three goroutines drive the steady state: the reader (pushing items into the chunk list), the matcher loop (scanning chunks against the current pattern), and the terminal loop (reading input and repainting). They synchronize through util.EventBox, a condition-variable-backed map of pending events.
Event flow
The header comment at the top of src/core.go summarizes the wiring:
Reader -> EvtReadFin
Reader -> EvtReadNew -> Matcher (restart)
Terminal -> EvtSearchNew:bool -> Matcher (restart)
Matcher -> EvtSearchProgress -> Terminal (update info)
Matcher -> EvtSearchFin -> Terminal (update list)sequenceDiagram
participant R as Reader
participant M as Matcher
participant T as Terminal
R->>M: EvtReadNew (new items pushed)
T->>M: EvtSearchNew (query changed)
M-->>T: EvtSearchProgress (% scanned)
M-->>T: EvtSearchFin (sorted Merger)
R->>M: EvtReadFin (input exhausted)
T->>R: terminate / restart on reloadThe coordinator inside Run (in src/core.go) drains the event box on every tick, applies the appropriate response (snapshot the chunk list, reset the matcher, repaint the terminal, kick off a reload command), and sleeps for an adaptive delay while reading.
Data path
graph LR
SRC[stdin / FZF_DEFAULT_COMMAND<br/>walker / channel] --> READ[Reader.feed<br/>src/reader.go]
READ --> CHUNK[ChunkList<br/>src/chunklist.go]
CHUNK --> SNAP[Snapshot]
SNAP --> SCAN[Matcher.scan<br/>partitioned by NumCPU]
SCAN --> ALGO[algo.FuzzyMatchV2<br/>src/algo/algo.go]
ALGO --> RESULT[Result + score<br/>src/result.go]
RESULT --> MERGE[Merger<br/>src/merger.go]
MERGE --> RENDER[Terminal.printList]Items are appended into fixed-size Chunk arrays (chunkSize items each) by the Reader. The Matcher partitions a snapshot of those chunks across runtime.NumCPU() (or --threads N) goroutines, runs the fuzzy / exact / prefix / suffix algorithms in src/algo/algo.go, and feeds a Merger that yields results in score order with tiebreakers controlled by --tiebreak.
Process forms
fzf can launch in a few different ways before any of the above happens:
| Mode | Trigger | Implementation |
|---|---|---|
| In-process TUI | default | Run in src/core.go directly drives the terminal. |
--tmux popup |
opts.useTmux() |
runTmux in src/tmux.go re-execs fzf inside a tmux display-popup. |
--popup (Zellij) |
opts.useZellij() |
runZellij in src/zellij.go re-execs fzf inside a Zellij floating pane. |
| Windows / mintty | needWinpty |
runWinpty in src/winpty_windows.go re-execs under winpty. |
| Filter mode | --filter |
Run short-circuits to a non-interactive scan + print path. |
--listen |
option | startHttpServer in src/server.go accepts JSON action requests over HTTP / Unix socket. |
The popup and winpty paths share runProxy in src/proxy.go, which wires named pipes between the outer process and the inner fzf binary so input/output keep flowing.
Renderer abstraction
src/tui/tui.go defines the Renderer interface. Two backends implement it:
src/tui/light.go— the default "light" renderer, which writes ANSI escape sequences directly to the TTY usinggolang.org/x/term. Optimized for size and startup time.src/tui/tcell.go— built only when thetcellbuild tag is set; usesgithub.com/gdamore/tcell/v2for richer terminal compatibility (notably needed for Windows console support before Windows Terminal).
The tcell backend lives behind a build tag so the default binary stays small. See systems/terminal-ui.
Options pipeline
Command-line parsing happens in src/options.go (4000+ lines). It's a hand-written parser rather than a flag library because fzf supports many composite flags (e.g. --bind=key:action+action+...), positional args, and lazy-evaluated transforms (e.g. transform-query) that need access to runtime state. ParseOptions in src/options.go returns an Options struct that the rest of the code reads.
Shell and editor integrations
The repository also ships first-class integrations that are not compiled into the Go core:
shell/key-bindings.{bash,zsh,fish}—Ctrl-T/Ctrl-R/Alt-Cbindings.shell/completion.{bash,zsh,fish}— fuzzy completion (**<TAB>).plugin/fzf.vim—:FZFcommand andfzf#runAPI for Vim/Neovim.bin/fzf-tmux— wrapper script for using fzf in a tmux split.bin/fzf-preview.sh— opinionated preview script handling images, archives, code, etc.
These scripts are embedded into the binary at build time via //go:embed in main.go, so fzf --bash, fzf --zsh, and fzf --fish print the corresponding integration scripts directly from the binary.
Where to read next
- systems/core-coordinator — the
Runloop in detail. - systems/matching — the
algopackage and pattern compiler. - systems/terminal-ui — the renderer and TUI layout.
- systems/options — option parsing and the action language.
- systems/http-server — the
--listenAPI.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.