Skip to content

Commit 6de320a

Browse files
ThomasWaldmanncasperdcl
authored andcommitted
zsh: prevent duplicate default REMAINDER spec in generated completions
When completing commands with a subparser that uses nargs=REMAINDER, zsh could emit: _arguments:comparguments:327: doubled rest argument definition: *::: :->repro Root cause: - The zsh template appended the default positional/rest specs (`': :{prefix}_commands'` and `'*::: :->{name}'`) on every function invocation. It only checked for generic variadic/remainder tokens (`(*)`, `(-)*`) and not whether the exact default entry (`*::: :->{name}`) had already been added. Repeated invocations led to duplicated rest specs and the `_arguments` error above. Fix: - Add a per-function guard variable `{prefix}_defaults_added`, initialized alongside `{prefix}_options` and set to `1` after the first append. This guarantees that the default `'*::: :->{name}'` is added at most once per session. - Keep (and extend) the presence checks: only append the defaults if neither a variadic/rest token (`(*)`, `(-)*`) nor the exact default entry is already present. Impact: - Eliminates duplicated `*::: :->…` entries and the “doubled rest argument definition” error in zsh, including across multiple invocations and after re-sourcing the file. - Generated zsh output is unchanged except for the guard variable lines.
1 parent 2f87e26 commit 6de320a

File tree

1 file changed

+10
-3
lines changed

1 file changed

+10
-3
lines changed

shtab/__init__.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -606,10 +606,14 @@ def command_case(prefix, options):
606606

607607
return f"""\
608608
{prefix}() {{
609-
local context state line curcontext="$curcontext" one_or_more='(*)' remainder='(-)*'
609+
local context state line curcontext="$curcontext" one_or_more='(*)' remainder='(-)*' default='*::: :->{name}'
610610
611-
if ((${{{prefix}_options[(I)${{(q)one_or_more}}*]}} + ${{{prefix}_options[(I)${{(q)remainder}}*]}} == 0)); then # noqa: E501
612-
{prefix}_options+=(': :{prefix}_commands' '*::: :->{name}')
611+
# Add default positional/remainder specs only if none exist, and only once per session
612+
if (( ! {prefix}_defaults_added )); then
613+
if (( ${{{prefix}_options[(I)${{(q)one_or_more}}*]}} + ${{{prefix}_options[(I)${{(q)remainder}}*]}} + ${{{prefix}_options[(I)${{(q)default}}]}} == 0 )); then
614+
{prefix}_options+=(': :{prefix}_commands' '*::: :->{name}')
615+
fi
616+
{prefix}_defaults_added=1
613617
fi
614618
_arguments -C -s ${prefix}_options
615619
@@ -631,6 +635,9 @@ def command_option(prefix, options):
631635
{prefix}_options=(
632636
{arguments}
633637
)
638+
639+
# guard to ensure default positional specs are added only once per session
640+
{prefix}_defaults_added=0
634641
"""
635642

636643
def command_list(prefix, options):

0 commit comments

Comments
 (0)