-
Notifications
You must be signed in to change notification settings - Fork 127
[BUG?] fzf never shown when subsequence-based unambiguous expansion biases toward a single match #560
Description
Problem
When using the default Oh My Zsh matcher-list (which includes l:|=* r:|=* for substring matching), fzf-tab can get stuck in a loop of inserting ever-growing "unambiguous" prefixes without ever showing the fzf popup, even when there are multiple matches.
This happens because zsh's l:|=* r:|=* matcher does subsequence matching (characters in order with gaps), not contiguous substring matching. The compstate[unambiguous] value computed by zsh can be a contiguous substring of one match but only a subsequence of another. fzf-tab trusts this value and inserts it, progressively committing toward one specific match.
Reproduction
Given directories:
drwxr-xr-x project_alpha_1234_final
drwxr-xr-x project_al_pha_benchmark_1234
With the default OMZ matcher-list:
zstyle ':completion:*' matcher-list 'm:{[:lower:][:upper:]}={[:upper:][:lower:]}' 'r:|=*' 'l:|=* r:|=*'- Type
vim somedir/1234and press Tab - fzf-tab inserts
_1234(unambiguous — both names contain_1234contiguously) →vim somedir/_1234 - Press Tab again
- Expected: fzf popup showing both matches
- Actual: fzf-tab inserts a longer "unambiguous" expansion like
alpha_1234— which is contiguous in the first filename but only a subsequence in the second (al...pha...1234). fzf never appears. - Pressing Tab again completes to the first match entirely. The second match was never offered as a choice.
Root cause
The decision logic in -ftb-complete (the * case around line 126) unconditionally trusts compstate[unambiguous]:
if (( ! _ftb_continue_last )) \
&& [[ $compstate[insert] == *"unambiguous" ]] \
&& [[ -n $compstate[unambiguous] ]] \
&& [[ "$compstate[unambiguous]" != "$compstate[quote]$IPREFIX$PREFIX$compstate[quote]" ]]; then
compstate[list]=
compstate[insert]=unambiguous
_ftb_finish=1
return 0
fiWith r:|=*, zsh allows extra characters between each typed character when computing the unambiguous expansion. This means compstate[unambiguous] can grow to a string that is only a subsequence match for some completions. Each Tab press produces a longer expansion biased toward one particular match, and fzf-tab keeps inserting it because the value always differs from what's currently typed.
This was confirmed using fzf-tab-debug (Ctrl-X .). The trace shows nmatches 2 but compstate[unambiguous] expanding to a value that is not a contiguous substring of all matches.
Proposed fix
Before inserting, validate that the unambiguous expansion is a contiguous substring of every matched word. If any match doesn't contain it contiguously, skip the insertion and fall through to the fzf popup.
In fzf-tab.zsh, replace the unambiguous check block (the * case around line 126) with:
*)
if (( ! _ftb_continue_last )) \
&& [[ $compstate[insert] == *"unambiguous" ]] \
&& [[ -n $compstate[unambiguous] ]] \
&& [[ "$compstate[unambiguous]" != "$compstate[quote]$IPREFIX$PREFIX$compstate[quote]" ]]; then
# Validate: only insert if the unambiguous expansion is a contiguous
# substring of ALL matched words. This prevents the subsequence-based
# expansion from r:|=* biasing toward a single match.
local _ftb_unamb_file="${compstate[unambiguous]##*/}"
local _ftb_unamb_valid=1 _ftb_cap_entry
for _ftb_cap_entry in "${_ftb_compcap[@]}"; do
if [[ "${_ftb_cap_entry%%$bs*}" != *"${_ftb_unamb_file}"* ]]; then
_ftb_unamb_valid=0
break
fi
done
if (( _ftb_unamb_valid )); then
compstate[list]=
compstate[insert]=unambiguous
_ftb_finish=1
return 0
fi
fiThis is a conservative change: it never inserts something the old code wouldn't have. It only refrains from inserting when the expansion is misleading, falling through to the fzf popup instead. The typical case (common prefix shared contiguously by all matches) is unaffected.
Environment
- zsh 5.9
- fzf-tab v1.2.0
- fzf 0.67
- Oh My Zsh default
matcher-list