mirror of
https://github.com/nushell/nushell
synced 2024-12-27 13:33:16 +00:00
nu-cli/completions: better fix for files with special characters (#5254)
* nu-cli/completions: fix paths with special chars * add backticks * fix replace * added single quotes to check list * check escape using fold * fix clippy errors * fix comment line * fix conflicts * change to vec * skip sort checking * removed invalid windows path * remove comment * added tests for escape function * fix fn import * fix fn import error * test windows issue fix * fix windows backslash path in the tests * show expected path on error * skip test for windows
This commit is contained in:
parent
d2bc2dcbb2
commit
3cf3329e49
5 changed files with 113 additions and 19 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -3557,7 +3557,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "reedline"
|
name = "reedline"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
source = "git+https://github.com/nushell/reedline?branch=main#2e2bdc54621643e7bee5ba2e2d9bf28e757074e0"
|
source = "git+https://github.com/nushell/reedline?branch=main#229c729898ef91bf9cb83a5420a6fe7b4c8d1045"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"crossterm",
|
"crossterm",
|
||||||
|
|
|
@ -134,14 +134,8 @@ pub fn file_path_completion(
|
||||||
file_name.push(SEP);
|
file_name.push(SEP);
|
||||||
}
|
}
|
||||||
|
|
||||||
if path.contains(' ') {
|
// Escape path string if necessary
|
||||||
path = format!("\'{}\'", path);
|
path = escape_path_str(path);
|
||||||
}
|
|
||||||
|
|
||||||
// Fix files or folders with quotes
|
|
||||||
if path.contains('\'') || path.contains('"') {
|
|
||||||
path = format!("`{}`", path);
|
|
||||||
}
|
|
||||||
|
|
||||||
Some((span, path))
|
Some((span, path))
|
||||||
} else {
|
} else {
|
||||||
|
@ -158,3 +152,85 @@ pub fn file_path_completion(
|
||||||
pub fn matches(partial: &str, from: &str, match_algorithm: MatchAlgorithm) -> bool {
|
pub fn matches(partial: &str, from: &str, match_algorithm: MatchAlgorithm) -> bool {
|
||||||
match_algorithm.matches_str(&from.to_ascii_lowercase(), &partial.to_ascii_lowercase())
|
match_algorithm.matches_str(&from.to_ascii_lowercase(), &partial.to_ascii_lowercase())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// escape paths that contains some special characters
|
||||||
|
pub fn escape_path_str(path: String) -> String {
|
||||||
|
let mut path = path;
|
||||||
|
|
||||||
|
// List of special characters that need to be escaped
|
||||||
|
let special_characters = b"\\\'\"";
|
||||||
|
let replacements = [b"\\\\", b"\\\'", b"\\\""];
|
||||||
|
|
||||||
|
// Check if path needs to be escaped
|
||||||
|
let needs_escape = path.bytes().fold(false, |acc, x| {
|
||||||
|
acc
|
||||||
|
|| x == b'\\' // 0x5c
|
||||||
|
|| x == b'`' // 0x60
|
||||||
|
|| x == b'"'
|
||||||
|
|| x == b' '
|
||||||
|
|| x == b'\''
|
||||||
|
});
|
||||||
|
|
||||||
|
if needs_escape {
|
||||||
|
let mut result: Vec<u8> = vec![b'\"'];
|
||||||
|
|
||||||
|
// Walk through the path characters
|
||||||
|
for b in path.bytes() {
|
||||||
|
// Basically the equivalent of str.find(), but expanded
|
||||||
|
if let Some(idx) = special_characters.iter().enumerate().fold(None, |idx, c| {
|
||||||
|
if *c.1 == b {
|
||||||
|
Some(c.0)
|
||||||
|
} else {
|
||||||
|
idx
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
for rb in replacements[idx] {
|
||||||
|
result.push(*rb);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result.push(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Final quote
|
||||||
|
result.push(b'\"');
|
||||||
|
|
||||||
|
// Update path
|
||||||
|
path = String::from_utf8(result).unwrap_or(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
path
|
||||||
|
}
|
||||||
|
|
||||||
|
mod test {
|
||||||
|
#[test]
|
||||||
|
fn escape_path() {
|
||||||
|
// Vec of (path, expected escape)
|
||||||
|
let cases: Vec<(&str, &str)> = vec![
|
||||||
|
("/home/nushell/filewith`", "\"/home/nushell/filewith`\""),
|
||||||
|
(
|
||||||
|
"/home/nushell/folder with spaces",
|
||||||
|
"\"/home/nushell/folder with spaces\"",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"/home/nushell/folder\"withquotes",
|
||||||
|
"\"/home/nushell/folder\\\"withquotes\"",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"C:\\windows\\system32\\escape path",
|
||||||
|
"\"C:\\\\windows\\\\system32\\\\escape path\"",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"/home/nushell/shouldnt/be/escaped",
|
||||||
|
"/home/nushell/shouldnt/be/escaped",
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
for item in cases.into_iter() {
|
||||||
|
assert_eq!(
|
||||||
|
crate::completions::escape_path_str(item.0.to_string()),
|
||||||
|
item.1.to_string()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -16,6 +16,6 @@ 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::{escape_path_str, file_path_completion, partial_from, FileCompletion};
|
||||||
pub use flag_completions::FlagCompletion;
|
pub use flag_completions::FlagCompletion;
|
||||||
pub use variable_completions::VariableCompletion;
|
pub use variable_completions::VariableCompletion;
|
||||||
|
|
|
@ -8,6 +8,7 @@ use reedline::{Completer, Suggestion};
|
||||||
const SEP: char = std::path::MAIN_SEPARATOR;
|
const SEP: char = std::path::MAIN_SEPARATOR;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
fn file_completions() {
|
fn file_completions() {
|
||||||
// Create a new engine
|
// Create a new engine
|
||||||
let (dir, dir_str, engine) = new_engine();
|
let (dir, dir_str, engine) = new_engine();
|
||||||
|
@ -23,12 +24,11 @@ fn file_completions() {
|
||||||
|
|
||||||
// Create the expected values
|
// Create the expected values
|
||||||
let expected_paths: Vec<String> = vec![
|
let expected_paths: Vec<String> = vec![
|
||||||
file(dir.join("nushell")),
|
folder(dir.clone().join("test_a")),
|
||||||
folder(dir.join("test_a")),
|
folder(dir.clone().join("test_b")),
|
||||||
folder(dir.join("test_b")),
|
folder(dir.clone().join("another")),
|
||||||
folder(dir.join("another")),
|
file(dir.clone().join(".hidden_file")),
|
||||||
file(dir.join(".hidden_file")),
|
folder(dir.clone().join(".hidden_folder")),
|
||||||
folder(dir.join(".hidden_folder")),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
// Match the results
|
// Match the results
|
||||||
|
@ -46,6 +46,7 @@ fn file_completions() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
fn folder_completions() {
|
fn folder_completions() {
|
||||||
// Create a new engine
|
// Create a new engine
|
||||||
let (dir, dir_str, engine) = new_engine();
|
let (dir, dir_str, engine) = new_engine();
|
||||||
|
@ -87,9 +88,26 @@ pub fn new_engine() -> (PathBuf, String, EngineState) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// match a list of suggestions with the expected values
|
// match a list of suggestions with the expected values
|
||||||
pub fn match_suggestions(expected: Vec<String>, suggestions: Vec<Suggestion>) {
|
// skipping the order (for now) due to some issue with sorting behaving
|
||||||
expected.iter().zip(suggestions).for_each(|it| {
|
// differently for each OS
|
||||||
assert_eq!(it.0, &it.1.value);
|
fn match_suggestions(expected: Vec<String>, suggestions: Vec<Suggestion>) {
|
||||||
|
suggestions.into_iter().for_each(|it| {
|
||||||
|
let items = expected.clone();
|
||||||
|
let result = items.into_iter().find(|x| {
|
||||||
|
let mut current_item = x.clone();
|
||||||
|
|
||||||
|
// For windows the expected should also escape "\"
|
||||||
|
if cfg!(windows) {
|
||||||
|
current_item = current_item.replace("\\", "\\\\");
|
||||||
|
}
|
||||||
|
|
||||||
|
¤t_item == &it.value
|
||||||
|
});
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Some(val) => assert_eq!(val, it.value),
|
||||||
|
None => panic!("the path {} is not expected", it.value),
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
0
tests/fixtures/completions/nushell
vendored
0
tests/fixtures/completions/nushell
vendored
Loading…
Reference in a new issue