aloxaf.fzf-tab/fzf-tab.zsh
Aloxaf 24105b1571
Some checks failed
macOS compability / test (push) Has been cancelled
Zsh compability / test (5.3.1) (push) Has been cancelled
Linux compability / test (archlinux:latest) (push) Has been cancelled
Linux compability / test (ubuntu:latest) (push) Has been cancelled
Zsh compability / test (5.4.2) (push) Has been cancelled
Zsh compability / test (5.5.1) (push) Has been cancelled
Zsh compability / test (5.6.2) (push) Has been cancelled
Zsh compability / test (5.7.1) (push) Has been cancelled
Zsh compability / test (5.8) (push) Has been cancelled
Zsh compability / test (5.9) (push) Has been cancelled
fix: crash on ffmpeg -i
close #176
2026-06-04 13:42:55 +08:00

560 lines
19 KiB
Bash

# temporarily change options
'builtin' 'local' '-a' '_ftb_opts'
[[ ! -o 'aliases' ]] || _ftb_opts+=('aliases')
[[ ! -o 'sh_glob' ]] || _ftb_opts+=('sh_glob')
[[ ! -o 'no_brace_expand' ]] || _ftb_opts+=('no_brace_expand')
'builtin' 'setopt' 'no_aliases' 'no_sh_glob' 'brace_expand'
# disable aliases
typeset _ftb_aliases="$(builtin alias -Lm '[^+]*')"
builtin unalias -m '[^+]*'
# thanks Valodim/zsh-capture-completion
-ftb-compadd() {
# parse all options
local -A apre hpre dscrs _oad _mesg
local -a isfile _opts __ expl
zparseopts -a _opts P:=apre p:=hpre d:=dscrs X+:=expl O:=_oad A:=_oad D:=_oad f=isfile \
i: S: s: I: x:=_mesg r: R: W: F: M+: E: q e Q n U C \
J:=__ V:=__ a=__ l=__ k=__ o::=__ 1=__ 2=__
# store $curcontext for further usage
_ftb_curcontext=${curcontext#:}
# just delegate and leave if any of -O, -A or -D are given or fzf-tab is not enabled
# or fzf-tab is disabled in the current context
if (( $#_oad != 0 || ! IN_FZF_TAB )) \
|| { -ftb-zstyle -m disabled-on "any" } \
|| { -ftb-zstyle -m disabled-on "files" && [[ -n $isfile ]] }; then
builtin compadd "$@"
return
fi
# store matches in $__hits and descriptions in $__dscr
local -a __hits __dscr
if (( $#dscrs == 1 )); then
__dscr=( "${(@P)${(v)dscrs}}" )
fi
builtin compadd -A __hits -D __dscr "$@"
local ret=$?
if (( $#__hits == 0 )); then
if is-at-least 5.9 && (( $#_mesg != 0 )); then
builtin compadd -x $_mesg
fi
return $ret
fi
# only store the fist `-X`
expl=$expl[2]
# keep order of group description
[[ -n $expl ]] && _ftb_groups+=$expl
# store these values in _ftb_compcap
local -a _ftb_compcap_keys=(apre hpre PREFIX SUFFIX IPREFIX ISUFFIX)
local key expanded __tmp_value=$'<\0>' # placeholder
# Shadow PREFIX locally so we can modify it for fzf-tab capture
# without permanently affecting the shell state for the final compadd.
local PREFIX="$PREFIX"
local PREFIX_ORIG="$PREFIX"
# Sanitize PREFIX locally before capture.
# We restrict this to approximate/correct contexts to avoid false positives.
# _approximate injects internal glob flags like (#a1) into PREFIX.
# Check "14.8.5 Approximate Matching" in Zsh docs (https://zsh.sourceforge.io/Doc/Release/Expansion.html).
if [[ $_ftb_curcontext == (*approximate*|*correct*) ]]; then
# 1. Strip (#a1) from start
PREFIX=${PREFIX/#\(\#a[0-9]##\)/}
# 2. Strip (#a1) after leading tilde (e.g. ~(#a1)/foo -> ~/foo)
PREFIX=${PREFIX/#\~\(\#a[0-9]##\)/~}
# If the prefix changed, we know this was a fuzzy match for which we stripped the glob.
if [[ $PREFIX_ORIG != $PREFIX ]]; then
# The sanitized prefix (e.g. "docn") won't strictly match the result (e.g. "docker").
# We must force -U (Unmatched) into fzf-tab's capture options so that *later*,
# when fzf-tab tries to insert the result, it uses -U to bypass checks.
# This aligns with standard Zsh behavior for complex completers (and how _approximate
# itself handles the 'original' group) to bypass strict prefix validation.
if [[ ${_opts[(I)-U]} -eq 0 ]]; then
_opts+=(-U)
fi
fi
fi
for key in $_ftb_compcap_keys; do
# (P)key fetches the value of the variable named by $key.
# Since we declared 'local PREFIX' above, this fetches the sanitized version of PREFIX.
expanded=${(P)key}
if [[ -n $expanded ]]; then
__tmp_value+=$'\0'$key$'\0'$expanded
fi
done
# Restore original fuzzy prefix so the `builtin compadd` below succeeds.
# (Standard compadd needs the fuzzy glob to validate the candidates).
PREFIX="$PREFIX_ORIG"
if [[ -n $expl ]]; then
# store group index
__tmp_value+=$'\0group\0'$_ftb_groups[(ie)$expl]
fi
if [[ -n $isfile ]]; then
# NOTE: need a extra ${} here or ~ expansion won't work
__tmp_value+=$'\0realdir\0'${${(Qe)~${:-$IPREFIX$hpre}}}
fi
_opts+=("${(@kv)apre}" "${(@kv)hpre}" $isfile)
__tmp_value+=$'\0args\0'${(pj:\1:)_opts}
if (( $+builtins[fzf-tab-compcap-generate] )); then
fzf-tab-compcap-generate __hits __dscr __tmp_value
else
# dscr - the string to show to users
# word - the string to be inserted
local dscr word i
for i in {1..$#__hits}; do
word=$__hits[i] dscr=$__dscr[i]
if [[ -n $dscr ]]; then
dscr=${dscr//$'\n'}
elif [[ -n $word ]]; then
dscr=$word
fi
_ftb_compcap+=$dscr$'\2'$__tmp_value$'\0word\0'$word
done
fi
# tell zsh that the match is successful
builtin compadd "$@"
}
-ftb-zstyle() {
zstyle $1 ":fzf-tab:$_ftb_curcontext" ${@:2}
}
-ftb-complete() {
local -Ua _ftb_groups
local choice choices _ftb_curcontext continuous_trigger print_query accept_line bs=$'\2' nul=$'\0'
local ret=0
# must run with user options; don't move `emulate -L zsh` above this line
(( $+builtins[fzf-tab-compcap-generate] )) && fzf-tab-compcap-generate -i
# Work around zsh's non-reentrant static pattern buffer. SIGCHLD can refresh
# ZLE while a completer is matching a parameter pattern; prompt expansion
# during that refresh can overwrite the active pattern.
() {
setopt localoptions no_monitor no_notify
COLUMNS=500 _ftb__main_complete "$@"
} "$@" || ret=$?
(( $+builtins[fzf-tab-compcap-generate] )) && fzf-tab-compcap-generate -o
emulate -L zsh -o extended_glob
local _ftb_query _ftb_complist=() _ftb_headers=() command opts
-ftb-generate-complist # sets `_ftb_complist`
-ftb-zstyle -s continuous-trigger continuous_trigger || {
[[ $OSTYPE == cygwin ]] && continuous_trigger=// || continuous_trigger=/
}
case $#_ftb_complist in
0) return 1;;
1)
choices=("EXPECT_KEY" "${_ftb_compcap[1]%$bs*}")
if (( _ftb_continue_last )); then
choices[1]=$continuous_trigger
fi
;;
*)
# Check for 'force' in compstate[list]:
# _approximate sets compstate[list] to "force" when showing corrections.
# If we don't check for this, fzf-tab sees an "unambiguous" prefix and exits early,
# which falls back to the standard Zsh menu.
if (( ! _ftb_continue_last )) \
&& [[ $compstate[insert] == *"unambiguous" ]] \
&& [[ -n $compstate[unambiguous] ]] \
&& [[ "$compstate[unambiguous]" != "$compstate[quote]$IPREFIX$PREFIX$compstate[quote]" ]] \
&& [[ $compstate[list] != *"force"* ]]; then
compstate[list]=
compstate[insert]=unambiguous
_ftb_finish=1
return 0
fi
-ftb-generate-query # sets `_ftb_query`
-ftb-generate-header # sets `_ftb_headers`
-ftb-zstyle -s print-query print_query || print_query=alt-enter
-ftb-zstyle -s accept-line accept_line
choices=("${(@f)"$(builtin print -rl -- $_ftb_headers $_ftb_complist | -ftb-fzf)"}")
ret=$?
# choices=(query_string expect_key returned_word)
# Handle the "print-query" action (e.g., Alt-Enter)
# or what the user manually typed in the fzf search bar, for which there is no match
# among the available options
#
# The next block allows the user to insert the raw text typed into the fzf prompt
# instead of selecting a generated match from the list.
#
# Logic:
# 1. Restore the original completion context (PREFIX, IPREFIX, etc.) from _ftb_compcap
# so Zsh knows exactly which part of the command line to replace.
# 2. Use `builtin compadd` to insert the raw query string ($choices[1]) as the match.
if [[ $choices[2] == $print_query ]] || [[ -n $choices[1] && $#choices == 1 ]] ; then
local -A v=("${(@0)${_ftb_compcap[1]}}")
local -a args=("${(@ps:\1:)v[args]}")
[[ -z $args[1] ]] && args=() # don't pass an empty string
IPREFIX=$v[IPREFIX] PREFIX=$v[PREFIX] SUFFIX=$v[SUFFIX] ISUFFIX=$v[ISUFFIX]
# NOTE: should I use `-U` here?, ../f\tabcd -> ../abcd
builtin compadd "${args[@]:--Q}" -Q -- $choices[1]
compstate[list]=
compstate[insert]=
if (( $#choices[1] > 0 )); then
compstate[insert]='1'
[[ $RBUFFER == ' '* ]] || compstate[insert]+=' '
fi
_ftb_finish=1
return $ret
fi
choices[1]=()
choices=("${(@)${(@)choices%$nul*}#*$nul}")
unset CTXT
;;
esac
if [[ -n $choices[1] && $choices[1] == $continuous_trigger ]]; then
typeset -gi _ftb_continue=1
typeset -gi _ftb_continue_last=1
fi
if [[ -n $choices[1] && $choices[1] == $accept_line ]]; then
typeset -gi _ftb_accept=1
fi
choices[1]=()
_ftb_choices=("${(@)choices}")
compstate[list]=
compstate[insert]=
return $ret
}
_fzf-tab-apply() {
local choice bs=$'\2'
for choice in "$_ftb_choices[@]"; do
local match=${_ftb_compcap[(r)${(b)choice}$bs*]}
if [[ -z $match ]]; then
local qchoice=${(q)choice}
match=${_ftb_compcap[(r)${(b)qchoice}$bs*]}
fi
[[ -z $match ]] && continue
local -A v=("${(@0)${match#*$bs}}")
local -a args=("${(@ps:\1:)v[args]}")
[[ -z $args[1] ]] && args=() # don't pass an empty string
IPREFIX=${v[IPREFIX]-} PREFIX=${v[PREFIX]-} SUFFIX=${v[SUFFIX]-} ISUFFIX=${v[ISUFFIX]-}
builtin compadd "${args[@]:--Q}" -Q -- "$v[word]"
done
compstate[list]=
if (( $#_ftb_choices == 1 )); then
compstate[insert]='1'
[[ $RBUFFER == ' '* ]] || compstate[insert]+=' '
elif (( $#_ftb_choices > 1 )); then
compstate[insert]='all'
fi
}
fzf-tab-debug() {
(( $+_ftb_debug_cnt )) || typeset -gi _ftb_debug_cnt
local tmp=${TMPPREFIX:-/tmp/zsh}-$$-fzf-tab-$(( ++_ftb_debug_cnt )).log
local -i debug_fd=-1 IN_FZF_TAB=1
{
exec {debug_fd}>&2 2>| $tmp
local -a debug_indent; debug_indent=( '%'{3..20}'(e. .)' )
local PROMPT4 PS4="${(j::)debug_indent}+%N:%i> "
functions -t -- -ftb-complete _fzf-tab-apply fzf-tab-complete
{
echo $ZSH_NAME $ZSH_VERSION
echo fzf-tab: $(-ftb-version)
typeset -p FZF_DEFAULT_OPTS
echo $commands[fzf] $(fzf --version)
} >&2
zle fzf-tab-complete
if (( debug_fd != -1 )); then
zle -M "fzf-tab-debug: Trace output left in $tmp"
fi
} always {
functions +t -- -ftb-complete _fzf-tab-apply fzf-tab-complete
(( debug_fd != -1 )) && exec 2>&$debug_fd {debug_fd}>&-
}
}
fzf-tab-complete() {
# this name must be ugly to avoid clashes
local -i _ftb_continue=1 _ftb_continue_last=0 _ftb_accept=0 ret=0
# hide the cursor until finishing completion, so that users won't see cursor up and down
# NOTE: MacOS Terminal doesn't support civis & cnorm
echoti civis >/dev/tty 2>/dev/null
while (( _ftb_continue )); do
local _ftb_choices=() _ftb_compcap=() _ftb_finish=0
_ftb_continue=0
local IN_FZF_TAB=1
{
zle .fzf-tab-orig-$_ftb_orig_widget || ret=$?
# The original completion widget (expand-or-complete or its wrappers)
# can return non-zero for ambiguous/list-only completions; apply choices
# when present. Also run apply on successful no-choice runs to clear list state
# inside the completion context (avoids duplicate warnings).
if (( ! _ftb_finish )) && { (( $#_ftb_choices )) || (( ! ret )); }; then
zle _fzf-tab-apply || ret=$?
fi
} always {
IN_FZF_TAB=0
}
if (( _ftb_continue )); then
zle .split-undo
zle .reset-prompt
zle -R
zle fzf-tab-dummy
fi
done
echoti cnorm >/dev/tty 2>/dev/null
zle .redisplay
(( _ftb_accept )) && zle .accept-line
return $ret
}
# this function does nothing, it is used to be wrapped by other plugins like f-sy-h.
# this make it possible to call the wrapper function without causing any other side effects.
fzf-tab-dummy() { }
zle -N fzf-tab-debug
zle -N fzf-tab-complete
zle -N fzf-tab-dummy
# this is registered as a completion widget
# so that we can have a clean completion list to only insert the results user selected
zle -C _fzf-tab-apply complete-word _fzf-tab-apply
disable-fzf-tab() {
emulate -L zsh -o extended_glob
(( $+_ftb_orig_widget )) || return 0
bindkey '^I' $_ftb_orig_widget
case $_ftb_orig_list_grouped in
0) zstyle ':completion:*' list-grouped false ;;
1) zstyle ':completion:*' list-grouped true ;;
2) zstyle -d ':completion:*' list-grouped ;;
esac
unset _ftb_orig_widget _ftb_orig_list_groupded
# unhook compadd so that _approximate can work properply
unfunction compadd 2>/dev/null
functions[_main_complete]=$functions[_ftb__main_complete]
functions[_approximate]=$functions[_ftb_approximate_orig]
# Don't remove .fzf-tab-orig-$_ftb_orig_widget as we won't be able to reliably
# create it if enable-fzf-tab is called again.
}
enable-fzf-tab() {
emulate -L zsh -o extended_glob
(( ! $+_ftb_orig_widget )) || disable-fzf-tab
typeset -g _ftb_orig_widget="${${$(builtin bindkey '^I')##* }:-expand-or-complete}"
if (( ! $+widgets[.fzf-tab-orig-$_ftb_orig_widget] )); then
# Widgets that get replaced by compinit.
local compinit_widgets=(
complete-word
delete-char-or-list
expand-or-complete
expand-or-complete-prefix
list-choices
menu-complete
menu-expand-or-complete
reverse-menu-complete
)
# Note: We prefix the name of the widget with '.' so that it doesn't get wrapped.
if [[ $widgets[$_ftb_orig_widget] == builtin &&
$compinit_widgets[(Ie)$_ftb_orig_widget] != 0 ]]; then
# We are initializing before compinit and being asked to fall back to a completion
# widget that isn't defined yet. Create our own copy of the widget ahead of time.
zle -C .fzf-tab-orig-$_ftb_orig_widget .$_ftb_orig_widget _main_complete
else
# Copy the widget before it's wrapped by zsh-autosuggestions and zsh-syntax-highlighting.
zle -A $_ftb_orig_widget .fzf-tab-orig-$_ftb_orig_widget
fi
fi
zstyle -t ':completion:*' list-grouped false
typeset -g _ftb_orig_list_grouped=$?
zstyle ':completion:*' list-grouped false
bindkey -M emacs '^I' fzf-tab-complete
bindkey -M viins '^I' fzf-tab-complete
bindkey -M emacs '^X.' fzf-tab-debug
bindkey -M viins '^X.' fzf-tab-debug
# make sure we can copy them
autoload +X -Uz _main_complete _approximate
# hook compadd
functions[compadd]=$functions[-ftb-compadd]
# hook _main_complete to trigger fzf-tab
functions[_ftb__main_complete]=$functions[_main_complete]
function _main_complete() { -ftb-complete "$@" }
# -----------------------------------------------------------------------
# DYNAMIC PATCH FOR _approximate
# -----------------------------------------------------------------------
# 1. Save original for disable-fzf-tab
functions[_ftb_approximate_orig]=$functions[_approximate]
# 2. Force load _approximate so we can see its source
autoload +X -Uz _approximate
# 3. Create the patched function
# We read the source code of _approximate and replace 'builtin compadd'
# with '-ftb-compadd'. The [[:space:]]## handles potential tabs/spaces.
functions[_ftb_approximate_patched]="${functions[_approximate]//builtin[[:space:]]##compadd/-ftb-compadd}"
# 4. Define the wrapper
function _approximate() {
# Temporarily remove fzf-tab's global compadd hook so that _approximate
# can function correctly. Here is why this is needed:
#
# _approximate contains a safeguard: `if (( ! $+functions[compadd] ))`.
# In normal zsh usage, compadd is a builtin with no user-defined function
# shadowing it, so this check is always true and _approximate proceeds to
# define its own local compadd wrapper — one that injects the fuzzy glob
# flag (#a1) into PREFIX before calling builtin compadd. This is the core
# mechanism that makes approximate matching work.
#
# The safeguard is meant to skip this step if the user has already defined
# a compadd function. fzf-tab is precisely such a case: it redefines compadd
# globally as -ftb-compadd. This causes the check to be false, preventing
# _approximate from defining its local wrapper and breaking approximate
# matching entirely.
#
# The cleanest solution is to temporarily unfunction compadd before calling
# _approximate, making the safeguard evaluate to true again. The hook is
# then restored in the always block below. Note that _approximate itself
# uses the same mechanics internally: it tracks whether it defined the local
# compadd wrapper and removes it in its own always block when done.
unfunction compadd
{
if (( IN_FZF_TAB )); then
# fzf-tab is active: call the patched version where
# builtin compadd → -ftb-compadd, so fzf-tab can capture corrections.
_ftb_approximate_patched "$@"
else
# Normal completion (fzf-tab not triggered): use the original
# _approximate so that curcontext and zstyle rules are fully preserved.
_ftb_approximate_orig "$@"
fi
} always {
# Restore fzf-tab's global hook.
functions[compadd]=$functions[-ftb-compadd]
}
}
}
toggle-fzf-tab() {
emulate -L zsh -o extended_glob
if (( $+_ftb_orig_widget )); then
disable-fzf-tab
else
enable-fzf-tab
fi
}
build-fzf-tab-module() {
{
pushd -q $FZF_TAB_HOME/modules
if -ftb-build-module $@; then
print -P "%F{green}%BThe module has been built successfully. Please restart zsh to apply it.%f%b"
else
print -P -u2 "%F{red}%BThe module building has failed. See the output above for details.%f%b"
return 1
fi
} always {
popd -q
}
}
zmodload zsh/zutil
zmodload zsh/mapfile
zmodload -F zsh/stat b:zstat
0="${${ZERO:-${0:#$ZSH_ARGZERO}}:-${(%):-%N}}"
0="${${(M)0:#/*}:-$PWD/$0}"
FZF_TAB_HOME="${0:A:h}"
typeset -ga _ftb_group_colors=(
$'\x1b[94m' $'\x1b[32m' $'\x1b[33m' $'\x1b[35m' $'\x1b[31m' $'\x1b[38;5;27m' $'\x1b[36m'
$'\x1b[38;5;100m' $'\x1b[38;5;98m' $'\x1b[91m' $'\x1b[38;5;80m' $'\x1b[92m'
$'\x1b[38;5;214m' $'\x1b[38;5;165m' $'\x1b[38;5;124m' $'\x1b[38;5;120m'
)
# init
() {
emulate -L zsh -o extended_glob
# Ensure we don't keep a stale value around.
# If the module is loaded, the variable is valid; unsetting would lose it
# since zmodload won't reload an already-loaded module.
if ! zmodload -e aloxaf/fzftab; then
unset FZF_TAB_MODULE_VERSION 2>/dev/null
fi
if (( ! $fpath[(I)$FZF_TAB_HOME/lib] )); then
fpath+=($FZF_TAB_HOME/lib)
fi
autoload -Uz is-at-least -- $FZF_TAB_HOME/lib/-#ftb*(:t)
if (( $+FZF_TAB_COMMAND || $+FZF_TAB_OPTS || $+FZF_TAB_QUERY || $+FZF_TAB_SINGLE_GROUP || $+fzf_tab_preview_init )) \
|| zstyle -m ":fzf-tab:*" command '*' \
|| zstyle -m ":fzf-tab:*" extra-opts '*'; then
print -P "%F{red}%B[fzf-tab] Sorry, your configuration is not supported anymore\n" \
"See https://github.com/Aloxaf/fzf-tab/pull/132 for more information%f%b"
fi
if [[ -n $FZF_TAB_HOME/modules/Src/aloxaf/fzftab.(so|bundle)(#qN) ]]; then
module_path+=("$FZF_TAB_HOME/modules/Src")
zmodload aloxaf/fzftab # if this fails, we fall back to ls-colors.zsh below
if [[ $FZF_TAB_MODULE_VERSION != "0.2.2" ]]; then
zmodload -u aloxaf/fzftab
local rebuild
print -Pn "%F{yellow}fzftab module needs to be rebuild, rebuild now?[Y/n]:%f"
read -q rebuild
if [[ $rebuild == y ]]; then
build-fzf-tab-module
zmodload aloxaf/fzftab
fi
fi
fi
# Only needed when the module is not available (or failed to load).
[[ $FZF_TAB_MODULE_VERSION = "0.2.2" ]] || source "$FZF_TAB_HOME"/lib/zsh-ls-colors/ls-colors.zsh fzf-tab-lscolors
}
enable-fzf-tab
zle -N toggle-fzf-tab
# restore aliases
eval "$_ftb_aliases"
builtin unset _ftb_aliases
# restore options
(( ${#_ftb_opts} )) && setopt ${_ftb_opts[@]}
'builtin' 'unset' '_ftb_opts'