diff --git a/src/algos/fzf/common.rs b/src/algos/fzf/fzf.rs similarity index 65% rename from src/algos/fzf/common.rs rename to src/algos/fzf/fzf.rs index 5838893..0c83a72 100644 --- a/src/algos/fzf/common.rs +++ b/src/algos/fzf/fzf.rs @@ -3,6 +3,98 @@ use core::ops::Range; use super::{query::*, *}; use crate::*; +/// TODO: docs +pub(super) trait Fzf { + /// TODO: docs + fn alloc_chars<'a>(&mut self, candidate: &str) -> &'a [char]; + + /// TODO: docs + fn scheme(&self) -> &Scheme; + + /// TODO: docs + fn fuzzy( + &mut self, + pattern: Pattern, + candidate: Candidate, + ranges: &mut MatchedRanges, + ) -> Option; + + /// TODO: docs + fn score( + &mut self, + pattern: Pattern, + candidate: Candidate, + ranges: &mut MatchedRanges, + ) -> Option { + let score = match pattern.match_type { + MatchType::Fuzzy => { + if pattern.is_inverse { + self.fuzzy::(pattern, candidate, ranges) + } else { + self.fuzzy::(pattern, candidate, ranges) + } + }, + + MatchType::Exact => { + todo!(); + }, + + MatchType::PrefixExact => { + todo!(); + }, + + MatchType::SuffixExact => { + todo!(); + }, + + MatchType::EqualExact => { + todo!(); + }, + }; + + match (score.is_some(), pattern.is_inverse) { + (true, false) => score, + (false, true) => Some(0), + _ => None, + } + } + + /// TODO: docs + #[inline(always)] + fn distance( + &mut self, + query: FzfQuery, + candidate: &str, + ranges: &mut MatchedRanges, + ) -> Option { + if query.is_empty() { + return Some(FzfDistance::from_score(0)); + } + + let candidate = if candidate.is_ascii() { + Candidate::Ascii(candidate.as_bytes()) + } else { + Candidate::Unicode(self.alloc_chars(candidate)) + }; + + match query.search_mode { + SearchMode::NotExtended(pattern) => self + .fuzzy::(pattern, candidate, ranges) + .map(FzfDistance::from_score), + + SearchMode::Extended(conditions) => { + let mut total_score: Score = 0; + for condition in conditions { + total_score += condition.iter().find_map(|pattern| { + self.score::(pattern, candidate, ranges) + })?; + } + Some(FzfDistance::from_score(total_score)) + }, + } + } +} + /// TODO: docs #[inline] pub(super) fn calculate_score( @@ -406,104 +498,104 @@ fn ignored_candidate_trailing_spaces( } } -#[cfg(test)] -mod tests { - #![allow(clippy::single_range_in_vec_init)] - - use super::*; - - #[test] - fn equal_match_1() { - let pattern = - Pattern::parse("^AbC$".chars().collect::>().leak()); - - let mut ranges_buf = MatchedRanges::default(); - - assert!(exact_match( - pattern, - "ABC", - AsciiCandidateOpts::new(true), - &Scheme::default(), - Some(&mut ranges_buf) - ) - .is_none()); - - { - ranges_buf = MatchedRanges::default(); - - assert!(exact_match( - pattern, - "AbC", - AsciiCandidateOpts::new(true), - &Scheme::default(), - Some(&mut ranges_buf) - ) - .is_some()); - - assert_eq!(ranges_buf.as_slice(), [0..3]); - } - - { - ranges_buf = MatchedRanges::default(); - - assert!(exact_match( - pattern, - "AbC ", - AsciiCandidateOpts::new(true), - &Scheme::default(), - Some(&mut ranges_buf) - ) - .is_some()); - - assert_eq!(ranges_buf.as_slice(), [0..3]); - } - - { - ranges_buf = MatchedRanges::default(); - - assert!(exact_match( - pattern, - " AbC ", - AsciiCandidateOpts::new(true), - &Scheme::default(), - Some(&mut ranges_buf) - ) - .is_some()); - - assert_eq!(ranges_buf.as_slice(), [1..4]); - } - - { - ranges_buf = MatchedRanges::default(); - - assert!(exact_match( - pattern, - " AbC", - AsciiCandidateOpts::new(true), - &Scheme::default(), - Some(&mut ranges_buf) - ) - .is_some()); - - assert_eq!(ranges_buf.as_slice(), [2..5]); - } - } - - #[test] - fn exact_match_1() { - let pattern = Pattern::parse("abc".chars().collect::>().leak()); - - let mut ranges_buf = MatchedRanges::default(); - - assert!(exact_match( - pattern, - "aabbcc abc", - AsciiCandidateOpts::new(true), - &Scheme::default(), - Some(&mut ranges_buf) - ) - .is_some()); - - assert_eq!(ranges_buf.as_slice(), [7..10]); - } -} +// #[cfg(test)] +// mod tests { +// #![allow(clippy::single_range_in_vec_init)] +// +// use super::*; +// +// #[test] +// fn equal_match_1() { +// let pattern = +// Pattern::parse("^AbC$".chars().collect::>().leak()); +// +// let mut ranges_buf = MatchedRanges::default(); +// +// assert!(exact_match( +// pattern, +// "ABC", +// todo!(), +// &Scheme::default(), +// Some(&mut ranges_buf) +// ) +// .is_none()); +// +// { +// ranges_buf = MatchedRanges::default(); +// +// assert!(exact_match( +// pattern, +// "AbC", +// todo!(), +// &Scheme::default(), +// Some(&mut ranges_buf) +// ) +// .is_some()); +// +// assert_eq!(ranges_buf.as_slice(), [0..3]); +// } +// +// { +// ranges_buf = MatchedRanges::default(); +// +// assert!(exact_match( +// pattern, +// "AbC ", +// todo!(), +// &Scheme::default(), +// Some(&mut ranges_buf) +// ) +// .is_some()); +// +// assert_eq!(ranges_buf.as_slice(), [0..3]); +// } +// +// { +// ranges_buf = MatchedRanges::default(); +// +// assert!(exact_match( +// pattern, +// " AbC ", +// todo!(), +// &Scheme::default(), +// Some(&mut ranges_buf) +// ) +// .is_some()); +// +// assert_eq!(ranges_buf.as_slice(), [1..4]); +// } +// +// { +// ranges_buf = MatchedRanges::default(); +// +// assert!(exact_match( +// pattern, +// " AbC", +// todo!(), +// &Scheme::default(), +// Some(&mut ranges_buf) +// ) +// .is_some()); +// +// assert_eq!(ranges_buf.as_slice(), [2..5]); +// } +// } +// +// #[test] +// fn exact_match_1() { +// let pattern = Pattern::parse("abc".chars().collect::>().leak()); +// +// let mut ranges_buf = MatchedRanges::default(); +// +// assert!(exact_match( +// pattern, +// "aabbcc abc", +// todo!(), +// &Scheme::default(), +// Some(&mut ranges_buf) +// ) +// .is_some()); +// +// assert_eq!(ranges_buf.as_slice(), [7..10]); +// } +// } diff --git a/src/algos/fzf/v1.rs b/src/algos/fzf/fzf_v1.rs similarity index 76% rename from src/algos/fzf/v1.rs rename to src/algos/fzf/fzf_v1.rs index 2745fad..7956a03 100644 --- a/src/algos/fzf/v1.rs +++ b/src/algos/fzf/fzf_v1.rs @@ -7,6 +7,9 @@ use crate::*; #[cfg_attr(docsrs, doc(cfg(feature = "fzf-v1")))] #[derive(Clone, Default)] pub struct FzfV1 { + /// TODO: docs + candidate_slab: CandidateSlab, + /// TODO: docs case_sensitivity: CaseSensitivity, @@ -39,31 +42,6 @@ impl FzfV1 { Self::default() } - /// TODO: docs - #[cfg(feature = "tests")] - pub fn scheme(&self) -> &Scheme { - &self.scheme - } - - /// TODO: docs - #[inline(always)] - fn score( - &mut self, - pattern: Pattern, - candidate: Candidate, - buf: Option<&mut MatchedRanges>, - ) -> Option { - let is_sensitive = match self.case_sensitivity { - CaseSensitivity::Sensitive => true, - CaseSensitivity::Insensitive => false, - CaseSensitivity::Smart => pattern.has_uppercase, - }; - - let opts = CandidateOpts::new(is_sensitive, self.normalization); - - fzf_v1(pattern, candidate, opts, &self.scheme, buf, ()) - } - /// TODO: docs #[inline(always)] pub fn with_case_sensitivity( @@ -104,51 +82,69 @@ impl Metric for FzfV1 { #[inline(always)] fn distance( &mut self, - _query: FzfQuery<'_>, - _candidate: &str, - ) -> Option> { - todo!(); + query: FzfQuery<'_>, + candidate: &str, + ) -> Option { + let ranges = &mut MatchedRanges::default(); + ::distance::(self, query, candidate, ranges) } #[inline] fn distance_and_ranges( &mut self, - _query: FzfQuery<'_>, - _candidate: &str, - _ranges_buf: &mut Vec>, + query: FzfQuery<'_>, + candidate: &str, + ranges: &mut MatchedRanges, ) -> Option { - todo!() + ::distance::(self, query, candidate, ranges) } } -/// TODO: docs -#[inline] -pub(super) fn fzf_v1( - pattern: Pattern, - _candidate: Candidate, - _opts: CandidateOpts, - _scheme: &Scheme, - _ranges_buf: Option<&mut MatchedRanges>, - _: (), -) -> Option { - // TODO: can we remove this? - if pattern.is_empty() { - return Some(0); +impl Fzf for FzfV1 { + #[inline(always)] + fn alloc_chars<'a>(&mut self, s: &str) -> &'a [char] { + unsafe { core::mem::transmute(self.candidate_slab.alloc(s)) } + } + + #[inline(always)] + fn scheme(&self) -> &Scheme { + &self.scheme } - todo!(); - - // let range_forward = forward_pass(pattern, candidate, opts)?; - // - // let start_backward = - // backward_pass(pattern, &candidate[range_forward.clone()], opts); - // - // let range = range_forward.start + start_backward..range_forward.end; - // - // let score = - // calculate_score(pattern, candidate, range, opts, scheme, ranges_buf); - // - // Some(score) + #[inline(always)] + fn fuzzy( + &mut self, + pattern: Pattern, + _candidate: Candidate, + _ranges: &mut MatchedRanges, + ) -> Option { + // TODO: can we remove this? + if pattern.is_empty() { + return Some(0); + } + + let is_sensitive = match self.case_sensitivity { + CaseSensitivity::Sensitive => true, + CaseSensitivity::Insensitive => false, + CaseSensitivity::Smart => pattern.has_uppercase, + }; + + let _opts = CandidateOpts::new(is_sensitive, self.normalization); + + todo!(); + + // let range_forward = forward_pass(pattern, candidate, opts)?; + // + // let start_backward = + // backward_pass(pattern, &candidate[range_forward.clone()], opts); + // + // let range = range_forward.start + start_backward..range_forward.end; + // + // let score = + // calculate_score(pattern, candidate, range, opts, scheme, ranges_buf); + // + // Some(score) + } } /// TODO: docs diff --git a/src/algos/fzf/v2.rs b/src/algos/fzf/fzf_v2.rs similarity index 73% rename from src/algos/fzf/v2.rs rename to src/algos/fzf/fzf_v2.rs index af83a04..76551fe 100644 --- a/src/algos/fzf/v2.rs +++ b/src/algos/fzf/fzf_v2.rs @@ -1,5 +1,3 @@ -use core::ops::Range; - use super::{query::*, slab::*, *}; use crate::*; @@ -45,12 +43,6 @@ impl FzfV2 { Self::default() } - /// TODO: docs - #[cfg(feature = "tests")] - pub fn scheme(&self) -> &Scheme { - &self.scheme - } - /// TODO: docs #[inline(always)] pub fn with_case_sensitivity( @@ -93,141 +85,100 @@ impl Metric for FzfV2 { &mut self, query: FzfQuery<'_>, candidate: &str, - ) -> Option> { - if query.is_empty() { - return Some(Match::default()); - } - - let candidate = if candidate.is_ascii() { - Candidate::Ascii(candidate.as_bytes()) - } else { - let chars = self.candidate_slab.alloc(candidate); - Candidate::Unicode(chars) - }; - - let mut buf = if self.with_matched_ranges { - Some(MatchedRanges::default()) - } else { - None - }; - - let conditions = match query.search_mode { - SearchMode::Extended(conditions) => conditions, - - SearchMode::NotExtended(pattern) => { - let is_sensitive = match self.case_sensitivity { - CaseSensitivity::Sensitive => true, - CaseSensitivity::Insensitive => false, - CaseSensitivity::Smart => pattern.has_uppercase, - }; - - let score = fzf_v2( - pattern, - candidate, - CandidateOpts::new(is_sensitive, self.normalization), - &self.scheme, - None, - &mut self.slab, - )?; - - let distance = FzfDistance::from_score(score); - - return Some(Match::new(distance, buf.unwrap_or_default())); - }, - }; - - let mut total_score = 0; - - for condition in conditions { - let score = condition.iter().find_map(|pattern| { - let is_sensitive = match self.case_sensitivity { - CaseSensitivity::Sensitive => true, - CaseSensitivity::Insensitive => false, - CaseSensitivity::Smart => pattern.has_uppercase, - }; - - pattern.score( - candidate, - CandidateOpts::new(is_sensitive, self.normalization), - &self.scheme, - buf.as_mut(), - &mut self.slab, - fzf_v2, - ) - })?; - - total_score += score; - } - - let distance = FzfDistance::from_score(total_score); - - Some(Match::new(distance, buf.unwrap_or_default())) + ) -> Option { + let ranges = &mut MatchedRanges::default(); + ::distance::(self, query, candidate, ranges) } - #[inline] + #[inline(always)] fn distance_and_ranges( &mut self, - _query: FzfQuery<'_>, - _candidate: &str, - _ranges_buf: &mut Vec>, + query: FzfQuery<'_>, + candidate: &str, + ranges: &mut MatchedRanges, ) -> Option { - todo!(); + ::distance::(self, query, candidate, ranges) } } -/// TODO: docs -#[inline] -pub(super) fn fzf_v2( - pattern: Pattern, - candidate: Candidate, - opts: CandidateOpts, - scheme: &Scheme, - ranges_buf: Option<&mut MatchedRanges>, - slab: &mut V2Slab, -) -> Option { - // TODO: can we remove this? - if pattern.is_empty() { - return Some(0); +impl Fzf for FzfV2 { + #[inline(always)] + fn alloc_chars<'a>(&mut self, s: &str) -> &'a [char] { + unsafe { core::mem::transmute(self.candidate_slab.alloc(s)) } + } + + #[inline(always)] + fn scheme(&self) -> &Scheme { + &self.scheme } - let (match_offsets, last_match_offset) = - matches(&mut slab.matched_indices, pattern, candidate, opts)?; + #[inline(always)] + fn fuzzy( + &mut self, + pattern: Pattern, + candidate: Candidate, + ranges: &mut MatchedRanges, + ) -> Option { + // TODO: can we remove this? + if pattern.is_empty() { + return Some(0); + } + + let is_sensitive = match self.case_sensitivity { + CaseSensitivity::Sensitive => true, + CaseSensitivity::Insensitive => false, + CaseSensitivity::Smart => pattern.has_uppercase, + }; - let first_offset = match_offsets[0]; + let opts = CandidateOpts::new(is_sensitive, self.normalization); - let initial_char_class = if first_offset == 0 { - scheme.initial_char_class - } else { - char_class(candidate.char(first_offset - 1), scheme) - }; + let (match_offsets, last_match_offset) = + matches(&mut self.slab.matched_indices, pattern, candidate, opts)?; - let mut candidate = CandidateV2::new( - candidate.slice(first_offset, last_match_offset), - &mut slab.bonus, - initial_char_class, - opts, - ); + let first_offset = match_offsets[0]; - // After slicing the candidate we need to move all the offsets back - // by the offset of the first match so that they still refer to the - // same characters. - match_offsets.iter_mut().for_each(|offset| *offset -= first_offset); + let start_byte_offset = + if RANGES { candidate.to_byte_offset(first_offset) } else { 0 }; - let (scores, consecutive, score, score_cell) = score( - &mut slab.scoring_matrix, - &mut slab.consecutive_matrix, - pattern, - &mut candidate, - match_offsets, - scheme, - ); + let initial_char_class = if first_offset == 0 { + self.scheme.initial_char_class + } else { + char_class(candidate.char(first_offset - 1), &self.scheme) + }; - if let Some(buf) = ranges_buf { - let candidate = candidate.into_base(); - matched_ranges(scores, consecutive, score_cell, candidate, 0, buf); - }; + let mut candidate = CandidateV2::new( + candidate.slice(first_offset, last_match_offset), + &mut self.slab.bonus, + initial_char_class, + opts, + ); + + // After slicing the candidate we move all the offsets back by the + // first offset. + match_offsets.iter_mut().for_each(|offset| *offset -= first_offset); + + let (scores, consecutive, score, score_cell) = score( + &mut self.slab.scoring_matrix, + &mut self.slab.consecutive_matrix, + pattern, + &mut candidate, + match_offsets, + &self.scheme, + ); + + if RANGES { + matched_ranges( + scores, + consecutive, + score_cell, + candidate.into_base(), + start_byte_offset, + ranges, + ); + }; - Some(score) + Some(score) + } } /// TODO: docs diff --git a/src/algos/fzf/mod.rs b/src/algos/fzf/mod.rs index 3bfdeed..f72109d 100644 --- a/src/algos/fzf/mod.rs +++ b/src/algos/fzf/mod.rs @@ -44,22 +44,26 @@ //! [extended-search]: https://github.com/junegunn/fzf#search-syntax mod candidate; -mod common; mod distance; +mod fzf; +#[cfg(feature = "fzf-v1")] +mod fzf_v1; +#[cfg(feature = "fzf-v1")] +mod fzf_v2; mod parser; mod query; mod scheme; mod scoring; mod slab; -#[cfg(feature = "fzf-v1")] -mod v1; -#[cfg(feature = "fzf-v1")] -mod v2; use candidate::*; -use common::*; pub use distance::FzfDistance; use distance::*; +use fzf::*; +#[cfg(feature = "fzf-v1")] +pub use fzf_v1::FzfV1; +#[cfg(feature = "fzf-v1")] +pub use fzf_v2::FzfV2; pub use parser::*; pub use query::FzfQuery; pub use scheme::FzfScheme; @@ -67,10 +71,6 @@ pub use scheme::FzfScheme; pub use scheme::Scheme; use scoring::*; use slab::*; -#[cfg(feature = "fzf-v1")] -pub use v1::FzfV1; -#[cfg(feature = "fzf-v1")] -pub use v2::FzfV2; #[doc(hidden)] pub mod bonus { diff --git a/src/algos/fzf/query.rs b/src/algos/fzf/query.rs index a857adc..bc5c169 100644 --- a/src/algos/fzf/query.rs +++ b/src/algos/fzf/query.rs @@ -1,18 +1,5 @@ use core::fmt::Write; -use super::*; -use crate::*; - -/// TODO: docs -type FuzzyAlgo = fn( - Pattern, - Candidate, - CandidateOpts, - &Scheme, - Option<&mut MatchedRanges>, - T, -) -> Option; - /// A parsed fzf query. /// /// This struct is created by the [`parse`](FzfParser::parse) method on @@ -298,54 +285,6 @@ impl<'a> Pattern<'a> { } } - /// TODO: docs - #[inline] - pub(super) fn score( - self, - candidate: Candidate, - opts: CandidateOpts, - scheme: &Scheme, - mut ranges_buf: Option<&mut MatchedRanges>, - extra: E, - fuzzy_algo: FuzzyAlgo, - ) -> Option { - if self.is_inverse { - ranges_buf = None; - } - - let result = match self.match_type { - MatchType::Fuzzy => { - fuzzy_algo(self, candidate, opts, scheme, ranges_buf, extra) - }, - - MatchType::Exact => { - todo!() - // exact_match(self, candidate, opts, scheme, ranges_buf) - }, - - MatchType::PrefixExact => { - todo!() - // prefix_match(self, candidate, opts, scheme, ranges_buf) - }, - - MatchType::SuffixExact => { - todo!() - // suffix_match(self, candidate, opts, scheme, ranges_buf) - }, - - MatchType::EqualExact => { - todo!() - // equal_match(self, candidate, opts, scheme, ranges_buf) - }, - }; - - match (result.is_some(), self.is_inverse) { - (true, false) => result, - (false, true) => Some(0), - _ => None, - } - } - /// TODO: docs #[inline(always)] pub(super) fn trailing_spaces(&self) -> usize { diff --git a/src/matched_ranges.rs b/src/matched_ranges.rs index 49d2a96..646ab83 100644 --- a/src/matched_ranges.rs +++ b/src/matched_ranges.rs @@ -4,7 +4,7 @@ use crate::tiny_vec::TinyVec; /// TODO: docs #[derive(Default)] -pub(crate) struct MatchedRanges { +pub struct MatchedRanges { ranges: TinyVec<8, Range>, } diff --git a/src/metric.rs b/src/metric.rs index 31c2d2d..1f57c5d 100644 --- a/src/metric.rs +++ b/src/metric.rs @@ -1,6 +1,4 @@ -use core::ops::Range; - -use crate::Match; +use crate::MatchedRanges; /// A trait representing a distance metric on strings. /// @@ -48,7 +46,7 @@ pub trait Metric { &mut self, query: Self::Query<'_>, candidate: &str, - ) -> Option>; + ) -> Option; /// This method always returns the same value as [`Self::distance`], but in /// the case of a match it also fills the provided buffer with the **byte** @@ -58,6 +56,6 @@ pub trait Metric { &mut self, _query: Self::Query<'_>, _candidate: &str, - _ranges_buf: &mut Vec>, + _ranges_buf: &mut MatchedRanges, ) -> Option; }