| use unicode_xid::UnicodeXID as _; |
| |
| /// Completion support for Bash |
| #[derive(Copy, Clone, PartialEq, Eq, Debug)] |
| pub struct Bash; |
| |
| impl crate::dynamic::Completer for Bash { |
| fn file_name(&self, name: &str) -> String { |
| format!("{name}.bash") |
| } |
| fn write_registration( |
| &self, |
| name: &str, |
| bin: &str, |
| completer: &str, |
| buf: &mut dyn std::io::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 completer = shlex::quote(completer); |
| |
| let script = r#" |
| _clap_complete_NAME() { |
| export IFS=$'\013' |
| export _CLAP_COMPLETE_INDEX=${COMP_CWORD} |
| export _CLAP_COMPLETE_COMP_TYPE=${COMP_TYPE} |
| if compopt +o nospace 2> /dev/null; then |
| export _CLAP_COMPLETE_SPACE=false |
| else |
| export _CLAP_COMPLETE_SPACE=true |
| fi |
| COMPREPLY=( $("COMPLETER" complete --shell bash -- "${COMP_WORDS[@]}") ) |
| if [[ $? != 0 ]]; then |
| unset COMPREPLY |
| elif [[ $SUPPRESS_SPACE == 1 ]] && [[ "${COMPREPLY-}" =~ [=/:]$ ]]; then |
| compopt -o nospace |
| fi |
| } |
| complete -o nospace -o bashdefault -F _clap_complete_NAME BIN |
| "# |
| .replace("NAME", &escaped_name) |
| .replace("BIN", bin) |
| .replace("COMPLETER", &completer) |
| .replace("UPPER", &upper_name); |
| |
| writeln!(buf, "{script}")?; |
| Ok(()) |
| } |
| fn write_complete( |
| &self, |
| cmd: &mut clap::Command, |
| args: Vec<std::ffi::OsString>, |
| current_dir: Option<&std::path::Path>, |
| buf: &mut dyn std::io::Write, |
| ) -> Result<(), std::io::Error> { |
| let index: usize = std::env::var("_CLAP_COMPLETE_INDEX") |
| .ok() |
| .and_then(|i| i.parse().ok()) |
| .unwrap_or_default(); |
| let _comp_type: CompType = std::env::var("_CLAP_COMPLETE_COMP_TYPE") |
| .ok() |
| .and_then(|i| i.parse().ok()) |
| .unwrap_or_default(); |
| let _space: Option<bool> = std::env::var("_CLAP_COMPLETE_SPACE") |
| .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)?; |
| |
| for (i, (completion, _)) in completions.iter().enumerate() { |
| if i != 0 { |
| write!(buf, "{}", ifs.as_deref().unwrap_or("\n"))?; |
| } |
| write!(buf, "{}", completion.to_string_lossy())?; |
| } |
| Ok(()) |
| } |
| } |
| |
| /// Type of completion attempted that caused a completion function to be called |
| #[derive(Copy, Clone, Debug, PartialEq, Eq)] |
| #[non_exhaustive] |
| 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 std::str::FromStr for CompType { |
| type Err = String; |
| |
| fn from_str(s: &str) -> Result<Self, Self::Err> { |
| match s { |
| "9" => Ok(Self::Normal), |
| "63" => Ok(Self::Successive), |
| "33" => Ok(Self::Alternatives), |
| "64" => Ok(Self::Unmodified), |
| "37" => Ok(Self::Menu), |
| _ => Err(format!("unsupported COMP_TYPE `{}`", s)), |
| } |
| } |
| } |
| |
| impl Default for CompType { |
| fn default() -> Self { |
| Self::Normal |
| } |
| } |