Commit Graph
1568 Commits
Author SHA1 Message Date
Junegunn Choi d2a99bb929 Drop unrecognized escape sequences instead of typing them
fzf consumed only the part of a sequence it recognized, and the rest was
typed into the query. CTRL-A sent as \e[97;5u became "97;5u", and CTRL-F
sent as \e[70;5u fired Home before typing ";5u". A BEL-terminated OSC
reply also aborted fzf, because the BEL that followed the typed payload
was read as CTRL-G.

Frame CSI and SS3 by their parameter and final byte ranges, OSC, DCS and
APC by their terminator. Drop the whole frame when the parser does not
recognize it, or recognizes only a prefix of it.

Take the second-chance read only when the sequence at the start of the
buffer is the only one and is unfinished. Otherwise it blocked until the
next key, holding back what followed, CTRL-G included.

Telling a terminal's sequence from an ALT key is the hard part, so these
keep what the parser made of them:

- An unterminated sequence, since that is how ALT-[, ALT-O, ALT-], ALT-P
  and ALT-_ arrive
- A CSI or SS3 of four bytes or fewer. It could be an ALT key and typed
  text, and rxvt sends keys of that size fzf does not know, such as \e[3^
- A final byte after an intermediate $. rxvt ends keys with $, as in
  \e[7$ and \e[23$, so the byte after it is the next key
- A string sequence whose payload does not start like a reply: digits and
  ';' for OSC, '>|', '!|', '[01]$r' or '[01]+r' for DCS, 'G' and a key
  for APC
- SOS and PM, which nothing sends

Within a string, an ESC ends it and introduces a sequence of its own, and
only OSC ends with BEL. The read loop does not wait for a string
terminator: that held ALT-], ALT-P and ALT-_ for ESCDELAY and let typed
bytes pile up until they looked like a sequence.
2026-10-05 09:25:28 +09:00
Junegunn Choi 961793cf39 Fix --gap-line cutting a grapheme cluster
CodeQL / Analyze (go) (push) Canceled after 0s
build / build (push) Canceled after 0s
Test fzf on macOS / build (push) Canceled after 0s
RepeatToFill filled the remaining width rune by rune, so a cluster
could be split at the right edge. Iterate grapheme clusters instead.

Fix #4920
2026-09-13 19:31:36 +09:00
Junegunn Choi b224480a98 Do not erase the line the prompt was on (#4918)
CodeQL / Analyze (go) (push) Canceled after 0s
build / build (push) Canceled after 0s
Test fzf on macOS / build (push) Canceled after 0s
- When the cursor is not at column 0, Init() makes space so fzf draws on
  the next line and the prompt line is left intact
- Close() undid that: it moved up onto the prompt line before ESC[J, so
  the erase wiped a line fzf never wrote on
- Shells repaint afterwards, so it showed as a flicker of the last prompt
  line. Reported on fish, also seen on bash and nushell. zsh erases the
  same line but repaints fast enough to hide it
- Erase our own area first, then step back and restore the column, so
  nothing on that line is touched, RPROMPT included

Fix #4913
2026-09-10 23:19:38 +09:00
Junegunn Choi 1578b7c3c3 Fix spurious exit while waiting for a key with --listen
- getch() charged EINTR against a 100 iteration limit meant for read
  attempts, and the count only reset once a byte was read
- So signals arriving during an idle prompt drained it, and running out
  returned getCharError, which became Event{Fatal} and exit status 2
- Only the cancellable path is affected, hence only --listen
- select() is not restarted by SA_RESTART, unlike the blocking read on
  the non-cancellable path, so it is the only one that sees EINTR
- waitReadable() already retries EINTR without a limit

Fix #4917
2026-09-10 17:56:15 +09:00
Junegunn Choi 7e6339979f Fix stale progress in --listen status payload (#4907)
CodeQL / Analyze (go) (push) Canceled after 0s
build / build (push) Canceled after 0s
Test fzf on macOS / build (push) Canceled after 0s
- progress was set to 100 by every UpdateList, partial results included,
  and never reset when a search started
- So a GET could report a new query, the previous result set, and
  progress 100, and a client had no way to tell it was stale
- Now cleared where the search is requested and set to 100 only on the
  final result. Query and the reset happen in the same locked section
  that dumpStatus locks, so query == X with progress 100 means the
  matches belong to X

Fix #4903
2026-09-06 19:36:40 +09:00
Junegunn Choi 1372d04f79 Fix adaptive height with an inline header or footer border
CodeQL / Analyze (go) (push) Canceled after 0s
build / build (push) Canceled after 0s
Test fzf on macOS / build (push) Canceled after 0s
- extraLines() used borderLines(), which reports zero for BorderInline
- Divider drawn for an inline section still takes a line, and
  resizeWindows() reserves it via addInline()
- So adaptive height came up one line short per inline section and the
  list scrolled
- Affects --header-lines-border, --header-border and --footer-border
  alike. All three set came up three lines short
- Without a list border the shape normalizes to BorderTop or BorderBottom,
  which report a line, hence --no-list-border looked like a fix

    seq 10 | fzf --height=~100% --list-border \
                 --header-lines=1 --header-lines-border=inline

Fix #4904
2026-09-06 11:27:17 +09:00
Junegunn Choi b395cfbd91 Fix --tiebreak=pathname with non-ASCII text
Fix #4902
2026-08-31 18:57:12 +09:00
Cyrus f7ae439ff5 Pin $SHELL to cmd in TestQuoteEntry and TestReplacePlaceholder
CodeQL / Analyze (go) (push) Canceled after 0s
build / build (push) Canceled after 0s
Test fzf on macOS / build (push) Canceled after 0s
2026-08-26 09:06:03 +09:00
Junegunn Choi d46bfd005a Keep a minimum wait for a sequence in flight
CodeQL / Analyze (go) (push) Canceled after 0s
build / build (push) Canceled after 0s
Test fzf on macOS / build (push) Canceled after 0s
ESCDELAY=0 makes the retry count zero, so the wait added in d0377ed had
no effect and the reply still leaked. Past the introducer these bytes
cannot be the ESC key, so waiting for them adds no Escape latency.

- Wait at least defaultEscDelay, honour a larger ESCDELAY
- Lone ESC still honours ESCDELAY=0, measured 3ms to exit either way

Fix #4899
2026-08-24 09:54:09 +09:00
Junegunn Choi d0377ed4dc Wait for rest of escape sequence before parsing (#4901)
CodeQL / Analyze (go) (push) Canceled after 0s
build / build (push) Canceled after 0s
Test fzf on macOS / build (push) Canceled after 0s
Read loop dropped its escDelay retry budget after every successful byte,
so a sequence split across reads reached the parser as a fragment, parsed
as ALT-[ with the remainder left behind as query text.

- fzf queries DECRQM at startup since dab626b, so a terminal answering
  late leaked "?2004;2$y" into the query
- Same split leaked modified keys and mouse sequences: CTRL-UP left "5A",
  SGR mouse left "0;1;1M"
- Bound unchanged, a stall longer than escDelay still falls back to ALT

Fix #4899
2026-08-24 00:40:06 +09:00
Cyrus bd4efa277b Use the platform quote placeholder for {fzf:prompt}
CodeQL / Analyze (go) (push) Canceled after 0s
build / build (push) Canceled after 0s
Test fzf on macOS / build (push) Canceled after 0s
Every other entry in this table goes through {{.O}}; this one had the
POSIX single quotes written in, so it could only pass off Windows.
2026-08-14 19:16:31 +09:00
Cyrus 63f6cfec5b Split the doubled and plain backslash cases in the quoting test
escapeArg only doubles a backslash that precedes a quote or ends the
argument, the same rule as syscall.EscapeArg, and @ is not a cmd
metacharacter so it is not caret-escaped. The Windows expectations here
still asked for both. Checked by round-tripping each entry through
cmd.exe into a program's argv: all ten come back byte for byte.
2026-08-14 19:16:31 +09:00
Cyrus d4b6ba781c Match the current temp file name in the powershell {f} test
The template writes to os.CreateTemp("", "fzf-temp-*"); the fzf-preview-
prefix went away with the --tmux work. The digit count was also pinned at
nine, but CreateTemp's suffix has no fixed width.
2026-08-14 19:16:31 +09:00
Cyrus 85a2a33612 Close the temp ttyout file in the SGR deduplication test
The file is opened under t.TempDir() and never closed, so the cleanup
that TempDir registers cannot remove it on Windows, where an open file
cannot be unlinked. The test itself passes; it is the cleanup that fails
the run. history_test.go and options_test.go already close theirs.
2026-08-14 19:16:30 +09:00
Junegunn Choi ca4c1b80e3 Erase 'p' on Terminal.app
CodeQL / Analyze (go) (push) Canceled after 0s
build / build (push) Canceled after 0s
Test fzf on macOS / build (push) Canceled after 0s
Terminal.app fails to properly handle DECRQM and leaves 'p' on the
screen.

Fix #4893
2026-08-11 23:42:09 +09:00
Junegunn Choi dab626bd9e Restore bracketed paste mode instead of forcing it off
CodeQL / Analyze (go) (push) Canceled after 0s
build / build (push) Canceled after 0s
Test fzf on macOS / build (push) Canceled after 0s
Ask the terminal whether the mode is already on (DECRQM) and put it back
that way on exit. Forcing it off broke pasting in shells that run fzf
from a line editor widget, which enable the mode only when the editor
starts. Terminals that do not answer fall back to disabling it.

- Startup queries go out in one write, cursor position last. Every
  terminal answers DSR, so its reply bounds the wait: a paste reply
  still missing by then means the terminal does not know the query.
- Bound the first read with select(2). Terminals that never answer
  escape sequences, such as FreeBSD virtual terminals, blocked startup
  until a key was pressed, and that keystroke was then discarded.

Fix #4887
Fix #2860
Fix #976
2026-08-10 22:36:25 +09:00
Junegunn Choi 2885df8395 Clean up comments and tests 2026-08-10 22:36:25 +09:00
Junegunn Choi 7b16e44f53 Copy the item text in replace-query
ToRunes aliases the rune array, and the editing actions append into
t.input in place when the cursor is not at the end, so the keystrokes
edit the item. Non-ASCII items only, ASCII gets a fresh slice.

    printf '한글abcde\n' | fzf --bind 'ctrl-y:replace-query'
    ctrl-y, Left, BSpace, ctrl-u  ->  한글abcee

Runes and ToRunes are now documented read-only. A stale fold bit was the
other symptom, letting the prefilter reject an item the general path
matches.
2026-08-08 19:48:04 +09:00
Junegunn Choi 9dfdba41f5 Prefilter rune-mode input for non-ASCII patterns
The scan only ran for ASCII patterns, so searching CJK text with a CJK
query still built the full score matrix. Scan for one byte of the pattern
rune and verify all four.

Which byte matters. Every ASCII rune contributes three zero bytes, so
U+AE00 scanned by its zero low byte hits on nearly every character of an
ASCII-heavy line. Pick a byte that cannot occur in an ASCII rune, else
any non-zero one.

A non-ASCII pattern rune is safe only when no other rune lowercases onto
it. Uncased is not sufficient: U+00DF has no simple uppercase, yet U+1E9E
lowercases to it, so the foldable set is excluded too.

Measured on 1.4M-line corpora, with a non-ASCII query:

- Every line CJK: 5.1x to 10.2x
- Mostly-ASCII paths behind a Hangul prefix: 5.6x to 6.0x, and 1.2x
  where every line matches so nothing can be rejected
- ASCII queries unchanged, kept off the non-inlinable guard
2026-08-08 19:48:04 +09:00
Junegunn Choi 465837f3ad Skip the normalization map for runes that cannot normalize
normalizeRune guarded with 0x00C0..0xFF61, which does not exclude Hangul,
CJK or Cyrillic, so every rune of those scripts hashed into the map only
to miss. Every key of the map folds to ASCII, so the bitmap added for the
rune prefilter rejects them without a lookup.

- Non-ASCII queries 1.21x where every line is CJK, 1.05x on mostly-ASCII
  paths behind a Hangul prefix
- ASCII queries unchanged, the prefilter already skips Phase 2 for them
- Normalization share of query time for a non-ASCII query: 17.2% -> 0%
2026-08-08 19:48:04 +09:00
Junegunn Choi a650900eda Prefilter rune-mode input
asciiFuzzyIndex gave up on non-ASCII lines, so every item ran the full
score matrix. A []rune is a fixed 4-byte stride, so the SIMD byte
scanners can run over it directly: find the low byte, then confirm
4-byte alignment and three zero bytes.

Case folding and normalization can turn a non-ASCII rune into the ASCII
char being searched, which the scan cannot see. ToChars now flags lines
holding such a rune and those keep the old path. Normalization is
Latin-only, so Hangul, CJK, Cyrillic, Greek, Hebrew, Arabic, Thai, kana
and emoji never set the flag.

Chars had no spare padding, so inBytes moves into a flags byte.

Measured on 1.4M-line corpora:

- Mostly-ASCII paths behind a Hangul prefix: 'conf' 1.8x, 'binutils'
  2.9x, 'ltversion' 4.0x, no-match 8.4x
- Every line CJK: 17x on both matching and non-matching queries
- ASCII input unchanged, non-ASCII patterns not covered yet
2026-08-08 19:48:04 +09:00
Junegunn Choi 793e58b558 Skip rune decoding for ASCII bytes in ToChars
utf8.DecodeRune already fast-paths ASCII, but it is too complex to inline
(cost 201 against a budget of 80), so a mostly-ASCII line pays one call
per byte just to be told the byte is ASCII.

Only the run after the first non-ASCII byte reaches the decode loop, so
the gain depends on where that byte falls.

- 70-rune ASCII line: 145ns -> 42ns in the decode loop
- Ingestion of 1.4M mostly-ASCII paths behind a Hangul prefix, where the
  loop covers the whole line: 377ms -> 233ms
- The same paths with the Hangul at the end, where it covers six bytes:
  207ms -> 196ms
- Break-even sits at ~100% non-ASCII runes: still 1.01x at 95%. Only a
  line holding no ASCII byte at all loses, by ~0.14ns per rune, ~5% of
  the loop
2026-08-08 19:48:04 +09:00
Junegunn Choi 759b7c3283 Avoid over-allocation in ToChars
- Capacity was byte length, over-allocating by bytes-per-rune (2-4x)
- Count non-continuation bytes with SWAR before allocating
- Invalid bytes undercount, never overcount, so append covers the gap
- Query performance unchanged, this is a memory fix
- The gain tracks bytes-per-rune, the cost tracks how much of the line
  follows the first non-ASCII byte, so the two move independently

Measured on 1.4M-line corpora:

- Every line CJK: RSS 362MB -> 255MB, ingestion -5%
- Mostly-ASCII paths behind a Hangul prefix: RSS 556MB -> 533MB,
  ingestion +3.5%, the counting pass covering the whole line
- The same paths with the Hangul at the end: RSS 563MB -> 535MB,
  ingestion +0.9%, the counting pass covering six bytes
2026-08-08 19:48:04 +09:00
Junegunn Choi 0579bb0e0d Break preview lines on IND to support chafa in preview window on tmux
CodeQL / Analyze (go) (push) Canceled after 0s
build / build (push) Canceled after 0s
Test fzf on macOS / build (push) Canceled after 0s
chafa ends each row of a Kitty Unicode placeholder image with CUB + IND
instead of a newline, so that an image drawn at a column offset survives
ONLCR, which would rewrite the newline as CR NL and pull the cursor back
to column 0. Both sequences were dropped, so the image arrived as a single
line and was re-wrapped, shifting every row after the first by the width
of the wrap sign. Without wrapping it was truncated to its first row.

The column is tracked across the breaks and restored with padding, so an
image indented by the preview command keeps its indent.

Fix #4885
2026-08-07 22:49:21 +09:00
Junegunn Choi 8cf40cb372 Do not split preview header through an image
--preview-window ~N draws the header in its own pass, resuming the body
in the middle of an image: an image arrives as one line but takes up many
rows. ~N is typically global and meant for text, while the same preview
command also renders images.
2026-07-29 23:01:11 +09:00
Junegunn Choi 57631684d8 Escape trailing semicolon in tmux command argument
tmux ends a command at an argument whose last character is ';', so a
--border-label ending in one was stored truncated. escapeTmuxSeparator
only covered a value that was exactly ';'.

  fzf --popup --border-label 'foo;'   ->  @fzf-border-label was 'foo'
2026-07-29 20:59:53 +09:00
Junegunn Choi 50e6c026ec Separate floating pane border label from pane title
--border-label of a tmux floating pane was stored in the pane title and
read back by pane-border-format as '#{pane_title}', so any program in the
pane could replace the border text by setting the title. On Zellij the
name was only set when the pane was created. Either way the built-in
label actions could not touch it, as fzf draws no border of its own
there.

- tmux: hold the label in pane-scoped @fzf-border-label. Set the option
  even without a label, so a later change-border-label has somewhere to
  write. Drop select-pane -T; the pane title is left to the user
- Zellij: run 'zellij action rename-pane' on label change. 'zellij run'
  has no option to set the environment of the new pane, so the command
  exports the target itself
- change-border-label and transform-border-label now update the native
  border, through the setter nativeLabelSetter picks by multiplexer
- The pane is named in a __FZF_INTERNAL_ variable that runProxy withholds
  from the environment replay, so the same name in the outer environment
  cannot redirect the update to another pane
2026-07-29 20:59:52 +09:00
Junegunn Choi b5f2ba4fd4 Wrap bare Kitty graphics sequences in tmux passthrough
A Kitty graphics command is an APC sequence, which tmux takes as a
request to set the pane title. A bare one therefore never reaches the
terminal and overwrites the title on the way, which is visible on the
border of a floating pane. Programs are expected to wrap it in the tmux
passthrough sequence themselves, but 'kitten icat --clear' does not,
unlike the image data it emits right after.

Sixel and iTerm2 sequences are left alone; tmux renders the former
itself and neither affects the title.

Related: #4870
2026-07-29 20:31:46 +09:00
Junegunn Choi 13a83e3475 Strengthen fast-path equivalence tests
- Vary normalize in the fuzz targets; document why the exhaustive sweep
  keeps it fixed (no-op for ASCII input)
- Seed the fuzz targets with patterns of the right length; single-char
  seeds were all two-char and got rejected
- Note that the disable* hook forbids parallel test execution
2026-07-27 21:33:01 +09:00
Junegunn Choi f779e6a4df Add fast path for two-character patterns in FuzzyMatchV2
For two ASCII characters, rows 0 and 1 of the score matrix collapse to
scalar running state, so Phase 2 and Phase 3 fuse into one pass with no
score arrays. withPos stores the two rows for the backtrace. Up to 1.4x
on two-char queries, the most common multi-char length.

Verify both fast paths against the general algorithm with exhaustive
(every short string over a class-complete alphabet) and fuzz tests,
runnable via the new make fuzz target.
2026-07-27 21:19:37 +09:00
Junegunn Choi 0efef298d2 Skip all-zero passes in radix sort of results
CodeQL / Analyze (go) (push) Canceled after 0s
build / build (push) Canceled after 0s
Test fzf on macOS / build (push) Canceled after 0s
Precompute OR of all sort keys; a byte position that is zero across every
key contributes a no-op pass. Default two-criteria setup leaves the low 32
bits zero, so 4 of 8 passes are skipped without a histogram scan. 2x on the
sort, 3-8% on high-match queries. No regression when all bytes are used.
2026-07-24 21:38:18 +09:00
Junegunn Choi 9c401f9c9a Add fast path for single-character patterns in FuzzyMatchV2
For single ASCII character, jump between occurrences with vectorized
search and score each directly from bonus matrix instead of running
full pipeline with prefilter and score arrays. 2x faster on 100K-line
benchmark corpus (5.4ms -> 2.5ms). Matters most on first keystroke
where candidate set is largest and result cache cannot help.
2026-07-23 21:27:29 +09:00
Junegunn Choi 224310d7a6 Fix nondeterministic highlight positions in FuzzyMatchV2 backtrace
Phase 3 writes C rows only from column F[r], but tie-breaking in the
backtrace could read row i+1 at column j+1 left of F[i+1]. With reused
slab, that cell holds data from previously processed item, so highlight
positions in equal-score ties depended on processing order.
2026-07-23 20:47:31 +09:00
Junegunn Choi 235a726fae Unregister interrupt signal handler when Loop exits
CodeQL / Analyze (go) (push) Canceled after 0s
build / build (push) Canceled after 0s
Test fzf on macOS / build (push) Canceled after 0s
Registration persisted across Run() calls, so host program using fzf as
library would keep trapping SIGINT/SIGTERM/SIGHUP after fzf finished
2026-07-20 13:12:12 +09:00
Junegunn Choi dc384afb70 Terminate resize notification when Loop exits
- Pass Loop context to notifyOnResize
- Windows: stop polling goroutine, close CONOUT$ handle on ctx.Done
- Unix: unregister SIGWINCH handler on ctx.Done
2026-07-20 13:09:06 +09:00
Cyrus 6e65d867dc Detect terminal resize on Windows (fix #4790)
Windows has no SIGWINCH and notifyOnResize was an empty TODO, so fzf
running in --height mode never noticed console size changes. Poll the
console screen buffer dimensions every 100ms and push a signal through
the same channel SIGWINCH uses on Unix. Full-screen mode is unaffected
as it goes through tcell which emits its own resize events.
2026-07-19 23:53:02 +08:00
Junegunn Choi e58c5e6bd8 Hide separator next to preview window at 'next' position when decidable
Decision must not depend on threshold chain resolution
(computePreviewSize consults noSeparatorLine); separator is hidden only
when every spec in the chain displays a 'next' preview window with a
border line facing the input section. Otherwise err on the side of
showing it.

- Without active previewer, preview position is resolved from first spec
  alone, so 'next' in a threshold alternative does not create input
  window and cannot hide the separator
- Empty preview command is undecidable; preview(...) action can display
  ad-hoc window at 'next' position at any time
- change-preview-window relayouts or repaints when separator visibility
  changes even if the active spec is unchanged, without dropping preview
  window forced by preview(...) action
2026-07-18 16:43:23 +09:00
Junegunn Choi 7fe4831be6 Apply facing-line rule to input border as well
Visible input border suppressed separator even when it draws no line
toward list section (bottom in default layout, top in reverse, vertical).
Suppress only when input border has a line on the list side; otherwise
fall through to adjacent section check.

- Requires header window to have content before treating it as facing
  section; hasHeaderWindow returns true with empty header when input
  border is visible
- Extract inputBorderFacesList and resolveInputBorderShape shared by
  options and terminal
- Add tests for separator suppression rules
2026-07-18 16:43:23 +09:00
Junegunn Choi 6030226835 Hide default separator when a border separates input from list
- Applies when adjacent section (header, header lines, or list) draws
  border line facing input section

    # No separator below header border
    fzf --style full --input-border none --header foo

    # Separator shown; nothing separates input from list
    fzf --style full --input-border none --header foo --no-header-border

- Evaluated dynamically so change-header/toggle-header update it
- Inline header is drawn inside list border and not adjacent to input;
  decide from section actually next to it
- Separator is kept when a preview window can be at 'next' position,
  right next to the input section
2026-07-18 16:43:23 +09:00
Junegunn Choi 3c0de84dab Skip redundant SGR sequences in light renderer
Track the last emitted SGR sequence and skip no-op transitions.
Reduces rendering output by 10-23% depending on the scenario.
2026-07-17 10:34:42 +09:00
Junegunn Choi 374b01242d Wrap frames in synchronized update mode (2026)
CodeQL / Analyze (go) (push) Canceled after 0s
build / build (push) Canceled after 0s
Test fzf on macOS / build (push) Canceled after 0s
Reduce flickering during rendering on supported terminals.
2026-07-16 23:20:29 +09:00
Junegunn Choi c0860259fa Use DECSC/DECRC instead of CSI s/u
Honored by more terminals, e.g. fixes cursor restoration with
--height --no-clear inside nvim terminal.
2026-07-16 23:00:39 +09:00
Junegunn Choi 1462f1358d Comment cleanup
CodeQL / Analyze (go) (push) Canceled after 0s
build / build (push) Canceled after 0s
Test fzf on macOS / build (push) Canceled after 0s
2026-07-15 18:30:17 +09:00
Junegunn Choi 8e340dabbc Use CHA for absolute horizontal cursor movement
Related:
- #4858
- https://github.com/zellij-org/zellij/issues/5370
2026-07-15 18:23:08 +09:00
Junegunn Choi 4dcedc01df Comment why floating pane requires TMUX_PANE
Blocking run-shell suspends client key processing until the command
exits, so a floating pane deadlocks; popup input bypasses the suspended
path. Fall back to display-popup when TMUX_PANE is not set.
2026-07-15 17:45:08 +09:00
Junegunn Choi e54f11c64e Default to native border for Zellij floating pane (#4854)
The native border is the handle for moving and resizing the pane with
the mouse, so use it by default, consistent with tmux. fzf draws its
own border only when a border style is explicitly specified with
--border. Extract the shared native-border decision into a helper.
2026-07-06 21:04:35 +09:00
Junegunn Choi f7392a8b63 Pre-create become file with O_EXCL
CodeQL / Analyze (go) (push) Has been cancelled
build / build (push) Has been cancelled
Test fzf on macOS / build (push) Has been cancelled
Prevents another user on a shared TMPDIR from planting a file or a
symbolic link at the predictable path while fzf is running, like the
exit status file of the floating pane. Reject an empty become command.
2026-07-05 22:00:55 +09:00
Junegunn Choi eee92b1b2a Clear border text of the floating pane when no label is given 2026-07-05 20:02:36 +09:00
Junegunn Choi 77e6394f50 Set --border-label as the title of the tmux floating pane (#4853)
The pane sets the options itself before running fzf, so that they are
in place no matter how quickly the command exits, targeted at
$TMUX_PANE; the default target would resolve to the active pane of
the session's current window.

- pane-border-format is set to '#{pane_title}' so that the label is
  displayed on the border when pane-border-status is enabled;
  pane-border-status itself is a window option in released tmux
  versions and is left alone
- When a border style is explicitly specified with --border, a popup
  is used instead of a floating pane so that the fzf-drawn border is
  the only border shown; give 'border-native' to force a floating pane
- 'none' and 'line' are treated as no border; fzf draws no box for
  either, so the label is displayed on the native border
- Remove 'border-fzf' which is now redundant; it was never released
- The title is escaped for select-pane -T which expands format
  expressions; a lone ';' is escaped as tmux would treat it as a
  command separator
- The label is skipped when ANSI stripping leaves an empty string
- --border-label-pos is ignored
- Fix remain-on-exit set on the original pane instead of the floating
  pane
2026-07-05 19:35:34 +09:00
Junegunn Choi 1e31e5dfbe Use floating pane instead of popup on tmux 3.7 or above (#4850) (#4852)
CodeQL / Analyze (go) (push) Has been cancelled
build / build (push) Has been cancelled
Test fzf on macOS / build (push) Has been cancelled
Unlike a popup, a floating pane is not modal; you can switch to other
panes and windows while fzf is running, move and resize the pane with
the mouse, zoom it to fullscreen, and use copy-mode in it.

- Floating pane always has a native border, so 'border-native' is
  implied; give new 'border-fzf' option to fall back to a popup where
  fzf draws its own border
- Popup is also used on tmux versions below 3.7, or when the window is
  too small to fit a floating pane
- new-pane does not block until the command finishes and does not
  propagate the exit status; block on a wait-for channel signaled by
  the pane and pass the exit status through a temporary file
- Watchdog process signals the channel when the pane is closed
  abnormally (e.g. kill-pane)
- Kill the pane when the proxy process is interrupted, like a popup
  dying with its client
- Unzoom the window before creating the floating pane; doing so over a
  zoomed window crashes the tmux server on 3.7b, and newer versions of
  tmux unzoom the window anyway
- Floating pane size excludes the border and the position is that of
  the content area; treat the requested size as the total footprint
  including the border for consistency with popups
- Close the pane on exit even when remain-on-exit is on
- Pre-create the exit status file with O_EXCL to prevent tampering on
  a shared TMPDIR
2026-07-05 14:15:11 +09:00