Howdie folks!
Fzf or fuzzy finder is a neat command line utility that choreographically helps select an element from a list.
I use it to search command history via Ctrl-R (requires fzf shell integration for Zsh). The problem: if you type a query that doesn't match any history entry and exit fzf (with ESC), your precious typed text vanishes. Gone. You have to retype it manually in the shell. Annoying when you realize what you want isn't in history after you've already typed it.
I was hit by this multiple times. Searched the Interweb, found others with same problem, but no ready-made solutions. So I decided to fix it myself.
Programming Zsh requires a lot of patience on my part - too much cruft accumulated over the years, too many ways to do something mixed with arcane syntax.
Anyway, meet fc the builtin fix command. It's the shell's history interface - fc -l lists history, fc -e edits it. The history command you use daily? Just an alias to fc.
In this case, fc -rl 1 does:
-r: reverse order (newest first)-l: list format with line numbers1: starting from entry 1 (entire history)Output format: linenum command which is perfect for piping to fzf. The line number becomes the handle for vi-fetch-history to retrieve the actual command later.
Here's the complete widget implementation:
# CTRL-R - Paste the selected command from history into the command line
fzf-history-widget() {
local selected num ret
setopt localoptions noglobsubst noposixbuiltins pipefail 2> /dev/null
selected=( $(fc -rl 1 |
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS \
-n2..,.. --tiebreak=index --bind=ctrl-s:toggle-sort,ctrl-k:kill-line \
--expect=ctrl-e $FZF_CTRL_R_OPTS --query=${(qqq)LBUFFER} \
+m --print-query" $(__fzfcmd)) )
ret=$?
if (( $selected[(I)ctrl-e] )); then
if [[ $ret = 0 ]]; then
[[ $selected[3] =~ ^[0-9]+$ ]] && shift selected
zle vi-fetch-history -n $selected[2]
elif [[ $ret = 1 ]]; then
shift -p selected
LBUFFER="${LBUFFER}${selected}"
fi
else
if [[ $ret = 0 ]]; then
zle vi-fetch-history -n $selected[2]
elif [[ $ret = 1 ]]; then
LBUFFER="${LBUFFER}${selected}"
fi
zle accept-line
fi
zle reset-prompt
return $ret
}
zle -N fzf-history-widget
bindkey '^R' fzf-history-widget
The magic happens in the fzf invocation. Key options:
--print-query: outputs the user's typed query as first line, regardless of match--expect=ctrl-e: outputs "ctrl-e" if pressed, allowing edit-vs-execute choice--query=${(qqq)LBUFFER}: pre-fills fzf with current command line buffer (LBUFFER is Zsh's line editor buffer left of cursor, triple-q escapes special chars)+m: single selection modeThe selected array captures fzf's multiline output (Zsh arrays are 1-indexed):
selected[1]: User's query string (from --print-query)selected[2]: Pressed key if --expect matched (e.g., "ctrl-e"), OR history line number if no key pressedselected[3]: Selected history entry (if any)Return codes: 0 = match selected, 1 = no match (ESC/empty selection), 130 = interrupt.
The nested conditionals handle four scenarios. First check: $selected[(I)ctrl-e] uses Zsh subscript flag (I) to search the array for "ctrl-e", returning its index (0 if not found).
Ctrl-e pressed (edit mode):
- Match found (ret=0): fetch history via vi-fetch-history -n (the -n flag uses the line number), don't execute
- No match (ret=1): shift -p removes "ctrl-e" from array, leaving query to append to buffer, don't execute
Enter pressed (execute mode):
- Match found (ret=0): fetch history and execute via accept-line
- No match (ret=1): append user's query to LBUFFER and execute
The quirk: when scrolling without typing, array positions shift - selected[3] gets the history number instead of selected[2]. The regex check ^[0-9]+$ detects this and normalizes via shift.
Finally, zle reset-prompt refreshes the command line with the updated LBUFFER content.
Interrupt (ret=130): code doesn't explicitly handle it, just returns the code and lets the shell deal with it.
With --print-query, user input survives in selected[1] even when ret=1, so no more lost commands when history doesn't match.
Add the function to your .zshrc after sourcing fzf shell integration. This replaces the default fzf-history-widget with the enhanced version. The bindkey '^R' line maps it to Ctrl-R.
Usage:
Fzf version 0.45.0 added built-in support via accept-or-print-query:
export FZF_CTRL_R_OPTS='--bind enter:accept-or-print-query'
Single line versus 30+ lines of Zsh. Progress.
The custom widget above remains useful if you need the Ctrl-e edit mode or are stuck on older fzf versions.