fix(completion): more quoting for file_completion/directory_completion (#15299)

# Description

Found inconsistent behaviors of `directory_completion` and
`file_completion`, https://github.com/nushell/nushell/issues/13951

https://github.com/nushell/reedline/pull/886

Also there're failing cases with such file names/dir names `foo(`,
`foo{`, `foo[`.
I think it doesn't harm to be more conservative at adding quotes, even
if it might be unnecessary for paired names like `foo{}`.

# User-Facing Changes

# Tests + Formatting

Adjusted

# After Submitting
This commit is contained in:
zc he 2025-03-15 22:17:59 +08:00 committed by GitHub
parent 8f634f4140
commit 74f62305b2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 43 additions and 19 deletions

View File

@ -276,7 +276,7 @@ pub fn complete_item(
});
FileSuggestion {
span,
path: escape_path(path, want_directory),
path: escape_path(path),
style,
is_dir,
}
@ -285,30 +285,30 @@ pub fn complete_item(
}
// Fix files or folders with quotes or hashes
pub fn escape_path(path: String, dir: bool) -> String {
pub fn escape_path(path: String) -> String {
// make glob pattern have the highest priority.
if nu_glob::is_glob(path.as_str()) {
if nu_glob::is_glob(path.as_str()) || path.contains('`') {
// expand home `~` for https://github.com/nushell/nushell/issues/13905
let pathbuf = nu_path::expand_tilde(path);
let path = pathbuf.to_string_lossy();
return if path.contains('\'') {
// decide to use double quote, also need to escape `"` in path
// or else users can't do anything with completed path either.
format!("\"{}\"", path.replace('"', r#"\""#))
if path.contains('\'') {
// decide to use double quotes
// Path as Debug will do the escaping for `"`, `\`
format!("{:?}", path)
} else {
format!("'{path}'")
};
}
let filename_contaminated = !dir && path.contains(['\'', '"', ' ', '#', '(', ')']);
let dirname_contaminated = dir && path.contains(['\'', '"', ' ', '#']);
let maybe_flag = path.starts_with('-');
let maybe_variable = path.starts_with('$');
let maybe_number = path.parse::<f64>().is_ok();
if filename_contaminated || dirname_contaminated || maybe_flag || maybe_variable || maybe_number
{
format!("`{path}`")
}
} else {
path
let contaminated =
path.contains(['\'', '"', ' ', '#', '(', ')', '{', '}', '[', ']', '|', ';']);
let maybe_flag = path.starts_with('-');
let maybe_variable = path.starts_with('$');
let maybe_number = path.parse::<f64>().is_ok();
if contaminated || maybe_flag || maybe_variable || maybe_number {
format!("`{path}`")
} else {
path
}
}
}

View File

@ -896,6 +896,7 @@ fn partial_completions() {
folder(dir.join("partial-a")),
folder(dir.join("partial-b")),
folder(dir.join("partial-c")),
format!("`{}`", folder(dir.join("partial-d("))),
];
// Match the results
@ -938,6 +939,7 @@ fn partial_completions() {
file(dir.join("partial-b").join("hello_b")),
file(dir.join("partial-b").join("hi_b")),
file(dir.join("partial-c").join("hello_c")),
format!("`{}`", file(dir.join("partial-d(").join(".gitkeep"))),
];
// Match the results
@ -985,6 +987,15 @@ fn partial_completions() {
.join("final_partial")
.join("somefile"),
),
format!(
"`{}`",
file(
dir.join("partial-d(")
.join("..")
.join("final_partial")
.join("somefile"),
)
),
];
// Match the results
@ -1065,6 +1076,16 @@ fn partial_completion_with_dot_expansions() {
.join("final_partial")
.join("somefile"),
),
format!(
"`{}`",
file(
dir.join("partial-d(")
.join("...")
.join("partial_completions")
.join("final_partial")
.join("somefile"),
)
),
];
// Match the results
@ -1389,6 +1410,9 @@ fn file_completion_quoted() {
"`-inf`",
"`4.2`",
"\'[a] bc.txt\'",
"`curly-bracket_{.txt`",
"`semicolon_;.txt`",
"'square-bracket_[.txt'",
"`te st.txt`",
"`te#st.txt`",
"`te'st.txt`",

View File

View File

View File

View File