Skip to content

Commit 3266c36

Browse files
authored
Merge pull request #5691 from epage/custom
feat(complete): Provide ArgValueCompleter
2 parents 87647d2 + 951762d commit 3266c36

File tree

10 files changed

+432
-151
lines changed

10 files changed

+432
-151
lines changed

Cargo.lock

-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

clap_complete/Cargo.toml

+2-3
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ bench = false
3737
clap = { path = "../", version = "4.5.15", default-features = false, features = ["std"] }
3838
clap_lex = { path = "../clap_lex", version = "0.7.0", optional = true }
3939
is_executable = { version = "1.0.1", optional = true }
40-
pathdiff = { version = "0.2.1", optional = true }
4140
shlex = { version = "1.1.0", optional = true }
4241
unicode-xid = { version = "0.2.2", optional = true }
4342

@@ -57,8 +56,8 @@ required-features = ["unstable-dynamic", "unstable-command"]
5756
[features]
5857
default = []
5958
unstable-doc = ["unstable-dynamic", "unstable-command"] # for docs.rs
60-
unstable-dynamic = ["dep:clap_lex", "dep:shlex", "dep:is_executable", "dep:pathdiff", "clap/unstable-ext"]
61-
unstable-command = ["unstable-dynamic", "dep:unicode-xid", "clap/derive", "dep:is_executable", "dep:pathdiff", "clap/unstable-ext"]
59+
unstable-dynamic = ["dep:clap_lex", "dep:shlex", "dep:is_executable", "clap/unstable-ext"]
60+
unstable-command = ["unstable-dynamic", "dep:unicode-xid", "clap/derive", "dep:is_executable", "clap/unstable-ext"]
6261
debug = ["clap/debug"]
6362

6463
[lints]

clap_complete/src/command/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ pub use shells::*;
5050
/// - [`ValueHint`][crate::ValueHint]
5151
/// - [`ValueEnum`][clap::ValueEnum]
5252
/// - [`ArgValueCandidates`][crate::ArgValueCandidates]
53+
/// - [`ArgValueCompleter`][crate::ArgValueCompleter]
5354
///
5455
/// **Warning:** `stdout` should not be written to before [`CompleteCommand::complete`] has had a
5556
/// chance to run.
@@ -122,6 +123,7 @@ impl CompleteCommand {
122123
/// - [`ValueHint`][crate::ValueHint]
123124
/// - [`ValueEnum`][clap::ValueEnum]
124125
/// - [`ArgValueCandidates`][crate::ArgValueCandidates]
126+
/// - [`ArgValueCompleter`][crate::ArgValueCompleter]
125127
///
126128
/// **Warning:** `stdout` should not be written to before [`CompleteArgs::complete`] has had a
127129
/// chance to run.

clap_complete/src/engine/candidate.rs

+6
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,9 @@ impl CompletionCandidate {
6565
self.hidden
6666
}
6767
}
68+
69+
impl<S: Into<OsString>> From<S> for CompletionCandidate {
70+
fn from(s: S) -> Self {
71+
CompletionCandidate::new(s.into())
72+
}
73+
}

clap_complete/src/engine/complete.rs

+11-62
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ use std::ffi::OsString;
33

44
use clap_lex::OsStrExt as _;
55

6+
use super::custom::complete_path;
67
use super::ArgValueCandidates;
8+
use super::ArgValueCompleter;
79
use super::CompletionCandidate;
810

911
/// Complete the given command, shell-agnostic
@@ -270,7 +272,9 @@ fn complete_arg_value(
270272
Err(value_os) => value_os,
271273
};
272274

273-
if let Some(completer) = arg.get::<ArgValueCandidates>() {
275+
if let Some(completer) = arg.get::<ArgValueCompleter>() {
276+
values.extend(completer.complete(value_os));
277+
} else if let Some(completer) = arg.get::<ArgValueCandidates>() {
274278
values.extend(complete_custom_arg_value(value_os, completer));
275279
} else if let Some(possible_values) = possible_values(arg) {
276280
if let Ok(value) = value {
@@ -289,17 +293,17 @@ fn complete_arg_value(
289293
// Should not complete
290294
}
291295
clap::ValueHint::Unknown | clap::ValueHint::AnyPath => {
292-
values.extend(complete_path(value_os, current_dir, |_| true));
296+
values.extend(complete_path(value_os, current_dir, &|_| true));
293297
}
294298
clap::ValueHint::FilePath => {
295-
values.extend(complete_path(value_os, current_dir, |p| p.is_file()));
299+
values.extend(complete_path(value_os, current_dir, &|p| p.is_file()));
296300
}
297301
clap::ValueHint::DirPath => {
298-
values.extend(complete_path(value_os, current_dir, |p| p.is_dir()));
302+
values.extend(complete_path(value_os, current_dir, &|p| p.is_dir()));
299303
}
300304
clap::ValueHint::ExecutablePath => {
301305
use is_executable::IsExecutable;
302-
values.extend(complete_path(value_os, current_dir, |p| p.is_executable()));
306+
values.extend(complete_path(value_os, current_dir, &|p| p.is_executable()));
303307
}
304308
clap::ValueHint::CommandName
305309
| clap::ValueHint::CommandString
@@ -312,7 +316,7 @@ fn complete_arg_value(
312316
}
313317
_ => {
314318
// Safe-ish fallback
315-
values.extend(complete_path(value_os, current_dir, |_| true));
319+
values.extend(complete_path(value_os, current_dir, &|_| true));
316320
}
317321
}
318322

@@ -341,69 +345,14 @@ fn rsplit_delimiter<'s, 'o>(
341345
Some((Some(prefix), Ok(value)))
342346
}
343347

344-
fn complete_path(
345-
value_os: &OsStr,
346-
current_dir: Option<&std::path::Path>,
347-
is_wanted: impl Fn(&std::path::Path) -> bool,
348-
) -> Vec<CompletionCandidate> {
349-
let mut completions = Vec::new();
350-
351-
let current_dir = match current_dir {
352-
Some(current_dir) => current_dir,
353-
None => {
354-
// Can't complete without a `current_dir`
355-
return Vec::new();
356-
}
357-
};
358-
let (existing, prefix) = value_os
359-
.split_once("\\")
360-
.unwrap_or((OsStr::new(""), value_os));
361-
let root = current_dir.join(existing);
362-
debug!("complete_path: root={root:?}, prefix={prefix:?}");
363-
let prefix = prefix.to_string_lossy();
364-
365-
for entry in std::fs::read_dir(&root)
366-
.ok()
367-
.into_iter()
368-
.flatten()
369-
.filter_map(Result::ok)
370-
{
371-
let raw_file_name = entry.file_name();
372-
if !raw_file_name.starts_with(&prefix) {
373-
continue;
374-
}
375-
376-
if entry.metadata().map(|m| m.is_dir()).unwrap_or(false) {
377-
let path = entry.path();
378-
let mut suggestion = pathdiff::diff_paths(&path, current_dir).unwrap_or(path);
379-
suggestion.push(""); // Ensure trailing `/`
380-
completions
381-
.push(CompletionCandidate::new(suggestion.as_os_str().to_owned()).help(None));
382-
} else {
383-
let path = entry.path();
384-
if is_wanted(&path) {
385-
let suggestion = pathdiff::diff_paths(&path, current_dir).unwrap_or(path);
386-
completions
387-
.push(CompletionCandidate::new(suggestion.as_os_str().to_owned()).help(None));
388-
}
389-
}
390-
}
391-
392-
completions
393-
}
394-
395348
fn complete_custom_arg_value(
396349
value: &OsStr,
397350
completer: &ArgValueCandidates,
398351
) -> Vec<CompletionCandidate> {
399352
debug!("complete_custom_arg_value: completer={completer:?}, value={value:?}");
400353

401-
let mut values = Vec::new();
402-
let custom_arg_values = completer.candidates();
403-
values.extend(custom_arg_values);
404-
354+
let mut values = completer.candidates();
405355
values.retain(|comp| comp.get_content().starts_with(&value.to_string_lossy()));
406-
407356
values
408357
}
409358

0 commit comments

Comments
 (0)