2021-06-20 01:07:26 +02:00
|
|
|
use std::borrow::Cow;
|
|
|
|
use std::path::{is_separator, Path, PathBuf};
|
2020-08-21 21:37:51 +02:00
|
|
|
|
2020-09-17 16:52:58 +02:00
|
|
|
use super::matchers::Matcher;
|
2021-06-16 22:20:01 +02:00
|
|
|
use crate::{Completer, CompletionContext, Suggestion};
|
2020-08-21 21:37:51 +02:00
|
|
|
|
2020-08-31 04:28:09 +02:00
|
|
|
const SEP: char = std::path::MAIN_SEPARATOR;
|
2020-08-21 21:37:51 +02:00
|
|
|
|
2020-09-16 06:37:43 +02:00
|
|
|
pub struct PathCompleter;
|
2020-08-21 21:37:51 +02:00
|
|
|
|
2021-06-20 01:07:26 +02:00
|
|
|
#[derive(Debug)]
|
2020-09-05 04:10:26 +02:00
|
|
|
pub struct PathSuggestion {
|
|
|
|
pub(crate) path: PathBuf,
|
|
|
|
pub(crate) suggestion: Suggestion,
|
|
|
|
}
|
|
|
|
|
2020-09-16 06:37:43 +02:00
|
|
|
impl PathCompleter {
|
2020-09-17 16:52:58 +02:00
|
|
|
pub fn path_suggestions(&self, partial: &str, matcher: &dyn Matcher) -> Vec<PathSuggestion> {
|
2021-06-20 01:07:26 +02:00
|
|
|
let (base_dir_name, partial) = {
|
|
|
|
// If partial is only a word we want to search in the current dir
|
|
|
|
let (base, rest) = partial.rsplit_once(is_separator).unwrap_or((".", partial));
|
|
|
|
// On windows, this standardizes paths to use \
|
|
|
|
let mut base = base.replace(is_separator, &SEP.to_string());
|
2020-08-31 04:28:09 +02:00
|
|
|
|
2021-06-20 01:07:26 +02:00
|
|
|
// rsplit_once removes the separator
|
|
|
|
base.push(SEP);
|
|
|
|
(base, rest)
|
2020-08-31 04:28:09 +02:00
|
|
|
};
|
|
|
|
|
2021-06-20 01:07:26 +02:00
|
|
|
let base_dir = nu_path::expand_path(Cow::Borrowed(Path::new(&base_dir_name)));
|
|
|
|
// This check is here as base_dir.read_dir() with base_dir == "" will open the current dir
|
|
|
|
// which we don't want in this case (if we did, base_dir would already be ".")
|
|
|
|
if base_dir == Path::new("") {
|
|
|
|
return Vec::new();
|
|
|
|
}
|
2020-08-31 04:28:09 +02:00
|
|
|
|
|
|
|
if let Ok(result) = base_dir.read_dir() {
|
|
|
|
result
|
|
|
|
.filter_map(|entry| {
|
|
|
|
entry.ok().and_then(|entry| {
|
|
|
|
let mut file_name = entry.file_name().to_string_lossy().into_owned();
|
2020-09-17 16:52:58 +02:00
|
|
|
if matcher.matches(partial, file_name.as_str()) {
|
2021-06-20 01:07:26 +02:00
|
|
|
let mut path = format!("{}{}", &base_dir_name, file_name);
|
2020-08-31 04:28:09 +02:00
|
|
|
if entry.file_type().map(|ft| ft.is_dir()).unwrap_or(false) {
|
2021-06-20 01:07:26 +02:00
|
|
|
path.push(SEP);
|
|
|
|
file_name.push(SEP);
|
2020-08-31 04:28:09 +02:00
|
|
|
}
|
2020-08-21 21:37:51 +02:00
|
|
|
|
2020-09-05 04:10:26 +02:00
|
|
|
Some(PathSuggestion {
|
|
|
|
path: entry.path(),
|
|
|
|
suggestion: Suggestion {
|
|
|
|
replacement: path,
|
|
|
|
display: file_name,
|
|
|
|
},
|
2020-08-31 04:28:09 +02:00
|
|
|
})
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
})
|
2020-08-21 21:37:51 +02:00
|
|
|
})
|
|
|
|
.collect()
|
|
|
|
} else {
|
|
|
|
Vec::new()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-09-16 06:37:43 +02:00
|
|
|
|
2021-06-16 22:20:01 +02:00
|
|
|
impl<Context> Completer<Context> for PathCompleter
|
|
|
|
where
|
|
|
|
Context: CompletionContext,
|
|
|
|
{
|
|
|
|
fn complete(&self, _ctx: &Context, partial: &str, matcher: &dyn Matcher) -> Vec<Suggestion> {
|
2020-09-17 16:52:58 +02:00
|
|
|
self.path_suggestions(partial, matcher)
|
2020-09-16 06:37:43 +02:00
|
|
|
.into_iter()
|
|
|
|
.map(|ps| ps.suggestion)
|
|
|
|
.collect()
|
|
|
|
}
|
|
|
|
}
|