Open-Source Wikis

/

fzf

/

fzf

/

Architecture

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 reload

The 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 using golang.org/x/term. Optimized for size and startup time.
  • src/tui/tcell.go — built only when the tcell build tag is set; uses github.com/gdamore/tcell/v2 for 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-C bindings.
  • shell/completion.{bash,zsh,fish} — fuzzy completion (**<TAB>).
  • plugin/fzf.vim:FZF command and fzf#run API 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.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Architecture – fzf wiki | Factory