Skip to content

Commit

Permalink
feat(complete): Add autocomplete for visible_alias
Browse files Browse the repository at this point in the history
Let's generate autocompletions for aliased subcommands.

    $ source before.zsh
    $ clap-test [TAB] <- gives me "foo bar --"
    $ clap-test foo [TAB] <- gives me "--my-flag"
    $ clap-test bar [TAB] <- no reaction

    $ source after.zsh
    $ clap-test [TAB] <- gives me "foo bar --"
    $ clap-test foo [TAB] <- gives me "--my-flag"
    $ clap-test bar [TAB] <- gives me "--my-flag"
  • Loading branch information
pzmarzly committed May 10, 2024
1 parent ece9726 commit 8a622a9
Show file tree
Hide file tree
Showing 10 changed files with 210 additions and 44 deletions.
7 changes: 7 additions & 0 deletions clap_builder/src/builder/command.rs
Expand Up @@ -3438,6 +3438,13 @@ impl Command {
&self.name
}

/// Get all known names of the cmd (i.e. primary name and visible aliases).
pub fn get_name_and_visible_aliases(&self) -> Vec<&str> {
let mut names = vec![self.name.as_str()];
names.extend(self.get_visible_aliases());
names
}

/// Get the version of the cmd.
#[inline]
pub fn get_version(&self) -> Option<&str> {
Expand Down
9 changes: 8 additions & 1 deletion clap_complete/src/generator/utils.rs
Expand Up @@ -47,8 +47,15 @@ pub fn subcommands(p: &Command) -> Vec<(String, String)> {
sc.get_name(),
sc_bin_name
);

subcmds.push((sc.get_name().to_string(), sc_bin_name.to_string()));

for alias in sc.get_visible_aliases() {
debug!(
"subcommands:iter: alias={}, bin_name={}",
alias, sc_bin_name
);
subcmds.push((alias.to_string(), sc_bin_name.to_string()));
}
}

subcmds
Expand Down
35 changes: 22 additions & 13 deletions clap_complete/src/shells/elvish.rs
Expand Up @@ -67,10 +67,13 @@ fn escape_help<T: ToString>(help: Option<&StyledStr>, data: T) -> String {
fn generate_inner(p: &Command, previous_command_name: &str) -> String {
debug!("generate_inner");

let command_name = if previous_command_name.is_empty() {
p.get_bin_name().expect(INTERNAL_ERROR_MSG).to_string()
let command_names = if previous_command_name.is_empty() {
vec![p.get_bin_name().expect(INTERNAL_ERROR_MSG).to_string()]
} else {
format!("{};{}", previous_command_name, &p.get_name())
p.get_name_and_visible_aliases()
.into_iter()
.map(|name| format!("{};{}", previous_command_name, name))
.collect()
};

let mut completions = String::new();
Expand Down Expand Up @@ -113,23 +116,29 @@ fn generate_inner(p: &Command, previous_command_name: &str) -> String {
}

for subcommand in p.get_subcommands() {
let data = &subcommand.get_name();
let tooltip = escape_help(subcommand.get_about(), data);
for name in subcommand.get_name_and_visible_aliases() {
let tooltip = escape_help(subcommand.get_about(), name);

completions.push_str(&preamble);
completions.push_str(format!("{data} '{tooltip}'").as_str());
completions.push_str(&preamble);
completions.push_str(format!("{name} '{tooltip}'").as_str());
}
}

let mut subcommands_cases = format!(
r"
let mut subcommands_cases = String::new();
for command_name in &command_names {
subcommands_cases.push_str(&format!(
r"
&'{}'= {{{}
}}",
&command_name, completions
);
&command_name, completions
));
}

for subcommand in p.get_subcommands() {
let subcommand_subcommands_cases = generate_inner(subcommand, &command_name);
subcommands_cases.push_str(&subcommand_subcommands_cases);
for command_name in &command_names {
let subcommand_subcommands_cases = generate_inner(subcommand, command_name);
subcommands_cases.push_str(&subcommand_subcommands_cases);
}
}

subcommands_cases
Expand Down
29 changes: 17 additions & 12 deletions clap_complete/src/shells/fish.rs
Expand Up @@ -75,7 +75,8 @@ fn gen_fish_inner(
.map(|command| format!("__fish_seen_subcommand_from {command}"))
.chain(
cmd.get_subcommands()
.map(|command| format!("not __fish_seen_subcommand_from {command}"))
.flat_map(Command::get_name_and_visible_aliases)
.map(|name| format!("not __fish_seen_subcommand_from {name}"))
)
.collect::<Vec<_>>()
.join("; and ")
Expand Down Expand Up @@ -135,24 +136,28 @@ fn gen_fish_inner(
}

for subcommand in cmd.get_subcommands() {
let mut template = basic_template.clone();
for subcommand_name in subcommand.get_name_and_visible_aliases() {
let mut template = basic_template.clone();

template.push_str(" -f");
template.push_str(format!(" -a \"{}\"", &subcommand.get_name()).as_str());
template.push_str(" -f");
template.push_str(format!(" -a \"{}\"", subcommand_name).as_str());

if let Some(data) = subcommand.get_about() {
template.push_str(format!(" -d '{}'", escape_help(data)).as_str())
}
if let Some(data) = subcommand.get_about() {
template.push_str(format!(" -d '{}'", escape_help(data)).as_str())
}

buffer.push_str(template.as_str());
buffer.push('\n');
buffer.push_str(template.as_str());
buffer.push('\n');
}
}

// generate options of subcommands
for subcommand in cmd.get_subcommands() {
let mut parent_commands: Vec<_> = parent_commands.into();
parent_commands.push(subcommand.get_name());
gen_fish_inner(root_command, &parent_commands, subcommand, buffer);
for subcommand_name in subcommand.get_name_and_visible_aliases() {
let mut parent_commands: Vec<_> = parent_commands.into();
parent_commands.push(subcommand_name);
gen_fish_inner(root_command, &parent_commands, subcommand, buffer);
}
}
}

Expand Down
41 changes: 24 additions & 17 deletions clap_complete/src/shells/powershell.rs
Expand Up @@ -72,10 +72,13 @@ fn escape_help<T: ToString>(help: Option<&StyledStr>, data: T) -> String {
fn generate_inner(p: &Command, previous_command_name: &str) -> String {
debug!("generate_inner");

let command_name = if previous_command_name.is_empty() {
p.get_bin_name().expect(INTERNAL_ERROR_MSG).to_string()
let command_names = if previous_command_name.is_empty() {
vec![p.get_bin_name().expect(INTERNAL_ERROR_MSG).to_string()]
} else {
format!("{};{}", previous_command_name, &p.get_name())
p.get_name_and_visible_aliases()
.into_iter()
.map(|name| format!("{};{}", previous_command_name, name))
.collect()
};

let mut completions = String::new();
Expand All @@ -90,27 +93,31 @@ fn generate_inner(p: &Command, previous_command_name: &str) -> String {
}

for subcommand in p.get_subcommands() {
let data = &subcommand.get_name();
let tooltip = escape_help(subcommand.get_about(), data);

completions.push_str(&preamble);
completions.push_str(
format!("'{data}', '{data}', [CompletionResultType]::ParameterValue, '{tooltip}')")
.as_str(),
);
for name in subcommand.get_name_and_visible_aliases() {
let tooltip = escape_help(subcommand.get_about(), name);
completions.push_str(&preamble);
completions.push_str(&format!(
"'{name}', '{name}', [CompletionResultType]::ParameterValue, '{tooltip}')"
));
}
}

let mut subcommands_cases = format!(
r"
let mut subcommands_cases = String::new();
for command_name in &command_names {
subcommands_cases.push_str(&format!(
r"
'{}' {{{}
break
}}",
&command_name, completions
);
command_name, completions
));
}

for subcommand in p.get_subcommands() {
let subcommand_subcommands_cases = generate_inner(subcommand, &command_name);
subcommands_cases.push_str(&subcommand_subcommands_cases);
for command_name in &command_names {
let subcommand_subcommands_cases = generate_inner(subcommand, command_name);
subcommands_cases.push_str(&subcommand_subcommands_cases);
}
}

subcommands_cases
Expand Down
16 changes: 15 additions & 1 deletion clap_complete/tests/snapshots/sub_subcommands.bash
Expand Up @@ -55,7 +55,7 @@ _my-app() {

case "${cmd}" in
my__app)
opts="-C -c -h -V --conf --config --help --version [file] first second test some_cmd help"
opts="-C -c -h -V --conf --config --help --version [file] first second test some_cmd some_cmd_alias help"
if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
Expand Down Expand Up @@ -152,6 +152,20 @@ _my-app() {
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
;;
my__app__some_cmd)
opts="-h -V --help --version sub_cmd help"
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
fi
case "${prev}" in
*)
COMPREPLY=()
;;
esac
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
;;
my__app__some_cmd__help)
opts="sub_cmd help"
if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then
Expand Down
24 changes: 24 additions & 0 deletions clap_complete/tests/snapshots/sub_subcommands.elvish
Expand Up @@ -28,6 +28,7 @@ set edit:completion:arg-completer[my-app] = {|@words|
cand --version 'Print version'
cand test 'tests things'
cand some_cmd 'top level subcommand'
cand some_cmd_alias 'top level subcommand'
cand help 'Print this message or the help of the given subcommand(s)'
}
&'my-app;test'= {
Expand All @@ -45,13 +46,28 @@ set edit:completion:arg-completer[my-app] = {|@words|
cand sub_cmd 'sub-subcommand'
cand help 'Print this message or the help of the given subcommand(s)'
}
&'my-app;some_cmd_alias'= {
cand -h 'Print help'
cand --help 'Print help'
cand -V 'Print version'
cand --version 'Print version'
cand sub_cmd 'sub-subcommand'
cand help 'Print this message or the help of the given subcommand(s)'
}
&'my-app;some_cmd;sub_cmd'= {
cand --config 'the other case to test'
cand -h 'Print help (see more with ''--help'')'
cand --help 'Print help (see more with ''--help'')'
cand -V 'Print version'
cand --version 'Print version'
}
&'my-app;some_cmd_alias;sub_cmd'= {
cand --config 'the other case to test'
cand -h 'Print help (see more with ''--help'')'
cand --help 'Print help (see more with ''--help'')'
cand -V 'Print version'
cand --version 'Print version'
}
&'my-app;some_cmd;help'= {
cand sub_cmd 'sub-subcommand'
cand help 'Print this message or the help of the given subcommand(s)'
Expand All @@ -60,6 +76,14 @@ set edit:completion:arg-completer[my-app] = {|@words|
}
&'my-app;some_cmd;help;help'= {
}
&'my-app;some_cmd_alias;help'= {
cand sub_cmd 'sub-subcommand'
cand help 'Print this message or the help of the given subcommand(s)'
}
&'my-app;some_cmd_alias;help;sub_cmd'= {
}
&'my-app;some_cmd_alias;help;help'= {
}
&'my-app;help'= {
cand test 'tests things'
cand some_cmd 'top level subcommand'
Expand Down
10 changes: 10 additions & 0 deletions clap_complete/tests/snapshots/sub_subcommands.fish
Expand Up @@ -3,6 +3,7 @@ complete -c my-app -n "__fish_use_subcommand" -s h -l help -d 'Print help'
complete -c my-app -n "__fish_use_subcommand" -s V -l version -d 'Print version'
complete -c my-app -n "__fish_use_subcommand" -f -a "test" -d 'tests things'
complete -c my-app -n "__fish_use_subcommand" -f -a "some_cmd" -d 'top level subcommand'
complete -c my-app -n "__fish_use_subcommand" -f -a "some_cmd_alias" -d 'top level subcommand'
complete -c my-app -n "__fish_use_subcommand" -f -a "help" -d 'Print this message or the help of the given subcommand(s)'
complete -c my-app -n "__fish_seen_subcommand_from test" -l case -d 'the case to test' -r
complete -c my-app -n "__fish_seen_subcommand_from test" -s h -l help -d 'Print help'
Expand All @@ -16,6 +17,15 @@ complete -c my-app -n "__fish_seen_subcommand_from some_cmd; and __fish_seen_sub
complete -c my-app -n "__fish_seen_subcommand_from some_cmd; and __fish_seen_subcommand_from sub_cmd" -s V -l version -d 'Print version'
complete -c my-app -n "__fish_seen_subcommand_from some_cmd; and __fish_seen_subcommand_from help; and not __fish_seen_subcommand_from sub_cmd; and not __fish_seen_subcommand_from help" -f -a "sub_cmd" -d 'sub-subcommand'
complete -c my-app -n "__fish_seen_subcommand_from some_cmd; and __fish_seen_subcommand_from help; and not __fish_seen_subcommand_from sub_cmd; and not __fish_seen_subcommand_from help" -f -a "help" -d 'Print this message or the help of the given subcommand(s)'
complete -c my-app -n "__fish_seen_subcommand_from some_cmd_alias; and not __fish_seen_subcommand_from sub_cmd; and not __fish_seen_subcommand_from help" -s h -l help -d 'Print help'
complete -c my-app -n "__fish_seen_subcommand_from some_cmd_alias; and not __fish_seen_subcommand_from sub_cmd; and not __fish_seen_subcommand_from help" -s V -l version -d 'Print version'
complete -c my-app -n "__fish_seen_subcommand_from some_cmd_alias; and not __fish_seen_subcommand_from sub_cmd; and not __fish_seen_subcommand_from help" -f -a "sub_cmd" -d 'sub-subcommand'
complete -c my-app -n "__fish_seen_subcommand_from some_cmd_alias; and not __fish_seen_subcommand_from sub_cmd; and not __fish_seen_subcommand_from help" -f -a "help" -d 'Print this message or the help of the given subcommand(s)'
complete -c my-app -n "__fish_seen_subcommand_from some_cmd_alias; and __fish_seen_subcommand_from sub_cmd" -l config -d 'the other case to test' -r -f -a "{Lest quotes\, aren\'t escaped. 'help,with,comma',Second to trigger display of options ''}"
complete -c my-app -n "__fish_seen_subcommand_from some_cmd_alias; and __fish_seen_subcommand_from sub_cmd" -s h -l help -d 'Print help (see more with \'--help\')'
complete -c my-app -n "__fish_seen_subcommand_from some_cmd_alias; and __fish_seen_subcommand_from sub_cmd" -s V -l version -d 'Print version'
complete -c my-app -n "__fish_seen_subcommand_from some_cmd_alias; and __fish_seen_subcommand_from help; and not __fish_seen_subcommand_from sub_cmd; and not __fish_seen_subcommand_from help" -f -a "sub_cmd" -d 'sub-subcommand'
complete -c my-app -n "__fish_seen_subcommand_from some_cmd_alias; and __fish_seen_subcommand_from help; and not __fish_seen_subcommand_from sub_cmd; and not __fish_seen_subcommand_from help" -f -a "help" -d 'Print this message or the help of the given subcommand(s)'
complete -c my-app -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from test; and not __fish_seen_subcommand_from some_cmd; and not __fish_seen_subcommand_from help" -f -a "test" -d 'tests things'
complete -c my-app -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from test; and not __fish_seen_subcommand_from some_cmd; and not __fish_seen_subcommand_from help" -f -a "some_cmd" -d 'top level subcommand'
complete -c my-app -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from test; and not __fish_seen_subcommand_from some_cmd; and not __fish_seen_subcommand_from help" -f -a "help" -d 'Print this message or the help of the given subcommand(s)'
Expand Down
29 changes: 29 additions & 0 deletions clap_complete/tests/snapshots/sub_subcommands.ps1
Expand Up @@ -31,6 +31,7 @@ Register-ArgumentCompleter -Native -CommandName 'my-app' -ScriptBlock {
[CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Print version')
[CompletionResult]::new('test', 'test', [CompletionResultType]::ParameterValue, 'tests things')
[CompletionResult]::new('some_cmd', 'some_cmd', [CompletionResultType]::ParameterValue, 'top level subcommand')
[CompletionResult]::new('some_cmd_alias', 'some_cmd_alias', [CompletionResultType]::ParameterValue, 'top level subcommand')
[CompletionResult]::new('help', 'help', [CompletionResultType]::ParameterValue, 'Print this message or the help of the given subcommand(s)')
break
}
Expand All @@ -51,6 +52,15 @@ Register-ArgumentCompleter -Native -CommandName 'my-app' -ScriptBlock {
[CompletionResult]::new('help', 'help', [CompletionResultType]::ParameterValue, 'Print this message or the help of the given subcommand(s)')
break
}
'my-app;some_cmd_alias' {
[CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help')
[CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help')
[CompletionResult]::new('-V', 'V ', [CompletionResultType]::ParameterName, 'Print version')
[CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Print version')
[CompletionResult]::new('sub_cmd', 'sub_cmd', [CompletionResultType]::ParameterValue, 'sub-subcommand')
[CompletionResult]::new('help', 'help', [CompletionResultType]::ParameterValue, 'Print this message or the help of the given subcommand(s)')
break
}
'my-app;some_cmd;sub_cmd' {
[CompletionResult]::new('--config', 'config', [CompletionResultType]::ParameterName, 'the other case to test')
[CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help (see more with ''--help'')')
Expand All @@ -59,6 +69,14 @@ Register-ArgumentCompleter -Native -CommandName 'my-app' -ScriptBlock {
[CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Print version')
break
}
'my-app;some_cmd_alias;sub_cmd' {
[CompletionResult]::new('--config', 'config', [CompletionResultType]::ParameterName, 'the other case to test')
[CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help (see more with ''--help'')')
[CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help (see more with ''--help'')')
[CompletionResult]::new('-V', 'V ', [CompletionResultType]::ParameterName, 'Print version')
[CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Print version')
break
}
'my-app;some_cmd;help' {
[CompletionResult]::new('sub_cmd', 'sub_cmd', [CompletionResultType]::ParameterValue, 'sub-subcommand')
[CompletionResult]::new('help', 'help', [CompletionResultType]::ParameterValue, 'Print this message or the help of the given subcommand(s)')
Expand All @@ -70,6 +88,17 @@ Register-ArgumentCompleter -Native -CommandName 'my-app' -ScriptBlock {
'my-app;some_cmd;help;help' {
break
}
'my-app;some_cmd_alias;help' {
[CompletionResult]::new('sub_cmd', 'sub_cmd', [CompletionResultType]::ParameterValue, 'sub-subcommand')
[CompletionResult]::new('help', 'help', [CompletionResultType]::ParameterValue, 'Print this message or the help of the given subcommand(s)')
break
}
'my-app;some_cmd_alias;help;sub_cmd' {
break
}
'my-app;some_cmd_alias;help;help' {
break
}
'my-app;help' {
[CompletionResult]::new('test', 'test', [CompletionResultType]::ParameterValue, 'tests things')
[CompletionResult]::new('some_cmd', 'some_cmd', [CompletionResultType]::ParameterValue, 'top level subcommand')
Expand Down

0 comments on commit 8a622a9

Please sign in to comment.