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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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