Skip to content

[BUG?] fzf never shown when subsequence-based unambiguous expansion biases toward a single match #560

@MahdiNazemi

Description

@MahdiNazemi

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:|=*'
  1. Type vim somedir/1234 and press Tab
  2. fzf-tab inserts _1234 (unambiguous — both names contain _1234 contiguously) → vim somedir/_1234
  3. Press Tab again
  4. Expected: fzf popup showing both matches
  5. 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.
  6. 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
fi

With 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
      fi

This 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions