mirror of
https://github.com/clap-rs/clap
synced 2025-03-04 23:37:32 +00:00
feat(complete): Add ArgValueCompleter
This commit is contained in:
parent
47aedc6906
commit
82a360aa54
7 changed files with 134 additions and 2 deletions
|
@ -50,6 +50,7 @@ pub use shells::*;
|
|||
/// - [`ValueHint`][crate::ValueHint]
|
||||
/// - [`ValueEnum`][clap::ValueEnum]
|
||||
/// - [`ArgValueCandidates`][crate::ArgValueCandidates]
|
||||
/// - [`ArgValueCompleter`][crate::ArgValueCompleter]
|
||||
///
|
||||
/// **Warning:** `stdout` should not be written to before [`CompleteCommand::complete`] has had a
|
||||
/// chance to run.
|
||||
|
@ -122,6 +123,7 @@ impl CompleteCommand {
|
|||
/// - [`ValueHint`][crate::ValueHint]
|
||||
/// - [`ValueEnum`][clap::ValueEnum]
|
||||
/// - [`ArgValueCandidates`][crate::ArgValueCandidates]
|
||||
/// - [`ArgValueCompleter`][crate::ArgValueCompleter]
|
||||
///
|
||||
/// **Warning:** `stdout` should not be written to before [`CompleteArgs::complete`] has had a
|
||||
/// chance to run.
|
||||
|
|
|
@ -5,6 +5,7 @@ use clap_lex::OsStrExt as _;
|
|||
|
||||
use super::custom::complete_path;
|
||||
use super::ArgValueCandidates;
|
||||
use super::ArgValueCompleter;
|
||||
use super::CompletionCandidate;
|
||||
|
||||
/// Complete the given command, shell-agnostic
|
||||
|
@ -271,7 +272,9 @@ fn complete_arg_value(
|
|||
Err(value_os) => value_os,
|
||||
};
|
||||
|
||||
if let Some(completer) = arg.get::<ArgValueCandidates>() {
|
||||
if let Some(completer) = arg.get::<ArgValueCompleter>() {
|
||||
values.extend(completer.complete(value_os));
|
||||
} else if let Some(completer) = arg.get::<ArgValueCandidates>() {
|
||||
values.extend(complete_custom_arg_value(value_os, completer));
|
||||
} else if let Some(possible_values) = possible_values(arg) {
|
||||
if let Ok(value) = value {
|
||||
|
|
|
@ -71,6 +71,85 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// Extend [`Arg`][clap::Arg] with a completer
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use clap::Parser;
|
||||
/// use clap_complete::engine::{ArgValueCompleter, CompletionCandidate};
|
||||
///
|
||||
/// fn custom_completer(current: &std::ffi::OsStr) -> Vec<CompletionCandidate> {
|
||||
/// let mut completions = vec![];
|
||||
/// let Some(current) = current.to_str() else {
|
||||
/// return completions;
|
||||
/// };
|
||||
///
|
||||
/// if "foo".starts_with(current) {
|
||||
/// completions.push(CompletionCandidate::new("foo"));
|
||||
/// }
|
||||
/// if "bar".starts_with(current) {
|
||||
/// completions.push(CompletionCandidate::new("bar"));
|
||||
/// }
|
||||
/// if "baz".starts_with(current) {
|
||||
/// completions.push(CompletionCandidate::new("baz"));
|
||||
/// }
|
||||
/// completions
|
||||
/// }
|
||||
///
|
||||
/// #[derive(Debug, Parser)]
|
||||
/// struct Cli {
|
||||
/// #[arg(long, add = ArgValueCompleter::new(custom_completer))]
|
||||
/// custom: Option<String>,
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Clone)]
|
||||
pub struct ArgValueCompleter(Arc<dyn ValueCompleter>);
|
||||
|
||||
impl ArgValueCompleter {
|
||||
/// Create a new `ArgValueCompleter` with a custom completer
|
||||
pub fn new<C>(completer: C) -> Self
|
||||
where
|
||||
C: ValueCompleter + 'static,
|
||||
{
|
||||
Self(Arc::new(completer))
|
||||
}
|
||||
|
||||
/// Candidates that match `current`
|
||||
///
|
||||
/// See [`CompletionCandidate`] for more information.
|
||||
pub fn complete(&self, current: &OsStr) -> Vec<CompletionCandidate> {
|
||||
self.0.complete(current)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for ArgValueCompleter {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(type_name::<Self>())
|
||||
}
|
||||
}
|
||||
|
||||
impl ArgExt for ArgValueCompleter {}
|
||||
|
||||
/// User-provided completion candidates for an [`Arg`][clap::Arg], see [`ArgValueCompleter`]
|
||||
///
|
||||
/// This is useful when predefined value hints are not enough.
|
||||
pub trait ValueCompleter: Send + Sync {
|
||||
/// All potential candidates for an argument.
|
||||
///
|
||||
/// See [`CompletionCandidate`] for more information.
|
||||
fn complete(&self, current: &OsStr) -> Vec<CompletionCandidate>;
|
||||
}
|
||||
|
||||
impl<F> ValueCompleter for F
|
||||
where
|
||||
F: Fn(&OsStr) -> Vec<CompletionCandidate> + Send + Sync,
|
||||
{
|
||||
fn complete(&self, current: &OsStr) -> Vec<CompletionCandidate> {
|
||||
self(current)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn complete_path(
|
||||
value_os: &OsStr,
|
||||
current_dir: Option<&std::path::Path>,
|
||||
|
|
|
@ -9,4 +9,6 @@ mod custom;
|
|||
pub use candidate::CompletionCandidate;
|
||||
pub use complete::complete;
|
||||
pub use custom::ArgValueCandidates;
|
||||
pub use custom::ArgValueCompleter;
|
||||
pub use custom::ValueCandidates;
|
||||
pub use custom::ValueCompleter;
|
||||
|
|
1
clap_complete/src/env/mod.rs
vendored
1
clap_complete/src/env/mod.rs
vendored
|
@ -20,6 +20,7 @@
|
|||
//! - [`ValueHint`][crate::ValueHint]
|
||||
//! - [`ValueEnum`][clap::ValueEnum]
|
||||
//! - [`ArgValueCandidates`][crate::ArgValueCandidates]
|
||||
//! - [`ArgValueCompleter`][crate::ArgValueCompleter]
|
||||
//!
|
||||
//! To source your completions:
|
||||
//!
|
||||
|
|
|
@ -81,6 +81,8 @@ pub use command::CompleteCommand;
|
|||
#[doc(inline)]
|
||||
#[cfg(feature = "unstable-dynamic")]
|
||||
pub use engine::ArgValueCandidates;
|
||||
#[cfg(feature = "unstable-dynamic")]
|
||||
pub use engine::ArgValueCompleter;
|
||||
#[doc(inline)]
|
||||
#[cfg(feature = "unstable-dynamic")]
|
||||
pub use engine::CompletionCandidate;
|
||||
|
|
|
@ -4,7 +4,7 @@ use std::fs;
|
|||
use std::path::Path;
|
||||
|
||||
use clap::{builder::PossibleValue, Command};
|
||||
use clap_complete::engine::{ArgValueCandidates, CompletionCandidate};
|
||||
use clap_complete::engine::{ArgValueCandidates, ArgValueCompleter, CompletionCandidate};
|
||||
use snapbox::assert_data_eq;
|
||||
|
||||
macro_rules! complete {
|
||||
|
@ -609,6 +609,49 @@ baz
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn suggest_custom_arg_completer() {
|
||||
fn custom_completer(current: &std::ffi::OsStr) -> Vec<CompletionCandidate> {
|
||||
let mut completions = vec![];
|
||||
let Some(current) = current.to_str() else {
|
||||
return completions;
|
||||
};
|
||||
|
||||
if "foo".starts_with(current) {
|
||||
completions.push(CompletionCandidate::new("foo"));
|
||||
}
|
||||
if "bar".starts_with(current) {
|
||||
completions.push(CompletionCandidate::new("bar"));
|
||||
}
|
||||
if "baz".starts_with(current) {
|
||||
completions.push(CompletionCandidate::new("baz"));
|
||||
}
|
||||
completions
|
||||
}
|
||||
|
||||
let mut cmd = Command::new("dynamic").arg(
|
||||
clap::Arg::new("custom")
|
||||
.long("custom")
|
||||
.add(ArgValueCompleter::new(custom_completer)),
|
||||
);
|
||||
|
||||
assert_data_eq!(
|
||||
complete!(cmd, "--custom [TAB]"),
|
||||
snapbox::str![[r#"
|
||||
foo
|
||||
bar
|
||||
baz
|
||||
"#]]
|
||||
);
|
||||
assert_data_eq!(
|
||||
complete!(cmd, "--custom b[TAB]"),
|
||||
snapbox::str![[r#"
|
||||
bar
|
||||
baz
|
||||
"#]]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn suggest_multi_positional() {
|
||||
let mut cmd = Command::new("dynamic")
|
||||
|
|
Loading…
Add table
Reference in a new issue