Skip to content

Commit ee6af99

Browse files
authored
Merge pull request #5743 from epage/sort
fix(complete): Sort by display order
2 parents 2450ca7 + 232ee10 commit ee6af99

File tree

7 files changed

+122
-57
lines changed

7 files changed

+122
-57
lines changed

clap_builder/src/builder/arg.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3870,6 +3870,12 @@ impl Arg {
38703870
self.long_help.as_ref()
38713871
}
38723872

3873+
/// Get the placement within help
3874+
#[inline]
3875+
pub fn get_display_order(&self) -> usize {
3876+
self.disp_ord.unwrap_or(999)
3877+
}
3878+
38733879
/// Get the help heading specified for this argument, if any
38743880
#[inline]
38753881
pub fn get_help_heading(&self) -> Option<&str> {
@@ -4422,11 +4428,6 @@ impl Arg {
44224428
pub(crate) fn is_multiple(&self) -> bool {
44234429
self.is_multiple_values_set() || matches!(*self.get_action(), ArgAction::Append)
44244430
}
4425-
4426-
#[cfg(feature = "help")]
4427-
pub(crate) fn get_display_order(&self) -> usize {
4428-
self.disp_ord.unwrap_or(999)
4429-
}
44304431
}
44314432

44324433
impl From<&'_ Arg> for Arg {

clap_builder/src/builder/command.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3462,6 +3462,12 @@ impl Command {
34623462
self.long_version.as_deref()
34633463
}
34643464

3465+
/// Get the placement within help
3466+
#[inline]
3467+
pub fn get_display_order(&self) -> usize {
3468+
self.disp_ord.unwrap_or(999)
3469+
}
3470+
34653471
/// Get the authors of the cmd.
34663472
#[inline]
34673473
pub fn get_author(&self) -> Option<&str> {
@@ -4777,11 +4783,6 @@ impl Command {
47774783
.map(|sc| sc.get_name())
47784784
}
47794785

4780-
#[cfg(feature = "help")]
4781-
pub(crate) fn get_display_order(&self) -> usize {
4782-
self.disp_ord.unwrap_or(999)
4783-
}
4784-
47854786
pub(crate) fn write_help_err(&self, mut use_long: bool) -> StyledStr {
47864787
debug!(
47874788
"Command::write_help_err: {}, use_long={:?}",

clap_complete/src/engine/candidate.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ pub struct CompletionCandidate {
99
value: OsString,
1010
help: Option<StyledStr>,
1111
id: Option<String>,
12+
tag: Option<StyledStr>,
13+
display_order: Option<usize>,
1214
hidden: bool,
1315
}
1416

@@ -36,6 +38,20 @@ impl CompletionCandidate {
3638
self
3739
}
3840

41+
/// Group candidates by tag
42+
///
43+
/// Future: these may become user-visible
44+
pub fn tag(mut self, tag: Option<StyledStr>) -> Self {
45+
self.tag = tag;
46+
self
47+
}
48+
49+
/// Sort weight within a [`CompletionCandidate::tag`]
50+
pub fn display_order(mut self, order: Option<usize>) -> Self {
51+
self.display_order = order;
52+
self
53+
}
54+
3955
/// Set the visibility of the completion candidate
4056
///
4157
/// Only shown when there is no visible candidate for completing the current argument.
@@ -74,6 +90,16 @@ impl CompletionCandidate {
7490
self.id.as_ref()
7591
}
7692

93+
/// Get the grouping tag
94+
pub fn get_tag(&self) -> Option<&StyledStr> {
95+
self.tag.as_ref()
96+
}
97+
98+
/// Get the grouping tag
99+
pub fn get_display_order(&self) -> Option<usize> {
100+
self.display_order
101+
}
102+
77103
/// Get the visibility of the completion candidate
78104
pub fn is_hide_set(&self) -> bool {
79105
self.hidden

clap_complete/src/engine/complete.rs

Lines changed: 67 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,20 @@ fn complete_arg(
279279
}
280280
});
281281

282+
let mut tags = Vec::new();
283+
for candidate in &completions {
284+
let tag = candidate.get_tag().cloned();
285+
if !tags.contains(&tag) {
286+
tags.push(tag);
287+
}
288+
}
289+
completions.sort_by_key(|c| {
290+
(
291+
tags.iter().position(|t| c.get_tag() == t.as_ref()),
292+
c.get_display_order(),
293+
)
294+
});
295+
282296
Ok(completions)
283297
}
284298

@@ -355,6 +369,17 @@ fn complete_arg_value(
355369
.map(|comp| comp.add_prefix(prefix))
356370
.collect();
357371
}
372+
values = values
373+
.into_iter()
374+
.map(|comp| {
375+
if comp.get_tag().is_some() {
376+
comp
377+
} else {
378+
comp.tag(Some(arg.to_string().into()))
379+
}
380+
})
381+
.collect();
382+
358383
values
359384
}
360385

@@ -389,13 +414,10 @@ fn complete_subcommand(value: &str, cmd: &clap::Command) -> Vec<CompletionCandid
389414
value
390415
);
391416

392-
let mut scs = subcommands(cmd)
417+
subcommands(cmd)
393418
.into_iter()
394419
.filter(|x| x.get_value().starts_with(value))
395-
.collect::<Vec<_>>();
396-
scs.sort();
397-
scs.dedup();
398-
scs
420+
.collect()
399421
}
400422

401423
/// Gets all the long options, their visible aliases and flags of a [`clap::Command`] with formatted `--` prefix.
@@ -407,10 +429,7 @@ fn longs_and_visible_aliases(p: &clap::Command) -> Vec<CompletionCandidate> {
407429
.filter_map(|a| {
408430
a.get_long_and_visible_aliases().map(|longs| {
409431
longs.into_iter().map(|s| {
410-
CompletionCandidate::new(format!("--{}", s))
411-
.help(a.get_help().cloned())
412-
.id(Some(format!("arg::{}", a.get_id())))
413-
.hide(a.is_hide_set())
432+
populate_arg_candidate(CompletionCandidate::new(format!("--{}", s)), a)
414433
})
415434
})
416435
})
@@ -426,9 +445,7 @@ fn hidden_longs_aliases(p: &clap::Command) -> Vec<CompletionCandidate> {
426445
.filter_map(|a| {
427446
a.get_aliases().map(|longs| {
428447
longs.into_iter().map(|s| {
429-
CompletionCandidate::new(format!("--{}", s))
430-
.help(a.get_help().cloned())
431-
.id(Some(format!("arg::{}", a.get_id())))
448+
populate_arg_candidate(CompletionCandidate::new(format!("--{}", s)), a)
432449
.hide(true)
433450
})
434451
})
@@ -446,21 +463,32 @@ fn shorts_and_visible_aliases(p: &clap::Command) -> Vec<CompletionCandidate> {
446463
.filter_map(|a| {
447464
a.get_short_and_visible_aliases().map(|shorts| {
448465
shorts.into_iter().map(|s| {
449-
CompletionCandidate::new(s.to_string())
450-
.help(
451-
a.get_help()
452-
.cloned()
453-
.or_else(|| a.get_long().map(|long| format!("--{long}").into())),
454-
)
455-
.id(Some(format!("arg::{}", a.get_id())))
456-
.hide(a.is_hide_set())
466+
populate_arg_candidate(CompletionCandidate::new(s.to_string()), a).help(
467+
a.get_help()
468+
.cloned()
469+
.or_else(|| a.get_long().map(|long| format!("--{long}").into())),
470+
)
457471
})
458472
})
459473
})
460474
.flatten()
461475
.collect()
462476
}
463477

478+
fn populate_arg_candidate(candidate: CompletionCandidate, arg: &clap::Arg) -> CompletionCandidate {
479+
candidate
480+
.help(arg.get_help().cloned())
481+
.id(Some(format!("arg::{}", arg.get_id())))
482+
.tag(Some(
483+
arg.get_help_heading()
484+
.unwrap_or("Options")
485+
.to_owned()
486+
.into(),
487+
))
488+
.display_order(Some(arg.get_display_order()))
489+
.hide(arg.is_hide_set())
490+
}
491+
464492
/// Get the possible values for completion
465493
fn possible_values(a: &clap::Arg) -> Option<Vec<clap::builder::PossibleValue>> {
466494
if !a.get_num_args().expect("built").takes_values() {
@@ -483,22 +511,32 @@ fn subcommands(p: &clap::Command) -> Vec<CompletionCandidate> {
483511
.flat_map(|sc| {
484512
sc.get_name_and_visible_aliases()
485513
.into_iter()
486-
.map(|s| {
487-
CompletionCandidate::new(s.to_string())
488-
.help(sc.get_about().cloned())
489-
.id(Some(format!("command::{}", sc.get_name())))
490-
.hide(sc.is_hide_set())
491-
})
514+
.map(|s| populate_command_candidate(CompletionCandidate::new(s.to_string()), p, sc))
492515
.chain(sc.get_aliases().map(|s| {
493-
CompletionCandidate::new(s.to_string())
494-
.help(sc.get_about().cloned())
495-
.id(Some(format!("command::{}", sc.get_name())))
516+
populate_command_candidate(CompletionCandidate::new(s.to_string()), p, sc)
496517
.hide(true)
497518
}))
498519
})
499520
.collect()
500521
}
501522

523+
fn populate_command_candidate(
524+
candidate: CompletionCandidate,
525+
cmd: &clap::Command,
526+
subcommand: &clap::Command,
527+
) -> CompletionCandidate {
528+
candidate
529+
.help(subcommand.get_about().cloned())
530+
.id(Some(format!("command::{}", subcommand.get_name())))
531+
.tag(Some(
532+
cmd.get_subcommand_help_heading()
533+
.unwrap_or("Commands")
534+
.to_owned()
535+
.into(),
536+
))
537+
.display_order(Some(subcommand.get_display_order()))
538+
.hide(subcommand.is_hide_set())
539+
}
502540
/// Parse the short flags and find the first `takes_values` option.
503541
fn parse_shortflags<'c, 's>(
504542
cmd: &'c clap::Command,

clap_complete/tests/testsuite/bash.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -255,8 +255,8 @@ fn complete_dynamic_env_toplevel() {
255255
let input = "exhaustive \t\t";
256256
let expected = snapbox::str![[r#"
257257
%
258-
action help last quote --global --help
259-
alias hint pacman value --generate --version
258+
action value last hint --global --help
259+
quote pacman alias help --generate --version
260260
"#]];
261261
let actual = runtime.complete(input, &term).unwrap();
262262
assert_data_eq!(actual, expected);
@@ -275,9 +275,9 @@ fn complete_dynamic_env_quoted_help() {
275275
let input = "exhaustive quote \t\t";
276276
let expected = snapbox::str![[r#"
277277
%
278-
cmd-backslash cmd-double-quotes escape-help --double-quotes --brackets --global
279-
cmd-backticks cmd-expansions help --backticks --expansions --help
280-
cmd-brackets cmd-single-quotes --single-quotes --backslash --choice --version
278+
cmd-single-quotes cmd-backslash escape-help --global --backslash --choice
279+
cmd-double-quotes cmd-brackets help --double-quotes --brackets --help
280+
cmd-backticks cmd-expansions --single-quotes --backticks --expansions --version
281281
"#]];
282282
let actual = runtime.complete(input, &term).unwrap();
283283
assert_data_eq!(actual, expected);

clap_complete/tests/testsuite/engine.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ fn suggest_subcommand_subset() {
3030
assert_data_eq!(
3131
complete!(cmd, "he"),
3232
snapbox::str![[r#"
33-
hello-moon
3433
hello-world
34+
hello-moon
3535
help Print this message or the help of the given subcommand(s)
3636
"#]],
3737
);
@@ -105,8 +105,8 @@ fn suggest_subcommand_aliases() {
105105
assert_data_eq!(
106106
complete!(cmd, "hello"),
107107
snapbox::str![[r#"
108-
hello-moon
109108
hello-world
109+
hello-moon
110110
"#]],
111111
);
112112
}
@@ -1099,26 +1099,26 @@ fn sort_and_filter() {
10991099
assert_data_eq!(
11001100
complete!(cmd, " [TAB]"),
11011101
snapbox::str![[r#"
1102-
help Print this message or the help of the given subcommand(s)
11031102
sub
1103+
help Print this message or the help of the given subcommand(s)
11041104
pos-a
11051105
pos-b
11061106
pos-c
11071107
--required-flag
11081108
--optional-flag
11091109
--long-flag
1110-
--help Print help
11111110
-s
1111+
--help Print help
11121112
"#]]
11131113
);
11141114
assert_data_eq!(
11151115
complete!(cmd, "-[TAB]"),
11161116
snapbox::str![[r#"
11171117
-r --required-flag
11181118
-o --optional-flag
1119+
--long-flag
11191120
-s
11201121
-h Print help
1121-
--long-flag
11221122
"#]]
11231123
);
11241124
assert_data_eq!(

clap_complete/tests/testsuite/fish.rs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -192,10 +192,9 @@ fn complete_dynamic_env_toplevel() {
192192
let input = "exhaustive \t\t";
193193
let expected = snapbox::str![[r#"
194194
% exhaustive action
195-
action last --global (everywhere)
196-
alias pacman --generate (generate)
197-
help (Print this message or the help of the given subcommand(s)) quote --help (Print help)
198-
hint value --version (Print version)
195+
action pacman hint --generate (generate)
196+
quote last help (Print this message or the help of the given subcommand(s)) --help (Print help)
197+
value alias --global (everywhere) --version (Print version)
199198
"#]];
200199
let actual = runtime.complete(input, &term).unwrap();
201200
assert_data_eq!(actual, expected);
@@ -214,22 +213,22 @@ fn complete_dynamic_env_quoted_help() {
214213
let input = "exhaustive quote \t\t";
215214
let expected = snapbox::str![[r#"
216215
% exhaustive quote
217-
cmd-backslash (Avoid '/n')
216+
cmd-single-quotes (Can be 'always', 'auto', or 'never')
217+
cmd-double-quotes (Can be "always", "auto", or "never")
218218
cmd-backticks (For more information see `echo test`)
219+
cmd-backslash (Avoid '/n')
219220
cmd-brackets (List packages [filter])
220-
cmd-double-quotes (Can be "always", "auto", or "never")
221221
cmd-expansions (Execute the shell command with $SHELL)
222-
cmd-single-quotes (Can be 'always', 'auto', or 'never')
223222
escape-help (/tab "')
224223
help (Print this message or the help of the given subcommand(s))
225224
--single-quotes (Can be 'always', 'auto', or 'never')
225+
--global (everywhere)
226226
--double-quotes (Can be "always", "auto", or "never")
227227
--backticks (For more information see `echo test`)
228228
--backslash (Avoid '/n')
229229
--brackets (List packages [filter])
230230
--expansions (Execute the shell command with $SHELL)
231231
--choice
232-
--global (everywhere)
233232
--help (Print help (see more with '--help'))
234233
--version (Print version)
235234
"#]];

0 commit comments

Comments
 (0)