Zsh Mailing List Archive
Messages sorted by:
Reverse Date,
Date,
Thread,
Author
[PATCH] _sudo: fix handling of envvar assignments
- X-seq: zsh-workers 54939
- From: dana <dana@xxxxxxx>
- To: zsh-workers@xxxxxxx
- Subject: [PATCH] _sudo: fix handling of envvar assignments
- Date: Fri, 10 Jul 2026 02:31:24 -0500
- Archived-at: <https://zsh.org/workers/54939>
- Feedback-id: i9be146f9:Fastmail
- List-id: <zsh-workers.zsh.org>
sudo completion has been broken since w/52260 (try just sudo <TAB>)
i think the approach there was informed by the sudo documentation's
implication that variable assignments have to appear before -i/-s. but
that's not the case. options and assignments can be interspersed in
any order or combination
in light of that, something like this?
dana
ps: i feel like `_command_names -e` should also complete aliases when
the preceding command name is itself an alias with a trailing space.
maybe it could have another option where you pass your $words[1] and it
checks it for you?
diff --git a/Completion/Unix/Command/_sudo b/Completion/Unix/Command/_sudo
index 33a9d6c0c..e3b3565ef 100644
--- a/Completion/Unix/Command/_sudo
+++ b/Completion/Unix/Command/_sudo
@@ -1,7 +1,7 @@
#compdef sudo sudoedit
local curcontext="$curcontext" environ e cmd cpp ret=1
-local -a context state line args _comp_priv_prefix
+local -a context state state_descr line args _comp_priv_prefix
local -A opt_args
zstyle -a ":completion:${curcontext}:" environ environ
@@ -41,7 +41,8 @@ args=(
#
# TODO: use _arguments' $opt_args to detect the cases '-u jrandom -e' and '-Ae'
if [[ $service = sudoedit ]] || (( $words[(i)-e] < $words[(i)^(*sudo|-[^-]*)] )) ; then
- args=( -A "-*" $args '!(-V --version -h --help)-e' '*:file:_files' )
+ args+=( '!(-V --version -h --help)-e' '*: :_files' )
+ _arguments -s -S -A '-?*' : $args && ret=0
else
cmd="$words[1]"
args+=(
@@ -53,38 +54,41 @@ else
'(-E -i --login -s --shell -e --edit)--preserve-env=-[preserve user environment when running command]::environment variable:_sequence _parameters -g "*export*"' \
'(-H --set-home -i --login -s --shell -e --edit)'{-H,--set-home}"[set HOME variable to target user's home dir]" \
'(-P --preserve-groups -i --login -s --shell -e --edit)'{-P,--preserve-groups}"[preserve group vector instead of setting to target's]" \
- '*:: :->normal'
+ '*: :->normal'
)
+ # sudo allows environment-variable assignments to be interspersed with
+ # options. this pattern represents the lax method it uses to determine whether
+ # an arg is an assignment
+ _arguments -s -S -A '(-?*|[^/=]*=*)' : $args && ret=0
fi
-_arguments -s -S $args && ret=0
-
if [[ $state = normal ]]; then
_comp_priv_prefix=(
$cmd -n
${(kv)opt_args[(I)(-[ugHEP]|--(user|group|set-home|preserve-env|preserve-groups))]}
)
+
+ # there's no special handling for assignments with -l. they're parsed as
+ # normal, just ignored. we'll ignore them too
if (( $+opt_args[-l] || $+opt_args[--list] )); then
+ CURRENT=$#line words=( "${(@)line}" )
_normal -p $service
return
fi
- while [[ $words[1] = *=* ]]; do
- if (( CURRENT == 1 )); then
- compstate[parameter]="${PREFIX%%\=*}"
- compset -P 1 '*='
- _value && ret=0
- return ret
- fi
- shift words
- (( CURRENT-- ))
- done
- if (( CURRENT == 1 )); then
+
+ # $line excludes words that match the -A pattern, except when it's the current
+ # word. so if $#line == 1 we haven't seen a command yet
+ if (( $#line == 1 )) && [[ $PREFIX == *=* ]]; then
+ compstate[parameter]="${PREFIX%%\=*}"
+ compset -P 1 '*='
+ _value && ret=0
+ elif (( $#line == 1 )); then
curcontext="${curcontext%:*:*}:-command-:"
_alternative \
'commands:: _command_names -e' \
- 'options:option:(-s --shell -l --login)' \
'parameters: :_parameters -g "*export*~*readonly*" -qS=' && ret=0
else
+ CURRENT=$#line words=( "${(@)line}" )
_normal && ret=0
fi
fi
Messages sorted by:
Reverse Date,
Date,
Thread,
Author