diff --git a/clap_complete/src/dynamic/completer.rs b/clap_complete/src/dynamic/completer.rs index 84891227b098..84af494d3e78 100644 --- a/clap_complete/src/dynamic/completer.rs +++ b/clap_complete/src/dynamic/completer.rs @@ -1,6 +1,7 @@ use std::ffi::OsStr; use std::ffi::OsString; +use clap::builder::StyledStr; use clap_lex::OsStrExt as _; /// Shell-specific completions @@ -31,7 +32,7 @@ pub fn complete( args: Vec, arg_index: usize, current_dir: Option<&std::path::Path>, -) -> Result, std::io::Error> { +) -> Result)>, std::io::Error> { cmd.build(); let raw_args = clap_lex::RawArgs::new(args.into_iter()); @@ -90,7 +91,7 @@ fn complete_arg( current_dir: Option<&std::path::Path>, pos_index: usize, is_escaped: bool, -) -> Result, std::io::Error> { +) -> Result)>, std::io::Error> { debug!( "complete_arg: arg={:?}, cmd={:?}, current_dir={:?}, pos_index={}, is_escaped={}", arg, @@ -109,18 +110,16 @@ fn complete_arg( completions.extend( complete_arg_value(value.to_str().ok_or(value), arg, current_dir) .into_iter() - .map(|os| { + .map(|(os, help)| { // HACK: Need better `OsStr` manipulation - format!("--{}={}", flag, os.to_string_lossy()).into() + (format!("--{}={}", flag, os.to_string_lossy()).into(), help) }), ) } } else { - completions.extend( - longs_and_visible_aliases(cmd) - .into_iter() - .filter_map(|f| f.starts_with(flag).then(|| format!("--{f}").into())), - ); + completions.extend(longs_and_visible_aliases(cmd).into_iter().filter_map( + |(f, help)| f.starts_with(flag).then(|| (format!("--{f}").into(), help)), + )); } } } else if arg.is_escape() || arg.is_stdio() || arg.is_empty() { @@ -128,7 +127,7 @@ fn complete_arg( completions.extend( longs_and_visible_aliases(cmd) .into_iter() - .map(|f| format!("--{f}").into()), + .map(|(f, help)| (format!("--{f}").into(), help)), ); } @@ -143,7 +142,7 @@ fn complete_arg( shorts_and_visible_aliases(cmd) .into_iter() // HACK: Need better `OsStr` manipulation - .map(|f| format!("{}{}", dash_or_arg, f).into()), + .map(|(f, help)| (format!("{}{}", dash_or_arg, f).into(), help)), ); } } @@ -166,7 +165,7 @@ fn complete_arg_value( value: Result<&str, &OsStr>, arg: &clap::Arg, current_dir: Option<&std::path::Path>, -) -> Vec { +) -> Vec<(OsString, Option)> { let mut values = Vec::new(); debug!("complete_arg_value: arg={arg:?}, value={value:?}"); @@ -174,7 +173,8 @@ fn complete_arg_value( if let Ok(value) = value { values.extend(possible_values.into_iter().filter_map(|p| { let name = p.get_name(); - name.starts_with(value).then(|| name.into()) + name.starts_with(value) + .then(|| (name.into(), p.get_help().cloned())) })); } } else { @@ -223,7 +223,7 @@ fn complete_path( value_os: &OsStr, current_dir: Option<&std::path::Path>, is_wanted: impl Fn(&std::path::Path) -> bool, -) -> Vec { +) -> Vec<(OsString, Option)> { let mut completions = Vec::new(); let current_dir = match current_dir { @@ -255,12 +255,12 @@ fn complete_path( let path = entry.path(); let mut suggestion = pathdiff::diff_paths(&path, current_dir).unwrap_or(path); suggestion.push(""); // Ensure trailing `/` - completions.push(suggestion.as_os_str().to_owned()); + completions.push((suggestion.as_os_str().to_owned(), None)); } else { let path = entry.path(); if is_wanted(&path) { let suggestion = pathdiff::diff_paths(&path, current_dir).unwrap_or(path); - completions.push(suggestion.as_os_str().to_owned()); + completions.push((suggestion.as_os_str().to_owned(), None)); } } } @@ -268,7 +268,7 @@ fn complete_path( completions } -fn complete_subcommand(value: &str, cmd: &clap::Command) -> Vec { +fn complete_subcommand(value: &str, cmd: &clap::Command) -> Vec<(OsString, Option)> { debug!( "complete_subcommand: cmd={:?}, value={:?}", cmd.get_name(), @@ -277,8 +277,8 @@ fn complete_subcommand(value: &str, cmd: &clap::Command) -> Vec { let mut scs = subcommands(cmd) .into_iter() - .filter(|x| x.starts_with(value)) - .map(OsString::from) + .filter(|x| x.0.starts_with(value)) + .map(|x| (OsString::from(&x.0), x.1)) .collect::>(); scs.sort(); scs.dedup(); @@ -287,29 +287,16 @@ fn complete_subcommand(value: &str, cmd: &clap::Command) -> Vec { /// Gets all the long options, their visible aliases and flags of a [`clap::Command`]. /// Includes `help` and `version` depending on the [`clap::Command`] settings. -fn longs_and_visible_aliases(p: &clap::Command) -> Vec { +pub fn longs_and_visible_aliases(p: &clap::Command) -> Vec<(String, Option)> { debug!("longs: name={}", p.get_name()); p.get_arguments() .filter_map(|a| { - if !a.is_positional() { - if a.get_visible_aliases().is_some() && a.get_long().is_some() { - let mut visible_aliases: Vec<_> = a - .get_visible_aliases() - .unwrap() - .into_iter() - .map(|s| s.to_string()) - .collect(); - visible_aliases.push(a.get_long().unwrap().to_string()); - Some(visible_aliases) - } else if a.get_visible_aliases().is_none() && a.get_long().is_some() { - Some(vec![a.get_long().unwrap().to_string()]) - } else { - None - } - } else { - None - } + a.get_long_and_visible_aliases().map(|longs| { + longs + .into_iter() + .map(|s| (s.to_string(), a.get_help().cloned())) + }) }) .flatten() .collect() @@ -317,31 +304,20 @@ fn longs_and_visible_aliases(p: &clap::Command) -> Vec { /// 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 { +pub fn shorts_and_visible_aliases(p: &clap::Command) -> Vec<(char, Option)> { debug!("shorts: name={}", p.get_name()); p.get_arguments() .filter_map(|a| { - if !a.is_positional() { - if a.get_visible_short_aliases().is_some() && a.get_short().is_some() { - let mut shorts_and_visible_aliases = a.get_visible_short_aliases().unwrap(); - shorts_and_visible_aliases.push(a.get_short().unwrap()); - Some(shorts_and_visible_aliases) - } else if a.get_visible_short_aliases().is_none() && a.get_short().is_some() { - Some(vec![a.get_short().unwrap()]) - } else { - None - } - } else { - None - } + a.get_short_and_visible_aliases() + .map(|shorts| shorts.into_iter().map(|s| (s, a.get_help().cloned()))) }) .flatten() .collect() } /// Get the possible values for completion -fn possible_values(a: &clap::Arg) -> Option> { +pub fn possible_values(a: &clap::Arg) -> Option> { if !a.get_num_args().expect("built").takes_values() { None } else { @@ -355,17 +331,12 @@ fn possible_values(a: &clap::Arg) -> Option> { /// /// Subcommand `rustup toolchain install` would be converted to /// `("install", "rustup toolchain install")`. -fn subcommands(p: &clap::Command) -> Vec { +pub fn subcommands(p: &clap::Command) -> Vec<(String, Option)> { debug!("subcommands: name={}", p.get_name()); debug!("subcommands: Has subcommands...{:?}", p.has_subcommands()); - let mut subcmds = vec![]; - - for sc in p.get_subcommands() { - debug!("subcommands:iter: name={}", sc.get_name(),); - - subcmds.push(sc.get_name().to_string()); - } - - subcmds + p.get_subcommands() + .map(|sc| (sc.get_name().to_string(), sc.get_about().cloned())) + .collect() } + diff --git a/clap_complete/src/dynamic/shells/bash.rs b/clap_complete/src/dynamic/shells/bash.rs index ebc43c6910fe..43c128e5b4ec 100644 --- a/clap_complete/src/dynamic/shells/bash.rs +++ b/clap_complete/src/dynamic/shells/bash.rs @@ -73,7 +73,7 @@ complete -o nospace -o bashdefault -F _clap_complete_NAME BIN let ifs: Option = 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, (completion, _)) in completions.iter().enumerate() { if i != 0 { write!(buf, "{}", ifs.as_deref().unwrap_or("\n"))?; } diff --git a/clap_complete/src/dynamic/shells/fish.rs b/clap_complete/src/dynamic/shells/fish.rs index 79b8bda543d4..a05e5b48d32b 100644 --- a/clap_complete/src/dynamic/shells/fish.rs +++ b/clap_complete/src/dynamic/shells/fish.rs @@ -30,8 +30,12 @@ impl crate::dynamic::Completer for Fish { let index = args.len() - 1; let completions = crate::dynamic::complete(cmd, args, index, current_dir)?; - for completion in completions { - writeln!(buf, "{}", completion.to_string_lossy())?; + for (completion, help) in completions { + write!(buf, "{}", completion.to_string_lossy())?; + if let Some(help) = help { + write!(buf, "\t{}", help.to_string().lines().next().unwrap_or_default())?; + } + writeln!(buf)?; } Ok(()) } diff --git a/clap_complete/tests/testsuite/dynamic.rs b/clap_complete/tests/testsuite/dynamic.rs index 8ab7461d033d..8a2d9e466b43 100644 --- a/clap_complete/tests/testsuite/dynamic.rs +++ b/clap_complete/tests/testsuite/dynamic.rs @@ -19,7 +19,7 @@ fn suggest_subcommand_subset() { clap_complete::dynamic::complete(&mut cmd, args, arg_index, current_dir).unwrap(); let completions = completions .into_iter() - .map(|s| s.to_string_lossy().into_owned()) + .map(|s| s.0.to_string_lossy().into_owned()) .collect::>(); assert_eq!(completions, ["hello-moon", "hello-world", "help"]); @@ -56,7 +56,7 @@ fn suggest_long_flag_subset() { clap_complete::dynamic::complete(&mut cmd, args, arg_index, current_dir).unwrap(); let completions = completions .into_iter() - .map(|s| s.to_string_lossy().into_owned()) + .map(|s| s.0.to_string_lossy().into_owned()) .collect::>(); assert_eq!(completions, ["--hello-world", "--hello-moon", "--help"]); @@ -82,7 +82,7 @@ fn suggest_possible_value_subset() { clap_complete::dynamic::complete(&mut cmd, args, arg_index, current_dir).unwrap(); let completions = completions .into_iter() - .map(|s| s.to_string_lossy().into_owned()) + .map(|s| s.0.to_string_lossy().into_owned()) .collect::>(); assert_eq!(completions, ["hello-world", "hello-moon"]); @@ -119,7 +119,7 @@ fn suggest_additional_short_flags() { clap_complete::dynamic::complete(&mut cmd, args, arg_index, current_dir).unwrap(); let completions = completions .into_iter() - .map(|s| s.to_string_lossy().into_owned()) + .map(|s| s.0.to_string_lossy().into_owned()) .collect::>(); assert_eq!(completions, ["-aa", "-ab", "-ac", "-ah"]); diff --git a/clap_complete/tests/testsuite/fish.rs b/clap_complete/tests/testsuite/fish.rs index 6e2bbeb46fe4..be7ba89f6124 100644 --- a/clap_complete/tests/testsuite/fish.rs +++ b/clap_complete/tests/testsuite/fish.rs @@ -162,9 +162,11 @@ fn complete_dynamic() { let input = "exhaustive \t"; let expected = r#"% exhaustive -action help pacman -h --global -alias hint quote -V --help -complete last value --generate --version"#; +action last -V (Print version) +alias pacman --generate (generate) +complete (Register shell completions for this program) quote --global (everywhere) +help (Print this message or the help of the given subcommand(s)) value --help (Print help) +hint -h (Print help) --version (Print version)"#; let actual = runtime.complete(input, &term).unwrap(); snapbox::assert_eq(expected, actual); }