diff --git a/src/algos/fzf/parser.rs b/src/algos/fzf/parser.rs index df467d1..98e4764 100644 --- a/src/algos/fzf/parser.rs +++ b/src/algos/fzf/parser.rs @@ -319,7 +319,7 @@ pub fn parse(s: &str) -> FzfQuery<'static> { #[cfg(test)] mod parse_tests { - use super::super::query::MatchType; + use super::super::query::*; use super::*; #[test] @@ -331,17 +331,9 @@ mod parse_tests { fn parse_query_single_fuzzy() { let query = parse("foo"); - let conditions = query.conditions(); - - assert_eq!(conditions.len(), 1); - - let condition = conditions.iter().next().unwrap(); - - let mut patterns = condition.or_patterns(); - - assert_eq!(patterns.len(), 1); - - let pattern = patterns.next().unwrap(); + let SearchMode::NotExtended(pattern) = query.search_mode else { + panic!(); + }; assert_eq!(pattern.into_string(), "foo"); diff --git a/src/algos/fzf/query.rs b/src/algos/fzf/query.rs index 4511216..00e3443 100644 --- a/src/algos/fzf/query.rs +++ b/src/algos/fzf/query.rs @@ -16,39 +16,67 @@ type FuzzyAlgo = fn( /// TODO: docs. #[derive(Clone, Copy)] pub struct FzfQuery<'a> { - conditions: &'a [Condition<'a>], + pub(super) search_mode: SearchMode<'a>, +} + +/// TODO: docs +#[derive(Clone, Copy)] +pub(super) enum SearchMode<'a> { + /// TODO: docs + Extended(&'a [Condition<'a>]), + + /// TODO: docs + NotExtended(Pattern<'a>), } impl core::fmt::Debug for FzfQuery<'_> { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - let s = self - .conditions - .iter() - .map(|condition| format!("{:?}", condition)) - .collect::>() - .join(" && "); + let s = match self.search_mode { + SearchMode::Extended(conditions) => conditions + .iter() + .map(|condition| format!("{:?}", condition)) + .collect::>() + .join(" && "), + + SearchMode::NotExtended(pattern) => pattern.into_string(), + }; f.debug_tuple("FzfQuery").field(&s).finish() } } impl<'a> FzfQuery<'a> { - /// TODO: docs - #[inline(always)] - pub(super) fn conditions(&self) -> &[Condition<'a>] { - self.conditions - } - /// TODO: docs #[inline] pub(super) fn is_empty(&self) -> bool { - self.conditions.is_empty() + match self.search_mode { + SearchMode::Extended(conditions) => conditions.is_empty(), + SearchMode::NotExtended(pattern) => pattern.is_empty(), + } } /// TODO: docs #[inline] pub(super) fn new(conditions: &'a [Condition<'a>]) -> Self { - Self { conditions } + // If there's only one condition with a single pattern, and that + // pattern is fuzzy, then we can use the non-extended search mode. + if conditions.len() == 1 { + let mut patterns = conditions[0].or_patterns(); + + let first_pattern = patterns + .next() + .expect("conditions always have at least one pattern"); + + if patterns.next().is_none() + && matches!(first_pattern.match_type, MatchType::Fuzzy) + { + return Self { + search_mode: SearchMode::NotExtended(first_pattern), + }; + } + } + + Self { search_mode: SearchMode::Extended(conditions) } } } @@ -149,6 +177,12 @@ impl<'a> Pattern<'a> { self.text.iter().copied() } + /// TODO: docs + #[inline] + pub(super) fn is_empty(&self) -> bool { + self.text.is_empty() + } + /// TODO: docs #[inline] pub(super) fn into_string(self) -> String { diff --git a/src/algos/fzf/v1.rs b/src/algos/fzf/v1.rs index 532a15b..e4169c8 100644 --- a/src/algos/fzf/v1.rs +++ b/src/algos/fzf/v1.rs @@ -81,7 +81,10 @@ impl Metric for FzfV1 { return None; } - let pattern = query.conditions()[0].or_patterns().next().unwrap(); + let pattern = match query.search_mode { + SearchMode::NotExtended(pattern) => pattern, + SearchMode::Extended(_) => todo!(), + }; let case_matcher = self.case_sensitivity.matcher(pattern.has_uppercase); diff --git a/src/algos/fzf/v2.rs b/src/algos/fzf/v2.rs index 4734f71..b1093a0 100644 --- a/src/algos/fzf/v2.rs +++ b/src/algos/fzf/v2.rs @@ -82,13 +82,38 @@ impl Metric for FzfV2 { return None; } + let is_candidate_ascii = candidate.is_ascii(); + + let conditions = match query.search_mode { + SearchMode::NotExtended(pattern) => { + let is_case_sensitive = match self.case_sensitivity { + CaseSensitivity::Sensitive => true, + CaseSensitivity::Insensitive => false, + CaseSensitivity::Smart => pattern.has_uppercase, + }; + + let (score, matched_ranges) = fzf_v2( + pattern, + candidate, + &self.scheme, + is_case_sensitive, + self.with_matched_ranges, + (&mut self.slab, is_candidate_ascii), + )?; + + let distance = FzfDistance::from_score(score); + + return Some(Match::new(distance, matched_ranges)); + }, + + SearchMode::Extended(conditions) => conditions, + }; + let mut total_score = 0; let mut matched_ranges = MatchedRanges::default(); - let is_candidate_ascii = candidate.is_ascii(); - - for condition in query.conditions() { + for condition in conditions { let (score, ranges) = condition.or_patterns().find_map(|pattern| { let is_case_sensitive = match self.case_sensitivity {