- A symlink in one root was skipped when its target was an ancestor of
another root, hiding files reachable only through it
- Fix and tests by @mabunemeh
Close#4944
- `masked +=` per token copied the whole prefix on every append
- ~1 MB POST to --listen blocked the server for ~30 s
- switch to strings.Builder
Reported by Zachariah Mithani (@zachariah-mithani, AploSec).
- --sync, --tac, --bench disable streaming filter, and the result was sorted
- Matcher.scan already checks pattern.sortable
Problem analyzed and patch submitted by @NavpreetSSidhu
Fix#4940
Co-authored-by: NavpreetSSidhu <116211004+NavpreetSSidhu@users.noreply.github.com>
- Same rule as change-with-nth and transform-with-nth
- Empty: default
- '..': all fields
- Non-empty invalid: ignored
- Previously, change-nth() and empty transform-nth output searched
all fields, and transform-nth could not restore default
- Partly reverts af0014ab. Use change-nth(..) to unset default
- Find current value in bound list and apply next valid,
non-current one
- Keep rotating bound list as a position hint, so repeated
values in the list still reach every position
- Treat empty --nth and '..' as the same value
- Ignore invalid nth values, also in transform-nth, as with-nth
does. Previously, they restored default --nth
- Previously, first value of rotated list was applied blindly
- Key press spent on value already applied, e.g. first value
equal to initial --nth
- Stuck on change-nth printed by transform (new action on
each run), as it always applied first value
- Add nextValue helper for reuse by change-layout
- Rotation of '|'-separated values applied to transform and
bg-transform variants, where a.a is the command template
- Output '1|2' turned the binding into '2|1', run as a shell
command on the next key press
- Output was also cut at '|', breaking --with-nth templates
like '{2}|{1}'
- Split and rotate only for change-nth and change-with-nth
* fix(nushell): adapt the completer to the 0.116 contract
Nushell 0.116 runs an external completer on a background worker
without the terminal unless the registered closure reaches a
command carrying the @interactive attribute. Interactivity is
seen through the closure to the command it calls, so wrap the
completion logic in an @interactive entry point and register a
closure that ends with the call to it.
Read the named `place` input instead of the deprecated
positional `spans`; $place.command carries the same token list.
An @interactive completer cannot decline with null (null reads
as a dismissed picker), so request the fallback explicitly when
the `**` trigger is absent, keeping plain file completion
working. The previous external completer is still called when
the trigger is absent.
The script now requires Nushell 0.116+, since @interactive is a
parse error on older versions.
Fixes#4932
* fix(nushell): retry the previous completer with the legacy spans input
The fallback to the previously configured external completer always
handed it the new `place` input. A completer that still declares the
legacy `spans` parameter errors on a record, so the fallback silently
produced no completions for pre-0.116 completers (e.g. older Carapace
installs).
Try `place` first, then retry with the token list, which is what the
old positional bridge handed such completers.
* docs(nushell): note the 0.116 requirement and load order
Requires Nushell 0.116.1+, where a completion can take the terminal
through the @interactive attribute, and the wrapper captures the
external completer present at load time.
FIFO path was predictable (${TMPDIR:-/tmp}/fzf-complete-fifo-$$) and
_fzf_feed_fifo ignored rm/mkfifo failures, so on a shared /tmp another
user could pre-create the path (symlink or FIFO) and have the victim's
redirect follow it.
Allocate the FIFO inside a per-invocation mktemp -d directory, check
mkfifo status, and clean up the directory instead of the bare path.
Reported-by: sb24CK (https://github.com/sb24CK)
Attached option form (e.g. -l50) captured size via ${arg:2} with no
validation, unlike the separate-argument form. For -l/-u the value
reached $((...)) arithmetic, where a[$(cmd)] runs cmd during option
parsing, before any tmux check. Same value was also expanded unquoted
into `tmux popup ... $opt`, allowing tmux flag injection.
Apply the separate-form regex to the attached form too. Rejects the
array-subscript payload and space-bearing geometry values that enabled
flag injection.
Reported-by: sb24CK (https://github.com/sb24CK)
- Use new-pane -A on tmux 3.8+ to keep floating pane above zoomed pane
- Detect -A from new-pane usage; keep unzoom fallback for 3.7
Fix#4927Fixjunegunn/fzf.vim#1625
Co-authored-by: Jere Viikari <jere.viikari@gmail.com>
- Indexes restart after reload, so same-index item was not seen as new
- With --id-nth, compare by key. No event if focused item keeps its key.
- Skip focus check while track is blocked
- Document that binding reload to focus can loop
Close#4930
The parser decided how many bytes a key used before checking them, so a
key that arrived in the same read was lost or misread.
- Recognize F1-F8 and modified Delete, PageUp and PageDown when more input
follows them, as with a paste or tmux send-keys. They required the
sequence to be the whole buffer.
- After \e[ and a digit, do not consume a byte that cannot continue the
sequence. ALT-[, 2 and Enter lost the Enter.
- Report Home and End for \e[7, \e[4 and \e[8 only before ~, $, ^ or @.
ALT-[, 4 and x fired End and lost the x.
- Look for the end of a mouse report within its frame. A later M swallowed
the keys before it, and repeated \e[< took quadratic time.
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, and never consume past
it: the parser could count a key typed after it. rxvt ends keys with $,
as in \e[7$ and \e[23$, so $ after plain digits is a final byte. Elsewhere
it stays intermediate, as in DECRPM replies.
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. That read has no
time limit, so what it returns may be the next key, typed any time later.
It is never dropped with the frame.
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 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.
- Selections were appended to the command line as-is, so a path with
spaces became several arguments and syntax in a file name was left
unquoted for the parser
- Read the selection with --print0 and serialize each path with `to nuon`,
so each one stays a single string literal
- Skip the insertion entirely when nothing was selected
- 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
- 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
The custom CTRL-T command is now executed by the script in the current
shell session, instead of being called by fzf through a subshell. This
ensures that the value of $dir can't be altered by user initialization
scripts, and is also a little faster.
Fix#4498
- 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
- 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
The window fzf was started from stays visible, and can be used while fzf
runs, which a popup inside Vim cannot offer.
Only where the pane is not modal, so tmux 3.7 or above, or Zellij 0.44 or
above with fzf 0.71.0 or above. A tmux popup from 3.3 to 3.6 cannot be left,
and below 3.3 fzf goes through the fzf-tmux script, whose options are
spelled differently. Both keep the window inside Vim. An explicit popup
layout is unaffected and still works from 3.3.
fzf checks tmux before Zellij, so tmux wins when both are set.
g:fzf_layout still wins, so anyone who set it sees no change.
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
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
* Run fzf asynchronously in the Vim plugin
Popup mode held the fzf process with system(), which froze Vim until fzf
exited. fzf in a popup draws in a pane of its own, so that process only
waits for it and does not need a window. Hold it with a job instead and
Vim keeps processing its event loop, which is what a live preview needs.
Nothing is displayed for the job. Falls back to the blocking path when
the job cannot start, so the sink still runs and temp files are removed.
- job_start() sets $TERM=dumb and the popup inherits the environment, so
fzf dropped to its 16-color scheme. Restore it via 'env', or in the
command itself before 8.0.902, when 'env' was added
- Fullscreen now uses a terminal buffer in a new tab on Vim too. use_term
lacked parentheses, so && bound tighter than || and the layout test was
dead on Neovim, which already behaved this way
- fzf#run returns an empty list in these modes. Callers use sink,
sinklist or exit, and the vader specs now wait for completion
- Append --no-tmux only when the spec asks for a Vim window, so --popup
in $FZF_DEFAULT_OPTS survives a spec with no layout option
Accept popup as a synonym of the tmux layout key, matching --popup being
the name of --tmux. popup wins when both are given.
s:tmux_enabled():
- Accept $ZELLIJ, which --popup covers as well
- Parse tmux -V with matchstr and compare with s:compare_versions. The
old string comparison against 'tmux 1.7' misreads 10.0
- Drop the fzf-tmux requirement on tmux 3.3 or above, where --tmux needs
no script. Removing the script silently disabled popups entirely
- Resolve the script where it is used, and anchor the legacy test to ^-
so a --tmux value containing a dash, as in 90%,60%,border-native, is
not mistaken for a legacy flag
* Turn g:loaded_fzf into a version marker
Callers cannot detect plugin-side behavior. fzf#exec() reports the version
of the binary, not of this plugin, and everything else here is script-local.
fzf.vim needs to know whether fzf#run is asynchronous in popup mode before
offering a key binding that depends on it.
The value was only ever read through the exists() guard, so raising it from
1 breaks nothing.
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.
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.
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.
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#4887Fix#2860Fix#976