mirror of
https://github.com/clap-rs/clap
synced 2024-11-10 06:44:16 +00:00
feat(complete): Show help in dynamic completions
This commit is contained in:
parent
b613548b03
commit
bdefebf663
5 changed files with 49 additions and 72 deletions
|
@ -1,6 +1,7 @@
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::ffi::OsString;
|
use std::ffi::OsString;
|
||||||
|
|
||||||
|
use clap::builder::StyledStr;
|
||||||
use clap_lex::OsStrExt as _;
|
use clap_lex::OsStrExt as _;
|
||||||
|
|
||||||
/// Shell-specific completions
|
/// Shell-specific completions
|
||||||
|
@ -31,7 +32,7 @@ pub fn complete(
|
||||||
args: Vec<std::ffi::OsString>,
|
args: Vec<std::ffi::OsString>,
|
||||||
arg_index: usize,
|
arg_index: usize,
|
||||||
current_dir: Option<&std::path::Path>,
|
current_dir: Option<&std::path::Path>,
|
||||||
) -> Result<Vec<std::ffi::OsString>, std::io::Error> {
|
) -> Result<Vec<(std::ffi::OsString, Option<StyledStr>)>, std::io::Error> {
|
||||||
cmd.build();
|
cmd.build();
|
||||||
|
|
||||||
let raw_args = clap_lex::RawArgs::new(args.into_iter());
|
let raw_args = clap_lex::RawArgs::new(args.into_iter());
|
||||||
|
@ -90,7 +91,7 @@ fn complete_arg(
|
||||||
current_dir: Option<&std::path::Path>,
|
current_dir: Option<&std::path::Path>,
|
||||||
pos_index: usize,
|
pos_index: usize,
|
||||||
is_escaped: bool,
|
is_escaped: bool,
|
||||||
) -> Result<Vec<std::ffi::OsString>, std::io::Error> {
|
) -> Result<Vec<(std::ffi::OsString, Option<StyledStr>)>, std::io::Error> {
|
||||||
debug!(
|
debug!(
|
||||||
"complete_arg: arg={:?}, cmd={:?}, current_dir={:?}, pos_index={}, is_escaped={}",
|
"complete_arg: arg={:?}, cmd={:?}, current_dir={:?}, pos_index={}, is_escaped={}",
|
||||||
arg,
|
arg,
|
||||||
|
@ -109,18 +110,16 @@ fn complete_arg(
|
||||||
completions.extend(
|
completions.extend(
|
||||||
complete_arg_value(value.to_str().ok_or(value), arg, current_dir)
|
complete_arg_value(value.to_str().ok_or(value), arg, current_dir)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|os| {
|
.map(|(os, help)| {
|
||||||
// HACK: Need better `OsStr` manipulation
|
// HACK: Need better `OsStr` manipulation
|
||||||
format!("--{}={}", flag, os.to_string_lossy()).into()
|
(format!("--{}={}", flag, os.to_string_lossy()).into(), help)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
completions.extend(
|
completions.extend(longs_and_visible_aliases(cmd).into_iter().filter_map(
|
||||||
longs_and_visible_aliases(cmd)
|
|(f, help)| f.starts_with(flag).then(|| (format!("--{f}").into(), help)),
|
||||||
.into_iter()
|
));
|
||||||
.filter_map(|f| f.starts_with(flag).then(|| format!("--{f}").into())),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if arg.is_escape() || arg.is_stdio() || arg.is_empty() {
|
} else if arg.is_escape() || arg.is_stdio() || arg.is_empty() {
|
||||||
|
@ -128,7 +127,7 @@ fn complete_arg(
|
||||||
completions.extend(
|
completions.extend(
|
||||||
longs_and_visible_aliases(cmd)
|
longs_and_visible_aliases(cmd)
|
||||||
.into_iter()
|
.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)
|
shorts_and_visible_aliases(cmd)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
// HACK: Need better `OsStr` manipulation
|
// 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>,
|
value: Result<&str, &OsStr>,
|
||||||
arg: &clap::Arg,
|
arg: &clap::Arg,
|
||||||
current_dir: Option<&std::path::Path>,
|
current_dir: Option<&std::path::Path>,
|
||||||
) -> Vec<OsString> {
|
) -> Vec<(OsString, Option<StyledStr>)> {
|
||||||
let mut values = Vec::new();
|
let mut values = Vec::new();
|
||||||
debug!("complete_arg_value: arg={arg:?}, value={value:?}");
|
debug!("complete_arg_value: arg={arg:?}, value={value:?}");
|
||||||
|
|
||||||
|
@ -174,7 +173,8 @@ fn complete_arg_value(
|
||||||
if let Ok(value) = value {
|
if let Ok(value) = value {
|
||||||
values.extend(possible_values.into_iter().filter_map(|p| {
|
values.extend(possible_values.into_iter().filter_map(|p| {
|
||||||
let name = p.get_name();
|
let name = p.get_name();
|
||||||
name.starts_with(value).then(|| name.into())
|
name.starts_with(value)
|
||||||
|
.then(|| (name.into(), p.get_help().cloned()))
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -223,7 +223,7 @@ fn complete_path(
|
||||||
value_os: &OsStr,
|
value_os: &OsStr,
|
||||||
current_dir: Option<&std::path::Path>,
|
current_dir: Option<&std::path::Path>,
|
||||||
is_wanted: impl Fn(&std::path::Path) -> bool,
|
is_wanted: impl Fn(&std::path::Path) -> bool,
|
||||||
) -> Vec<OsString> {
|
) -> Vec<(OsString, Option<StyledStr>)> {
|
||||||
let mut completions = Vec::new();
|
let mut completions = Vec::new();
|
||||||
|
|
||||||
let current_dir = match current_dir {
|
let current_dir = match current_dir {
|
||||||
|
@ -255,12 +255,12 @@ fn complete_path(
|
||||||
let path = entry.path();
|
let path = entry.path();
|
||||||
let mut suggestion = pathdiff::diff_paths(&path, current_dir).unwrap_or(path);
|
let mut suggestion = pathdiff::diff_paths(&path, current_dir).unwrap_or(path);
|
||||||
suggestion.push(""); // Ensure trailing `/`
|
suggestion.push(""); // Ensure trailing `/`
|
||||||
completions.push(suggestion.as_os_str().to_owned());
|
completions.push((suggestion.as_os_str().to_owned(), None));
|
||||||
} else {
|
} else {
|
||||||
let path = entry.path();
|
let path = entry.path();
|
||||||
if is_wanted(&path) {
|
if is_wanted(&path) {
|
||||||
let suggestion = pathdiff::diff_paths(&path, current_dir).unwrap_or(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
|
completions
|
||||||
}
|
}
|
||||||
|
|
||||||
fn complete_subcommand(value: &str, cmd: &clap::Command) -> Vec<OsString> {
|
fn complete_subcommand(value: &str, cmd: &clap::Command) -> Vec<(OsString, Option<StyledStr>)> {
|
||||||
debug!(
|
debug!(
|
||||||
"complete_subcommand: cmd={:?}, value={:?}",
|
"complete_subcommand: cmd={:?}, value={:?}",
|
||||||
cmd.get_name(),
|
cmd.get_name(),
|
||||||
|
@ -277,8 +277,8 @@ fn complete_subcommand(value: &str, cmd: &clap::Command) -> Vec<OsString> {
|
||||||
|
|
||||||
let mut scs = subcommands(cmd)
|
let mut scs = subcommands(cmd)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|x| x.starts_with(value))
|
.filter(|x| x.0.starts_with(value))
|
||||||
.map(OsString::from)
|
.map(|x| (OsString::from(&x.0), x.1))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
scs.sort();
|
scs.sort();
|
||||||
scs.dedup();
|
scs.dedup();
|
||||||
|
@ -287,29 +287,16 @@ fn complete_subcommand(value: &str, cmd: &clap::Command) -> Vec<OsString> {
|
||||||
|
|
||||||
/// 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`].
|
||||||
/// Includes `help` and `version` depending on the [`clap::Command`] settings.
|
/// Includes `help` and `version` depending on the [`clap::Command`] settings.
|
||||||
fn longs_and_visible_aliases(p: &clap::Command) -> Vec<String> {
|
fn longs_and_visible_aliases(p: &clap::Command) -> Vec<(String, Option<StyledStr>)> {
|
||||||
debug!("longs: name={}", p.get_name());
|
debug!("longs: name={}", p.get_name());
|
||||||
|
|
||||||
p.get_arguments()
|
p.get_arguments()
|
||||||
.filter_map(|a| {
|
.filter_map(|a| {
|
||||||
if !a.is_positional() {
|
a.get_long_and_visible_aliases().map(|longs| {
|
||||||
if a.get_visible_aliases().is_some() && a.get_long().is_some() {
|
longs
|
||||||
let mut visible_aliases: Vec<_> = a
|
|
||||||
.get_visible_aliases()
|
|
||||||
.unwrap()
|
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|s| s.to_string())
|
.map(|s| (s.to_string(), a.get_help().cloned()))
|
||||||
.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
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.flatten()
|
.flatten()
|
||||||
.collect()
|
.collect()
|
||||||
|
@ -317,24 +304,13 @@ fn longs_and_visible_aliases(p: &clap::Command) -> Vec<String> {
|
||||||
|
|
||||||
/// Gets all the short options, their visible aliases and flags of a [`clap::Command`].
|
/// Gets all the short options, their visible aliases and flags of a [`clap::Command`].
|
||||||
/// Includes `h` and `V` depending on the [`clap::Command`] settings.
|
/// Includes `h` and `V` depending on the [`clap::Command`] settings.
|
||||||
fn shorts_and_visible_aliases(p: &clap::Command) -> Vec<char> {
|
fn shorts_and_visible_aliases(p: &clap::Command) -> Vec<(char, Option<StyledStr>)> {
|
||||||
debug!("shorts: name={}", p.get_name());
|
debug!("shorts: name={}", p.get_name());
|
||||||
|
|
||||||
p.get_arguments()
|
p.get_arguments()
|
||||||
.filter_map(|a| {
|
.filter_map(|a| {
|
||||||
if !a.is_positional() {
|
a.get_short_and_visible_aliases()
|
||||||
if a.get_visible_short_aliases().is_some() && a.get_short().is_some() {
|
.map(|shorts| shorts.into_iter().map(|s| (s, a.get_help().cloned())))
|
||||||
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
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.flatten()
|
.flatten()
|
||||||
.collect()
|
.collect()
|
||||||
|
@ -355,17 +331,12 @@ fn possible_values(a: &clap::Arg) -> Option<Vec<clap::builder::PossibleValue>> {
|
||||||
///
|
///
|
||||||
/// Subcommand `rustup toolchain install` would be converted to
|
/// Subcommand `rustup toolchain install` would be converted to
|
||||||
/// `("install", "rustup toolchain install")`.
|
/// `("install", "rustup toolchain install")`.
|
||||||
fn subcommands(p: &clap::Command) -> Vec<String> {
|
fn subcommands(p: &clap::Command) -> Vec<(String, Option<StyledStr>)> {
|
||||||
debug!("subcommands: name={}", p.get_name());
|
debug!("subcommands: name={}", p.get_name());
|
||||||
debug!("subcommands: Has subcommands...{:?}", p.has_subcommands());
|
debug!("subcommands: Has subcommands...{:?}", p.has_subcommands());
|
||||||
|
|
||||||
let mut subcmds = vec![];
|
p.get_subcommands()
|
||||||
|
.map(|sc| (sc.get_name().to_string(), sc.get_about().cloned()))
|
||||||
for sc in p.get_subcommands() {
|
.collect()
|
||||||
debug!("subcommands:iter: name={}", sc.get_name(),);
|
|
||||||
|
|
||||||
subcmds.push(sc.get_name().to_string());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
subcmds
|
|
||||||
}
|
|
||||||
|
|
|
@ -73,7 +73,7 @@ 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 ifs: Option<String> = std::env::var("IFS").ok().and_then(|i| i.parse().ok());
|
||||||
let completions = crate::dynamic::complete(cmd, args, index, current_dir)?;
|
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 {
|
if i != 0 {
|
||||||
write!(buf, "{}", ifs.as_deref().unwrap_or("\n"))?;
|
write!(buf, "{}", ifs.as_deref().unwrap_or("\n"))?;
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,8 +30,12 @@ impl crate::dynamic::Completer for Fish {
|
||||||
let index = args.len() - 1;
|
let index = args.len() - 1;
|
||||||
let completions = crate::dynamic::complete(cmd, args, index, current_dir)?;
|
let completions = crate::dynamic::complete(cmd, args, index, current_dir)?;
|
||||||
|
|
||||||
for completion in completions {
|
for (completion, help) in completions {
|
||||||
writeln!(buf, "{}", completion.to_string_lossy())?;
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ fn suggest_subcommand_subset() {
|
||||||
clap_complete::dynamic::complete(&mut cmd, args, arg_index, current_dir).unwrap();
|
clap_complete::dynamic::complete(&mut cmd, args, arg_index, current_dir).unwrap();
|
||||||
let completions = completions
|
let completions = completions
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|s| s.to_string_lossy().into_owned())
|
.map(|s| s.0.to_string_lossy().into_owned())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
assert_eq!(completions, ["hello-moon", "hello-world", "help"]);
|
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();
|
clap_complete::dynamic::complete(&mut cmd, args, arg_index, current_dir).unwrap();
|
||||||
let completions = completions
|
let completions = completions
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|s| s.to_string_lossy().into_owned())
|
.map(|s| s.0.to_string_lossy().into_owned())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
assert_eq!(completions, ["--hello-world", "--hello-moon", "--help"]);
|
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();
|
clap_complete::dynamic::complete(&mut cmd, args, arg_index, current_dir).unwrap();
|
||||||
let completions = completions
|
let completions = completions
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|s| s.to_string_lossy().into_owned())
|
.map(|s| s.0.to_string_lossy().into_owned())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
assert_eq!(completions, ["hello-world", "hello-moon"]);
|
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();
|
clap_complete::dynamic::complete(&mut cmd, args, arg_index, current_dir).unwrap();
|
||||||
let completions = completions
|
let completions = completions
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|s| s.to_string_lossy().into_owned())
|
.map(|s| s.0.to_string_lossy().into_owned())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
assert_eq!(completions, ["-aa", "-ab", "-ac", "-ah"]);
|
assert_eq!(completions, ["-aa", "-ab", "-ac", "-ah"]);
|
||||||
|
|
|
@ -162,9 +162,11 @@ fn complete_dynamic() {
|
||||||
|
|
||||||
let input = "exhaustive \t";
|
let input = "exhaustive \t";
|
||||||
let expected = r#"% exhaustive
|
let expected = r#"% exhaustive
|
||||||
action help pacman -h --global
|
action last -V (Print version)
|
||||||
alias hint quote -V --help
|
alias pacman --generate (generate)
|
||||||
complete last value --generate --version"#;
|
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();
|
let actual = runtime.complete(input, &term).unwrap();
|
||||||
snapbox::assert_eq(expected, actual);
|
snapbox::assert_eq(expected, actual);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue