mirror of
https://github.com/nushell/nushell
synced 2024-12-26 04:53:09 +00:00
Update path completions to handle spaces (#5419)
This commit is contained in:
parent
1a52460695
commit
e36649f74b
6 changed files with 94 additions and 26 deletions
|
@ -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 => {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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")]
|
||||
|
|
|
@ -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") {
|
||||
|
|
Loading…
Reference in a new issue