From ee099e39046150f00394769591185805d4cf4e92 Mon Sep 17 00:00:00 2001 From: David Aguilar Date: Mon, 9 Jan 2023 02:50:58 -0800 Subject: [PATCH] fix(complete): Support two multi-length arguments Emit the value_terminator into the zsh completion pattern when completing multi-length arguments on zsh to avoid doubled rest-arguments definitions: $ my-app _arguments:comparguments:325: doubled rest argument definition: *::second -- second set of of multi-length arguments: Follow the suggestion from https://github.com/clap-rs/clap/issues/3266#issuecomment-1007901407 by including the value terminator in order to resolve the ambiguity. Closes #3022 Signed-off-by: David Aguilar --- clap_complete/src/shells/zsh.rs | 10 ++++- clap_complete/tests/bash.rs | 12 ++++++ clap_complete/tests/common.rs | 15 ++++++++ clap_complete/tests/elvish.rs | 12 ++++++ clap_complete/tests/fish.rs | 12 ++++++ clap_complete/tests/powershell.rs | 12 ++++++ .../snapshots/two_multi_length_arguments.bash | 38 +++++++++++++++++++ .../two_multi_length_arguments.elvish | 26 +++++++++++++ .../snapshots/two_multi_length_arguments.fish | 1 + .../snapshots/two_multi_length_arguments.ps1 | 32 ++++++++++++++++ .../snapshots/two_multi_length_arguments.zsh | 31 +++++++++++++++ clap_complete/tests/zsh.rs | 12 ++++++ 12 files changed, 212 insertions(+), 1 deletion(-) create mode 100644 clap_complete/tests/snapshots/two_multi_length_arguments.bash create mode 100644 clap_complete/tests/snapshots/two_multi_length_arguments.elvish create mode 100644 clap_complete/tests/snapshots/two_multi_length_arguments.fish create mode 100644 clap_complete/tests/snapshots/two_multi_length_arguments.ps1 create mode 100644 clap_complete/tests/snapshots/two_multi_length_arguments.zsh diff --git a/clap_complete/src/shells/zsh.rs b/clap_complete/src/shells/zsh.rs index 580de77a2ecd..ba198f9329b3 100644 --- a/clap_complete/src/shells/zsh.rs +++ b/clap_complete/src/shells/zsh.rs @@ -621,13 +621,21 @@ fn write_positionals_of(p: &Command) -> String { debug!("write_positionals_of;"); let mut ret = vec![]; + let mut cardinality_value; for arg in p.get_positionals() { debug!("write_positionals_of:iter: arg={}", arg.get_id()); let num_args = arg.get_num_args().expect("built"); + let cardinality = if num_args.max_values() > 1 { - "*:" + match arg.get_value_terminator() { + Some(terminator) => { + cardinality_value = format!("*{}:", escape_value(&terminator)); + cardinality_value.as_str() + } + None => "*:", + } } else if !arg.is_required_set() { ":" } else { diff --git a/clap_complete/tests/bash.rs b/clap_complete/tests/bash.rs index a3e454deaf44..10370037bb28 100644 --- a/clap_complete/tests/bash.rs +++ b/clap_complete/tests/bash.rs @@ -84,6 +84,18 @@ fn value_hint() { ); } +#[test] +fn two_multi_length_arguments() { + let name = "my-app"; + let cmd = common::two_multi_length_arguments_command(name); + common::assert_matches_path( + "tests/snapshots/two_multi_length_arguments.bash", + clap_complete::shells::Bash, + cmd, + name, + ); +} + #[cfg(feature = "unstable-dynamic")] #[test] fn register_minimal() { diff --git a/clap_complete/tests/common.rs b/clap_complete/tests/common.rs index ab52fe93a71d..54e4bf9eefde 100644 --- a/clap_complete/tests/common.rs +++ b/clap_complete/tests/common.rs @@ -252,6 +252,21 @@ pub fn value_hint_command(name: &'static str) -> clap::Command { ) } +pub fn two_multi_length_arguments_command(name: &'static str) -> clap::Command { + clap::Command::new(name) + .arg( + clap::Arg::new("first") + .help("first set of multi-length arguments") + .num_args(1..) + .value_terminator("--"), + ) + .arg( + clap::Arg::new("second") + .help("second set of of multi-length arguments") + .raw(true), + ) +} + pub fn assert_matches_path( expected_path: impl AsRef, gen: impl clap_complete::Generator, diff --git a/clap_complete/tests/elvish.rs b/clap_complete/tests/elvish.rs index 76beb98366e1..5a320deabdcd 100644 --- a/clap_complete/tests/elvish.rs +++ b/clap_complete/tests/elvish.rs @@ -83,3 +83,15 @@ fn value_hint() { name, ); } + +#[test] +fn two_multi_length_arguments() { + let name = "my-app"; + let cmd = common::two_multi_length_arguments_command(name); + common::assert_matches_path( + "tests/snapshots/two_multi_length_arguments.elvish", + clap_complete::shells::Elvish, + cmd, + name, + ); +} diff --git a/clap_complete/tests/fish.rs b/clap_complete/tests/fish.rs index 92c1c4a4efc4..db561ba8c2ba 100644 --- a/clap_complete/tests/fish.rs +++ b/clap_complete/tests/fish.rs @@ -83,3 +83,15 @@ fn value_hint() { name, ); } + +#[test] +fn two_multi_length_arguments() { + let name = "my-app"; + let cmd = common::two_multi_length_arguments_command(name); + common::assert_matches_path( + "tests/snapshots/two_multi_length_arguments.fish", + clap_complete::shells::Fish, + cmd, + name, + ); +} diff --git a/clap_complete/tests/powershell.rs b/clap_complete/tests/powershell.rs index fa6657d37239..78342412d8dd 100644 --- a/clap_complete/tests/powershell.rs +++ b/clap_complete/tests/powershell.rs @@ -83,3 +83,15 @@ fn value_hint() { name, ); } + +#[test] +fn two_multi_length_arguments_command() { + let name = "my-app"; + let cmd = common::two_multi_length_arguments_command(name); + common::assert_matches_path( + "tests/snapshots/two_multi_length_arguments.ps1", + clap_complete::shells::PowerShell, + cmd, + name, + ); +} diff --git a/clap_complete/tests/snapshots/two_multi_length_arguments.bash b/clap_complete/tests/snapshots/two_multi_length_arguments.bash new file mode 100644 index 000000000000..22afa8be8ac0 --- /dev/null +++ b/clap_complete/tests/snapshots/two_multi_length_arguments.bash @@ -0,0 +1,38 @@ +_my-app() { + local i cur prev opts cmds + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + cmd="" + opts="" + + for i in ${COMP_WORDS[@]} + do + case "${cmd},${i}" in + ",$1") + cmd="my__app" + ;; + *) + ;; + esac + done + + case "${cmd}" in + my__app) + opts="-h --help [first]... [second]..." + if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then + COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) + return 0 + fi + case "${prev}" in + *) + COMPREPLY=() + ;; + esac + COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) + return 0 + ;; + esac +} + +complete -F _my-app -o bashdefault -o default my-app diff --git a/clap_complete/tests/snapshots/two_multi_length_arguments.elvish b/clap_complete/tests/snapshots/two_multi_length_arguments.elvish new file mode 100644 index 000000000000..89b26e0005a0 --- /dev/null +++ b/clap_complete/tests/snapshots/two_multi_length_arguments.elvish @@ -0,0 +1,26 @@ + +use builtin; +use str; + +set edit:completion:arg-completer[my-app] = {|@words| + fn spaces {|n| + builtin:repeat $n ' ' | str:join '' + } + fn cand {|text desc| + edit:complex-candidate $text &display=$text' '(spaces (- 14 (wcswidth $text)))$desc + } + var command = 'my-app' + for word $words[1..-1] { + if (str:has-prefix $word '-') { + break + } + set command = $command';'$word + } + var completions = [ + &'my-app'= { + cand -h 'Print help' + cand --help 'Print help' + } + ] + $completions[$command] +} diff --git a/clap_complete/tests/snapshots/two_multi_length_arguments.fish b/clap_complete/tests/snapshots/two_multi_length_arguments.fish new file mode 100644 index 000000000000..a33c671acf51 --- /dev/null +++ b/clap_complete/tests/snapshots/two_multi_length_arguments.fish @@ -0,0 +1 @@ +complete -c my-app -s h -l help -d 'Print help' diff --git a/clap_complete/tests/snapshots/two_multi_length_arguments.ps1 b/clap_complete/tests/snapshots/two_multi_length_arguments.ps1 new file mode 100644 index 000000000000..30262afe031d --- /dev/null +++ b/clap_complete/tests/snapshots/two_multi_length_arguments.ps1 @@ -0,0 +1,32 @@ + +using namespace System.Management.Automation +using namespace System.Management.Automation.Language + +Register-ArgumentCompleter -Native -CommandName 'my-app' -ScriptBlock { + param($wordToComplete, $commandAst, $cursorPosition) + + $commandElements = $commandAst.CommandElements + $command = @( + 'my-app' + for ($i = 1; $i -lt $commandElements.Count; $i++) { + $element = $commandElements[$i] + if ($element -isnot [StringConstantExpressionAst] -or + $element.StringConstantType -ne [StringConstantType]::BareWord -or + $element.Value.StartsWith('-') -or + $element.Value -eq $wordToComplete) { + break + } + $element.Value + }) -join ';' + + $completions = @(switch ($command) { + 'my-app' { + [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help') + [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help') + break + } + }) + + $completions.Where{ $_.CompletionText -like "$wordToComplete*" } | + Sort-Object -Property ListItemText +} diff --git a/clap_complete/tests/snapshots/two_multi_length_arguments.zsh b/clap_complete/tests/snapshots/two_multi_length_arguments.zsh new file mode 100644 index 000000000000..be8998b325cb --- /dev/null +++ b/clap_complete/tests/snapshots/two_multi_length_arguments.zsh @@ -0,0 +1,31 @@ +#compdef my-app + +autoload -U is-at-least + +_my-app() { + typeset -A opt_args + typeset -a _arguments_options + local ret=1 + + if is-at-least 5.2; then + _arguments_options=(-s -S -C) + else + _arguments_options=(-s -C) + fi + + local context curcontext="$curcontext" state line + _arguments "${_arguments_options[@]}" \ +'-h[Print help]' \ +'--help[Print help]' \ +'*--::first -- first set of multi-length arguments:' \ +'*::second -- second set of of multi-length arguments:' \ +&& ret=0 +} + +(( $+functions[_my-app_commands] )) || +_my-app_commands() { + local commands; commands=() + _describe -t commands 'my-app commands' commands "$@" +} + +_my-app "$@" diff --git a/clap_complete/tests/zsh.rs b/clap_complete/tests/zsh.rs index df6a27781fee..b86ca90af1ec 100644 --- a/clap_complete/tests/zsh.rs +++ b/clap_complete/tests/zsh.rs @@ -83,3 +83,15 @@ fn value_hint() { name, ); } + +#[test] +fn two_multi_length_arguments_command() { + let name = "my-app"; + let cmd = common::two_multi_length_arguments_command(name); + common::assert_matches_path( + "tests/snapshots/two_multi_length_arguments.zsh", + clap_complete::shells::Zsh, + cmd, + name, + ); +}