Skip to content

Commit 0f3a37b

Browse files
committed
feat: Group options by tag (in zsh)
1 parent 94f3378 commit 0f3a37b

4 files changed

Lines changed: 138 additions & 43 deletions

File tree

clap_complete/src/env/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,11 @@
6363
//! echo "source <(COMPLETE=zsh your_program)" >> ~/.zshrc
6464
//! ```
6565
//!
66+
//! To show completions grouped by category (options, commands, etc.), also add:
67+
//! ```zsh
68+
//! echo "zstyle ':completion:*:descriptions' format '%d'" >> ~/.zshrc
69+
//! ```
70+
//!
6671
//! To disable completions, you can set `COMPLETE=` or `COMPLETE=0`
6772
6873
mod shells;

clap_complete/src/env/shells.rs

Lines changed: 51 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -376,25 +376,61 @@ function _clap_dynamic_completer_NAME() {
376376
)}")
377377
378378
if [[ -n $completions ]]; then
379-
local -a dirs=()
380-
local -a other=()
379+
local -A tag_map tag_map_dirs
381380
local completion
381+
local tag
382+
local value
383+
local to_add
384+
382385
for completion in $completions; do
383-
local value="${completion%%:*}"
386+
IFS=: read -r tag value <<< "$completion"
387+
384388
if [[ "$value" == */ ]]; then
385389
local dir_no_slash="${value%/}"
386-
if [[ "$completion" == *:* ]]; then
387-
local desc="${completion#*:}"
388-
dirs+=("$dir_no_slash:$desc")
390+
391+
if [[ "$value" == *:* ]]; then
392+
local desc="${value#*:}"
393+
to_add="$dir_no_slash:$desc"
389394
else
390-
dirs+=("$dir_no_slash")
395+
to_add="$dir_no_slash"
391396
fi
397+
398+
tag_map_dirs[$tag]+=${tag_map_dirs[$tag]:+$'\n'}"$to_add"
392399
else
393-
other+=("$completion")
400+
tag_map[$tag]+=${tag_map[$tag]:+$'\n'}"$value"
394401
fi
395402
done
396-
[[ -n $dirs ]] && _describe 'values' dirs -S '/' -r '/'
397-
[[ -n $other ]] && _describe 'values' other
403+
404+
local -A tags
405+
for k in ${(k)tag_map} ${(k)tag_map_dirs}; do
406+
tags[$k]=1
407+
done
408+
409+
local extglob=${options[extendedglob]}
410+
setopt extendedglob
411+
for tag in ${(Ok)tags}; do
412+
# _describe -t tag cannot begin with a `-`
413+
local desc_tag=${tag##-##}
414+
local values=("${(@f)tag_map[$tag]}") # split on newline
415+
416+
if (( $#values )); then
417+
if [[ -n "$desc_tag" ]]; then
418+
_describe -t "$desc_tag" "$desc_tag" values
419+
else
420+
_describe 'options' values
421+
fi
422+
fi
423+
424+
values=("${(@f)tag_map_dirs[$tag]}")
425+
if (( $#values )); then
426+
if [[ -n "$desc_tag" ]]; then
427+
_describe -t "${desc_tag}-dirs" "$desc_tag" values -S '/' -r '/'
428+
else
429+
_describe 'options' values -S '/' -r '/'
430+
fi
431+
fi
432+
done
433+
[[ $extglob == on ]] || unsetopt extendedglob
398434
fi
399435
}
400436
@@ -431,6 +467,11 @@ compdef _clap_dynamic_completer_NAME BIN"#
431467
if i != 0 {
432468
write!(buf, "{}", ifs.as_deref().unwrap_or("\n"))?;
433469
}
470+
471+
let tag = candidate.get_tag().map(|t| t.to_string()).unwrap_or_default();
472+
473+
write!(buf, "{}:", Self::escape_value(&tag))?;
474+
434475
write!(
435476
buf,
436477
"{}",

clap_complete/tests/snapshots/home/dynamic-env/exhaustive/zsh/zsh/_exhaustive

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,25 +11,61 @@ function _clap_dynamic_completer_exhaustive() {
1111
)}")
1212

1313
if [[ -n $completions ]]; then
14-
local -a dirs=()
15-
local -a other=()
14+
local -A tag_map tag_map_dirs
1615
local completion
16+
local tag
17+
local value
18+
local to_add
19+
1720
for completion in $completions; do
18-
local value="${completion%%:*}"
21+
IFS=: read -r tag value <<< "$completion"
22+
1923
if [[ "$value" == */ ]]; then
2024
local dir_no_slash="${value%/}"
21-
if [[ "$completion" == *:* ]]; then
22-
local desc="${completion#*:}"
23-
dirs+=("$dir_no_slash:$desc")
25+
26+
if [[ "$value" == *:* ]]; then
27+
local desc="${value#*:}"
28+
to_add="$dir_no_slash:$desc"
2429
else
25-
dirs+=("$dir_no_slash")
30+
to_add="$dir_no_slash"
2631
fi
32+
33+
tag_map_dirs[$tag]+=${tag_map_dirs[$tag]:+$'\n'}"$to_add"
2734
else
28-
other+=("$completion")
35+
tag_map[$tag]+=${tag_map[$tag]:+$'\n'}"$value"
36+
fi
37+
done
38+
39+
local -A tags
40+
for k in ${(k)tag_map} ${(k)tag_map_dirs}; do
41+
tags[$k]=1
42+
done
43+
44+
local extglob=${options[extendedglob]}
45+
setopt extendedglob
46+
for tag in ${(Ok)tags}; do
47+
# _describe -t tag cannot begin with a `-`
48+
local desc_tag=${tag##-##}
49+
local values=("${(@f)tag_map[$tag]}") # split on newline
50+
51+
if (( $#values )); then
52+
if [[ -n "$desc_tag" ]]; then
53+
_describe -t "$desc_tag" "$desc_tag" values
54+
else
55+
_describe 'options' values
56+
fi
57+
fi
58+
59+
values=("${(@f)tag_map_dirs[$tag]}")
60+
if (( $#values )); then
61+
if [[ -n "$desc_tag" ]]; then
62+
_describe -t "${desc_tag}-dirs" "$desc_tag" values -S '/' -r '/'
63+
else
64+
_describe 'options' values -S '/' -r '/'
65+
fi
2966
fi
3067
done
31-
[[ -n $dirs ]] && _describe 'values' dirs -S '/' -r '/'
32-
[[ -n $other ]] && _describe 'values' other
68+
[[ $extglob == on ]] || unsetopt extendedglob
3369
fi
3470
}
3571

clap_complete/tests/testsuite/zsh.rs

Lines changed: 36 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -253,9 +253,9 @@ fn complete_dynamic_env_toplevel() {
253253
% exhaustive
254254
--generate -- generate
255255
--help -- Print help
256-
help -- Print this message or the help of the given subcommand(s)
257-
--empty-choice alias global last quote
258-
action empty hint pacman value
256+
help -- Print this message or the help of the given subcommand(s)
257+
alias --empty-choice hint pacman value
258+
action empty global last quote
259259
"#]];
260260
let actual = runtime.complete(input, &term).unwrap();
261261
assert_data_eq!(actual, expected);
@@ -275,16 +275,22 @@ fn complete_dynamic_env_quoted_help() {
275275
let input = "exhaustive quote \t\t";
276276
let expected = snapbox::str![[r#"
277277
% exhaustive quote
278-
--help -- Print help (see more with '--help')
279-
cmd-backslash --backslash -- Avoid '/n'
280-
cmd-backticks --backticks -- For more information see `echo test`
281-
cmd-brackets --brackets -- List packages [filter]
282-
cmd-double-quotes --double-quotes -- Can be "always", "auto", or "never"
283-
cmd-expansions --expansions -- Execute the shell command with $SHELL
284-
cmd-single-quotes --single-quotes -- Can be 'always', 'auto', or 'never'
285-
escape-help -- /tab/t"'
286-
help -- Print this message or the help of the given subcommand(s)
287-
--choice
278+
--backslash -- Avoid '/n'
279+
--backticks -- For more information see `echo test`
280+
--brackets -- List packages [filter]
281+
cmd-backslash -- Avoid '/n'
282+
cmd-backticks -- For more information see `echo test`
283+
cmd-brackets -- List packages [filter]
284+
cmd-double-quotes -- Can be "always", "auto", or "never"
285+
cmd-expansions -- Execute the shell command with $SHELL
286+
cmd-single-quotes -- Can be 'always', 'auto', or 'never'
287+
--double-quotes -- Can be "always", "auto", or "never"
288+
escape-help -- /tab "'
289+
--expansions -- Execute the shell command with $SHELL
290+
--help -- Print help (see more with '--help')
291+
help -- Print this message or the help of the given subcommand(s)
292+
--single-quotes -- Can be 'always', 'auto', or 'never'
293+
--choice
288294
"#]];
289295
let actual = runtime.complete(input, &term).unwrap();
290296
assert_data_eq!(actual, expected);
@@ -392,16 +398,22 @@ fn complete_dynamic_empty_space() {
392398
let input = "exhaustive quote -\x1b[D\x1b[D\t\t";
393399
let expected = snapbox::str![[r#"
394400
% exhaustive quote -
395-
--help -- Print help (see more with '--help')
396-
cmd-backslash --backslash -- Avoid '/n'
397-
cmd-backticks --backticks -- For more information see `echo test`
398-
cmd-brackets --brackets -- List packages [filter]
399-
cmd-double-quotes --double-quotes -- Can be "always", "auto", or "never"
400-
cmd-expansions --expansions -- Execute the shell command with $SHELL
401-
cmd-single-quotes --single-quotes -- Can be 'always', 'auto', or 'never'
402-
escape-help -- /tab/t"'
403-
help -- Print this message or the help of the given subcommand(s)
404-
--choice
401+
--backslash -- Avoid '/n'
402+
--backticks -- For more information see `echo test`
403+
--brackets -- List packages [filter]
404+
cmd-backslash -- Avoid '/n'
405+
cmd-backticks -- For more information see `echo test`
406+
cmd-brackets -- List packages [filter]
407+
cmd-double-quotes -- Can be "always", "auto", or "never"
408+
cmd-expansions -- Execute the shell command with $SHELL
409+
cmd-single-quotes -- Can be 'always', 'auto', or 'never'
410+
--double-quotes -- Can be "always", "auto", or "never"
411+
escape-help -- /tab "'
412+
--expansions -- Execute the shell command with $SHELL
413+
--help -- Print help (see more with '--help')
414+
help -- Print this message or the help of the given subcommand(s)
415+
--single-quotes -- Can be 'always', 'auto', or 'never'
416+
--choice
405417
"#]];
406418
let actual = runtime.complete(input, &term).unwrap();
407419
assert_data_eq!(actual, expected);
@@ -455,6 +467,7 @@ fn complete_dynamic_tagged_options() {
455467
let expected = snapbox::str![[r#"
456468
% zstyle ':completion:*:descriptions' format '%d'
457469
% exhaustive -
470+
Options
458471
--generate -- generate
459472
-h -- Print help
460473
--empty-choice

0 commit comments

Comments
 (0)