mirror of
https://github.com/nushell/nushell.git
synced 2025-05-08 12:04:25 +02:00
Enable exact match behavior for any path with slashes (#15458)
# Description Closes #14794. This PR enables the strict exact match behavior requested in #13204 and #14794 for any path containing a slash (#13302 implemented this for paths ending in slashes). If any of the components along the way *don't* exactly match a directory, then the next components will use the old Fish-like completion behavior rather than the strict behavior. This change only affects those using prefix matching. Fuzzy matching remains unaffected. # User-Facing Changes Suppose you have the following directory structure: ``` - foo - bar - xyzzy - barbaz - xyzzy - foobar - bar - xyzzy - barbaz - xyzzy ``` - If you type `cd foo<TAB>`, you will be suggested `[foo, foobar]` - This is because `foo` is the last component of the path, so the strict behavior isn't activated - Similarly, `foo/bar` will show you `[foo/bar, foo/barbaz]` - If you type `foo/bar/x`, you will be suggested `[foo/bar/xyzzy]` - This is because `foo` and `bar` both exactly matched a directory - If you type `foo/b/x`, you will be suggested `[foo/bar/xyzzy, foo/barbaz/xyzzy]` - This is because `foo` matches a directory exactly, so `foobar/*` won't be suggested, but `b` doesn't exactly match a directory, so both `bar` and `barbaz` are suggested - If you type `f/b/x`, you will be suggested all four of the `xyzzy` files above - If you type `f/bar/x`, you will be suggested all four of the `xyzzy` files above - Since `f` doesn't exactly match a directory, every component after it won't use the strict matching behavior (even though `bar` exactly matches a directory) # Tests + Formatting # After Submitting This is a pretty minor change but should be mentioned somewhere in the release notes in case it surprises someone. --------- Co-authored-by: 132ikl <132@ikl.sh>
This commit is contained in:
parent
9aba96604b
commit
5c2bcd068b
@ -22,18 +22,22 @@ pub struct PathBuiltFromString {
|
|||||||
/// Recursively goes through paths that match a given `partial`.
|
/// Recursively goes through paths that match a given `partial`.
|
||||||
/// built: State struct for a valid matching path built so far.
|
/// built: State struct for a valid matching path built so far.
|
||||||
///
|
///
|
||||||
|
/// `want_directory`: Whether we want only directories as completion matches.
|
||||||
|
/// Some commands like `cd` can only be run on directories whereas others
|
||||||
|
/// like `ls` can be run on regular files as well.
|
||||||
|
///
|
||||||
/// `isdir`: whether the current partial path has a trailing slash.
|
/// `isdir`: whether the current partial path has a trailing slash.
|
||||||
/// Parsing a path string into a pathbuf loses that bit of information.
|
/// Parsing a path string into a pathbuf loses that bit of information.
|
||||||
///
|
///
|
||||||
/// want_directory: Whether we want only directories as completion matches.
|
/// `enable_exact_match`: Whether match algorithm is Prefix and all previous components
|
||||||
/// Some commands like `cd` can only be run on directories whereas others
|
/// of the path matched a directory exactly.
|
||||||
/// like `ls` can be run on regular files as well.
|
|
||||||
fn complete_rec(
|
fn complete_rec(
|
||||||
partial: &[&str],
|
partial: &[&str],
|
||||||
built_paths: &[PathBuiltFromString],
|
built_paths: &[PathBuiltFromString],
|
||||||
options: &CompletionOptions,
|
options: &CompletionOptions,
|
||||||
want_directory: bool,
|
want_directory: bool,
|
||||||
isdir: bool,
|
isdir: bool,
|
||||||
|
enable_exact_match: bool,
|
||||||
) -> Vec<PathBuiltFromString> {
|
) -> Vec<PathBuiltFromString> {
|
||||||
if let Some((&base, rest)) = partial.split_first() {
|
if let Some((&base, rest)) = partial.split_first() {
|
||||||
if base.chars().all(|c| c == '.') && (isdir || !rest.is_empty()) {
|
if base.chars().all(|c| c == '.') && (isdir || !rest.is_empty()) {
|
||||||
@ -46,7 +50,14 @@ fn complete_rec(
|
|||||||
built
|
built
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
return complete_rec(rest, &built_paths, options, want_directory, isdir);
|
return complete_rec(
|
||||||
|
rest,
|
||||||
|
&built_paths,
|
||||||
|
options,
|
||||||
|
want_directory,
|
||||||
|
isdir,
|
||||||
|
enable_exact_match,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,27 +97,26 @@ fn complete_rec(
|
|||||||
// Serves as confirmation to ignore longer completions for
|
// Serves as confirmation to ignore longer completions for
|
||||||
// components in between.
|
// components in between.
|
||||||
if !rest.is_empty() || isdir {
|
if !rest.is_empty() || isdir {
|
||||||
|
// Don't show longer completions if we have an exact match (#13204, #14794)
|
||||||
|
let exact_match = enable_exact_match
|
||||||
|
&& (if options.case_sensitive {
|
||||||
|
entry_name.eq(base)
|
||||||
|
} else {
|
||||||
|
entry_name.eq_ignore_case(base)
|
||||||
|
});
|
||||||
completions.extend(complete_rec(
|
completions.extend(complete_rec(
|
||||||
rest,
|
rest,
|
||||||
&[built],
|
&[built],
|
||||||
options,
|
options,
|
||||||
want_directory,
|
want_directory,
|
||||||
isdir,
|
isdir,
|
||||||
|
exact_match,
|
||||||
));
|
));
|
||||||
} else {
|
|
||||||
completions.push(built);
|
|
||||||
}
|
|
||||||
|
|
||||||
// For https://github.com/nushell/nushell/issues/13204
|
|
||||||
if isdir && options.match_algorithm == MatchAlgorithm::Prefix {
|
|
||||||
let exact_match = if options.case_sensitive {
|
|
||||||
entry_name.eq(base)
|
|
||||||
} else {
|
|
||||||
entry_name.to_folded_case().eq(&base.to_folded_case())
|
|
||||||
};
|
|
||||||
if exact_match {
|
if exact_match {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
completions.push(built);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
@ -255,6 +265,7 @@ pub fn complete_item(
|
|||||||
options,
|
options,
|
||||||
want_directory,
|
want_directory,
|
||||||
isdir,
|
isdir,
|
||||||
|
options.match_algorithm == MatchAlgorithm::Prefix,
|
||||||
)
|
)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|mut p| {
|
.map(|mut p| {
|
||||||
|
@ -951,10 +951,11 @@ fn partial_completions() {
|
|||||||
// Create the expected values
|
// Create the expected values
|
||||||
let expected_paths = [
|
let expected_paths = [
|
||||||
file(dir.join("partial").join("hello.txt")),
|
file(dir.join("partial").join("hello.txt")),
|
||||||
|
folder(dir.join("partial").join("hol")),
|
||||||
file(dir.join("partial-a").join("have_ext.exe")),
|
file(dir.join("partial-a").join("have_ext.exe")),
|
||||||
file(dir.join("partial-a").join("have_ext.txt")),
|
file(dir.join("partial-a").join("have_ext.txt")),
|
||||||
file(dir.join("partial-a").join("hello")),
|
file(dir.join("partial-a").join("hello")),
|
||||||
file(dir.join("partial-a").join("hola")),
|
folder(dir.join("partial-a").join("hola")),
|
||||||
file(dir.join("partial-b").join("hello_b")),
|
file(dir.join("partial-b").join("hello_b")),
|
||||||
file(dir.join("partial-b").join("hi_b")),
|
file(dir.join("partial-b").join("hi_b")),
|
||||||
file(dir.join("partial-c").join("hello_c")),
|
file(dir.join("partial-c").join("hello_c")),
|
||||||
@ -971,11 +972,12 @@ fn partial_completions() {
|
|||||||
// Create the expected values
|
// Create the expected values
|
||||||
let expected_paths = [
|
let expected_paths = [
|
||||||
file(dir.join("partial").join("hello.txt")),
|
file(dir.join("partial").join("hello.txt")),
|
||||||
|
folder(dir.join("partial").join("hol")),
|
||||||
file(dir.join("partial-a").join("anotherfile")),
|
file(dir.join("partial-a").join("anotherfile")),
|
||||||
file(dir.join("partial-a").join("have_ext.exe")),
|
file(dir.join("partial-a").join("have_ext.exe")),
|
||||||
file(dir.join("partial-a").join("have_ext.txt")),
|
file(dir.join("partial-a").join("have_ext.txt")),
|
||||||
file(dir.join("partial-a").join("hello")),
|
file(dir.join("partial-a").join("hello")),
|
||||||
file(dir.join("partial-a").join("hola")),
|
folder(dir.join("partial-a").join("hola")),
|
||||||
file(dir.join("partial-b").join("hello_b")),
|
file(dir.join("partial-b").join("hello_b")),
|
||||||
file(dir.join("partial-b").join("hi_b")),
|
file(dir.join("partial-b").join("hi_b")),
|
||||||
file(dir.join("partial-c").join("hello_c")),
|
file(dir.join("partial-c").join("hello_c")),
|
||||||
@ -2215,15 +2217,43 @@ fn exact_match() {
|
|||||||
|
|
||||||
let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack));
|
let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack));
|
||||||
|
|
||||||
|
// Troll case to test if exact match logic works case insensitively
|
||||||
let target_dir = format!("open {}", folder(dir.join("pArTiAL")));
|
let target_dir = format!("open {}", folder(dir.join("pArTiAL")));
|
||||||
let suggestions = completer.complete(&target_dir, target_dir.len());
|
let suggestions = completer.complete(&target_dir, target_dir.len());
|
||||||
|
|
||||||
// Since it's an exact match, only 'partial' should be suggested, not
|
|
||||||
// 'partial-a' and stuff. Implemented in #13302
|
|
||||||
match_suggestions(
|
match_suggestions(
|
||||||
&vec![file(dir.join("partial").join("hello.txt")).as_str()],
|
&vec![
|
||||||
|
file(dir.join("partial").join("hello.txt")).as_str(),
|
||||||
|
folder(dir.join("partial").join("hol")).as_str(),
|
||||||
|
],
|
||||||
&suggestions,
|
&suggestions,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let target_dir = format!("open {}", file(dir.join("partial").join("h")));
|
||||||
|
let suggestions = completer.complete(&target_dir, target_dir.len());
|
||||||
|
match_suggestions(
|
||||||
|
&vec![
|
||||||
|
file(dir.join("partial").join("hello.txt")).as_str(),
|
||||||
|
folder(dir.join("partial").join("hol")).as_str(),
|
||||||
|
],
|
||||||
|
&suggestions,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Even though "hol" is an exact match, the first component ("part") wasn't an
|
||||||
|
// exact match, so we include partial-a/hola
|
||||||
|
let target_dir = format!("open {}", file(dir.join("part").join("hol")));
|
||||||
|
let suggestions = completer.complete(&target_dir, target_dir.len());
|
||||||
|
match_suggestions(
|
||||||
|
&vec![
|
||||||
|
folder(dir.join("partial").join("hol")).as_str(),
|
||||||
|
folder(dir.join("partial-a").join("hola")).as_str(),
|
||||||
|
],
|
||||||
|
&suggestions,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Exact match behavior shouldn't be enabled if the path has no slashes
|
||||||
|
let target_dir = format!("open {}", file(dir.join("partial")));
|
||||||
|
let suggestions = completer.complete(&target_dir, target_dir.len());
|
||||||
|
assert!(suggestions.len() > 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[ignore = "was reverted, still needs fixing"]
|
#[ignore = "was reverted, still needs fixing"]
|
||||||
|
0
tests/fixtures/partial_completions/partial/hol/foo.txt
vendored
Normal file
0
tests/fixtures/partial_completions/partial/hol/foo.txt
vendored
Normal file
Loading…
Reference in New Issue
Block a user