mirror of
https://github.com/nushell/nushell.git
synced 2025-08-10 23:44:52 +02:00
Substring Match Algorithm (#15511)
<!-- if this PR closes one or more issues, you can automatically link the PR with them by using one of the [*linking keywords*](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword), e.g. - this PR should close #xxxx - fixes #xxxx you can also mention related issues, PRs or discussions! --> # Description <!-- Thank you for improving Nushell. Please, check our [contributing guide](../CONTRIBUTING.md) and talk to the core team before making major changes. Description of your pull request goes here. **Provide examples and/or screenshots** if your changes affect the user experience. --> This PR should close #15474 . # User-Facing Changes <!-- List of all changes that impact the user experience here. This helps us keep track of breaking changes. --> When users set the match algorithm to 'substring' by modifying `$env.config` to `$env.config.completions.algorithm = "substring"``), completions are done based on substring matches. This was previously possible by setting `positional` to be false in custom completers, but doing so now logs a warning as this feature is set to be deprecated and replaced by the new way of setting the matching algorithm to substring based.
This commit is contained in:
@ -646,7 +646,6 @@ impl NuCompleter {
|
||||
case_sensitive: config.completions.case_sensitive,
|
||||
match_algorithm: config.completions.algorithm.into(),
|
||||
sort: config.completions.sort,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
completer.fetch(
|
||||
|
@ -18,6 +18,12 @@ pub enum MatchAlgorithm {
|
||||
/// "git switch" is matched by "git sw"
|
||||
Prefix,
|
||||
|
||||
/// Only show suggestions which have a substring matching with the given input
|
||||
///
|
||||
/// Example:
|
||||
/// "git checkout" is matched by "checkout"
|
||||
Substring,
|
||||
|
||||
/// Only show suggestions which contain the input chars at any place
|
||||
///
|
||||
/// Example:
|
||||
@ -36,6 +42,10 @@ enum State<T> {
|
||||
/// Holds (haystack, item)
|
||||
items: Vec<(String, T)>,
|
||||
},
|
||||
Substring {
|
||||
/// Holds (haystack, item)
|
||||
items: Vec<(String, T)>,
|
||||
},
|
||||
Fuzzy {
|
||||
matcher: Matcher,
|
||||
atom: Atom,
|
||||
@ -64,6 +74,18 @@ impl<T> NuMatcher<'_, T> {
|
||||
state: State::Prefix { items: Vec::new() },
|
||||
}
|
||||
}
|
||||
MatchAlgorithm::Substring => {
|
||||
let lowercase_needle = if options.case_sensitive {
|
||||
needle.to_owned()
|
||||
} else {
|
||||
needle.to_folded_case()
|
||||
};
|
||||
NuMatcher {
|
||||
options,
|
||||
needle: lowercase_needle,
|
||||
state: State::Substring { items: Vec::new() },
|
||||
}
|
||||
}
|
||||
MatchAlgorithm::Fuzzy => {
|
||||
let atom = Atom::new(
|
||||
needle,
|
||||
@ -102,11 +124,21 @@ impl<T> NuMatcher<'_, T> {
|
||||
} else {
|
||||
Cow::Owned(haystack.to_folded_case())
|
||||
};
|
||||
let matches = if self.options.positional {
|
||||
haystack_folded.starts_with(self.needle.as_str())
|
||||
let matches = haystack_folded.starts_with(self.needle.as_str());
|
||||
if matches {
|
||||
if let Some(item) = item {
|
||||
items.push((haystack.to_string(), item));
|
||||
}
|
||||
}
|
||||
matches
|
||||
}
|
||||
State::Substring { items } => {
|
||||
let haystack_folded = if self.options.case_sensitive {
|
||||
Cow::Borrowed(haystack)
|
||||
} else {
|
||||
haystack_folded.contains(self.needle.as_str())
|
||||
Cow::Owned(haystack.to_folded_case())
|
||||
};
|
||||
let matches = haystack_folded.contains(self.needle.as_str());
|
||||
if matches {
|
||||
if let Some(item) = item {
|
||||
items.push((haystack.to_string(), item));
|
||||
@ -148,7 +180,7 @@ impl<T> NuMatcher<'_, T> {
|
||||
/// Get all the items that matched (sorted)
|
||||
pub fn results(self) -> Vec<T> {
|
||||
match self.state {
|
||||
State::Prefix { mut items, .. } => {
|
||||
State::Prefix { mut items, .. } | State::Substring { mut items, .. } => {
|
||||
items.sort_by(|(haystack1, _), (haystack2, _)| {
|
||||
let cmp_sensitive = haystack1.cmp(haystack2);
|
||||
if self.options.case_sensitive {
|
||||
@ -195,6 +227,7 @@ impl From<CompletionAlgorithm> for MatchAlgorithm {
|
||||
fn from(value: CompletionAlgorithm) -> Self {
|
||||
match value {
|
||||
CompletionAlgorithm::Prefix => MatchAlgorithm::Prefix,
|
||||
CompletionAlgorithm::Substring => MatchAlgorithm::Substring,
|
||||
CompletionAlgorithm::Fuzzy => MatchAlgorithm::Fuzzy,
|
||||
}
|
||||
}
|
||||
@ -206,6 +239,7 @@ impl TryFrom<String> for MatchAlgorithm {
|
||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||
match value.as_str() {
|
||||
"prefix" => Ok(Self::Prefix),
|
||||
"substring" => Ok(Self::Substring),
|
||||
"fuzzy" => Ok(Self::Fuzzy),
|
||||
_ => Err(InvalidMatchAlgorithm::Unknown),
|
||||
}
|
||||
@ -230,7 +264,6 @@ impl std::error::Error for InvalidMatchAlgorithm {}
|
||||
#[derive(Clone)]
|
||||
pub struct CompletionOptions {
|
||||
pub case_sensitive: bool,
|
||||
pub positional: bool,
|
||||
pub match_algorithm: MatchAlgorithm,
|
||||
pub sort: CompletionSort,
|
||||
}
|
||||
@ -239,7 +272,6 @@ impl Default for CompletionOptions {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
case_sensitive: true,
|
||||
positional: true,
|
||||
match_algorithm: MatchAlgorithm::Prefix,
|
||||
sort: Default::default(),
|
||||
}
|
||||
@ -256,6 +288,9 @@ mod test {
|
||||
#[case(MatchAlgorithm::Prefix, "example text", "", true)]
|
||||
#[case(MatchAlgorithm::Prefix, "example text", "examp", true)]
|
||||
#[case(MatchAlgorithm::Prefix, "example text", "text", false)]
|
||||
#[case(MatchAlgorithm::Substring, "example text", "", true)]
|
||||
#[case(MatchAlgorithm::Substring, "example text", "text", true)]
|
||||
#[case(MatchAlgorithm::Substring, "example text", "mplxt", false)]
|
||||
#[case(MatchAlgorithm::Fuzzy, "example text", "", true)]
|
||||
#[case(MatchAlgorithm::Fuzzy, "example text", "examp", true)]
|
||||
#[case(MatchAlgorithm::Fuzzy, "example text", "ext", true)]
|
||||
|
@ -1,5 +1,6 @@
|
||||
use crate::completions::{
|
||||
completer::map_value_completions, Completer, CompletionOptions, SemanticSuggestion,
|
||||
completer::map_value_completions, Completer, CompletionOptions, MatchAlgorithm,
|
||||
SemanticSuggestion,
|
||||
};
|
||||
use nu_engine::eval_call;
|
||||
use nu_protocol::{
|
||||
@ -102,10 +103,10 @@ impl<T: Completer> Completer for CustomCompletion<T> {
|
||||
{
|
||||
completion_options.case_sensitive = case_sensitive;
|
||||
}
|
||||
if let Some(positional) =
|
||||
options.get("positional").and_then(|val| val.as_bool().ok())
|
||||
{
|
||||
completion_options.positional = positional;
|
||||
let positional =
|
||||
options.get("positional").and_then(|val| val.as_bool().ok());
|
||||
if positional.is_some() {
|
||||
log::warn!("Use of the positional option is deprecated. Use the substring match algorithm instead.");
|
||||
}
|
||||
if let Some(algorithm) = options
|
||||
.get("completion_algorithm")
|
||||
@ -113,6 +114,11 @@ impl<T: Completer> Completer for CustomCompletion<T> {
|
||||
.and_then(|option| option.try_into().ok())
|
||||
{
|
||||
completion_options.match_algorithm = algorithm;
|
||||
if let Some(false) = positional {
|
||||
if completion_options.match_algorithm == MatchAlgorithm::Prefix {
|
||||
completion_options.match_algorithm = MatchAlgorithm::Substring
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user