usage g completion-init zsh (still) breaks default completion with _files (followup of #692)

Open
#712 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
3/5
Estimated time
1-2 days
Newbie friendliness
55/100
Issue type
Bug
Clarity
Mostly clear
Activity status
Quiet
Tech stack
zsh
Domain
cli

Research direction

Start with usage g completion-init zsh and the generated _usage_default_complete entry point shown in the issue. Reproduce the emacs --<TAB> fallback behavior with and without emulate -L zsh, then verify that default _files completion no longer inserts * or fails while usage completions still work.

Written by the indexing model from the issue text.

Description

Something still isn't quite right. With the fix, I don't get the error message anymore, for sure. But:

$ source <(usage g completion-init zsh)
$ emacs --<TAB>[completion inserts "\*" here]

$ source <(usage g completion-init zsh | sed -e 's/emulate -L zsh//')
$ emacs --<TAB>[bell, nothing is inserted]

I asked the AI again:

That confirms the diagnosis: emulate -L zsh is clobbering the completion system’s expected option state.

The * insertion is a second symptom of the same thing. Adding nonomatch only suppresses the error; it lets the bad fallback proceed, so _files treats the internal * pattern as a literal candidate and inserts it.

Better fix

Do not call _files after emulate -L zsh.

Instead, preserve the caller’s completion options around the generated code, or only use emulate in the branch that calls usage.

Try this structure:

_usage_default_complete() {
    local cmd cmdpath
    cmd="${words[1]}"

    if [[ "$cmd" == */* ]]; then
        cmdpath="$cmd"
    elif (( ${+commands[$cmd]} )); then
        cmdpath="${commands[$cmd]}"
    fi

    if [[ -n "$cmdpath" && -f "$cmdpath" ]]; then
        local first
        if IFS= read -r first < "$cmdpath" 2>/dev/null && [[ "$first" == "#!"*"usage"* ]]; then
            if (( ${+commands[usage]} )); then
                emulate -L zsh

                local -a values=() descs=() inserts=()
                local needs_menu=0 line
                while IFS= read -r line; do
                  local -a parts=("${(@ps:\t:)line}")
                  values+=("${parts[1]}")
                  descs+=("${parts[2]}")
                  inserts+=("${parts[3]}")
                  [[ "${parts[3]}" == "'"* ]] && needs_menu=1
                done < <(command usage complete-word --shell zsh -f "$cmdpath" --cword=$((CURRENT - 1)) -- "${(Q)words[@]}")

                (( needs_menu )) && compstate[insert]=menu

                if (( ${#inserts[@]} )); then
                  local -a _usage_display=()
                  local _usage_i _usage_max=0 _usage_v _usage_pad
                  for _usage_v in "${values[@]}"; do
                    (( ${#_usage_v} > _usage_max )) && _usage_max=${#_usage_v}
                  done
                  for ((_usage_i=1; _usage_i<=${#values[@]}; _usage_i++)); do
                    if [[ -n "${descs[_usage_i]}" ]]; then
                      _usage_pad=$(( _usage_max - ${#values[_usage_i]} ))
                      _usage_display+=("${values[_usage_i]}${(l:_usage_pad:: :)}  -- ${descs[_usage_i]}")
                    else
                      _usage_display+=("${values[_usage_i]}")
                    fi
                  done
                  compadd -l -d _usage_display -U -Q -S '' -a inserts
                fi
                return $?
            fi
        fi
    fi

    _files
}

Smaller patch

Move:

emulate -L zsh

from the top of the function to inside this branch:

if (( ${+commands[usage]} )); then
    emulate -L zsh
    ...
fi

That way _files runs in the original completion environment.

Why deleting it works

Completion functions are already called in a special context. _files expects that context. Top-level emulate -L zsh resets options too broadly, so fallback completion breaks.

setopt nonomatch masks the exception but leaves the completion semantics wrong, hence literal * insertion.

Originally posted by @dhanak in #692

Dominant language
Rust
Stars
1k
Forks
59
Avg merge
5h 58m
Merged PRs (30d)
362

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from jdx/usage

All issues in jdx/usage

Similar issues

More Rust issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.