From 5000d58f38cf1d47ae88ef221bd269e4afd3aca0 Mon Sep 17 00:00:00 2001 From: Pawel Zmarzly Date: Sat, 27 Apr 2024 15:07:22 +0200 Subject: [PATCH] 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" --- clap_builder/src/builder/command.rs | 7 +++ clap_complete/src/generator/utils.rs | 9 +++- clap_complete/src/shells/elvish.rs | 35 +++++++----- clap_complete/src/shells/fish.rs | 29 +++++----- clap_complete/src/shells/powershell.rs | 41 ++++++++------ .../tests/snapshots/sub_subcommands.bash | 16 +++++- .../tests/snapshots/sub_subcommands.elvish | 24 +++++++++ .../tests/snapshots/sub_subcommands.fish | 10 ++++ .../tests/snapshots/sub_subcommands.ps1 | 29 ++++++++++ .../tests/snapshots/sub_subcommands.zsh | 54 +++++++++++++++++++ 10 files changed, 210 insertions(+), 44 deletions(-) diff --git a/clap_builder/src/builder/command.rs b/clap_builder/src/builder/command.rs index 3674948b4f4..aca4313f24b 100644 --- a/clap_builder/src/builder/command.rs +++ b/clap_builder/src/builder/command.rs @@ -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> { diff --git a/clap_complete/src/generator/utils.rs b/clap_complete/src/generator/utils.rs index 6ea10d2ab20..a9dd3173b88 100644 --- a/clap_complete/src/generator/utils.rs +++ b/clap_complete/src/generator/utils.rs @@ -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 diff --git a/clap_complete/src/shells/elvish.rs b/clap_complete/src/shells/elvish.rs index 170576ad1a6..eb81e78b1c1 100644 --- a/clap_complete/src/shells/elvish.rs +++ b/clap_complete/src/shells/elvish.rs @@ -67,10 +67,13 @@ fn escape_help(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 diff --git a/clap_complete/src/shells/fish.rs b/clap_complete/src/shells/fish.rs index 1ce27390812..24c44aec3d5 100644 --- a/clap_complete/src/shells/fish.rs +++ b/clap_complete/src/shells/fish.rs @@ -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::>() .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); + } } } diff --git a/clap_complete/src/shells/powershell.rs b/clap_complete/src/shells/powershell.rs index c5e3c0ba73a..0c84c5fdbfb 100644 --- a/clap_complete/src/shells/powershell.rs +++ b/clap_complete/src/shells/powershell.rs @@ -72,10 +72,13 @@ fn escape_help(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 diff --git a/clap_complete/tests/snapshots/sub_subcommands.bash b/clap_complete/tests/snapshots/sub_subcommands.bash index ef1925b6f94..a6a4663503d 100644 --- a/clap_complete/tests/snapshots/sub_subcommands.bash +++ b/clap_complete/tests/snapshots/sub_subcommands.bash @@ -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 diff --git a/clap_complete/tests/snapshots/sub_subcommands.elvish b/clap_complete/tests/snapshots/sub_subcommands.elvish index d42e344c495..68b72f66f20 100644 --- a/clap_complete/tests/snapshots/sub_subcommands.elvish +++ b/clap_complete/tests/snapshots/sub_subcommands.elvish @@ -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' diff --git a/clap_complete/tests/snapshots/sub_subcommands.fish b/clap_complete/tests/snapshots/sub_subcommands.fish index cec51a63a1f..644e37ad494 100644 --- a/clap_complete/tests/snapshots/sub_subcommands.fish +++ b/clap_complete/tests/snapshots/sub_subcommands.fish @@ -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)' diff --git a/clap_complete/tests/snapshots/sub_subcommands.ps1 b/clap_complete/tests/snapshots/sub_subcommands.ps1 index 3880c2442ae..1cdaf0627c3 100644 --- a/clap_complete/tests/snapshots/sub_subcommands.ps1 +++ b/clap_complete/tests/snapshots/sub_subcommands.ps1 @@ -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') diff --git a/clap_complete/tests/snapshots/sub_subcommands.zsh b/clap_complete/tests/snapshots/sub_subcommands.zsh index 09f4bd7cf17..a931fefd619 100644 --- a/clap_complete/tests/snapshots/sub_subcommands.zsh +++ b/clap_complete/tests/snapshots/sub_subcommands.zsh @@ -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" \