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