How fzf Preview Window Rendering Works Across Different Terminal Emulators

fzf renders its preview window by creating a dedicated pane, executing the preview command, and drawing the output with ANSI-aware text wrapping, while transparently forwarding terminal-specific graphics escape sequences (Kitty, iTerm2, Sixel, tmux) to the underlying emulator.

The junegunn/fzf command-line fuzzy finder uses a sophisticated three-stage pipeline to render preview content in any terminal emulator. Whether displaying plain text or rich graphics, fzf adapts to the capabilities of your terminal while maintaining its own UI layout integrity.

How fzf Creates and Sizes the Preview Window

When fzf initializes its interface, it first determines whether a preview pane is required by checking the needPreviewWindow condition. If enabled, the resizePreviewWindows closure in src/terminal.go (lines 2318‑2376) calculates the dimensions based on the --preview-window option stored in the previewOpts struct.

The sizing logic respects minimum height constraints through minPreviewSize() (lines 1962‑1974), which enforces a minHeight of 3 lines while accounting for border styles. The function creates two distinct windows: pborder for the preview border (rendered with ColPreviewBorder) and pwindow for the content area (rendered with ColPreview).

The Preview Rendering Pipeline

When the preview command produces output, the util.Executor streams stdout line-by-line into the previewer.lines buffer. During the UI redraw cycle, printPreview() determines whether the content has changed by comparing the previewer.version and previewer.offset against the previously rendered state (t.previewed).

If regeneration is necessary, renderPreviewArea() invokes renderPreviewText() (lines 80‑158) to handle the actual drawing. This function manages:

  • ANSI-aware text wrapping via wordWrapAnsiLine() (lines 11‑76), which respects color codes while breaking lines at width boundaries
  • Continuation indicators using the --preview-wrap-sign option (t.previewWrapSign) to mark wrapped lines
  • Line height calculation through previewLineHeight() (lines 63‑99) to determine how many terminal rows a given content line occupies

Terminal-Specific Graphics Passthrough

fzf detects and forwards terminal passthrough escape sequences without interpretation, allowing the underlying emulator to render images directly. The detection regexes initialize in init() (lines 76‑90):

passThroughBeginRegex = regexp.MustCompile(`\x1bPtmux;\x1b\x1b|\x1b(_G|P[0-9;]*q)|\x1b]1337;`)
passThroughEndTmuxRegex = regexp.MustCompile(`[^\x1b]\x1b\\`)

During rendering, extractPassThroughs() (lines 17‑34) isolates these sequences, and t.tui.PassThrough() forwards them to the terminal.

Supported Protocols

Protocol Detection Handling
Kitty graphics \x1b_G…\x1b\\ Forwarded directly; terminal handles placement
iTerm2 images \x1b]1337;…\x07 or \x1b]1337;…\x1b\\ Detected via isItermImage; assumes full preview height
Sixel \x1bP…\x1b\\ Detected via isSixel; calculates requiredLines using t.termSize.PxHeight and terminal pixel dimensions
tmux passthrough \x1bPtmux;\x1b\x1b…\x1b\\ Outer wrapper stripped, inner sequence forwarded

When images exceed available space, renderPreviewText() draws a wireframe placeholder using t.makeImageBorder and sets wireframe = true to indicate the content cannot fit.

Scrolling and Follow Mode

The preview pane supports scrolling through renderPreviewScrollbar() (lines 82‑115), which draws indicators in the pborder window. When using --follow with --preview, the followOffset() function (lines 36‑61) calculates the correct line offset by accounting for wrapped line heights via previewLineHeight().

Practical Code Examples

Basic Text Preview

fd -H --type f | fzf --preview 'head -n 20 {}'

The head command writes plain text to stdout. fzf stores this in previewer.lines and renders it through renderPreviewText() with standard ANSI-aware wrapping.

Kitty Graphics Protocol

fd -H --type f | fzf \
    --preview 'kitty +kitten icat --transfer-mode file {}' \
    --preview-window right,70%,border-left

The icat kitten emits Kitty graphics escape sequences (\x1b_G…\x1b\\). fzf extracts these via extractPassThroughs() and forwards them through t.tui.PassThrough(), allowing the terminal to render the image directly in the preview pane.

Sixel Image Preview

fd -H --type f | fzf \
    --preview 'catimg -w 80 {}' \
    --preview-window up,40%

The catimg utility outputs Sixel data (\x1bP…\x1b\\). fzf detects this via isSixel and calculates the required visual height using t.termSize.PxHeight. If the terminal reports pixel dimensions, fzf computes exact line requirements; otherwise, it assumes full-height rendering or draws a wireframe placeholder if space is insufficient.

tmux Passthrough Mode

fzf --preview 'git diff {}' \
    --preview-window right,30% \
    --tmux

When running inside a tmux popup, graphic escape sequences are wrapped in tmux's passthrough format (\x1bPtmux;\x1b\x1b…\x1b\\). fzf's passThroughBeginRegex detects this wrapper, extractPassThroughs() strips the outer layer, and the inner sequence is forwarded to the underlying terminal emulator.

Summary

  • Window Creation: fzf builds the preview pane through resizePreviewWindows() in src/terminal.go, creating separate windows for borders (pborder) and content (pwindow) based on previewOpts from --preview-window.

  • Text Rendering: The renderPreviewText() function handles ANSI-aware wrapping via wordWrapAnsiLine(), calculates line heights with previewLineHeight(), and manages continuation indicators through --preview-wrap-sign.

  • Graphics Passthrough: fzf detects Kitty, iTerm2, Sixel, and tmux escape sequences using passThroughBeginRegex and extractPassThroughs(), forwarding them unchanged via t.tui.PassThrough() while calculating visual height requirements for Sixel and iTerm2 images.

  • Scrolling: Scrollbar rendering occurs in renderPreviewScrollbar(), with follow-mode support through followOffset() that accounts for wrapped line heights.

Frequently Asked Questions

How does fzf handle image previews in different terminal emulators?

fzf does not render images itself. Instead, it detects graphics escape sequences (Kitty protocol, iTerm2 inline images, or Sixel) in the preview command output and forwards them directly to the terminal via t.tui.PassThrough(). The terminal emulator handles the actual image rendering, while fzf manages the layout space and calculates whether the image fits within the preview window dimensions.

What happens when a Sixel image is too large for the preview window?

When fzf detects Sixel data (\x1bP…\x1b\\) via isSixel, it calculates the required visual height using t.termSize.PxHeight and the terminal's pixel dimensions. If the image exceeds available space, renderPreviewText() draws a wireframe placeholder using t.makeImageBorder instead of the actual image, preventing layout corruption while indicating that image content is present but cannot be displayed.

How does fzf support tmux pop-up windows with image previews?

When running inside tmux, graphic escape sequences are wrapped in tmux's passthrough format (\x1bPtmux;\x1b\x1b…\x1b\\). fzf's passThroughBeginRegex detects this wrapper in init(), and extractPassThroughs() strips the outer layer during rendering. The inner sequence is then forwarded via t.tui.PassThrough(), allowing the underlying terminal emulator to receive the graphics commands even when fzf runs inside a tmux popup.

Can fzf preview window rendering impact the main list pane layout?

Yes. When the preview window is positioned on the right or left, resizePreviewWindows() adjusts the available width for the main list. The code may add margin columns (innerWidth++) and relocate the scrollbar through listStickToRight adjustments to accommodate the preview pane dimensions. If the preview is hidden or the command returns no output, the UI clears the preview area and reclaims the space for the main list.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →