Skip to content

Commit

Permalink
Merge pull request #5691 from epage/custom
Browse files Browse the repository at this point in the history
feat(complete): Provide ArgValueCompleter
  • Loading branch information
epage authored Aug 21, 2024
2 parents 87647d2 + 951762d commit 3266c36
Show file tree
Hide file tree
Showing 10 changed files with 432 additions and 151 deletions.
1 change: 0 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 2 additions & 3 deletions clap_complete/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ bench = false
clap = { path = "../", version = "4.5.15", default-features = false, features = ["std"] }
clap_lex = { path = "../clap_lex", version = "0.7.0", optional = true }
is_executable = { version = "1.0.1", optional = true }
pathdiff = { version = "0.2.1", optional = true }
shlex = { version = "1.1.0", optional = true }
unicode-xid = { version = "0.2.2", optional = true }

Expand All @@ -57,8 +56,8 @@ required-features = ["unstable-dynamic", "unstable-command"]
[features]
default = []
unstable-doc = ["unstable-dynamic", "unstable-command"] # for docs.rs
unstable-dynamic = ["dep:clap_lex", "dep:shlex", "dep:is_executable", "dep:pathdiff", "clap/unstable-ext"]
unstable-command = ["unstable-dynamic", "dep:unicode-xid", "clap/derive", "dep:is_executable", "dep:pathdiff", "clap/unstable-ext"]
unstable-dynamic = ["dep:clap_lex", "dep:shlex", "dep:is_executable", "clap/unstable-ext"]
unstable-command = ["unstable-dynamic", "dep:unicode-xid", "clap/derive", "dep:is_executable", "clap/unstable-ext"]
debug = ["clap/debug"]

[lints]
Expand Down
2 changes: 2 additions & 0 deletions clap_complete/src/command/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
6 changes: 6 additions & 0 deletions clap_complete/src/engine/candidate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,9 @@ impl CompletionCandidate {
self.hidden
}
}

impl<S: Into<OsString>> From<S> for CompletionCandidate {
fn from(s: S) -> Self {
CompletionCandidate::new(s.into())
}
}
73 changes: 11 additions & 62 deletions clap_complete/src/engine/complete.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ use std::ffi::OsString;

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
Expand Down Expand Up @@ -270,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 {
Expand All @@ -289,17 +293,17 @@ fn complete_arg_value(
// Should not complete
}
clap::ValueHint::Unknown | clap::ValueHint::AnyPath => {
values.extend(complete_path(value_os, current_dir, |_| true));
values.extend(complete_path(value_os, current_dir, &|_| true));
}
clap::ValueHint::FilePath => {
values.extend(complete_path(value_os, current_dir, |p| p.is_file()));
values.extend(complete_path(value_os, current_dir, &|p| p.is_file()));
}
clap::ValueHint::DirPath => {
values.extend(complete_path(value_os, current_dir, |p| p.is_dir()));
values.extend(complete_path(value_os, current_dir, &|p| p.is_dir()));
}
clap::ValueHint::ExecutablePath => {
use is_executable::IsExecutable;
values.extend(complete_path(value_os, current_dir, |p| p.is_executable()));
values.extend(complete_path(value_os, current_dir, &|p| p.is_executable()));
}
clap::ValueHint::CommandName
| clap::ValueHint::CommandString
Expand All @@ -312,7 +316,7 @@ fn complete_arg_value(
}
_ => {
// Safe-ish fallback
values.extend(complete_path(value_os, current_dir, |_| true));
values.extend(complete_path(value_os, current_dir, &|_| true));
}
}

Expand Down Expand Up @@ -341,69 +345,14 @@ fn rsplit_delimiter<'s, 'o>(
Some((Some(prefix), Ok(value)))
}

fn complete_path(
value_os: &OsStr,
current_dir: Option<&std::path::Path>,
is_wanted: impl Fn(&std::path::Path) -> bool,
) -> Vec<CompletionCandidate> {
let mut completions = Vec::new();

let current_dir = match current_dir {
Some(current_dir) => current_dir,
None => {
// Can't complete without a `current_dir`
return Vec::new();
}
};
let (existing, prefix) = value_os
.split_once("\\")
.unwrap_or((OsStr::new(""), value_os));
let root = current_dir.join(existing);
debug!("complete_path: root={root:?}, prefix={prefix:?}");
let prefix = prefix.to_string_lossy();

for entry in std::fs::read_dir(&root)
.ok()
.into_iter()
.flatten()
.filter_map(Result::ok)
{
let raw_file_name = entry.file_name();
if !raw_file_name.starts_with(&prefix) {
continue;
}

if entry.metadata().map(|m| m.is_dir()).unwrap_or(false) {
let path = entry.path();
let mut suggestion = pathdiff::diff_paths(&path, current_dir).unwrap_or(path);
suggestion.push(""); // Ensure trailing `/`
completions
.push(CompletionCandidate::new(suggestion.as_os_str().to_owned()).help(None));
} else {
let path = entry.path();
if is_wanted(&path) {
let suggestion = pathdiff::diff_paths(&path, current_dir).unwrap_or(path);
completions
.push(CompletionCandidate::new(suggestion.as_os_str().to_owned()).help(None));
}
}
}

completions
}

fn complete_custom_arg_value(
value: &OsStr,
completer: &ArgValueCandidates,
) -> Vec<CompletionCandidate> {
debug!("complete_custom_arg_value: completer={completer:?}, value={value:?}");

let mut values = Vec::new();
let custom_arg_values = completer.candidates();
values.extend(custom_arg_values);

let mut values = completer.candidates();
values.retain(|comp| comp.get_content().starts_with(&value.to_string_lossy()));

values
}

Expand Down
Loading

0 comments on commit 3266c36

Please sign in to comment.