usage g completion-init zsh (still) breaks default completion with _files (followup of #692)
- Dominant language
- Rust
- Stars
- 1k
- Forks
- 59
- Avg merge
- 5h 58m
- Merged PRs (30d)
- 362
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 --[completion inserts "\*" here]
>
> $ source <(usage g completion-init zsh | sed -e 's/emulate -L zsh//')
> $ emacs --[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:
>
> ```zsh
> _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:
>
> ```zsh
> emulate -L zsh
> ```
>
> from the top of the function to inside this branch:
>
> ```zsh
> 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](https://github.com/jdx/usage/issues/692#issuecomment-4787138514)_
Contributor guide
Research direction
Start with `usage g completion-init zsh` and the generated `_usage_default_complete` entry point shown in the issue. Reproduce the `emacs --` 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.
Assessment
- Tech stack
- zsh
- Domain
- cli
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100