Update path completions to handle spaces (#5419)

This commit is contained in:
JT 2022-05-03 12:37:38 +12:00 committed by GitHub
parent 1a52460695
commit e36649f74b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 94 additions and 26 deletions

View file

@ -1,6 +1,7 @@
use std::fmt::Display;
use fuzzy_matcher::{skim::SkimMatcherV2, FuzzyMatcher};
use nu_parser::trim_quotes_str;
#[derive(Copy, Clone)]
pub enum SortBy {
@ -28,6 +29,8 @@ pub enum MatchAlgorithm {
impl MatchAlgorithm {
/// Returns whether the `needle` search text matches the given `haystack`.
pub fn matches_str(&self, haystack: &str, needle: &str) -> bool {
let haystack = trim_quotes_str(haystack);
let needle = trim_quotes_str(needle);
match *self {
MatchAlgorithm::Prefix => haystack.starts_with(needle),
MatchAlgorithm::Fuzzy => {

View file

@ -1,4 +1,4 @@
use crate::completions::{file_path_completion, Completer, CompletionOptions};
use crate::completions::{matches, Completer, CompletionOptions};
use nu_protocol::{
engine::{EngineState, StateWorkingSet},
levenshtein_distance, Span,
@ -7,6 +7,8 @@ use reedline::Suggestion;
use std::path::Path;
use std::sync::Arc;
use super::{partial_from, prepend_base_dir, MatchAlgorithm};
const SEP: char = std::path::MAIN_SEPARATOR;
#[derive(Clone)]
@ -41,23 +43,17 @@ impl Completer for DirectoryCompletion {
let partial = String::from_utf8_lossy(&prefix).to_string();
// Filter only the folders
let output: Vec<_> = file_path_completion(span, &partial, &cwd, options.match_algorithm)
let output: Vec<_> = directory_completion(span, &partial, &cwd, options.match_algorithm)
.into_iter()
.filter_map(move |x| {
if x.1.ends_with(SEP) {
return Some(Suggestion {
value: x.1,
description: None,
extra: None,
span: reedline::Span {
start: x.0.start - offset,
end: x.0.end - offset,
},
append_whitespace: false,
});
}
None
.map(move |x| Suggestion {
value: x.1,
description: None,
extra: None,
span: reedline::Span {
start: x.0.start - offset,
end: x.0.end - offset,
},
append_whitespace: false,
})
.collect();
@ -101,3 +97,63 @@ impl Completer for DirectoryCompletion {
non_hidden
}
}
pub fn directory_completion(
span: nu_protocol::Span,
partial: &str,
cwd: &str,
match_algorithm: MatchAlgorithm,
) -> Vec<(nu_protocol::Span, String)> {
let original_input = partial;
let (base_dir_name, partial) = partial_from(partial);
let base_dir = nu_path::expand_path_with(&base_dir_name, cwd);
// 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();
}
if let Ok(result) = base_dir.read_dir() {
return result
.filter_map(|entry| {
entry.ok().and_then(|entry| {
if let Ok(metadata) = entry.metadata() {
if metadata.is_dir() {
let mut file_name = entry.file_name().to_string_lossy().into_owned();
if matches(&partial, &file_name, match_algorithm) {
let mut path = if prepend_base_dir(original_input, &base_dir_name) {
format!("{}{}", base_dir_name, file_name)
} else {
file_name.to_string()
};
if entry.path().is_dir() {
path.push(SEP);
file_name.push(SEP);
}
// Fix files or folders with quotes
if path.contains('\'') || path.contains('"') || path.contains(' ') {
path = format!("`{}`", path);
}
Some((span, path))
} else {
None
}
} else {
None
}
} else {
None
}
})
})
.collect();
}
Vec::new()
}

View file

@ -95,7 +95,7 @@ impl Completer for FileCompletion {
}
pub fn partial_from(input: &str) -> (String, String) {
let partial = input.replace('\'', "");
let partial = input.replace('`', "");
// 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));
@ -141,12 +141,8 @@ pub fn file_path_completion(
file_name.push(SEP);
}
if path.contains(' ') {
path = format!("\'{}\'", path);
}
// Fix files or folders with quotes
if path.contains('\'') || path.contains('"') {
if path.contains('\'') || path.contains('"') || path.contains(' ') {
path = format!("`{}`", path);
}
@ -167,7 +163,7 @@ pub fn matches(partial: &str, from: &str, match_algorithm: MatchAlgorithm) -> bo
}
/// Returns whether the base_dir should be prepended to the file path
fn prepend_base_dir(input: &str, base_dir: &str) -> bool {
pub fn prepend_base_dir(input: &str, base_dir: &str) -> bool {
if base_dir == format!(".{}", SEP) {
// if the current base_dir path is the local folder we only add a "./" prefix if the user
// input already includes a local folder prefix.

View file

@ -16,6 +16,8 @@ pub use completion_options::{CompletionOptions, MatchAlgorithm, SortBy};
pub use custom_completions::CustomCompletion;
pub use directory_completions::DirectoryCompletion;
pub use dotnu_completions::DotNuCompletion;
pub use file_completions::{file_path_completion, partial_from, FileCompletion};
pub use file_completions::{
file_path_completion, matches, partial_from, prepend_base_dir, FileCompletion,
};
pub use flag_completions::FlagCompletion;
pub use variable_completions::VariableCompletion;

View file

@ -18,7 +18,7 @@ pub use parse_keywords::*;
pub use parser::{
is_math_expression_like, parse, parse_block, parse_duration_bytes, parse_external_call,
trim_quotes, unescape_unquote_string, Import,
trim_quotes, trim_quotes_str, unescape_unquote_string, Import,
};
#[cfg(feature = "plugin")]

View file

@ -126,6 +126,17 @@ pub fn trim_quotes(bytes: &[u8]) -> &[u8] {
}
}
pub fn trim_quotes_str(s: &str) -> &str {
if (s.starts_with('"') && s.ends_with('"') && s.len() > 1)
|| (s.starts_with('\'') && s.ends_with('\'') && s.len() > 1)
|| (s.starts_with('`') && s.ends_with('`') && s.len() > 1)
{
&s[1..(s.len() - 1)]
} else {
s
}
}
pub fn check_call(command: Span, sig: &Signature, call: &Call) -> Option<ParseError> {
// Allow the call to pass if they pass in the help flag
if call.named_iter().any(|(n, _, _)| n.item == "help") {