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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy