summaryrefslogtreecommitdiffstats
path: root/vendor/clap_complete/src/dynamic.rs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/clap_complete/src/dynamic.rs')
-rw-r--r--vendor/clap_complete/src/dynamic.rs551
1 files changed, 551 insertions, 0 deletions
diff --git a/vendor/clap_complete/src/dynamic.rs b/vendor/clap_complete/src/dynamic.rs
new file mode 100644
index 000000000..929841ec8
--- /dev/null
+++ b/vendor/clap_complete/src/dynamic.rs
@@ -0,0 +1,551 @@
+//! Complete commands within shells
+
+/// Complete commands within bash
+pub mod bash {
+ use std::ffi::OsString;
+ use std::io::Write;
+
+ use unicode_xid::UnicodeXID;
+
+ #[derive(clap::Subcommand)]
+ #[command(hide = true)]
+ #[allow(missing_docs)]
+ #[derive(Clone, Debug)]
+ pub enum CompleteCommand {
+ /// Register shell completions for this program
+ Complete(CompleteArgs),
+ }
+
+ #[derive(clap::Args)]
+ #[command(group = clap::ArgGroup::new("complete").multiple(true).conflicts_with("register"))]
+ #[allow(missing_docs)]
+ #[derive(Clone, Debug)]
+ pub struct CompleteArgs {
+ /// Path to write completion-registration to
+ #[arg(long, required = true)]
+ register: Option<std::path::PathBuf>,
+
+ #[arg(
+ long,
+ required = true,
+ value_name = "COMP_CWORD",
+ hide_short_help = true,
+ group = "complete"
+ )]
+ index: Option<usize>,
+
+ #[arg(long, hide_short_help = true, group = "complete")]
+ ifs: Option<String>,
+
+ #[arg(
+ long = "type",
+ required = true,
+ hide_short_help = true,
+ group = "complete"
+ )]
+ comp_type: Option<CompType>,
+
+ #[arg(long, hide_short_help = true, group = "complete")]
+ space: bool,
+
+ #[arg(
+ long,
+ conflicts_with = "space",
+ hide_short_help = true,
+ group = "complete"
+ )]
+ no_space: bool,
+
+ #[arg(raw = true, hide_short_help = true, group = "complete")]
+ comp_words: Vec<OsString>,
+ }
+
+ impl CompleteCommand {
+ /// Process the completion request
+ pub fn complete(&self, cmd: &mut clap::Command) -> std::convert::Infallible {
+ self.try_complete(cmd).unwrap_or_else(|e| e.exit());
+ std::process::exit(0)
+ }
+
+ /// Process the completion request
+ pub fn try_complete(&self, cmd: &mut clap::Command) -> clap::error::Result<()> {
+ debug!("CompleteCommand::try_complete: {:?}", self);
+ let CompleteCommand::Complete(args) = self;
+ if let Some(out_path) = args.register.as_deref() {
+ let mut buf = Vec::new();
+ let name = cmd.get_name();
+ let bin = cmd.get_bin_name().unwrap_or_else(|| cmd.get_name());
+ register(name, [bin], bin, &Behavior::default(), &mut buf)?;
+ if out_path == std::path::Path::new("-") {
+ std::io::stdout().write_all(&buf)?;
+ } else if out_path.is_dir() {
+ let out_path = out_path.join(file_name(name));
+ std::fs::write(out_path, buf)?;
+ } else {
+ std::fs::write(out_path, buf)?;
+ }
+ } else {
+ let index = args.index.unwrap_or_default();
+ let comp_type = args.comp_type.unwrap_or_default();
+ let space = match (args.space, args.no_space) {
+ (true, false) => Some(true),
+ (false, true) => Some(false),
+ (true, true) => {
+ unreachable!("`--space` and `--no-space` set, clap should prevent this")
+ }
+ (false, false) => None,
+ }
+ .unwrap();
+ let current_dir = std::env::current_dir().ok();
+ let completions = complete(
+ cmd,
+ args.comp_words.clone(),
+ index,
+ comp_type,
+ space,
+ current_dir.as_deref(),
+ )?;
+
+ let mut buf = Vec::new();
+ for (i, completion) in completions.iter().enumerate() {
+ if i != 0 {
+ write!(&mut buf, "{}", args.ifs.as_deref().unwrap_or("\n"))?;
+ }
+ write!(&mut buf, "{}", completion.to_string_lossy())?;
+ }
+ std::io::stdout().write_all(&buf)?;
+ }
+
+ Ok(())
+ }
+ }
+
+ /// The recommended file name for the registration code
+ pub fn file_name(name: &str) -> String {
+ format!("{}.bash", name)
+ }
+
+ /// Define the completion behavior
+ pub enum Behavior {
+ /// Bare bones behavior
+ Minimal,
+ /// Fallback to readline behavior when no matches are generated
+ Readline,
+ /// Customize bash's completion behavior
+ Custom(String),
+ }
+
+ impl Default for Behavior {
+ fn default() -> Self {
+ Self::Readline
+ }
+ }
+
+ /// Generate code to register the dynamic completion
+ pub fn register(
+ name: &str,
+ executables: impl IntoIterator<Item = impl AsRef<str>>,
+ completer: &str,
+ behavior: &Behavior,
+ buf: &mut dyn Write,
+ ) -> Result<(), std::io::Error> {
+ let escaped_name = name.replace('-', "_");
+ debug_assert!(
+ escaped_name.chars().all(|c| c.is_xid_continue()),
+ "`name` must be an identifier, got `{}`",
+ escaped_name
+ );
+ let mut upper_name = escaped_name.clone();
+ upper_name.make_ascii_uppercase();
+
+ let executables = executables
+ .into_iter()
+ .map(|s| shlex::quote(s.as_ref()).into_owned())
+ .collect::<Vec<_>>()
+ .join(" ");
+
+ let options = match behavior {
+ Behavior::Minimal => "-o nospace -o bashdefault",
+ Behavior::Readline => "-o nospace -o default -o bashdefault",
+ Behavior::Custom(c) => c.as_str(),
+ };
+
+ let completer = shlex::quote(completer);
+
+ let script = r#"
+_clap_complete_NAME() {
+ local IFS=$'\013'
+ local SUPPRESS_SPACE=0
+ if compopt +o nospace 2> /dev/null; then
+ SUPPRESS_SPACE=1
+ fi
+ if [[ ${SUPPRESS_SPACE} == 1 ]]; then
+ SPACE_ARG="--no-space"
+ else
+ SPACE_ARG="--space"
+ fi
+ COMPREPLY=( $("COMPLETER" complete --index ${COMP_CWORD} --type ${COMP_TYPE} ${SPACE_ARG} --ifs="$IFS" -- "${COMP_WORDS[@]}") )
+ if [[ $? != 0 ]]; then
+ unset COMPREPLY
+ elif [[ $SUPPRESS_SPACE == 1 ]] && [[ "${COMPREPLY-}" =~ [=/:]$ ]]; then
+ compopt -o nospace
+ fi
+}
+complete OPTIONS -F _clap_complete_NAME EXECUTABLES
+"#
+ .replace("NAME", &escaped_name)
+ .replace("EXECUTABLES", &executables)
+ .replace("OPTIONS", options)
+ .replace("COMPLETER", &completer)
+ .replace("UPPER", &upper_name);
+
+ writeln!(buf, "{}", script)?;
+ Ok(())
+ }
+
+ /// Type of completion attempted that caused a completion function to be called
+ #[derive(Copy, Clone, Debug, PartialEq, Eq)]
+ #[non_exhaustive]
+ pub enum CompType {
+ /// Normal completion
+ Normal,
+ /// List completions after successive tabs
+ Successive,
+ /// List alternatives on partial word completion
+ Alternatives,
+ /// List completions if the word is not unmodified
+ Unmodified,
+ /// Menu completion
+ Menu,
+ }
+
+ impl clap::ValueEnum for CompType {
+ fn value_variants<'a>() -> &'a [Self] {
+ &[
+ Self::Normal,
+ Self::Successive,
+ Self::Alternatives,
+ Self::Unmodified,
+ Self::Menu,
+ ]
+ }
+ fn to_possible_value(&self) -> ::std::option::Option<clap::builder::PossibleValue> {
+ match self {
+ Self::Normal => {
+ let value = "9";
+ debug_assert_eq!(b'\t'.to_string(), value);
+ Some(
+ clap::builder::PossibleValue::new(value)
+ .alias("normal")
+ .help("Normal completion"),
+ )
+ }
+ Self::Successive => {
+ let value = "63";
+ debug_assert_eq!(b'?'.to_string(), value);
+ Some(
+ clap::builder::PossibleValue::new(value)
+ .alias("successive")
+ .help("List completions after successive tabs"),
+ )
+ }
+ Self::Alternatives => {
+ let value = "33";
+ debug_assert_eq!(b'!'.to_string(), value);
+ Some(
+ clap::builder::PossibleValue::new(value)
+ .alias("alternatives")
+ .help("List alternatives on partial word completion"),
+ )
+ }
+ Self::Unmodified => {
+ let value = "64";
+ debug_assert_eq!(b'@'.to_string(), value);
+ Some(
+ clap::builder::PossibleValue::new(value)
+ .alias("unmodified")
+ .help("List completions if the word is not unmodified"),
+ )
+ }
+ Self::Menu => {
+ let value = "37";
+ debug_assert_eq!(b'%'.to_string(), value);
+ Some(
+ clap::builder::PossibleValue::new(value)
+ .alias("menu")
+ .help("Menu completion"),
+ )
+ }
+ }
+ }
+ }
+
+ impl Default for CompType {
+ fn default() -> Self {
+ Self::Normal
+ }
+ }
+
+ /// Complete the command specified
+ pub fn complete(
+ cmd: &mut clap::Command,
+ args: Vec<std::ffi::OsString>,
+ arg_index: usize,
+ _comp_type: CompType,
+ _trailing_space: bool,
+ current_dir: Option<&std::path::Path>,
+ ) -> Result<Vec<std::ffi::OsString>, std::io::Error> {
+ cmd.build();
+
+ let raw_args = clap_lex::RawArgs::new(args.into_iter());
+ let mut cursor = raw_args.cursor();
+ let mut target_cursor = raw_args.cursor();
+ raw_args.seek(
+ &mut target_cursor,
+ clap_lex::SeekFrom::Start(arg_index as u64),
+ );
+ // As we loop, `cursor` will always be pointing to the next item
+ raw_args.next_os(&mut target_cursor);
+
+ // TODO: Multicall support
+ if !cmd.is_no_binary_name_set() {
+ raw_args.next_os(&mut cursor);
+ }
+
+ let mut current_cmd = &*cmd;
+ let mut pos_index = 1;
+ let mut is_escaped = false;
+ while let Some(arg) = raw_args.next(&mut cursor) {
+ if cursor == target_cursor {
+ return complete_arg(&arg, current_cmd, current_dir, pos_index, is_escaped);
+ }
+
+ debug!(
+ "complete::next: Begin parsing '{:?}' ({:?})",
+ arg.to_value_os(),
+ arg.to_value_os().as_raw_bytes()
+ );
+
+ if let Ok(value) = arg.to_value() {
+ if let Some(next_cmd) = current_cmd.find_subcommand(value) {
+ current_cmd = next_cmd;
+ pos_index = 0;
+ continue;
+ }
+ }
+
+ if is_escaped {
+ pos_index += 1;
+ } else if arg.is_escape() {
+ is_escaped = true;
+ } else if let Some(_long) = arg.to_long() {
+ } else if let Some(_short) = arg.to_short() {
+ } else {
+ pos_index += 1;
+ }
+ }
+
+ Err(std::io::Error::new(
+ std::io::ErrorKind::Other,
+ "No completion generated",
+ ))
+ }
+
+ fn complete_arg(
+ arg: &clap_lex::ParsedArg<'_>,
+ cmd: &clap::Command,
+ current_dir: Option<&std::path::Path>,
+ pos_index: usize,
+ is_escaped: bool,
+ ) -> Result<Vec<std::ffi::OsString>, std::io::Error> {
+ debug!(
+ "complete_arg: arg={:?}, cmd={:?}, current_dir={:?}, pos_index={}, is_escaped={}",
+ arg,
+ cmd.get_name(),
+ current_dir,
+ pos_index,
+ is_escaped
+ );
+ let mut completions = Vec::new();
+
+ if !is_escaped {
+ if let Some((flag, value)) = arg.to_long() {
+ if let Ok(flag) = flag {
+ if let Some(value) = value {
+ if let Some(arg) = cmd.get_arguments().find(|a| a.get_long() == Some(flag))
+ {
+ completions.extend(
+ complete_arg_value(value.to_str().ok_or(value), arg, current_dir)
+ .into_iter()
+ .map(|os| {
+ // HACK: Need better `OsStr` manipulation
+ format!("--{}={}", flag, os.to_string_lossy()).into()
+ }),
+ )
+ }
+ } else {
+ completions.extend(
+ crate::generator::utils::longs_and_visible_aliases(cmd)
+ .into_iter()
+ .filter_map(|f| {
+ f.starts_with(flag).then(|| format!("--{}", f).into())
+ }),
+ );
+ }
+ }
+ } else if arg.is_escape() || arg.is_stdio() || arg.is_empty() {
+ // HACK: Assuming knowledge of is_escape / is_stdio
+ completions.extend(
+ crate::generator::utils::longs_and_visible_aliases(cmd)
+ .into_iter()
+ .map(|f| format!("--{}", f).into()),
+ );
+ }
+
+ if arg.is_empty() || arg.is_stdio() || arg.is_short() {
+ // HACK: Assuming knowledge of is_stdio
+ completions.extend(
+ crate::generator::utils::shorts_and_visible_aliases(cmd)
+ .into_iter()
+ // HACK: Need better `OsStr` manipulation
+ .map(|f| format!("{}{}", arg.to_value_os().to_str_lossy(), f).into()),
+ );
+ }
+ }
+
+ if let Some(positional) = cmd
+ .get_positionals()
+ .find(|p| p.get_index() == Some(pos_index))
+ {
+ completions.extend(complete_arg_value(arg.to_value(), positional, current_dir));
+ }
+
+ if let Ok(value) = arg.to_value() {
+ completions.extend(complete_subcommand(value, cmd));
+ }
+
+ Ok(completions)
+ }
+
+ fn complete_arg_value(
+ value: Result<&str, &clap_lex::RawOsStr>,
+ arg: &clap::Arg,
+ current_dir: Option<&std::path::Path>,
+ ) -> Vec<OsString> {
+ let mut values = Vec::new();
+ debug!("complete_arg_value: arg={:?}, value={:?}", arg, value);
+
+ if let Some(possible_values) = crate::generator::utils::possible_values(arg) {
+ 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())
+ }));
+ }
+ } else {
+ let value_os = match value {
+ Ok(value) => clap_lex::RawOsStr::from_str(value),
+ Err(value_os) => value_os,
+ };
+ match arg.get_value_hint() {
+ clap::ValueHint::Other => {
+ // Should not complete
+ }
+ clap::ValueHint::Unknown | clap::ValueHint::AnyPath => {
+ values.extend(complete_path(value_os, current_dir, |_| true));
+ }
+ clap::ValueHint::FilePath => {
+ values.extend(complete_path(value_os, current_dir, |p| p.is_file()));
+ }
+ clap::ValueHint::DirPath => {
+ values.extend(complete_path(value_os, current_dir, |p| p.is_dir()));
+ }
+ clap::ValueHint::ExecutablePath => {
+ use is_executable::IsExecutable;
+ values.extend(complete_path(value_os, current_dir, |p| p.is_executable()));
+ }
+ clap::ValueHint::CommandName
+ | clap::ValueHint::CommandString
+ | clap::ValueHint::CommandWithArguments
+ | clap::ValueHint::Username
+ | clap::ValueHint::Hostname
+ | clap::ValueHint::Url
+ | clap::ValueHint::EmailAddress => {
+ // No completion implementation
+ }
+ _ => {
+ // Safe-ish fallback
+ values.extend(complete_path(value_os, current_dir, |_| true));
+ }
+ }
+ values.sort();
+ }
+
+ values
+ }
+
+ fn complete_path(
+ value_os: &clap_lex::RawOsStr,
+ current_dir: Option<&std::path::Path>,
+ is_wanted: impl Fn(&std::path::Path) -> bool,
+ ) -> Vec<OsString> {
+ let mut completions = Vec::new();
+
+ let current_dir = match current_dir {
+ Some(current_dir) => current_dir,
+ None => {
+ // Can't complete without a `current_dir`
+ return Vec::new();
+ }
+ };
+ let (existing, prefix) = value_os
+ .split_once('\\')
+ .unwrap_or((clap_lex::RawOsStr::from_str(""), value_os));
+ let root = current_dir.join(existing.to_os_str());
+ debug!("complete_path: root={:?}, prefix={:?}", root, prefix);
+
+ for entry in std::fs::read_dir(&root)
+ .ok()
+ .into_iter()
+ .flatten()
+ .filter_map(Result::ok)
+ {
+ let raw_file_name = clap_lex::RawOsString::new(entry.file_name());
+ if !raw_file_name.starts_with_os(prefix) {
+ continue;
+ }
+
+ if entry.metadata().map(|m| m.is_dir()).unwrap_or(false) {
+ 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());
+ } 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
+ }
+
+ fn complete_subcommand(value: &str, cmd: &clap::Command) -> Vec<OsString> {
+ debug!(
+ "complete_subcommand: cmd={:?}, value={:?}",
+ cmd.get_name(),
+ value
+ );
+
+ let mut scs = crate::generator::utils::all_subcommands(cmd)
+ .into_iter()
+ .filter(|x| x.0.starts_with(value))
+ .map(|x| OsString::from(&x.0))
+ .collect::<Vec<_>>();
+ scs.sort();
+ scs.dedup();
+ scs
+ }
+}