mirror of
https://github.com/nushell/nushell.git
synced 2025-04-09 13:28:50 +02:00
Update path completions to handle spaces (#5419)
This commit is contained in:
parent
1a52460695
commit
e36649f74b
@ -1,6 +1,7 @@
|
|||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
|
||||||
use fuzzy_matcher::{skim::SkimMatcherV2, FuzzyMatcher};
|
use fuzzy_matcher::{skim::SkimMatcherV2, FuzzyMatcher};
|
||||||
|
use nu_parser::trim_quotes_str;
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub enum SortBy {
|
pub enum SortBy {
|
||||||
@ -28,6 +29,8 @@ pub enum MatchAlgorithm {
|
|||||||
impl MatchAlgorithm {
|
impl MatchAlgorithm {
|
||||||
/// Returns whether the `needle` search text matches the given `haystack`.
|
/// Returns whether the `needle` search text matches the given `haystack`.
|
||||||
pub fn matches_str(&self, haystack: &str, needle: &str) -> bool {
|
pub fn matches_str(&self, haystack: &str, needle: &str) -> bool {
|
||||||
|
let haystack = trim_quotes_str(haystack);
|
||||||
|
let needle = trim_quotes_str(needle);
|
||||||
match *self {
|
match *self {
|
||||||
MatchAlgorithm::Prefix => haystack.starts_with(needle),
|
MatchAlgorithm::Prefix => haystack.starts_with(needle),
|
||||||
MatchAlgorithm::Fuzzy => {
|
MatchAlgorithm::Fuzzy => {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use crate::completions::{file_path_completion, Completer, CompletionOptions};
|
use crate::completions::{matches, Completer, CompletionOptions};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{EngineState, StateWorkingSet},
|
engine::{EngineState, StateWorkingSet},
|
||||||
levenshtein_distance, Span,
|
levenshtein_distance, Span,
|
||||||
@ -7,6 +7,8 @@ use reedline::Suggestion;
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use super::{partial_from, prepend_base_dir, MatchAlgorithm};
|
||||||
|
|
||||||
const SEP: char = std::path::MAIN_SEPARATOR;
|
const SEP: char = std::path::MAIN_SEPARATOR;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -41,11 +43,9 @@ impl Completer for DirectoryCompletion {
|
|||||||
let partial = String::from_utf8_lossy(&prefix).to_string();
|
let partial = String::from_utf8_lossy(&prefix).to_string();
|
||||||
|
|
||||||
// Filter only the folders
|
// 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()
|
.into_iter()
|
||||||
.filter_map(move |x| {
|
.map(move |x| Suggestion {
|
||||||
if x.1.ends_with(SEP) {
|
|
||||||
return Some(Suggestion {
|
|
||||||
value: x.1,
|
value: x.1,
|
||||||
description: None,
|
description: None,
|
||||||
extra: None,
|
extra: None,
|
||||||
@ -54,10 +54,6 @@ impl Completer for DirectoryCompletion {
|
|||||||
end: x.0.end - offset,
|
end: x.0.end - offset,
|
||||||
},
|
},
|
||||||
append_whitespace: false,
|
append_whitespace: false,
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
@ -101,3 +97,63 @@ impl Completer for DirectoryCompletion {
|
|||||||
non_hidden
|
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) {
|
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
|
// 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));
|
let (base, rest) = partial.rsplit_once(is_separator).unwrap_or((".", &partial));
|
||||||
@ -141,12 +141,8 @@ pub fn file_path_completion(
|
|||||||
file_name.push(SEP);
|
file_name.push(SEP);
|
||||||
}
|
}
|
||||||
|
|
||||||
if path.contains(' ') {
|
|
||||||
path = format!("\'{}\'", path);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fix files or folders with quotes
|
// Fix files or folders with quotes
|
||||||
if path.contains('\'') || path.contains('"') {
|
if path.contains('\'') || path.contains('"') || path.contains(' ') {
|
||||||
path = format!("`{}`", path);
|
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
|
/// 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 base_dir == format!(".{}", SEP) {
|
||||||
// if the current base_dir path is the local folder we only add a "./" prefix if the user
|
// 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.
|
// 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 custom_completions::CustomCompletion;
|
||||||
pub use directory_completions::DirectoryCompletion;
|
pub use directory_completions::DirectoryCompletion;
|
||||||
pub use dotnu_completions::DotNuCompletion;
|
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 flag_completions::FlagCompletion;
|
||||||
pub use variable_completions::VariableCompletion;
|
pub use variable_completions::VariableCompletion;
|
||||||
|
@ -18,7 +18,7 @@ pub use parse_keywords::*;
|
|||||||
|
|
||||||
pub use parser::{
|
pub use parser::{
|
||||||
is_math_expression_like, parse, parse_block, parse_duration_bytes, parse_external_call,
|
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")]
|
#[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> {
|
pub fn check_call(command: Span, sig: &Signature, call: &Call) -> Option<ParseError> {
|
||||||
// Allow the call to pass if they pass in the help flag
|
// Allow the call to pass if they pass in the help flag
|
||||||
if call.named_iter().any(|(n, _, _)| n.item == "help") {
|
if call.named_iter().any(|(n, _, _)| n.item == "help") {
|
||||||
|
Loading…
Reference in New Issue
Block a user