fzf — Structure#

Layout pattern#

Custom / Flat-with-subpackages

fzf departs from the standard Go layout (cmd/, internal/, pkg/). The single binary’s main.go lives at the repo root, and all core logic is placed in a flat src/ package (package fzf). Sub-concerns (algo, tui, util, protector) get their own sub-packages under src/, but there is no cmd/ directory, no internal/, and no pkg/. This is a deliberately minimal, tool-oriented layout that prioritises simplicity over Go convention.

Directory map#

fzf/
├── main.go               # Single entry point; wires options → fzf.Run(), embeds shell scripts
├── go.mod / go.sum       # Module definition
├── Makefile              # Build, test, release, lint targets
├── Dockerfile            # Integration-test container (Ruby test runner + Go build)
├── install / install.ps1 # Shell/PowerShell install scripts (not Go)
│
├── src/                  # Core package (package fzf) — all business logic
│   ├── algo/             # Fuzzy matching algorithm (Smith-Waterman-like, SIMD variants)
│   ├── tui/              # Terminal UI abstraction (light renderer + tcell backend)
│   ├── util/             # Low-level utilities (eventbox, slab, atomics, chars)
│   └── protector/        # OS-specific process protector (OpenBSD pledge(2))
│
├── shell/                # Shell integration scripts (bash/zsh/fish key-bindings & completion)
├── bin/                  # Compiled binary destination + helper scripts (fzf-tmux, fzf-preview.sh)
├── plugin/               # Vim/Neovim plugin (fzf.vim — single Vimscript file)
├── man/man1/             # Man pages (fzf.1, fzf-tmux.1)
├── doc/                  # Extra documentation (CHANGELOG mirror, screenshots)
├── test/                 # Integration tests (Ruby minitest runner + lib helpers + vim tests)
└── .github/workflows/    # CI: linux.yml, macos.yml, winget.yml, codeql, depsreview, typos

Entry points#

FileBinaryWhat it does
main.gofzfParses CLI options, prints shell integration scripts on request (--bash/--zsh/--fish/--man), then calls fzf.Run(options) for normal operation

There is exactly one binary. fzf-tmux (in bin/) is a shell script wrapper, not a Go binary.

Package organization#

  • github.com/junegunn/fzf/src (package fzf) — The monolithic core. Contains the reader (stdin/file/command), matcher (pattern + chunk-based parallel search), merger (result aggregation), terminal (interactive TUI event loop), server (HTTP event-action API), options parser, and all supporting types (Item, Result, Chunk, etc.). No internal/ or public/private split — everything in src/ is a single package exported at the module level.

  • github.com/junegunn/fzf/src/algo — Fuzzy matching engine. Implements Smith-Waterman-inspired dynamic programming with bonus scoring for camelCase/path boundaries. Has assembly-backed SIMD helpers (indexbyte2_amd64.s, indexbyte2_arm64.s) and platform fallbacks (indexbyte2_other.go).

  • github.com/junegunn/fzf/src/tui — Terminal UI abstraction layer. Defines the Renderer interface and ships two implementations: LightRenderer (direct termios/ANSI; default) and TcellRenderer (tcell-backed; selected with -tags tcell). Platform splits: light_unix.go / light_windows.go, ttyname_unix.go / ttyname_windows.go.

  • github.com/junegunn/fzf/src/util — Shared low-level utilities: Chars (rune/byte string wrapper), Slab (memory pool for algo), EventBox (thread-safe multi-event notification bus), AtomicBool, atexit registration, and platform-split I/O helpers.

  • github.com/junegunn/fzf/src/protector — Minimal OS security hardening. On OpenBSD, calls pledge(2) to restrict syscalls; on other platforms is a no-op (protector.go).

  • Layering: The dependency direction is: main.gosrcsrc/tui, src/algo, src/util; src/protector is used only by main.go. There is no circular dependency. The src package is the heaviest layer and owns all cross-cutting state. Sub-packages are kept deliberately thin and algorithm/infrastructure-focused.

Build system#

  • Build tool: make (primary), goreleaser (release distribution)
  • Key targets:
    • make all / make target/<BINARY>go build for the host architecture, producing target/fzf-<os>_<arch>
    • make testgo test across all four Go packages
    • make itest — Ruby integration test runner (test/runner.rb)
    • make bench — Go benchmark suite in src/
    • make buildgoreleaser build --snapshot (multi-arch local build)
    • make release — Full goreleaser release to GitHub (requires GITHUB_TOKEN)
    • make lintgofmt, rubocop, shell script linting
    • make install — Copies built binary to bin/fzf
    • make docker / make docker-test — Build and run the integration test container
  • Cross-compilation: Makefile defines targets for 10 architectures (386, amd64, arm5–8, s390x, ppc64le, riscv64, loong64); goreleaser manages the full release matrix.
  • Version injection: -ldflags "-X main.version=... -X main.revision=..." bakes git tag and commit hash into the binary.
  • Docker: Yes — single-stage Dockerfile based on rubylang/ruby:3.4.1-noble, installs Go and runs the Ruby integration test suite. Not a production image; purely for CI-equivalent local testing.

Notable structural decisions#

  1. Root-level main.go instead of cmd/: By placing main.go at the root, fzf keeps the project feeling like a single-purpose tool rather than a multi-binary application. This also means go install github.com/junegunn/fzf@latest works without a subpath — a significant usability win for end-users.

  2. Single monolithic src/ package: All interactive session logic — reader, matcher, terminal, server, options — lives in one package. This avoids unnecessary abstraction churn and makes the codebase easier to navigate, at the cost of package-level encapsulation. For a project of this size (~57 non-test source files) it is an appropriate trade-off.

  3. Dual TUI backend via build tags: The tui/ package ships a LightRenderer (default, minimal deps) and a TcellRenderer (selected via -tags tcell). This lets the default binary stay smaller and faster while still supporting the full tcell feature set for users who need it.

  4. Shell scripts embedded via //go:embed: All shell integrations (key-bindings and completions for bash/zsh/fish) and the man page are embedded into the binary at compile time. Users get fzf --bash, fzf --zsh, fzf --fish sub-commands with no external file dependencies — a clean zero-install-step design.

  5. Ruby integration tests alongside Go unit tests: The test/ directory holds a Ruby minitest suite that drives a real terminal through tmux. This is an intentional choice to test the full interactive UX (keystrokes, rendering, selection) in ways that Go’s testing package cannot easily do. The Dockerfile makes this reproducible.

  6. Platform splits without CGo: Cross-platform terminal handling is achieved entirely through Go build constraints (_unix.go / _windows.go file suffixes and //go:build tags), keeping the binary CGo-free and trivially cross-compilable.