1 use unicode_xid::UnicodeXID as _; 2 3 /// Completion support for Bash 4 #[derive(Copy, Clone, PartialEq, Eq, Debug)] 5 pub struct Bash; 6 7 impl crate::dynamic::Completer for Bash { file_name(&self, name: &str) -> String8 fn file_name(&self, name: &str) -> String { 9 format!("{name}.bash") 10 } write_registration( &self, name: &str, bin: &str, completer: &str, buf: &mut dyn std::io::Write, ) -> Result<(), std::io::Error>11 fn write_registration( 12 &self, 13 name: &str, 14 bin: &str, 15 completer: &str, 16 buf: &mut dyn std::io::Write, 17 ) -> Result<(), std::io::Error> { 18 let escaped_name = name.replace('-', "_"); 19 debug_assert!( 20 escaped_name.chars().all(|c| c.is_xid_continue()), 21 "`name` must be an identifier, got `{escaped_name}`" 22 ); 23 let mut upper_name = escaped_name.clone(); 24 upper_name.make_ascii_uppercase(); 25 26 let completer = shlex::quote(completer); 27 28 let script = r#" 29 _clap_complete_NAME() { 30 export IFS=$'\013' 31 export _CLAP_COMPLETE_INDEX=${COMP_CWORD} 32 export _CLAP_COMPLETE_COMP_TYPE=${COMP_TYPE} 33 if compopt +o nospace 2> /dev/null; then 34 export _CLAP_COMPLETE_SPACE=false 35 else 36 export _CLAP_COMPLETE_SPACE=true 37 fi 38 COMPREPLY=( $("COMPLETER" complete --shell bash -- "${COMP_WORDS[@]}") ) 39 if [[ $? != 0 ]]; then 40 unset COMPREPLY 41 elif [[ $SUPPRESS_SPACE == 1 ]] && [[ "${COMPREPLY-}" =~ [=/:]$ ]]; then 42 compopt -o nospace 43 fi 44 } 45 complete -o nospace -o bashdefault -F _clap_complete_NAME BIN 46 "# 47 .replace("NAME", &escaped_name) 48 .replace("BIN", bin) 49 .replace("COMPLETER", &completer) 50 .replace("UPPER", &upper_name); 51 52 writeln!(buf, "{script}")?; 53 Ok(()) 54 } 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>55 fn write_complete( 56 &self, 57 cmd: &mut clap::Command, 58 args: Vec<std::ffi::OsString>, 59 current_dir: Option<&std::path::Path>, 60 buf: &mut dyn std::io::Write, 61 ) -> Result<(), std::io::Error> { 62 let index: usize = std::env::var("_CLAP_COMPLETE_INDEX") 63 .ok() 64 .and_then(|i| i.parse().ok()) 65 .unwrap_or_default(); 66 let _comp_type: CompType = std::env::var("_CLAP_COMPLETE_COMP_TYPE") 67 .ok() 68 .and_then(|i| i.parse().ok()) 69 .unwrap_or_default(); 70 let _space: Option<bool> = std::env::var("_CLAP_COMPLETE_SPACE") 71 .ok() 72 .and_then(|i| i.parse().ok()); 73 let ifs: Option<String> = std::env::var("IFS").ok().and_then(|i| i.parse().ok()); 74 let completions = crate::dynamic::complete(cmd, args, index, current_dir)?; 75 76 for (i, (completion, _)) in completions.iter().enumerate() { 77 if i != 0 { 78 write!(buf, "{}", ifs.as_deref().unwrap_or("\n"))?; 79 } 80 write!(buf, "{}", completion.to_string_lossy())?; 81 } 82 Ok(()) 83 } 84 } 85 86 /// Type of completion attempted that caused a completion function to be called 87 #[derive(Copy, Clone, Debug, PartialEq, Eq)] 88 #[non_exhaustive] 89 enum CompType { 90 /// Normal completion 91 Normal, 92 /// List completions after successive tabs 93 Successive, 94 /// List alternatives on partial word completion 95 Alternatives, 96 /// List completions if the word is not unmodified 97 Unmodified, 98 /// Menu completion 99 Menu, 100 } 101 102 impl std::str::FromStr for CompType { 103 type Err = String; 104 from_str(s: &str) -> Result<Self, Self::Err>105 fn from_str(s: &str) -> Result<Self, Self::Err> { 106 match s { 107 "9" => Ok(Self::Normal), 108 "63" => Ok(Self::Successive), 109 "33" => Ok(Self::Alternatives), 110 "64" => Ok(Self::Unmodified), 111 "37" => Ok(Self::Menu), 112 _ => Err(format!("unsupported COMP_TYPE `{}`", s)), 113 } 114 } 115 } 116 117 impl Default for CompType { default() -> Self118 fn default() -> Self { 119 Self::Normal 120 } 121 } 122