Skip to content

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

Description

@dhanak

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions