mirror of
https://github.com/clap-rs/clap
synced 2024-12-13 22:32:33 +00:00
feat(clap_complete): Support to complete custom value of argument
This commit is contained in:
parent
bbb2e6fdde
commit
7fd7b3e40b
4 changed files with 112 additions and 5 deletions
|
@ -168,7 +168,7 @@ string = ["clap_builder/string"] # Allow runtime generated strings
|
|||
|
||||
# In-work features
|
||||
unstable-v5 = ["clap_builder/unstable-v5", "clap_derive?/unstable-v5", "deprecated"]
|
||||
unstable-ext = []
|
||||
unstable-ext = ["clap_builder/unstable-ext"]
|
||||
unstable-styles = ["clap_builder/unstable-styles"] # deprecated
|
||||
|
||||
[lib]
|
||||
|
|
|
@ -57,7 +57,7 @@ required-features = ["unstable-dynamic"]
|
|||
[features]
|
||||
default = []
|
||||
unstable-doc = ["unstable-dynamic"] # for docs.rs
|
||||
unstable-dynamic = ["dep:clap_lex", "dep:shlex", "dep:unicode-xid", "clap/derive", "dep:is_executable", "dep:pathdiff"]
|
||||
unstable-dynamic = ["dep:clap_lex", "dep:shlex", "dep:unicode-xid", "clap/derive", "dep:is_executable", "dep:pathdiff", "clap/unstable-ext"]
|
||||
debug = ["clap/debug"]
|
||||
|
||||
[lints]
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
use core::num;
|
||||
use std::any::type_name;
|
||||
use std::ffi::OsStr;
|
||||
use std::ffi::OsString;
|
||||
use std::sync::Arc;
|
||||
|
||||
use clap::builder::ArgExt;
|
||||
use clap::builder::StyledStr;
|
||||
use clap_lex::OsStrExt as _;
|
||||
|
||||
|
@ -349,6 +351,12 @@ fn complete_arg_value(
|
|||
})
|
||||
}));
|
||||
}
|
||||
} else if let Some(completer) = arg.get::<ArgValueCompleter>() {
|
||||
let value_os = match value {
|
||||
Ok(value) => OsStr::new(value),
|
||||
Err(value_os) => value_os,
|
||||
};
|
||||
values.extend(complete_custom_arg_value(value_os, completer));
|
||||
} else {
|
||||
let value_os = match value {
|
||||
Ok(value) => OsStr::new(value),
|
||||
|
@ -385,6 +393,7 @@ fn complete_arg_value(
|
|||
values.extend(complete_path(value_os, current_dir, |_| true));
|
||||
}
|
||||
}
|
||||
|
||||
values.sort();
|
||||
}
|
||||
|
||||
|
@ -442,6 +451,21 @@ fn complete_path(
|
|||
completions
|
||||
}
|
||||
|
||||
fn complete_custom_arg_value(
|
||||
value: &OsStr,
|
||||
completer: &ArgValueCompleter,
|
||||
) -> Vec<CompletionCandidate> {
|
||||
debug!("complete_custom_arg_value: completer={completer:?}, value={value:?}");
|
||||
|
||||
let mut values = Vec::new();
|
||||
let custom_arg_values = completer.0.completions();
|
||||
values.extend(custom_arg_values);
|
||||
|
||||
values.retain(|comp| comp.get_content().starts_with(&value.to_string_lossy()));
|
||||
|
||||
values
|
||||
}
|
||||
|
||||
fn complete_subcommand(value: &str, cmd: &clap::Command) -> Vec<CompletionCandidate> {
|
||||
debug!(
|
||||
"complete_subcommand: cmd={:?}, value={:?}",
|
||||
|
@ -701,3 +725,61 @@ impl CompletionCandidate {
|
|||
self.hidden
|
||||
}
|
||||
}
|
||||
|
||||
/// User-provided completion candidates for an argument.
|
||||
///
|
||||
/// This is useful when predefined value hints are not enough.
|
||||
pub trait CustomCompleter: Send + Sync {
|
||||
/// All potential candidates for an argument.
|
||||
///
|
||||
/// See [`CompletionCandidate`] for more information.
|
||||
fn completions(&self) -> Vec<CompletionCandidate>;
|
||||
}
|
||||
|
||||
impl<F> CustomCompleter for F
|
||||
where
|
||||
F: Fn() -> Vec<CompletionCandidate> + Send + Sync,
|
||||
{
|
||||
fn completions(&self) -> Vec<CompletionCandidate> {
|
||||
self()
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper for custom completer
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use clap::Parser;
|
||||
/// use clap_complete::dynamic::{ArgValueCompleter, CompletionCandidate};
|
||||
///
|
||||
/// #[derive(Debug, Parser)]
|
||||
/// struct Cli {
|
||||
/// #[arg(long, add = ArgValueCompleter::new(|| { vec![
|
||||
/// CompletionCandidate::new("foo"),
|
||||
/// CompletionCandidate::new("bar"),
|
||||
/// CompletionCandidate::new("baz")] }))]
|
||||
/// custom: Option<String>,
|
||||
/// }
|
||||
///
|
||||
/// ```
|
||||
#[derive(Clone)]
|
||||
pub struct ArgValueCompleter(Arc<dyn CustomCompleter>);
|
||||
|
||||
impl ArgValueCompleter {
|
||||
/// Create a new `ArgValueCompleter` with a custom completer
|
||||
pub fn new<C: CustomCompleter>(completer: C) -> Self
|
||||
where
|
||||
C: 'static + CustomCompleter,
|
||||
{
|
||||
Self(Arc::new(completer))
|
||||
}
|
||||
}
|
||||
|
||||
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 {}
|
||||
|
|
|
@ -4,6 +4,7 @@ use std::fs;
|
|||
use std::path::Path;
|
||||
|
||||
use clap::{builder::PossibleValue, Command};
|
||||
use clap_complete::dynamic::{ArgValueCompleter, CompletionCandidate, CustomCompleter};
|
||||
use snapbox::assert_data_eq;
|
||||
|
||||
macro_rules! complete {
|
||||
|
@ -592,9 +593,33 @@ val3
|
|||
|
||||
#[test]
|
||||
fn suggest_custom_arg_value() {
|
||||
let mut cmd = Command::new("dynamic").arg(clap::Arg::new("custom").long("custom"));
|
||||
#[derive(Debug)]
|
||||
struct MyCustomCompleter {}
|
||||
|
||||
assert_data_eq!(complete!(cmd, "--custom [TAB]"), snapbox::str![""],);
|
||||
impl CustomCompleter for MyCustomCompleter {
|
||||
fn completions(&self) -> Vec<CompletionCandidate> {
|
||||
vec![
|
||||
CompletionCandidate::new("custom1"),
|
||||
CompletionCandidate::new("custom2"),
|
||||
CompletionCandidate::new("custom3"),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
let mut cmd = Command::new("dynamic").arg(
|
||||
clap::Arg::new("custom")
|
||||
.long("custom")
|
||||
.add::<ArgValueCompleter>(ArgValueCompleter::new(MyCustomCompleter {})),
|
||||
);
|
||||
|
||||
assert_data_eq!(
|
||||
complete!(cmd, "--custom [TAB]"),
|
||||
snapbox::str![
|
||||
"custom1
|
||||
custom2
|
||||
custom3"
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
Loading…
Reference in a new issue