From 7b06d9a358f1841231481ef959ff6d1158d7aba8 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Fri, 20 Sep 2024 11:07:01 -0500 Subject: [PATCH 1/2] feat(complete): Allow de-duplicating of candidates --- clap_complete/src/engine/candidate.rs | 14 ++++++++++++++ clap_complete/src/engine/complete.rs | 8 ++++++++ 2 files changed, 22 insertions(+) diff --git a/clap_complete/src/engine/candidate.rs b/clap_complete/src/engine/candidate.rs index c75b46eb..6edf7d2c 100644 --- a/clap_complete/src/engine/candidate.rs +++ b/clap_complete/src/engine/candidate.rs @@ -8,6 +8,7 @@ use clap::builder::StyledStr; pub struct CompletionCandidate { value: OsString, help: Option, + id: Option, hidden: bool, } @@ -27,6 +28,14 @@ impl CompletionCandidate { self } + /// Only first for a given Id is shown + /// + /// To reduce the risk of conflicts, this should likely contain a namespace. + pub fn id(mut self, id: Option) -> Self { + self.id = id; + self + } + /// Set the visibility of the completion candidate /// /// Only shown when there is no visible candidate for completing the current argument. @@ -60,6 +69,11 @@ impl CompletionCandidate { self.help.as_ref() } + /// Get the id used for de-duplicating + pub fn get_id(&self) -> Option<&String> { + self.id.as_ref() + } + /// Get the visibility of the completion candidate pub fn is_hide_set(&self) -> bool { self.hidden diff --git a/clap_complete/src/engine/complete.rs b/clap_complete/src/engine/complete.rs index b0eb0754..d2de0010 100644 --- a/clap_complete/src/engine/complete.rs +++ b/clap_complete/src/engine/complete.rs @@ -257,6 +257,14 @@ fn complete_arg( if completions.iter().any(|a| !a.is_hide_set()) { completions.retain(|a| !a.is_hide_set()); } + let mut seen_ids = std::collections::HashSet::new(); + completions.retain(move |a| { + if let Some(id) = a.get_id().cloned() { + seen_ids.insert(id) + } else { + true + } + }); Ok(completions) } From 89a9d79a4c6a91a78a3b299547d14ee3d8bcf101 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Fri, 20 Sep 2024 11:07:17 -0500 Subject: [PATCH 2/2] fix(complete): De-duplicate built-in candidates Part of #5058 --- clap_complete/src/engine/complete.rs | 5 ++ clap_complete/tests/testsuite/bash.rs | 11 ++-- clap_complete/tests/testsuite/elvish.rs | 11 ++-- clap_complete/tests/testsuite/engine.rs | 71 ++----------------------- clap_complete/tests/testsuite/fish.rs | 37 +++++++------ clap_complete/tests/testsuite/zsh.rs | 11 ++-- 6 files changed, 46 insertions(+), 100 deletions(-) diff --git a/clap_complete/src/engine/complete.rs b/clap_complete/src/engine/complete.rs index d2de0010..fb0463e4 100644 --- a/clap_complete/src/engine/complete.rs +++ b/clap_complete/src/engine/complete.rs @@ -396,6 +396,7 @@ fn longs_and_visible_aliases(p: &clap::Command) -> Vec { longs.into_iter().map(|s| { CompletionCandidate::new(format!("--{}", s)) .help(a.get_help().cloned()) + .id(Some(format!("arg::{}", a.get_id()))) .hide(a.is_hide_set()) }) }) @@ -414,6 +415,7 @@ fn hidden_longs_aliases(p: &clap::Command) -> Vec { longs.into_iter().map(|s| { CompletionCandidate::new(format!("--{}", s)) .help(a.get_help().cloned()) + .id(Some(format!("arg::{}", a.get_id()))) .hide(true) }) }) @@ -433,6 +435,7 @@ fn shorts_and_visible_aliases(p: &clap::Command) -> Vec { shorts.into_iter().map(|s| { CompletionCandidate::new(s.to_string()) .help(a.get_help().cloned()) + .id(Some(format!("arg::{}", a.get_id()))) .hide(a.is_hide_set()) }) }) @@ -466,11 +469,13 @@ fn subcommands(p: &clap::Command) -> Vec { .map(|s| { CompletionCandidate::new(s.to_string()) .help(sc.get_about().cloned()) + .id(Some(format!("command::{}", sc.get_name()))) .hide(sc.is_hide_set()) }) .chain(sc.get_aliases().map(|s| { CompletionCandidate::new(s.to_string()) .help(sc.get_about().cloned()) + .id(Some(format!("command::{}", sc.get_name()))) .hide(true) })) }) diff --git a/clap_complete/tests/testsuite/bash.rs b/clap_complete/tests/testsuite/bash.rs index ffafd170..f1580fd7 100644 --- a/clap_complete/tests/testsuite/bash.rs +++ b/clap_complete/tests/testsuite/bash.rs @@ -255,8 +255,8 @@ fn complete_dynamic_env_toplevel() { let input = "exhaustive \t\t"; let expected = snapbox::str![[r#" % -action help last quote --global --help -h -alias hint pacman value --generate --version -V +action help last quote --global --help +alias hint pacman value --generate --version "#]]; let actual = runtime.complete(input, &term).unwrap(); assert_data_eq!(actual, expected); @@ -275,10 +275,9 @@ fn complete_dynamic_env_quoted_help() { let input = "exhaustive quote \t\t"; let expected = snapbox::str![[r#" % -cmd-backslash cmd-expansions --single-quotes --brackets --help -cmd-backticks cmd-single-quotes --double-quotes --expansions --version -cmd-brackets escape-help --backticks --choice -h -cmd-double-quotes help --backslash --global -V +cmd-backslash cmd-double-quotes escape-help --double-quotes --brackets --global +cmd-backticks cmd-expansions help --backticks --expansions --help +cmd-brackets cmd-single-quotes --single-quotes --backslash --choice --version "#]]; let actual = runtime.complete(input, &term).unwrap(); assert_data_eq!(actual, expected); diff --git a/clap_complete/tests/testsuite/elvish.rs b/clap_complete/tests/testsuite/elvish.rs index 2dbf9437..99932586 100644 --- a/clap_complete/tests/testsuite/elvish.rs +++ b/clap_complete/tests/testsuite/elvish.rs @@ -198,8 +198,8 @@ fn complete_dynamic_env_toplevel() { let expected = snapbox::str![[r#" % exhaustive --generate COMPLETING argument ---generate --help -V action help last quote ---global --version -h alias hint pacman value +--generate --help action help last quote +--global --version alias hint pacman value "#]]; let actual = runtime.complete(input, &term).unwrap(); assert_data_eq!(actual, expected); @@ -219,10 +219,9 @@ fn complete_dynamic_env_quoted_help() { let expected = snapbox::str![[r#" % exhaustive quote --backslash COMPLETING argument ---backslash --double-quotes --single-quotes cmd-backslash cmd-expansions ---backticks --expansions --version cmd-backticks cmd-single-quotes ---brackets --global -V cmd-brackets escape-help ---choice --help -h cmd-double-quotes help +--backslash --choice --global --version cmd-brackets cmd-single-quotes +--backticks --double-quotes --help cmd-backslash cmd-double-quotes escape-help +--brackets --expansions --single-quotes cmd-backticks cmd-expansions help "#]]; let actual = runtime.complete(input, &term).unwrap(); assert_data_eq!(actual, expected); diff --git a/clap_complete/tests/testsuite/engine.rs b/clap_complete/tests/testsuite/engine.rs index f3539856..8659cc38 100644 --- a/clap_complete/tests/testsuite/engine.rs +++ b/clap_complete/tests/testsuite/engine.rs @@ -75,19 +75,12 @@ fn suggest_hidden_subcommand_and_aliases() { assert_data_eq!( complete!(cmd, "test"), - snapbox::str![[r#" -test_visible -test_visible-alias_visible -"#]] + snapbox::str!["test_visible"] ); assert_data_eq!( complete!(cmd, "test_h"), - snapbox::str![[r#" -test_hidden -test_hidden-alias_hidden -test_hidden-alias_visible -"#]] + snapbox::str!["test_hidden"] ); assert_data_eq!( @@ -119,9 +112,7 @@ fn suggest_subcommand_aliases() { complete!(cmd, "hello"), snapbox::str![[r#" hello-moon -hello-moon-foo hello-world -hello-world-foo "#]], ); } @@ -167,19 +158,12 @@ fn suggest_hidden_long_flag_aliases() { assert_data_eq!( complete!(cmd, "--test"), - snapbox::str![[r#" ---test_visible ---test_visible-alias_visible -"#]] + snapbox::str!["--test_visible"] ); assert_data_eq!( complete!(cmd, "--test_h"), - snapbox::str![[r#" ---test_hidden ---test_hidden-alias_visible ---test_hidden-alias_hidden -"#]] + snapbox::str!["--test_hidden"] ); assert_data_eq!( @@ -287,7 +271,6 @@ hello-world Say hello to the world hello-moon goodbye-world --help Print help (see more with '--help') --h Print help (see more with '--help') "#]], ); } @@ -361,10 +344,6 @@ pos_c --stream --count --help Print help --F --S --c --h Print help "#]] ); @@ -433,9 +412,6 @@ val3 --certain-num --uncertain-num --help Print help --Y --N --h Print help "#]] ); @@ -457,9 +433,6 @@ val3 --certain-num --uncertain-num --help Print help --Y --N --h Print help "#]] ); @@ -469,9 +442,6 @@ val3 --certain-num --uncertain-num --help Print help --Y --N --h Print help "#]] ); @@ -499,9 +469,6 @@ val3 --certain-num --uncertain-num --help Print help --Y --N --h Print help "#]] ); @@ -523,9 +490,6 @@ val3 --certain-num --uncertain-num --help Print help --Y --N --h Print help "#]] ); @@ -535,9 +499,6 @@ val3 --certain-num --uncertain-num --help Print help --Y --N --h Print help "#]] ); } @@ -775,8 +736,6 @@ pos_b pos_c --format --help Print help --F --h Print help "#]] ); @@ -794,8 +753,6 @@ pos_c snapbox::str![[r#" --format --help Print help --F --h Print help "#]] ); @@ -935,8 +892,6 @@ b_pos c_pos --delimiter --help Print help --D --h Print help "#]]); assert_data_eq!(complete!(cmd, " -- a_pos,[TAB]"), snapbox::str![[r#" @@ -1028,16 +983,12 @@ fn suggest_positional_long_allow_hyphen() { pos_b --format --help Print help --F --h Print help "#]] ); assert_data_eq!(complete!(cmd, "-F --json --pos_a [TAB]"), snapbox::str![[r#" pos_b --format --help Print help --F --h Print help "#]]); assert_data_eq!( @@ -1076,15 +1027,11 @@ fn suggest_positional_short_allow_hyphen() { pos_b --format --help Print help --F --h Print help "#]]); assert_data_eq!(complete!(cmd, "-F --json -a [TAB]"), snapbox::str![[r#" pos_b --format --help Print help --F --h Print help "#]]); assert_data_eq!( @@ -1125,30 +1072,20 @@ pos-a pos-b pos-c --required-flag ---required-flag2 --optional-flag ---2optional-flag --long-flag --help Print help --r --o -s --h Print help "#]] ); assert_data_eq!( complete!(cmd, "-[TAB]"), snapbox::str![[r#" --required-flag ---required-flag2 --optional-flag ---2optional-flag --long-flag --help Print help --r --o -s --h Print help "#]] ); } diff --git a/clap_complete/tests/testsuite/fish.rs b/clap_complete/tests/testsuite/fish.rs index 2e67e06e..47ff82e7 100644 --- a/clap_complete/tests/testsuite/fish.rs +++ b/clap_complete/tests/testsuite/fish.rs @@ -192,11 +192,10 @@ fn complete_dynamic_env_toplevel() { let input = "exhaustive \t\t"; let expected = snapbox::str![[r#" % exhaustive action -action pacman --help (Print help) -alias quote --version (Print version) -help (Print this message or the help of the given subcommand(s)) value -h (Print help) -hint --global (everywhere) -V (Print version) -last --generate (generate) +action last --global (everywhere) +alias pacman --generate (generate) +help (Print this message or the help of the given subcommand(s)) quote --help (Print help) +hint value --version (Print version) "#]]; let actual = runtime.complete(input, &term).unwrap(); assert_data_eq!(actual, expected); @@ -215,16 +214,24 @@ fn complete_dynamic_env_quoted_help() { let input = "exhaustive quote \t\t"; let expected = snapbox::str![[r#" % exhaustive quote -cmd-backslash (Avoid '/n') --backticks (For more information see `echo test`) -cmd-backticks (For more information see `echo test`) --backslash (Avoid '/n') -cmd-brackets (List packages [filter]) --brackets (List packages [filter]) -cmd-double-quotes (Can be "always", "auto", or "never") --expansions (Execute the shell command with $SHELL) -cmd-expansions (Execute the shell command with $SHELL) --choice -cmd-single-quotes (Can be 'always', 'auto', or 'never') --global (everywhere) -escape-help (/tab "') --help (Print help (see more with '--help')) -help (Print this message or the help of the given subcommand(s)) --version (Print version) ---single-quotes (Can be 'always', 'auto', or 'never') -h (Print help (see more with '--help')) ---double-quotes (Can be "always", "auto", or "never") -V (Print version) +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)) +--single-quotes (Can be 'always', 'auto', or 'never') +--double-quotes (Can be "always", "auto", or "never") +--backticks (For more information see `echo test`) +--backslash (Avoid '/n') +--brackets (List packages [filter]) +--expansions (Execute the shell command with $SHELL) +--choice +--global (everywhere) +--help (Print help (see more with '--help')) +--version (Print version) "#]]; let actual = runtime.complete(input, &term).unwrap(); assert_data_eq!(actual, expected); diff --git a/clap_complete/tests/testsuite/zsh.rs b/clap_complete/tests/testsuite/zsh.rs index 2331ff4d..d052596c 100644 --- a/clap_complete/tests/testsuite/zsh.rs +++ b/clap_complete/tests/testsuite/zsh.rs @@ -185,8 +185,8 @@ fn complete_dynamic_env_toplevel() { let input = "exhaustive \t\t"; let expected = snapbox::str![[r#" % exhaustive ---generate --help -V action help last quote ---global --version -h alias hint pacman value +--generate --help action help last quote +--global --version alias hint pacman value "#]]; let actual = runtime.complete(input, &term).unwrap(); assert_data_eq!(actual, expected); @@ -205,10 +205,9 @@ fn complete_dynamic_env_quoted_help() { let input = "exhaustive quote \t\t"; let expected = snapbox::str![[r#" % exhaustive quote ---backslash --double-quotes --single-quotes cmd-backslash cmd-expansions ---backticks --expansions --version cmd-backticks cmd-single-quotes ---brackets --global -V cmd-brackets escape-help ---choice --help -h cmd-double-quotes help +--backslash --choice --global --version cmd-brackets cmd-single-quotes +--backticks --double-quotes --help cmd-backslash cmd-double-quotes escape-help +--brackets --expansions --single-quotes cmd-backticks cmd-expansions help "#]]; let actual = runtime.complete(input, &term).unwrap(); assert_data_eq!(actual, expected);