Skip to content

Commit 70d8334

Browse files
authored
Merge pull request #5669 from epage/reorg
fix(complete): Correct version check
2 parents 2d8138a + cddbb56 commit 70d8334

File tree

12 files changed

+427
-439
lines changed

12 files changed

+427
-439
lines changed

clap_complete/src/dynamic/candidate.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,6 @@ use std::ffi::OsString;
44
use clap::builder::StyledStr;
55

66
/// A shell-agnostic completion candidate
7-
///
8-
/// [`Shell`][crate::dynamic::shells::Shell] will adapt what it can to the
9-
/// current shell.
107
#[derive(Default, Debug, PartialEq, Eq, PartialOrd, Ord)]
118
pub struct CompletionCandidate {
129
content: OsString,
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
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

Comments
 (0)