Repository: clap-rs/clap
Pull request #5583 (2024-07-17 09:07:08)

feat(clap_complete): Support hiding long flags and their long aliases
<!--
Thanks for helping out!

Please link the appropriate issue from your PR.

If you don't have an issue, we'd recommend starting with one first so the PR can focus on the
implementation (unless its an obvious bug or documentation fix that will have
little conversation).
-->
Based on PR: https://github.com/clap-rs/clap/pull/5549
Related issue: https://github.com/clap-rs/clap/issues/3951

#### Works in this PR
-  refactor: Add `CompletionCandidate`  to replace `(OsString, Option<StyledStr>)`. There will be a new field named `visible` for  `CompletionCandidate`. If we don't need this struct, there will be a type `(OsString, Option<StyledStr>, bool)`.
-  Support hiding long flags and their long aliases. The specific behavior is reflected in the differences between the test commit and the feat commit.


shannmu: In the process of modifying the code, I feel that `CompleteCandidate` has made the code more complex. If it's just to add the `visible` field, should I remove the refactor commit to get this PR merged faster? (Previously, I hoped to use CompleteCandidate to replace ((OsString, Option<StyledStr>, bool)), making the code simpler and adding other fields more centralized if we had to).
epage: > In the process of modifying the code, I feel that CompleteCandidate has made the code more complex. If it's just to add the visible field, should I remove the refactor commit to get this PR merged faster? (Previously, I hoped to use CompleteCandidate to replace ((OsString, Option, bool)), making the code simpler and adding other fields more centralized if we had to).

I think its worth keeping.  Overuse of tuples can make the code confusing.  We were already pushing the use of tuples and this would absolutely break it.

Using a struct also offers us more API stability for when we go live with this.
epage: Thanks for the updates!  This is looking great and look forward to the next round of improvements!

Patch:
diff --git a/clap_builder/src/builder/arg.rs b/clap_builder/src/builder/arg.rs
index d0929e51..a40d0cca 100644
--- a/clap_builder/src/builder/arg.rs
+++ b/clap_builder/src/builder/arg.rs
@@ -3952,6 +3952,21 @@ impl Arg {
         Some(longs)
     }
 
+    /// Get hidden aliases for this argument, if any
+    #[inline]
+    pub fn get_aliases(&self) -> Option<Vec<&str>> {
+        if self.aliases.is_empty() {
+            None
+        } else {
+            Some(
+                self.aliases
+                    .iter()
+                    .filter_map(|(s, v)| if !*v { Some(s.as_str()) } else { None })
+                    .collect(),
+            )
+        }
+    }
+
     /// Get the names of possible values for this argument. Only useful for user
     /// facing applications, such as building help messages or man files
     pub fn get_possible_values(&self) -> Vec<PossibleValue> {
diff --git a/clap_complete/src/dynamic/completer.rs b/clap_complete/src/dynamic/completer.rs
index 4c6d9c92..640e2d57 100644
--- a/clap_complete/src/dynamic/completer.rs
+++ b/clap_complete/src/dynamic/completer.rs
@@ -32,7 +32,7 @@ pub fn complete(
     args: Vec<OsString>,
     arg_index: usize,
     current_dir: Option<&std::path::Path>,
-) -> Result<Vec<(OsString, Option<StyledStr>)>, std::io::Error> {
+) -> Result<Vec<CompletionCandidate>, std::io::Error> {
     cmd.build();
 
     let raw_args = clap_lex::RawArgs::new(args);
@@ -91,7 +91,7 @@ fn complete_arg(
     current_dir: Option<&std::path::Path>,
     pos_index: usize,
     is_escaped: bool,
-) -> Result<Vec<(OsString, Option<StyledStr>)>, std::io::Error> {
+) -> Result<Vec<CompletionCandidate>, std::io::Error> {
     debug!(
         "complete_arg: arg={:?}, cmd={:?}, current_dir={:?}, pos_index={}, is_escaped={}",
         arg,
@@ -100,7 +100,7 @@ fn complete_arg(
         pos_index,
         is_escaped
     );
-    let mut completions = Vec::new();
+    let mut completions = Vec::<CompletionCandidate>::new();
 
     if !is_escaped {
         if let Some((flag, value)) = arg.to_long() {
@@ -112,23 +112,33 @@ fn complete_arg(
                                 .into_iter()
                                 .map(|(os, help)| {
                                     // HACK: Need better `OsStr` manipulation
-                                    (format!("--{}={}", flag, os.to_string_lossy()).into(), help)
+                                    CompletionCandidate::new(format!(
+                                        "--{}={}",
+                                        flag,
+                                        os.to_string_lossy()
+                                    ))
+                                    .help(help)
+                                    .visible(true)
                                 }),
                         );
                     }
                 } else {
-                    completions.extend(longs_and_visible_aliases(cmd).into_iter().filter_map(
-                        |(f, help)| f.starts_with(flag).then(|| (format!("--{f}").into(), help)),
-                    ));
+                    completions.extend(longs_and_visible_aliases(cmd).into_iter().filter(|comp| {
+                        comp.get_content()
+                            .starts_with(format!("--{}", flag).as_str())
+                    }));
+
+                    completions.extend(hidden_longs_aliases(cmd).into_iter().filter(|comp| {
+                        comp.get_content()
+                            .starts_with(format!("--{}", flag).as_str())
+                    }))
                 }
             }
         } else if arg.is_escape() || arg.is_stdio() || arg.is_empty() {
             // HACK: Assuming knowledge of is_escape / is_stdio
-            completions.extend(
-                longs_and_visible_aliases(cmd)
-                    .into_iter()
-                    .map(|(f, help)| (format!("--{f}").into(), help)),
-            );
+            completions.extend(longs_and_visible_aliases(cmd));
+
+            completions.extend(hidden_longs_aliases(cmd));
         }
 
         if arg.is_empty() || arg.is_stdio() || arg.is_short() {
@@ -142,7 +152,15 @@ fn complete_arg(
                 shorts_and_visible_aliases(cmd)
                     .into_iter()
                     // HACK: Need better `OsStr` manipulation
-                    .map(|(f, help)| (format!("{}{}", dash_or_arg, f).into(), help)),
+                    .map(|comp| {
+                        CompletionCandidate::new(format!(
+                            "{}{}",
+                            dash_or_arg,
+                            comp.get_content().to_string_lossy()
+                        ))
+                        .help(comp.get_help().cloned())
+                        .visible(true)
+                    }),
             );
         }
     }
@@ -151,13 +169,21 @@ fn complete_arg(
         .get_positionals()
         .find(|p| p.get_index() == Some(pos_index))
     {
-        completions.extend(complete_arg_value(arg.to_value(), positional, current_dir));
+        completions.extend(
+            complete_arg_value(arg.to_value(), positional, current_dir)
+                .into_iter()
+                .map(|(os, help)| CompletionCandidate::new(os).help(help).visible(true)),
+        );
     }
 
     if let Ok(value) = arg.to_value() {
         completions.extend(complete_subcommand(value, cmd));
     }
 
+    if completions.iter().any(|a| a.is_visible()) {
+        completions.retain(|a| a.is_visible())
+    }
+
     Ok(completions)
 }
 
@@ -174,7 +200,7 @@ fn complete_arg_value(
             values.extend(possible_values.into_iter().filter_map(|p| {
                 let name = p.get_name();
                 name.starts_with(value)
-                    .then(|| (name.into(), p.get_help().cloned()))
+                    .then(|| (OsString::from(name), p.get_help().cloned()))
             }));
         }
     } else {
@@ -268,7 +294,7 @@ fn complete_path(
     completions
 }
 
-fn complete_subcommand(value: &str, cmd: &clap::Command) -> Vec<(OsString, Option<StyledStr>)> {
+fn complete_subcommand(value: &str, cmd: &clap::Command) -> Vec<CompletionCandidate> {
     debug!(
         "complete_subcommand: cmd={:?}, value={:?}",
         cmd.get_name(),
@@ -277,25 +303,44 @@ fn complete_subcommand(value: &str, cmd: &clap::Command) -> Vec<(OsString, Optio
 
     let mut scs = subcommands(cmd)
         .into_iter()
-        .filter(|x| x.0.starts_with(value))
-        .map(|x| (OsString::from(&x.0), x.1))
+        .filter(|x| x.content.starts_with(value))
         .collect::<Vec<_>>();
     scs.sort();
     scs.dedup();
     scs
 }
 
-/// Gets all the long options, their visible aliases and flags of a [`clap::Command`].
+/// Gets all the long options, their visible aliases and flags of a [`clap::Command`] with formatted `--` prefix.
 /// Includes `help` and `version` depending on the [`clap::Command`] settings.
-fn longs_and_visible_aliases(p: &clap::Command) -> Vec<(String, Option<StyledStr>)> {
+fn longs_and_visible_aliases(p: &clap::Command) -> Vec<CompletionCandidate> {
     debug!("longs: name={}", p.get_name());
 
     p.get_arguments()
         .filter_map(|a| {
             a.get_long_and_visible_aliases().map(|longs| {
-                longs
-                    .into_iter()
-                    .map(|s| (s.to_string(), a.get_help().cloned()))
+                longs.into_iter().map(|s| {
+                    CompletionCandidate::new(format!("--{}", s.to_string()))
+                        .help(a.get_help().cloned())
+                        .visible(!a.is_hide_set())
+                })
+            })
+        })
+        .flatten()
+        .collect()
+}
+
+/// Gets all the long hidden aliases and flags of a [`clap::Command`].
+fn hidden_longs_aliases(p: &clap::Command) -> Vec<CompletionCandidate> {
+    debug!("longs: name={}", p.get_name());
+
+    p.get_arguments()
+        .filter_map(|a| {
+            a.get_aliases().map(|longs| {
+                longs.into_iter().map(|s| {
+                    CompletionCandidate::new(format!("--{}", s.to_string()))
+                        .help(a.get_help().cloned())
+                        .visible(false)
+                })
             })
         })
         .flatten()
@@ -304,13 +349,18 @@ fn longs_and_visible_aliases(p: &clap::Command) -> Vec<(String, Option<StyledStr
 
 /// Gets all the short options, their visible aliases and flags of a [`clap::Command`].
 /// Includes `h` and `V` depending on the [`clap::Command`] settings.
-fn shorts_and_visible_aliases(p: &clap::Command) -> Vec<(char, Option<StyledStr>)> {
+fn shorts_and_visible_aliases(p: &clap::Command) -> Vec<CompletionCandidate> {
     debug!("shorts: name={}", p.get_name());
 
     p.get_arguments()
         .filter_map(|a| {
-            a.get_short_and_visible_aliases()
-                .map(|shorts| shorts.into_iter().map(|s| (s, a.get_help().cloned())))
+            a.get_short_and_visible_aliases().map(|shorts| {
+                shorts.into_iter().map(|s| {
+                    CompletionCandidate::new(s.to_string())
+                        .help(a.get_help().cloned())
+                        .visible(!a.is_hide_set())
+                })
+            })
         })
         .flatten()
         .collect()
@@ -331,14 +381,70 @@ fn possible_values(a: &clap::Arg) -> Option<Vec<clap::builder::PossibleValue>> {
 ///
 /// Subcommand `rustup toolchain install` would be converted to
 /// `("install", "rustup toolchain install")`.
-fn subcommands(p: &clap::Command) -> Vec<(String, Option<StyledStr>)> {
+fn subcommands(p: &clap::Command) -> Vec<CompletionCandidate> {
     debug!("subcommands: name={}", p.get_name());
     debug!("subcommands: Has subcommands...{:?}", p.has_subcommands());
     p.get_subcommands()
         .flat_map(|sc| {
-            sc.get_name_and_visible_aliases()
-                .into_iter()
-                .map(|s| (s.to_string(), sc.get_about().cloned()))
+            sc.get_name_and_visible_aliases().into_iter().map(|s| {
+                CompletionCandidate::new(s.to_string())
+                    .help(sc.get_about().cloned())
+                    .visible(true)
+            })
         })
         .collect()
 }
+
+/// A completion candidate definition
+///
+/// This makes it easier to add more fields to completion candidate,
+/// rather than using `(OsString, Option<StyledStr>)` or `(String, Option<StyledStr>)` to represent a completion candidate
+#[derive(Default, Debug, PartialEq, Eq, PartialOrd, Ord)]
+pub struct CompletionCandidate {
+    /// Main completion candidate content
+    content: OsString,
+
+    /// Help message with a completion candidate
+    help: Option<StyledStr>,
+
+    /// Whether the completion candidate is visible
+    visible: bool,
+}
+
+impl CompletionCandidate {
+    /// Create a new completion candidate
+    pub fn new(content: impl Into<OsString>) -> Self {
+        let content = content.into();
+        Self {
+            content,
+            ..Default::default()
+        }
+    }
+
+    /// Set the help message of the completion candidate
+    pub fn help(mut self, help: Option<StyledStr>) -> Self {
+        self.help = help;
+        self
+    }
+
+    /// Set the visibility of the completion candidate
+    pub fn visible(mut self, visible: bool) -> Self {
+        self.visible = visible;
+        self
+    }
+
+    /// Get the content of the completion candidate
+    pub fn get_content(&self) -> &OsStr {
+        &self.content
+    }
+
+    /// Get the help message of the completion candidate
+    pub fn get_help(&self) -> Option<&StyledStr> {
+        self.help.as_ref()
+    }
+
+    /// Get the visibility of the completion candidate
+    pub fn is_visible(&self) -> bool {
+        self.visible
+    }
+}
diff --git a/clap_complete/src/dynamic/shells/bash.rs b/clap_complete/src/dynamic/shells/bash.rs
index 1308e67a..d01a932b 100644
--- a/clap_complete/src/dynamic/shells/bash.rs
+++ b/clap_complete/src/dynamic/shells/bash.rs
@@ -73,11 +73,11 @@ complete -o nospace -o bashdefault -F _clap_complete_NAME BIN
         let ifs: Option<String> = std::env::var("IFS").ok().and_then(|i| i.parse().ok());
         let completions = crate::dynamic::complete(cmd, args, index, current_dir)?;
 
-        for (i, (completion, _)) in completions.iter().enumerate() {
+        for (i, candidate) in completions.iter().enumerate() {
             if i != 0 {
                 write!(buf, "{}", ifs.as_deref().unwrap_or("\n"))?;
             }
-            write!(buf, "{}", completion.to_string_lossy())?;
+            write!(buf, "{}", candidate.get_content().to_string_lossy())?;
         }
         Ok(())
     }
diff --git a/clap_complete/src/dynamic/shells/elvish.rs b/clap_complete/src/dynamic/shells/elvish.rs
index 57351b17..323226c6 100644
--- a/clap_complete/src/dynamic/shells/elvish.rs
+++ b/clap_complete/src/dynamic/shells/elvish.rs
@@ -47,11 +47,11 @@ set edit:completion:arg-completer[BIN] = { |@words|
         let ifs: Option<String> = std::env::var("_CLAP_IFS").ok().and_then(|i| i.parse().ok());
         let completions = crate::dynamic::complete(cmd, args, index, current_dir)?;
 
-        for (i, (completion, _)) in completions.iter().enumerate() {
+        for (i, candidate) in completions.iter().enumerate() {
             if i != 0 {
                 write!(buf, "{}", ifs.as_deref().unwrap_or("\n"))?;
             }
-            write!(buf, "{}", completion.to_string_lossy())?;
+            write!(buf, "{}", candidate.get_content().to_string_lossy())?;
         }
         Ok(())
     }
diff --git a/clap_complete/src/dynamic/shells/fish.rs b/clap_complete/src/dynamic/shells/fish.rs
index cfb95195..269539e0 100644
--- a/clap_complete/src/dynamic/shells/fish.rs
+++ b/clap_complete/src/dynamic/shells/fish.rs
@@ -30,9 +30,9 @@ impl crate::dynamic::Completer for Fish {
         let index = args.len() - 1;
         let completions = crate::dynamic::complete(cmd, args, index, current_dir)?;
 
-        for (completion, help) in completions {
-            write!(buf, "{}", completion.to_string_lossy())?;
-            if let Some(help) = help {
+        for candidate in completions {
+            write!(buf, "{}", candidate.get_content().to_string_lossy())?;
+            if let Some(help) = candidate.get_help() {
                 write!(
                     buf,
                     "\t{}",
diff --git a/clap_complete/src/dynamic/shells/mod.rs b/clap_complete/src/dynamic/shells/mod.rs
index 405d2abb..b7fed5da 100644
--- a/clap_complete/src/dynamic/shells/mod.rs
+++ b/clap_complete/src/dynamic/shells/mod.rs
@@ -20,78 +20,6 @@ use crate::dynamic::Completer as _;
 /// A subcommand definition to `flatten` into your CLI
 ///
 /// This provides a one-stop solution for integrating completions into your CLI
-///
-/// # Examples
-///
-/// The following example shows how to integrate completions into your CLI and generate completions.
-///
-/// 1. Build an application with derive API.
-/// 2. Call `clap_complete::dynamic::shells::CompleteCommand::augment_subcommands` to add the `complete` subcommand into the application.
-/// 3. Call `get_matches()`, or any of the other normal methods directly after.
-///
-/// For example:
-///
-/// ```no_run
-/// // src/main.rs
-/// use clap::{CommandFactory, FromArgMatches, Parser, Subcommand};
-/// use clap_complete::dynamic::shells::CompleteCommand;
-///
-/// #[derive(Parser, Debug)]
-/// #[clap(name = "dynamic", about = "A dynamic command line tool")]
-/// struct Cli {
-/// 	/// The subcommand to run complete
-/// 	#[command(subcommand)]
-///     complete: Option<CompleteCommand>,
-///     /// Input file path
-///     #[clap(short, long, value_hint = clap::ValueHint::FilePath)]
-///     input: Option<String>,
-///     /// Output format
-///     #[clap(short = 'F', long, value_parser = ["json", "yaml", "toml"])]
-///     format: Option<String>,
-/// }
-///
-/// fn main() {
-///     let cli = Cli::parse();
-///     if let Some(completions) = cli.complete {
-///         completions.complete(&mut Cli::command());
-///		}
-///
-/// 	// normal logic continues...
-/// }
-///```
-///
-/// # Usage for complete subcommand:
-///
-/// To generate shell completion scripts and source them, we can use the following command.
-///
-/// **NOTE**: If you have set a custom shell configuration file,
-/// please remember to modify the redirection output file in the following command.
-///
-/// - Bash
-/// 	```bash
-/// 	echo "source <(your_program complete --shell bash --register -)" >> ~/.bashrc
-/// 	```
-///
-/// - Fish
-/// 	```fish
-/// 	echo "source (your_program complete --shell fish --register - | psub)" >> ~/.config/fish/config.fish
-/// 	```
-///
-/// - Zsh
-/// 	```zsh
-/// 	echo "source <(your_program complete --shell zsh --register -)" >> ~/.zshrc
-/// 	```
-///
-/// - Elvish
-/// 	```elvish
-/// 	echo "eval (your_program complete --shell elvish --register -)" >> ~/.elvish/rc.elv
-/// 	```
-///
-/// - Powershell
-/// 	```powershell
-/// 	echo "your_program complete --shell powershell --register - | Invoke-Expression" >> $PROFILE
-/// 	```
-///
 #[derive(clap::Subcommand)]
 #[allow(missing_docs)]
 #[derive(Clone, Debug)]
diff --git a/clap_complete/src/dynamic/shells/zsh.rs b/clap_complete/src/dynamic/shells/zsh.rs
index d6e699f7..4326cd16 100644
--- a/clap_complete/src/dynamic/shells/zsh.rs
+++ b/clap_complete/src/dynamic/shells/zsh.rs
@@ -54,11 +54,11 @@ compdef _clap_dynamic_completer BIN"#
         }
         let completions = crate::dynamic::complete(cmd, args, index, current_dir)?;
 
-        for (i, (completion, _)) in completions.iter().enumerate() {
+        for (i, candidate) in completions.iter().enumerate() {
             if i != 0 {
                 write!(buf, "{}", ifs.as_deref().unwrap_or("\n"))?;
             }
-            write!(buf, "{}", completion.to_string_lossy())?;
+            write!(buf, "{}", candidate.get_content().to_string_lossy())?;
         }
         Ok(())
     }


Test patch:
diff --git a/clap_complete/tests/testsuite/dynamic.rs b/clap_complete/tests/testsuite/dynamic.rs
index fa482782..df8bac01 100644
--- a/clap_complete/tests/testsuite/dynamic.rs
+++ b/clap_complete/tests/testsuite/dynamic.rs
@@ -23,11 +23,35 @@ fn suggest_subcommand_subset() {
         .subcommand(Command::new("hello-moon"))
         .subcommand(Command::new("goodbye-world"));
 
-    assert_data_eq!(complete!(cmd, "he"), snapbox::str![[r#"
+    assert_data_eq!(
+        complete!(cmd, "he"),
+        snapbox::str![[r#"
 hello-moon
 hello-world
 help	Print this message or the help of the given subcommand(s)
-"#]],);
+"#]],
+    );
+}
+
+#[test]
+fn suggest_hidden_long_flags() {
+    let mut cmd = Command::new("exhaustive")
+        .arg(clap::Arg::new("hello-world-visible").long("hello-world-visible"))
+        .arg(
+            clap::Arg::new("hello-world-hidden")
+                .long("hello-world-hidden")
+                .hide(true),
+        );
+
+    assert_data_eq!(
+        complete!(cmd, "--hello-world"),
+        snapbox::str!["--hello-world-visible"]
+    );
+
+    assert_data_eq!(
+        complete!(cmd, "--hello-world-h"),
+        snapbox::str!["--hello-world-hidden"]
+    )
 }
 
 #[test]
@@ -60,6 +84,51 @@ hello-world-foo"
     );
 }
 
+#[test]
+fn suggest_hidden_long_flag_aliases() {
+    let mut cmd = Command::new("exhaustive")
+        .arg(
+            clap::Arg::new("test_visible")
+                .long("test_visible")
+                .visible_alias("test_visible-alias_visible")
+                .alias("test_visible-alias_hidden"),
+        )
+        .arg(
+            clap::Arg::new("test_hidden")
+                .long("test_hidden")
+                .visible_alias("test_hidden-alias_visible")
+                .alias("test_hidden-alias_hidden")
+                .hide(true),
+        );
+
+    assert_data_eq!(
+        complete!(cmd, "--test"),
+        snapbox::str![
+            "--test_visible
+--test_visible-alias_visible"
+        ]
+    );
+
+    assert_data_eq!(
+        complete!(cmd, "--test_h"),
+        snapbox::str![
+            "--test_hidden
+--test_hidden-alias_visible
+--test_hidden-alias_hidden"
+        ]
+    );
+
+    assert_data_eq!(
+        complete!(cmd, "--test_visible-alias_h"),
+        snapbox::str!["--test_visible-alias_hidden"]
+    );
+
+    assert_data_eq!(
+        complete!(cmd, "--test_hidden-alias_h"),
+        snapbox::str!["--test_hidden-alias_hidden"]
+    );
+}
+
 #[test]
 fn suggest_long_flag_subset() {
     let mut cmd = Command::new("exhaustive")
@@ -79,11 +148,14 @@ fn suggest_long_flag_subset() {
                 .action(clap::ArgAction::Count),
         );
 
-    assert_data_eq!(complete!(cmd, "--he"), snapbox::str![[r#"
+    assert_data_eq!(
+        complete!(cmd, "--he"),
+        snapbox::str![[r#"
 --hello-world
 --hello-moon
 --help	Print help
-"#]],);
+"#]],
+    );
 }
 
 #[test]
@@ -95,10 +167,13 @@ fn suggest_possible_value_subset() {
         "goodbye-world".into(),
     ]));
 
-    assert_data_eq!(complete!(cmd, "hello"), snapbox::str![[r#"
+    assert_data_eq!(
+        complete!(cmd, "hello"),
+        snapbox::str![[r#"
 hello-world	Say hello to the world
 hello-moon
-"#]],);
+"#]],
+    );
 }
 
 #[test]
@@ -120,12 +195,15 @@ fn suggest_additional_short_flags() {
                 .action(clap::ArgAction::Count),
         );
 
-    assert_data_eq!(complete!(cmd, "-a"), snapbox::str![[r#"
+    assert_data_eq!(
+        complete!(cmd, "-a"),
+        snapbox::str![[r#"
 -aa
 -ab
 -ac
 -ah	Print help
-"#]],);
+"#]],
+    );
 }
 
 #[test]
@@ -138,13 +216,16 @@ fn suggest_subcommand_positional() {
         ]),
     ));
 
-    assert_data_eq!(complete!(cmd, "hello-world [TAB]"), snapbox::str![[r#"
+    assert_data_eq!(
+        complete!(cmd, "hello-world [TAB]"),
+        snapbox::str![[r#"
 --help	Print help (see more with '--help')
 -h	Print help (see more with '--help')
 hello-world	Say hello to the world
 hello-moon
 goodbye-world
-"#]],);
+"#]],
+    );
 }
 
 fn complete(cmd: &mut Command, args: impl AsRef<str>, current_dir: Option<&Path>) -> String {
@@ -171,9 +252,9 @@ fn complete(cmd: &mut Command, args: impl AsRef<str>, current_dir: Option<&Path>
     clap_complete::dynamic::complete(cmd, args, arg_index, current_dir)
         .unwrap()
         .into_iter()
-        .map(|(compl, help)| {
-            let compl = compl.to_str().unwrap();
-            if let Some(help) = help {
+        .map(|candidate| {
+            let compl = candidate.get_content().to_str().unwrap();
+            if let Some(help) = candidate.get_help() {
                 format!("{compl}\t{help}")
             } else {
                 compl.to_owned()