summaryrefslogtreecommitdiff
path: root/src/dynamic/shells/bash.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/dynamic/shells/bash.rs')
-rw-r--r--src/dynamic/shells/bash.rs121
1 files changed, 121 insertions, 0 deletions
diff --git a/src/dynamic/shells/bash.rs b/src/dynamic/shells/bash.rs
new file mode 100644
index 0000000..43c128e
--- /dev/null
+++ b/src/dynamic/shells/bash.rs
@@ -0,0 +1,121 @@
+use unicode_xid::UnicodeXID as _;
+
+/// Bash completions
+#[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
+ }
+}