diff --git a/clap_complete/src/env/shells.rs b/clap_complete/src/env/shells.rs index a46d5d439ee..8163713b06f 100644 --- a/clap_complete/src/env/shells.rs +++ b/clap_complete/src/env/shells.rs @@ -2,6 +2,7 @@ use std::ffi::OsString; use std::str::FromStr; use super::EnvCompleter; +use clap::builder::StyledStr; /// Bash completion adapter #[derive(Copy, Clone, PartialEq, Eq, Debug)] @@ -376,25 +377,39 @@ function _clap_dynamic_completer_NAME() { )}") if [[ -n $completions ]]; then + local -A tag_map local -a dirs=() - local -a other=() local completion + local tag + local value + for completion in $completions; do - local value="${completion%%:*}" + IFS=: read -r tag value <<< "$completion" if [[ "$value" == */ ]]; then local dir_no_slash="${value%/}" - if [[ "$completion" == *:* ]]; then - local desc="${completion#*:}" + if [[ "$value" == *:* ]]; then + local desc="${value#*:}" dirs+=("$dir_no_slash:$desc") else dirs+=("$dir_no_slash") fi else - other+=("$completion") + if (( ${+tag_map["$tag"]} )); then # key exists? + tag_map["$tag"]+=$'\n'"$value" + else + tag_map["$tag"]="$value" + fi + fi + done + [[ -n $dirs ]] && _describe -t dirs 'values' dirs -S '/' -r '/' + for tag in ${(k)tag_map}; do + values=("${(@f)tag_map[$tag]}") # split on newline + if [[ -n $tag ]]; then + _describe -t "$tag" "$tag options" values + else + _describe "options" values fi done - [[ -n $dirs ]] && _describe 'values' dirs -S '/' -r '/' - [[ -n $other ]] && _describe 'values' other fi } @@ -431,6 +446,12 @@ compdef _clap_dynamic_completer_NAME BIN"# if i != 0 { write!(buf, "{}", ifs.as_deref().unwrap_or("\n"))?; } + write!( + buf, + "{}:", + candidate.get_tag().unwrap_or(&StyledStr::from("")), + )?; + write!( buf, "{}", diff --git a/clap_complete/tests/snapshots/home/dynamic-env/exhaustive/zsh/zsh/_exhaustive b/clap_complete/tests/snapshots/home/dynamic-env/exhaustive/zsh/zsh/_exhaustive index 0090fd7b5e1..e3ff52525ac 100644 --- a/clap_complete/tests/snapshots/home/dynamic-env/exhaustive/zsh/zsh/_exhaustive +++ b/clap_complete/tests/snapshots/home/dynamic-env/exhaustive/zsh/zsh/_exhaustive @@ -11,25 +11,39 @@ function _clap_dynamic_completer_exhaustive() { )}") if [[ -n $completions ]]; then + local -A tag_map local -a dirs=() - local -a other=() local completion + local tag + local value + for completion in $completions; do - local value="${completion%%:*}" + IFS=: read -r tag value <<< "$completion" if [[ "$value" == */ ]]; then local dir_no_slash="${value%/}" - if [[ "$completion" == *:* ]]; then - local desc="${completion#*:}" + if [[ "$value" == *:* ]]; then + local desc="${value#*:}" dirs+=("$dir_no_slash:$desc") else dirs+=("$dir_no_slash") fi else - other+=("$completion") + if (( ${+tag_map["$tag"]} )); then # key exists? + tag_map["$tag"]+=$'\n'"$value" + else + tag_map["$tag"]="$value" + fi + fi + done + [[ -n $dirs ]] && _describe -t dirs 'values' dirs -S '/' -r '/' + for tag in ${(k)tag_map}; do + values=("${(@f)tag_map[$tag]}") # split on newline + if [[ -n $tag ]]; then + _describe -t "$tag" "$tag options" values + else + _describe "options" values fi done - [[ -n $dirs ]] && _describe 'values' dirs -S '/' -r '/' - [[ -n $other ]] && _describe 'values' other fi } diff --git a/clap_complete/tests/testsuite/zsh.rs b/clap_complete/tests/testsuite/zsh.rs index b41f0e631bd..1db2752d9e6 100644 --- a/clap_complete/tests/testsuite/zsh.rs +++ b/clap_complete/tests/testsuite/zsh.rs @@ -253,7 +253,7 @@ fn complete_dynamic_env_toplevel() { % exhaustive --generate -- generate --help -- Print help -help -- Print this message or the help of the given subcommand(s) +help -- Print this message or the help of the given subcommand(s) --empty-choice alias global last quote action empty hint pacman value "#]]; @@ -275,15 +275,21 @@ fn complete_dynamic_env_quoted_help() { let input = "exhaustive quote \t\t"; let expected = snapbox::str![[r#" % exhaustive quote ---help -- Print help (see more with '--help') -cmd-backslash --backslash -- Avoid '/n' -cmd-backticks --backticks -- For more information see `echo test` -cmd-brackets --brackets -- List packages [filter] -cmd-double-quotes --double-quotes -- Can be "always", "auto", or "never" -cmd-expansions --expansions -- Execute the shell command with $SHELL -cmd-single-quotes --single-quotes -- Can be 'always', 'auto', or 'never' -escape-help -- /tab/t"' -help -- Print this message or the help of the given subcommand(s) +--backslash -- Avoid '/n' +--backticks -- For more information see `echo test` +--brackets -- List packages [filter] +--double-quotes -- Can be "always", "auto", or "never" +--expansions -- Execute the shell command with $SHELL +--help -- Print help (see more with '--help') +--single-quotes -- Can be 'always', 'auto', or 'never' +cmd-backslash -- Avoid '/n' +cmd-backticks -- For more information see `echo test` +cmd-brackets -- List packages [filter] +cmd-double-quotes -- Can be "always", "auto", or "never" +cmd-expansions -- Execute the shell command with $SHELL +cmd-single-quotes -- Can be 'always', 'auto', or 'never' +escape-help -- /tab "' +help -- Print this message or the help of the given subcommand(s) --choice "#]]; let actual = runtime.complete(input, &term).unwrap(); @@ -392,15 +398,21 @@ fn complete_dynamic_empty_space() { let input = "exhaustive quote -\x1b[D\x1b[D\t\t"; let expected = snapbox::str![[r#" % exhaustive quote - ---help -- Print help (see more with '--help') -cmd-backslash --backslash -- Avoid '/n' -cmd-backticks --backticks -- For more information see `echo test` -cmd-brackets --brackets -- List packages [filter] -cmd-double-quotes --double-quotes -- Can be "always", "auto", or "never" -cmd-expansions --expansions -- Execute the shell command with $SHELL -cmd-single-quotes --single-quotes -- Can be 'always', 'auto', or 'never' -escape-help -- /tab/t"' -help -- Print this message or the help of the given subcommand(s) +--backslash -- Avoid '/n' +--backticks -- For more information see `echo test` +--brackets -- List packages [filter] +--double-quotes -- Can be "always", "auto", or "never" +--expansions -- Execute the shell command with $SHELL +--help -- Print help (see more with '--help') +--single-quotes -- Can be 'always', 'auto', or 'never' +cmd-backslash -- Avoid '/n' +cmd-backticks -- For more information see `echo test` +cmd-brackets -- List packages [filter] +cmd-double-quotes -- Can be "always", "auto", or "never" +cmd-expansions -- Execute the shell command with $SHELL +cmd-single-quotes -- Can be 'always', 'auto', or 'never' +escape-help -- /tab "' +help -- Print this message or the help of the given subcommand(s) --choice "#]]; let actual = runtime.complete(input, &term).unwrap(); @@ -435,3 +447,33 @@ tests/examples.rs tests/snapshots tests/testsuite let actual = runtime.complete(input, &term).unwrap(); assert_data_eq!(actual, expected); } + +#[test] +#[cfg(all(unix, feature = "unstable-dynamic"))] +#[cfg(feature = "unstable-shell-tests")] +fn complete_dynamic_tagged_options() { + if !common::has_command(CMD) { + return; + } + + let term = completest::Term::new(); + let mut runtime = common::load_runtime::("dynamic-env", "exhaustive"); + + let input = [ + "zstyle ':completion:*' group-name ''", + "zstyle ':completion:*:descriptions' format '%d'", + "exhaustive -\t\t", + ].join("\n"); + + let expected = snapbox::str![[r#" +% zstyle ':completion:*' group-name '' +% zstyle ':completion:*:descriptions' format '%d' +% exhaustive - +"Options" options +--generate -- generate +-h -- Print help +--empty-choice +"#]]; + let actual = runtime.complete(&input, &term).unwrap(); + assert_data_eq!(actual, expected); +}