Skip to content

Commit 0f59d55

Browse files
authored
Merge pull request #6027 from Alpha1337k/master
Fix: external_subcommand does not 'tab complete' in ZSH
2 parents 3716f9f + e2aa2f0 commit 0f59d55

File tree

12 files changed

+411
-1
lines changed

12 files changed

+411
-1
lines changed

clap_complete/src/aot/shells/zsh.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,11 @@ fn get_args_of(parent: &Command, p_global: Option<&Command>) -> String {
353353

354354
let subcommand_text = format!("\"*::: :->{name}\" \\", name = parent.get_name());
355355
segments.push(subcommand_text);
356-
};
356+
} else if parent.is_allow_external_subcommands_set() {
357+
// If the command has an external subcommand value parser, we need to
358+
// add a catch-all for the subcommand. Otherwise there would be no autocompletion whatsoever.
359+
segments.push(String::from("\"*::external_command:_default\" \\"));
360+
}
357361

358362
segments.push(String::from("&& ret=0"));
359363
segments.join("\n")
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
_my-app() {
2+
local i cur prev opts cmd
3+
COMPREPLY=()
4+
if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then
5+
cur="$2"
6+
else
7+
cur="${COMP_WORDS[COMP_CWORD]}"
8+
fi
9+
prev="$3"
10+
cmd=""
11+
opts=""
12+
13+
for i in "${COMP_WORDS[@]:0:COMP_CWORD}"
14+
do
15+
case "${cmd},${i}" in
16+
",$1")
17+
cmd="my__app"
18+
;;
19+
my__app,external)
20+
cmd="my__app__external"
21+
;;
22+
my__app,help)
23+
cmd="my__app__help"
24+
;;
25+
my__app__help,external)
26+
cmd="my__app__help__external"
27+
;;
28+
my__app__help,help)
29+
cmd="my__app__help__help"
30+
;;
31+
*)
32+
;;
33+
esac
34+
done
35+
36+
case "${cmd}" in
37+
my__app)
38+
opts="-h --help external help"
39+
if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then
40+
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
41+
return 0
42+
fi
43+
case "${prev}" in
44+
*)
45+
COMPREPLY=()
46+
;;
47+
esac
48+
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
49+
return 0
50+
;;
51+
my__app__external)
52+
opts="-h --help"
53+
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
54+
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
55+
return 0
56+
fi
57+
case "${prev}" in
58+
*)
59+
COMPREPLY=()
60+
;;
61+
esac
62+
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
63+
return 0
64+
;;
65+
my__app__help)
66+
opts="external help"
67+
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
68+
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
69+
return 0
70+
fi
71+
case "${prev}" in
72+
*)
73+
COMPREPLY=()
74+
;;
75+
esac
76+
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
77+
return 0
78+
;;
79+
my__app__help__external)
80+
opts=""
81+
if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then
82+
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
83+
return 0
84+
fi
85+
case "${prev}" in
86+
*)
87+
COMPREPLY=()
88+
;;
89+
esac
90+
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
91+
return 0
92+
;;
93+
my__app__help__help)
94+
opts=""
95+
if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then
96+
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
97+
return 0
98+
fi
99+
case "${prev}" in
100+
*)
101+
COMPREPLY=()
102+
;;
103+
esac
104+
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
105+
return 0
106+
;;
107+
esac
108+
}
109+
110+
if [[ "${BASH_VERSINFO[0]}" -eq 4 && "${BASH_VERSINFO[1]}" -ge 4 || "${BASH_VERSINFO[0]}" -gt 4 ]]; then
111+
complete -F _my-app -o nosort -o bashdefault -o default my-app
112+
else
113+
complete -F _my-app -o bashdefault -o default my-app
114+
fi
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
2+
use builtin;
3+
use str;
4+
5+
set edit:completion:arg-completer[my-app] = {|@words|
6+
fn spaces {|n|
7+
builtin:repeat $n ' ' | str:join ''
8+
}
9+
fn cand {|text desc|
10+
edit:complex-candidate $text &display=$text' '(spaces (- 14 (wcswidth $text)))$desc
11+
}
12+
var command = 'my-app'
13+
for word $words[1..-1] {
14+
if (str:has-prefix $word '-') {
15+
break
16+
}
17+
set command = $command';'$word
18+
}
19+
var completions = [
20+
&'my-app'= {
21+
cand -h 'Print help'
22+
cand --help 'Print help'
23+
cand external 'An external subcommand'
24+
cand help 'Print this message or the help of the given subcommand(s)'
25+
}
26+
&'my-app;external'= {
27+
cand -h 'Print help'
28+
cand --help 'Print help'
29+
}
30+
&'my-app;help'= {
31+
cand external 'An external subcommand'
32+
cand help 'Print this message or the help of the given subcommand(s)'
33+
}
34+
&'my-app;help;external'= {
35+
}
36+
&'my-app;help;help'= {
37+
}
38+
]
39+
$completions[$command]
40+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Print an optspec for argparse to handle cmd's options that are independent of any subcommand.
2+
function __fish_my_app_global_optspecs
3+
string join \n h/help
4+
end
5+
6+
function __fish_my_app_needs_command
7+
# Figure out if the current invocation already has a command.
8+
set -l cmd (commandline -opc)
9+
set -e cmd[1]
10+
argparse -s (__fish_my_app_global_optspecs) -- $cmd 2>/dev/null
11+
or return
12+
if set -q argv[1]
13+
# Also print the command, so this can be used to figure out what it is.
14+
echo $argv[1]
15+
return 1
16+
end
17+
return 0
18+
end
19+
20+
function __fish_my_app_using_subcommand
21+
set -l cmd (__fish_my_app_needs_command)
22+
test -z "$cmd"
23+
and return 1
24+
contains -- $cmd[1] $argv
25+
end
26+
27+
complete -c my-app -n "__fish_my_app_needs_command" -s h -l help -d 'Print help'
28+
complete -c my-app -n "__fish_my_app_needs_command" -f -a "external" -d 'An external subcommand'
29+
complete -c my-app -n "__fish_my_app_needs_command" -f -a "help" -d 'Print this message or the help of the given subcommand(s)'
30+
complete -c my-app -n "__fish_my_app_using_subcommand external" -s h -l help -d 'Print help'
31+
complete -c my-app -n "__fish_my_app_using_subcommand help; and not __fish_seen_subcommand_from external help" -f -a "external" -d 'An external subcommand'
32+
complete -c my-app -n "__fish_my_app_using_subcommand help; and not __fish_seen_subcommand_from external help" -f -a "help" -d 'Print this message or the help of the given subcommand(s)'
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
2+
using namespace System.Management.Automation
3+
using namespace System.Management.Automation.Language
4+
5+
Register-ArgumentCompleter -Native -CommandName 'my-app' -ScriptBlock {
6+
param($wordToComplete, $commandAst, $cursorPosition)
7+
8+
$commandElements = $commandAst.CommandElements
9+
$command = @(
10+
'my-app'
11+
for ($i = 1; $i -lt $commandElements.Count; $i++) {
12+
$element = $commandElements[$i]
13+
if ($element -isnot [StringConstantExpressionAst] -or
14+
$element.StringConstantType -ne [StringConstantType]::BareWord -or
15+
$element.Value.StartsWith('-') -or
16+
$element.Value -eq $wordToComplete) {
17+
break
18+
}
19+
$element.Value
20+
}) -join ';'
21+
22+
$completions = @(switch ($command) {
23+
'my-app' {
24+
[CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help')
25+
[CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help')
26+
[CompletionResult]::new('external', 'external', [CompletionResultType]::ParameterValue, 'An external subcommand')
27+
[CompletionResult]::new('help', 'help', [CompletionResultType]::ParameterValue, 'Print this message or the help of the given subcommand(s)')
28+
break
29+
}
30+
'my-app;external' {
31+
[CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help')
32+
[CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help')
33+
break
34+
}
35+
'my-app;help' {
36+
[CompletionResult]::new('external', 'external', [CompletionResultType]::ParameterValue, 'An external subcommand')
37+
[CompletionResult]::new('help', 'help', [CompletionResultType]::ParameterValue, 'Print this message or the help of the given subcommand(s)')
38+
break
39+
}
40+
'my-app;help;external' {
41+
break
42+
}
43+
'my-app;help;help' {
44+
break
45+
}
46+
})
47+
48+
$completions.Where{ $_.CompletionText -like "$wordToComplete*" } |
49+
Sort-Object -Property ListItemText
50+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
#compdef my-app
2+
3+
autoload -U is-at-least
4+
5+
_my-app() {
6+
typeset -A opt_args
7+
typeset -a _arguments_options
8+
local ret=1
9+
10+
if is-at-least 5.2; then
11+
_arguments_options=(-s -S -C)
12+
else
13+
_arguments_options=(-s -C)
14+
fi
15+
16+
local context curcontext="$curcontext" state line
17+
_arguments "${_arguments_options[@]}" : \
18+
'-h[Print help]' \
19+
'--help[Print help]' \
20+
":: :_my-app_commands" \
21+
"*::: :->my-app" \
22+
&& ret=0
23+
case $state in
24+
(my-app)
25+
words=($line[1] "${words[@]}")
26+
(( CURRENT += 1 ))
27+
curcontext="${curcontext%:*:*}:my-app-command-$line[1]:"
28+
case $line[1] in
29+
(external)
30+
_arguments "${_arguments_options[@]}" : \
31+
'-h[Print help]' \
32+
'--help[Print help]' \
33+
"*::external_command:_default" \
34+
&& ret=0
35+
;;
36+
(help)
37+
_arguments "${_arguments_options[@]}" : \
38+
":: :_my-app__help_commands" \
39+
"*::: :->help" \
40+
&& ret=0
41+
42+
case $state in
43+
(help)
44+
words=($line[1] "${words[@]}")
45+
(( CURRENT += 1 ))
46+
curcontext="${curcontext%:*:*}:my-app-help-command-$line[1]:"
47+
case $line[1] in
48+
(external)
49+
_arguments "${_arguments_options[@]}" : \
50+
&& ret=0
51+
;;
52+
(help)
53+
_arguments "${_arguments_options[@]}" : \
54+
&& ret=0
55+
;;
56+
esac
57+
;;
58+
esac
59+
;;
60+
esac
61+
;;
62+
esac
63+
}
64+
65+
(( $+functions[_my-app_commands] )) ||
66+
_my-app_commands() {
67+
local commands; commands=(
68+
'external:An external subcommand' \
69+
'help:Print this message or the help of the given subcommand(s)' \
70+
)
71+
_describe -t commands 'my-app commands' commands "$@"
72+
}
73+
(( $+functions[_my-app__external_commands] )) ||
74+
_my-app__external_commands() {
75+
local commands; commands=()
76+
_describe -t commands 'my-app external commands' commands "$@"
77+
}
78+
(( $+functions[_my-app__help_commands] )) ||
79+
_my-app__help_commands() {
80+
local commands; commands=(
81+
'external:An external subcommand' \
82+
'help:Print this message or the help of the given subcommand(s)' \
83+
)
84+
_describe -t commands 'my-app help commands' commands "$@"
85+
}
86+
(( $+functions[_my-app__help__external_commands] )) ||
87+
_my-app__help__external_commands() {
88+
local commands; commands=()
89+
_describe -t commands 'my-app help external commands' commands "$@"
90+
}
91+
(( $+functions[_my-app__help__help_commands] )) ||
92+
_my-app__help__help_commands() {
93+
local commands; commands=()
94+
_describe -t commands 'my-app help help commands' commands "$@"
95+
}
96+
97+
if [ "$funcstack[1]" = "_my-app" ]; then
98+
_my-app "$@"
99+
else
100+
compdef _my-app my-app
101+
fi

clap_complete/tests/testsuite/bash.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,18 @@ fn sub_subcommands() {
8282
);
8383
}
8484

85+
#[test]
86+
fn external_subcommands() {
87+
let name = "my-app";
88+
let cmd = common::external_subcommand(name);
89+
common::assert_matches(
90+
snapbox::file!["../snapshots/external_subcommands.bash"],
91+
clap_complete::shells::Bash,
92+
cmd,
93+
name,
94+
);
95+
}
96+
8597
#[test]
8698
fn custom_bin_name() {
8799
let name = "my-app";

clap_complete/tests/testsuite/common.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,15 @@ pub(crate) fn special_commands_command(name: &'static str) -> clap::Command {
7575
.subcommand(clap::Command::new("some-hidden-cmd").hide(true))
7676
}
7777

78+
pub(crate) fn external_subcommand(name: &'static str) -> clap::Command {
79+
clap::Command::new(name)
80+
.subcommand(
81+
clap::Command::new("external")
82+
.allow_external_subcommands(true)
83+
.about("An external subcommand")
84+
)
85+
}
86+
7887
pub(crate) fn quoting_command(name: &'static str) -> clap::Command {
7988
clap::Command::new(name)
8089
.version("3.0")

0 commit comments

Comments
 (0)