0.59.0 #
(February 2025)
Highlights #
- Enhanced
path
scheme to prioritize file name matches click-header
event now sets$FZF_CLICK_HEADER_WORD
and$FZF_CLICK_HEADER_NTH
, allowing you to implement a clickable header for changing the search scopesearch
andtransform-search
action to extend the search syntax of fzf--header-lines-border
to display header from--header-lines
with a separate border--no-input
option to completely disable and hide the input section
Prioritizing file name matches #
Since fzf is a general-purpose text filter, its algorithm was designed to
“generally” work well with any kind of input. However, admittedly, there is no
true one-size-fits-all solution, and you may want to tweak the algorithm and
some of the settings depending on the type of the input. To make this process
easier, fzf provides a set of “scheme"s for some common input types.
i.e. default
, path
, and history
.
This release enhances path
scheme to prioritize matches occurring in the
file name of the path. Here is an example.
git ls-files | fzf -qs | git ls-files | fzf -qs --scheme=path |
---|---|
src/LICENSE | src/server.go |
src/ansi.go | src/util/slab.go |
src/core.go | .github/workflows/sponsors.yml |
src/item.go | src/LICENSE |
src/tmux.go | src/ansi.go |
src/cache.go | src/core.go |
src/proxy.go | src/item.go |
You can see with --scheme=path
, the matches with the search term occuring in
their names are prioritized over the rest.
Automatic scheme selection #
fzf will automatically choose path
scheme
- when the input is a TTY device, where fzf would start its built-in walker or run
$FZF_DEFAULT_COMMAND
which is usually a command for listing files, - but not when
reload
ortransform
action is bound tostart
event, because in that case, fzf can’t be sure of the input type.
# Built-in walker is used, and --scheme=path is automatically applied
fzf
# fzf runs $FZF_DEFAULT_COMMAND, and --scheme=path is automatically applied
export FZF_DEFAULT_COMMAND='fd --type f --hidden'
fzf
However, this automatic selection may not always be desirable. If you observe suboptimal behavior, explicitly specify the scheme you want.
Note: I’m not entirely sure if this automatic selection is really a good idea. I wanted more users to benefit from the enhancement without having to change their settings. But it might make fzf less predictable. Please let me know if you have any feedback.
Understanding the path
scheme
#
path
scheme does two things:
- Adjust the scoring algorithm to give the highest bonus points to the
matching characters after the path separators. Characters after whitespaces
are given less bonus points.
- On the contrary, in the
default
scheme, characters after whitespaces are given the highest bonus points.
- On the contrary, in the
- Apply
--tiebreak=pathname,length
option
The additional pathname
tiebreak is what’s new in this release. It involves
more computation, so the path
scheme will be slightly slower than the
default
scheme. However, the difference in performance should be negligible
in most cases.
click-header
enhancements
#
When you click on the header, click-header
event is triggered and it exports
the following environment variables.
Variable | Description |
---|---|
$FZF_CLICK_HEADER_LINE | Clicked line number starting from 1 |
$FZF_CLICK_HEADER_COLUMN | Clicked column number starting from 1 |
$FZF_CLICK_HEADER_WORD | The word that was clicked (new) |
$FZF_CLICK_HEADER_NTH | The nth expression for the clicked word (new) |
$FZF_KEY | The name of the last key or event. Now it reports click types as well. click , ctrl-click , alt-click , shift-click , double-click , etc. |
You can use these variables, especially the last three, to implement a clickable header that changes the search scope.
# Click on the header line to limit search scope
ps -ef | fzf --style full --layout reverse --header-lines 1 \
--header-border bottom --no-list-border \
--color fg:dim,nth:regular \
--bind 'click-header:transform-nth(
echo $FZF_CLICK_HEADER_NTH
)+transform-prompt(
echo "$FZF_CLICK_HEADER_WORD> "
)'
Now the kill
completion implements a more sophisticated version that allows
selecting/toggling multiple columns with {ctrl,alt,shift}-click
.
search
and transform-search
action
#
The new search
and transform-search
action allow you to trigger an fzf
search with an arbitrary query string. This frees fzf from strictly following
the prompt input, enabling custom search syntax.
In the example below, transform
action is used to conditionally trigger
reload
for ripgrep, followed by search
for fzf. The first word of the
query initiates the Ripgrep process to generate the initial results, while the
remainder of the query is passed to fzf for secondary filtering.
#!/usr/bin/env bash
# Controlling Ripgrep search and fzf search simultaneously
export TEMP=$(mktemp -u)
trap 'rm -f "$TEMP"' EXIT
INITIAL_QUERY="${*:-}"
TRANSFORMER='
rg_pat={q:1} # The first word is passed to ripgrep
fzf_pat={q:2..} # The rest are passed to fzf
if ! [[ -r "$TEMP" ]] || [[ $rg_pat != $(cat "$TEMP") ]]; then
echo "$rg_pat" > "$TEMP"
printf "reload:sleep 0.1; rg --column --line-number --no-heading --color=always --smart-case %q || true" "$rg_pat"
fi
echo "+search:$fzf_pat"
'
fzf --ansi --disabled --query "$INITIAL_QUERY" \
--with-shell 'bash -c' \
--bind "start,change:transform:$TRANSFORMER" \
--color "hl:-1:underline,hl+:-1:underline:reverse" \
--delimiter : \
--preview 'bat --color=always {1} --highlight-line {2}' \
--preview-window 'up,60%,border-line,+{2}+3/3,~3' \
--bind 'enter:become(vim {1} +{2})'

- As you can see, the first term
return
is passed to ripgrep, the primary filter, and the second termabc
is passed to fzf, making it perform secondary filtering over the results from ripgrep.
Info
Note that we bind the same tranform
action to start
and change
at once.
--bind "start,change:transform:$TRANSFORMER"
This was not possible in the previous versions.
Secondary border for the header lines #
After experimenting with --header-border
, I realized that there are cases
where you want to treat the header from --header-lines
differently from the
header from --header
. Here’s an example. This is a classic use case of
--header-lines
.
ps -ef | fzf --style full --layout reverse --header-lines 1

Let’s say you want to provide a binding for reloading the list, and tell the users about it using the header.
ps -ef | fzf --style full --layout reverse --header-lines 1 \
--bind 'ctrl-r:reload(ps -ef)' --header 'Press CTRL-R to reload'

Okay, but I’d prefer to have some visual separation between the two headers
because they serve different purposes. I used to add new line characters to
--header
, but now we have a better option, --header-lines-border
.
ps -ef | fzf --style full --layout reverse --header-lines 1 \
--bind 'ctrl-r:reload(ps -ef)' --header 'Press CTRL-R to reload' \
--header-lines-border bottom --no-list-border

Unlike the other border types, none
option has a different meaning here. It
is used to still separate the two headers, but without an additional border.
ps -ef | fzf --style full --layout reverse --header-lines 1 \
--bind 'ctrl-r:reload(ps -ef)' --header 'Press CTRL-R to reload' \
--header-lines-border none

--header-first
option will only affect the header from --header
.
ps -ef | fzf --style full --layout reverse --header-lines 1 \
--bind 'ctrl-r:reload(ps -ef)' --header 'Press CTRL-R to reload' \
--header-lines-border none --header-first

Hiding input section #
--no-input
option completely disables and hides the input section. You can
no longer type in queries. What good is it? You may want to use it to create
a very simple menu with no input capability.
(echo Yes; echo No) | fzf --height=~100% --no-input --reverse --style full

Although we can’t type in search queries, we can use the new search
and
transform-search
actions to trigger searches like in the example below.
# Click header to trigger search
fzf --header '[src] [test]' --no-input --layout reverse \
--header-border bottom --input-border \
--bind 'click-header:transform-search:echo ${FZF_CLICK_HEADER_WORD:1:-1}'

Here is another example that implements Vim-like mode switching.
fzf --layout reverse-list --no-input \
--bind 'j:down,k:up,/:show-input+unbind(j,k,/)' \
--bind 'enter,esc,ctrl-c:transform:
if [[ $FZF_INPUT_STATE = enabled ]]; then
echo "rebind(j,k,/)+hide-input"
elif [[ $FZF_KEY = enter ]]; then
echo accept
else
echo abort
fi
'
(FZF_INPUT_STATE
is one of enabled
, disabled
, or hidden
.)
Personally, I don’t see myself using this feature much, but if you have a creative use case, I’d love to hear about it.
bell
action 🔔
#
Last, and, yes, least. We have a new action called bell
that rings the terminal bell. You can use it to provide feedback to the user.
# Press CTRL-Y to copy the current line to the clipboard and ring the bell
fzf --bind 'ctrl-y:execute-silent(echo -n {} | pbcopy)+bell'