|
| 1 | +//! [`<bin> complete`][CompleteCommand] completion integration |
| 2 | +//! |
| 3 | +//! - If you aren't using a subcommand, see [`CompleteCommand`] |
| 4 | +//! - If you are using subcommands, see [`CompleteArgs`] |
| 5 | +//! |
| 6 | +//! To source your completions: |
| 7 | +//! |
| 8 | +//! Bash |
| 9 | +//! ```bash |
| 10 | +//! echo "source <(your_program complete bash)" >> ~/.bashrc |
| 11 | +//! ``` |
| 12 | +//! |
| 13 | +//! Elvish |
| 14 | +//! ```elvish |
| 15 | +//! echo "eval (your_program complete elvish)" >> ~/.elvish/rc.elv |
| 16 | +//! ``` |
| 17 | +//! |
| 18 | +//! Fish |
| 19 | +//! ```fish |
| 20 | +//! echo "source (your_program complete fish | psub)" >> ~/.config/fish/config.fish |
| 21 | +//! ``` |
| 22 | +//! |
| 23 | +//! Powershell |
| 24 | +//! ```powershell |
| 25 | +//! echo "your_program complete powershell | Invoke-Expression" >> $PROFILE |
| 26 | +//! ``` |
| 27 | +//! |
| 28 | +//! Zsh |
| 29 | +//! ```zsh |
| 30 | +//! echo "source <(your_program complete zsh)" >> ~/.zshrc |
| 31 | +//! ``` |
| 32 | +
|
| 33 | +mod shells; |
| 34 | + |
| 35 | +use std::ffi::OsString; |
| 36 | +use std::io::Write as _; |
| 37 | + |
| 38 | +pub use shells::*; |
| 39 | + |
| 40 | +/// A completion subcommand to add to your CLI |
| 41 | +/// |
| 42 | +/// **Warning:** `stdout` should not be written to before [`CompleteCommand::complete`] has had a |
| 43 | +/// chance to run. |
| 44 | +/// |
| 45 | +/// # Examples |
| 46 | +/// |
| 47 | +/// To integrate completions into an application without subcommands: |
| 48 | +/// ```no_run |
| 49 | +/// // src/main.rs |
| 50 | +/// use clap::{CommandFactory, FromArgMatches, Parser, Subcommand}; |
| 51 | +/// use clap_complete::dynamic::CompleteCommand; |
| 52 | +/// |
| 53 | +/// #[derive(Parser, Debug)] |
| 54 | +/// #[clap(name = "dynamic", about = "A dynamic command line tool")] |
| 55 | +/// struct Cli { |
| 56 | +/// /// The subcommand to run complete |
| 57 | +/// #[command(subcommand)] |
| 58 | +/// complete: Option<CompleteCommand>, |
| 59 | +/// |
| 60 | +/// /// Input file path |
| 61 | +/// #[clap(short, long, value_hint = clap::ValueHint::FilePath)] |
| 62 | +/// input: Option<String>, |
| 63 | +/// /// Output format |
| 64 | +/// #[clap(short = 'F', long, value_parser = ["json", "yaml", "toml"])] |
| 65 | +/// format: Option<String>, |
| 66 | +/// } |
| 67 | +/// |
| 68 | +/// fn main() { |
| 69 | +/// let cli = Cli::parse(); |
| 70 | +/// if let Some(completions) = cli.complete { |
| 71 | +/// completions.complete(&mut Cli::command()); |
| 72 | +/// } |
| 73 | +/// |
| 74 | +/// // normal logic continues... |
| 75 | +/// } |
| 76 | +///``` |
| 77 | +#[derive(clap::Subcommand)] |
| 78 | +#[allow(missing_docs)] |
| 79 | +#[derive(Clone, Debug)] |
| 80 | +#[command(about = None, long_about = None)] |
| 81 | +pub enum CompleteCommand { |
| 82 | + /// Register shell completions for this program |
| 83 | + #[command(hide = true)] |
| 84 | + Complete(CompleteArgs), |
| 85 | +} |
| 86 | + |
| 87 | +impl CompleteCommand { |
| 88 | + /// Process the completion request and exit |
| 89 | + /// |
| 90 | + /// **Warning:** `stdout` should not be written to before this has had a |
| 91 | + /// chance to run. |
| 92 | + pub fn complete(&self, cmd: &mut clap::Command) -> std::convert::Infallible { |
| 93 | + self.try_complete(cmd).unwrap_or_else(|e| e.exit()); |
| 94 | + std::process::exit(0) |
| 95 | + } |
| 96 | + |
| 97 | + /// Process the completion request |
| 98 | + /// |
| 99 | + /// **Warning:** `stdout` should not be written to before or after this has run. |
| 100 | + pub fn try_complete(&self, cmd: &mut clap::Command) -> clap::error::Result<()> { |
| 101 | + debug!("CompleteCommand::try_complete: {self:?}"); |
| 102 | + let CompleteCommand::Complete(args) = self; |
| 103 | + args.try_complete(cmd) |
| 104 | + } |
| 105 | +} |
| 106 | + |
| 107 | +/// A completion subcommand to add to your CLI |
| 108 | +/// |
| 109 | +/// **Warning:** `stdout` should not be written to before [`CompleteArgs::complete`] has had a |
| 110 | +/// chance to run. |
| 111 | +/// |
| 112 | +/// # Examples |
| 113 | +/// |
| 114 | +/// To integrate completions into an application without subcommands: |
| 115 | +/// ```no_run |
| 116 | +/// // src/main.rs |
| 117 | +/// use clap::{CommandFactory, FromArgMatches, Parser, Subcommand}; |
| 118 | +/// use clap_complete::dynamic::CompleteArgs; |
| 119 | +/// |
| 120 | +/// #[derive(Parser, Debug)] |
| 121 | +/// #[clap(name = "dynamic", about = "A dynamic command line tool")] |
| 122 | +/// struct Cli { |
| 123 | +/// #[command(subcommand)] |
| 124 | +/// complete: Command, |
| 125 | +/// } |
| 126 | +/// |
| 127 | +/// #[derive(Subcommand, Debug)] |
| 128 | +/// enum Command { |
| 129 | +/// Complete(CompleteArgs), |
| 130 | +/// Print, |
| 131 | +/// } |
| 132 | +/// |
| 133 | +/// fn main() { |
| 134 | +/// let cli = Cli::parse(); |
| 135 | +/// match cli.complete { |
| 136 | +/// Command::Complete(completions) => { |
| 137 | +/// completions.complete(&mut Cli::command()); |
| 138 | +/// }, |
| 139 | +/// Command::Print => { |
| 140 | +/// println!("Hello world!"); |
| 141 | +/// } |
| 142 | +/// } |
| 143 | +/// } |
| 144 | +///``` |
| 145 | +#[derive(clap::Args, Clone, Debug)] |
| 146 | +#[command(about = None, long_about = None)] |
| 147 | +pub struct CompleteArgs { |
| 148 | + /// Specify shell to complete for |
| 149 | + #[arg(value_name = "NAME")] |
| 150 | + shell: Option<Shell>, |
| 151 | + |
| 152 | + #[arg(raw = true, value_name = "ARG", hide = true)] |
| 153 | + comp_words: Option<Vec<OsString>>, |
| 154 | +} |
| 155 | + |
| 156 | +impl CompleteArgs { |
| 157 | + /// Process the completion request and exit |
| 158 | + /// |
| 159 | + /// **Warning:** `stdout` should not be written to before this has had a |
| 160 | + /// chance to run. |
| 161 | + pub fn complete(&self, cmd: &mut clap::Command) -> std::convert::Infallible { |
| 162 | + self.try_complete(cmd).unwrap_or_else(|e| e.exit()); |
| 163 | + std::process::exit(0) |
| 164 | + } |
| 165 | + |
| 166 | + /// Process the completion request |
| 167 | + /// |
| 168 | + /// **Warning:** `stdout` should not be written to before or after this has run. |
| 169 | + pub fn try_complete(&self, cmd: &mut clap::Command) -> clap::error::Result<()> { |
| 170 | + debug!("CompleteCommand::try_complete: {self:?}"); |
| 171 | + |
| 172 | + let shell = self.shell.or_else(Shell::from_env).ok_or_else(|| { |
| 173 | + std::io::Error::new( |
| 174 | + std::io::ErrorKind::Other, |
| 175 | + "unknown shell, please specify the name of your shell", |
| 176 | + ) |
| 177 | + })?; |
| 178 | + |
| 179 | + if let Some(comp_words) = self.comp_words.as_ref() { |
| 180 | + let current_dir = std::env::current_dir().ok(); |
| 181 | + |
| 182 | + let mut buf = Vec::new(); |
| 183 | + shell.write_complete(cmd, comp_words.clone(), current_dir.as_deref(), &mut buf)?; |
| 184 | + std::io::stdout().write_all(&buf)?; |
| 185 | + } else { |
| 186 | + let name = cmd.get_name(); |
| 187 | + let bin = cmd.get_bin_name().unwrap_or_else(|| cmd.get_name()); |
| 188 | + |
| 189 | + let mut buf = Vec::new(); |
| 190 | + shell.write_registration(name, bin, bin, &mut buf)?; |
| 191 | + std::io::stdout().write_all(&buf)?; |
| 192 | + } |
| 193 | + |
| 194 | + Ok(()) |
| 195 | + } |
| 196 | +} |
| 197 | + |
| 198 | +/// Shell-integration for completions |
| 199 | +/// |
| 200 | +/// This will generally be called by [`CompleteCommand`] or [`CompleteArgs`]. |
| 201 | +/// |
| 202 | +/// This handles adapting between the shell and [`completer`][crate::dynamic::complete()]. |
| 203 | +/// A `CommandCompleter` can choose how much of that lives within the registration script and or |
| 204 | +/// lives in [`CommandCompleter::write_complete`]. |
| 205 | +pub trait CommandCompleter { |
| 206 | + /// Register for completions |
| 207 | + /// |
| 208 | + /// Write the `buf` the logic needed for calling into `<cmd> complete`, passing needed |
| 209 | + /// arguments to [`CommandCompleter::write_complete`] through the environment. |
| 210 | + fn write_registration( |
| 211 | + &self, |
| 212 | + name: &str, |
| 213 | + bin: &str, |
| 214 | + completer: &str, |
| 215 | + buf: &mut dyn std::io::Write, |
| 216 | + ) -> Result<(), std::io::Error>; |
| 217 | + /// Complete the given command |
| 218 | + /// |
| 219 | + /// Adapt information from arguments and [`CommandCompleter::write_registration`]-defined env |
| 220 | + /// variables to what is needed for [`completer`][crate::dynamic::complete()]. |
| 221 | + /// |
| 222 | + /// Write out the [`CompletionCandidate`][crate::dynamic::CompletionCandidate]s in a way the shell will understand. |
| 223 | + fn write_complete( |
| 224 | + &self, |
| 225 | + cmd: &mut clap::Command, |
| 226 | + args: Vec<OsString>, |
| 227 | + current_dir: Option<&std::path::Path>, |
| 228 | + buf: &mut dyn std::io::Write, |
| 229 | + ) -> Result<(), std::io::Error>; |
| 230 | +} |
0 commit comments