Open-Source Wikis

/

fzf

/

Systems

/

Process modes

junegunn/fzf

Process modes

Purpose

fzf can run as a plain in-process TUI, or it can re-execute itself inside a wrapper that owns the terminal. The wrappers exist so a single command-line invocation can pop fzf inside a tmux popup, a Zellij floating pane, or a Windows winpty session, without the calling shell having to know about it.

Files

File Purpose
src/proxy.go The shared "spawn a wrapper, pipe stdin/stdout through fifos, handle ExitBecome" logic.
src/proxy_unix.go POSIX-specific helpers (named-pipe creation).
src/proxy_windows.go Windows equivalents.
src/tmux.go runTmux: builds the tmux display-popup command line and calls runProxy.
src/zellij.go runZellij: same idea for Zellij.
src/winpty.go runWinpty: re-execs fzf under winpty on Windows when ConPTY is unavailable.
src/winpty_windows.go Windows-specific implementation.
bin/fzf-tmux Older shell-script wrapper that predates the in-binary --tmux. Still shipped for users on tmux < 3.3.

Key abstractions

Symbol File Description
runProxy(commandPrefix, cmdBuilder, opts, withExports) src/proxy.go The shared entry point. Creates fifos, spawns the wrapper, copies bytes, propagates ExitBecome.
popupArgStr(args, opts) src/proxy.go Builds the inner fzf command line by quoting os.Args, dropping options that don't make sense inside a popup, and adding --no-popup --no-height.
fifo(name) src/proxy.go Allocates a named pipe under os.TempDir().
runTmux(args, opts) src/tmux.go Wraps runProxy with tmux display-popup arguments.
runZellij(args, opts) src/zellij.go Wraps runProxy with Zellij's floating pane invocation.
runWinpty(args, opts) src/winpty.go Wraps runProxy with winpty on Windows.
becomeSuffix = ".become" src/proxy.go Filename suffix for the file the inner fzf writes when become(...) fires.

How it works

graph TD
    OUTER[Outer fzf process<br/>main.go] --> CHECK{popup or<br/>winpty?}
    CHECK -- yes --> PROXY[runProxy<br/>src/proxy.go]
    CHECK -- no --> RUN[fzf.Run<br/>direct TUI]
    PROXY --> FIFO[Create input/output fifos]
    PROXY --> WRAPPER[Spawn wrapper:<br/>tmux / zellij / winpty]
    WRAPPER --> INNER[Inner fzf process]
    INNER --> WRITES[Writes selection to output fifo]
    PROXY --> READS[Outer reads output fifo,<br/>writes to user stdout]
    WRITES --> READS
    INNER -- ExitBecome --> BECOME[Outer reads .become file,<br/>execs target program]

Step-by-step

  1. The outer fzf process parses options. If --tmux (or --popup for Zellij, or needWinpty) is set, it calls runTmux / runZellij / runWinpty before doing anything else.
  2. popupArgStr builds the inner fzf command line: it shell-quotes os.Args[0], appends the user's options minus the popup-relevant ones, and adds --no-popup --no-height.
  3. runProxy creates two named pipes (fzf-proxy-input-<ns> and fzf-proxy-output-<ns>) under $TMPDIR. If the input is a TTY, only the output pipe is needed; otherwise the outer process spools its stdin into the input pipe.
  4. runProxy writes a small shell snippet to a temp file (exports + command) and asks the wrapper to run it. The exports preserve environment variables, including bash-exported functions (BASH_FUNC_*).
  5. The wrapper (e.g. tmux display-popup -E -d <dir> ... sh /tmp/...) launches the inner fzf inside the popup. That inner process is a normal fzf.Run call; it doesn't know it's nested.
  6. The inner fzf writes its selection to the output pipe. The outer process copies the pipe to its own stdout (or to opts.Output if set programmatically).
  7. become actions: if the inner fzf wants to fall through to a target command (fzf --bind 'enter:become(vim {})'), it writes the command + environment to <temp>.become and exits with ExitBecome. The outer process detects this exit code, reads the file, and re-execs the target command using util.Executor.Become (which calls syscall.Exec on POSIX, replacing the outer process). This is how become can leave the user inside vim after fzf disappears, even from inside a tmux popup.

Tmux popup specifics

src/tmux.go translates --tmux options into tmux display-popup flags:

  • Position: up-xC -y0, down-xC -y9999, left-x0 -yC, right-xR -yC, center-xC -yC.
  • Width / height come from opts.Tmux.width / .height.
  • Border: -B is added unless opts.Tmux.border is true.

This requires tmux 3.3+ for the -E flag. Users on older tmux can use bin/fzf-tmux (a shell script) which uses a regular split window.

Zellij popup

Zellij gained equivalent functionality more recently. src/zellij.go follows the same runProxy pattern, building zellij run --floating ... invocations.

Winpty

On Windows without ConPTY, terminals can't allocate a real PTY. runWinpty re-execs fzf under winpty.exe (which the user must have installed) so the inner process gets a working PTY. The detection logic is in needWinpty(opts) in src/winpty_windows.go.

Integration points

  • From core.go: the very first thing Run does (when opts.Filter == nil) is consult opts.useTmux(), opts.useZellij(), and needWinpty(opts). If any returns true, Run delegates to the corresponding wrapper and returns its exit code.
  • From the inner process: the inner fzf doesn't need to know it's nested. Special case: --no-popup and --no-height are added to the inner command line so the user's own popup/height options don't accidentally compose.
  • util.Executor.Become: shared with the regular (non-popup) become path. The proxy reads the <temp>.become file and forwards env + command to the executor.

Entry points for modification

  • New popup backend: add a runX function and an opts.useX() predicate. Reuse runProxy and provide a cmdBuilder that returns the right *exec.Cmd.
  • Tmux options: if you add to opts.Tmux, extend popupArgStr to optionally honor it inside the popup, and runTmux to forward it to display-popup.
  • Become file format: the format is "command \x00 env\n env\n ..."; if you change it, both runProxy and the inner-process become writer must agree.
  • Windows path: proxy_unix.go and proxy_windows.go are siblings; keep them in sync. mintty has a quirk with reading from named pipes that's worked around in runProxy (it falls back to cat <pipe> | inner).

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

Process modes – fzf wiki | Factory