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`.
|
||||
/// 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.
|
||||
/// Parsing a path string into a pathbuf loses that bit of information.
|
||||
///
|
||||
/// 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.
|
||||
/// `enable_exact_match`: Whether match algorithm is Prefix and all previous components
|
||||
/// of the path matched a directory exactly.
|
||||
fn complete_rec(
|
||||
partial: &[&str],
|
||||
built_paths: &[PathBuiltFromString],
|
||||
options: &CompletionOptions,
|
||||
want_directory: bool,
|
||||
isdir: bool,
|
||||
enable_exact_match: bool,
|
||||
) -> Vec<PathBuiltFromString> {
|
||||
if let Some((&base, rest)) = partial.split_first() {
|
||||
if base.chars().all(|c| c == '.') && (isdir || !rest.is_empty()) {
|
||||
@ -46,7 +50,14 @@ fn complete_rec(
|
||||
built
|
||||
})
|
||||
.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
|
||||
// components in between.
|
||||
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(
|
||||
rest,
|
||||
&[built],
|
||||
options,
|
||||
want_directory,
|
||||
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 {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
completions.push(built);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
@ -255,6 +265,7 @@ pub fn complete_item(
|
||||
options,
|
||||
want_directory,
|
||||
isdir,
|
||||
options.match_algorithm == MatchAlgorithm::Prefix,
|
||||
)
|
||||
.into_iter()
|
||||
.map(|mut p| {
|
||||
|
@ -951,10 +951,11 @@ fn partial_completions() {
|
||||
// Create the expected values
|
||||
let expected_paths = [
|
||||
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.txt")),
|
||||
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("hi_b")),
|
||||
file(dir.join("partial-c").join("hello_c")),
|
||||
@ -971,11 +972,12 @@ fn partial_completions() {
|
||||
// Create the expected values
|
||||
let expected_paths = [
|
||||
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("have_ext.exe")),
|
||||
file(dir.join("partial-a").join("have_ext.txt")),
|
||||
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("hi_b")),
|
||||
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));
|
||||
|
||||
// Troll case to test if exact match logic works case insensitively
|
||||
let target_dir = format!("open {}", folder(dir.join("pArTiAL")));
|
||||
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(
|
||||
&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,
|
||||
);
|
||||
|
||||
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"]
|
||||
|
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