Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 28 additions & 7 deletions clap_complete/src/env/shells.rs
Comment thread
epage marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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
Comment on lines 388 to +402
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean we aren't getting the / handling if a tag is present?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The opposite in fact - if a / is there, we'll handle it (since we test that first) and only perform the option grouping for remaining options

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Items with / should still have grouping though

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
}

Expand Down Expand Up @@ -431,6 +446,12 @@ compdef _clap_dynamic_completer_NAME BIN"#
if i != 0 {
write!(buf, "{}", ifs.as_deref().unwrap_or("\n"))?;
}
write!(
buf,
"{}:",
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is : is in a tag?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose we have two options:

  • Pick a different separator - but what if that separator is in the tag?
  • Avoid : in tags, either by filtering them out at this point, or making it part of the tag requirement

What do you think?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can pick an unlikely separator, like _SEP_

candidate.get_tag().unwrap_or(&StyledStr::from("")),
)?;

write!(
buf,
"{}",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
80 changes: 61 additions & 19 deletions clap_complete/tests/testsuite/zsh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
"#]];
Expand All @@ -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();
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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::<RuntimeBuilder>("dynamic-env", "exhaustive");

let input = [
"zstyle ':completion:*' group-name ''",
"zstyle ':completion:*:descriptions' format '%d'",
Comment on lines +451 to +452
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are these needed?

"exhaustive -\t\t",
].join("\n");

let expected = snapbox::str![[r#"
% zstyle ':completion:*' group-name ''
% zstyle ':completion:*:descriptions' format '%d'
% exhaustive -
"Options" options
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is redundant. In --help, we just list the header. We should likely do the same here as well.

--generate -- generate
-h -- Print help
--empty-choice
"#]];
let actual = runtime.complete(&input, &term).unwrap();
assert_data_eq!(actual, expected);
}