feat(complete): Add autocomplete for visible_alias

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"
This commit is contained in:
Pawel Zmarzly 2024-04-27 15:07:22 +02:00
parent be15bd5d96
commit 5000d58f38
10 changed files with 210 additions and 44 deletions

View file

@ -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> {

View file

@ -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

View file

@ -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();
@ -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

View file

@ -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 ")
@ -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);
}
}
}

View file

@ -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();
@ -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

View file

@ -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
@ -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

View file

@ -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'= {
@ -45,6 +46,14 @@ 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'')'
@ -52,6 +61,13 @@ set edit:completion:arg-completer[my-app] = {|@words|
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)'
@ -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'

View file

@ -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'
@ -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)'

View file

@ -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
}
@ -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'')')
@ -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)')
@ -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')

View file

@ -97,6 +97,60 @@ esac
;;
esac
;;
(some_cmd_alias)
_arguments "${_arguments_options[@]}" \
'-h[Print help]' \
'--help[Print help]' \
'-V[Print version]' \
'--version[Print version]' \
":: :_my-app__some_cmd_commands" \
"*::: :->some_cmd" \
&& ret=0
case $state in
(some_cmd)
words=($line[1] "${words[@]}")
(( CURRENT += 1 ))
curcontext="${curcontext%:*:*}:my-app-some_cmd-command-$line[1]:"
case $line[1] in
(sub_cmd)
_arguments "${_arguments_options[@]}" \
'--config=[the other case to test]: :((Lest\ quotes,\ aren'\''t\ escaped.\:"help,with,comma"
Second\ to\ trigger\ display\ of\ options\:""))' \
'-h[Print help (see more with '\''--help'\'')]' \
'--help[Print help (see more with '\''--help'\'')]' \
'-V[Print version]' \
'--version[Print version]' \
&& ret=0
;;
(help)
_arguments "${_arguments_options[@]}" \
":: :_my-app__some_cmd__help_commands" \
"*::: :->help" \
&& ret=0
case $state in
(help)
words=($line[1] "${words[@]}")
(( CURRENT += 1 ))
curcontext="${curcontext%:*:*}:my-app-some_cmd-help-command-$line[1]:"
case $line[1] in
(sub_cmd)
_arguments "${_arguments_options[@]}" \
&& ret=0
;;
(help)
_arguments "${_arguments_options[@]}" \
&& ret=0
;;
esac
;;
esac
;;
esac
;;
esac
;;
(help)
_arguments "${_arguments_options[@]}" \
":: :_my-app__help_commands" \