diff --git a/src/algos/fzf/parser.rs b/src/algos/fzf/parser.rs index 5cc6dd9..b02dd52 100644 --- a/src/algos/fzf/parser.rs +++ b/src/algos/fzf/parser.rs @@ -178,6 +178,78 @@ fn parse(query: &str) -> Vec> { conditions } +const OR_BLOCK_SEPARATOR: &str = "|"; + +/// TODO: docs +struct OrBlocks<'a> { + /// TODO: docs + words: Words<'a>, + + /// TODO: docs + next: Option< as Iterator>::Item>, + + /// TODO: docs + is_done: bool, +} + +impl<'a> OrBlocks<'a> { + #[inline] + fn new(s: &'a str) -> Self { + Self { words: Words::new(s), next: None, is_done: false } + } +} + +impl<'a> Iterator for OrBlocks<'a> { + type Item = Vec< as Iterator>::Item>; + + #[inline] + fn next(&mut self) -> Option { + if self.is_done { + return None; + } + + // TODO: docs + let mut blocks; + + // TODO: docs + let mut looking_for_or; + + if let Some(first_block) = self.next.take() { + blocks = vec![first_block]; + looking_for_or = true; + } else { + blocks = Vec::new(); + looking_for_or = false; + } + + loop { + let Some(word) = self.words.next() else { + self.is_done = true; + break; + }; + + let word_is_condition = word != OR_BLOCK_SEPARATOR; + + if word_is_condition { + if looking_for_or { + self.next = Some(word); + break; + } else { + blocks.push(word); + looking_for_or = true; + continue; + } + } + + if looking_for_or { + looking_for_or = false; + } + } + + (!blocks.is_empty()).then_some(blocks) + } +} + /// An iterator over the words of a string. /// /// Here, a "word" is simply a string of consecutive non-ascii-space @@ -302,6 +374,85 @@ fn strip_spaces(s: &str) -> &str { &s[leading_spaces..] } +#[cfg(test)] +mod parse_tests { + use super::*; + + #[test] + fn parse_query_empty() { + assert!(parse("").is_empty()); + } + + #[test] + fn parse_query_single_fuzzy() { + let conditions = parse("foo"); + + assert_eq!(conditions.len(), 1); + + let condition = conditions.into_iter().next().unwrap(); + + let mut patterns = condition.or_patterns(); + + assert_eq!(patterns.len(), 1); + + let pattern = patterns.next().unwrap(); + + assert_eq!(pattern.into_string(), "foo"); + + assert_eq!(pattern.match_type, MatchType::Fuzzy); + } +} + +#[cfg(test)] +mod or_blocks_tests { + use super::*; + + #[test] + fn or_blocks_empty() { + let mut blocks = OrBlocks::new(""); + assert!(blocks.next().is_none()); + } + + #[test] + fn or_blocks_single() { + let mut blocks = OrBlocks::new("foo"); + assert_eq!(blocks.next().unwrap(), ["foo"]); + assert_eq!(blocks.next(), None); + } + + #[test] + fn or_blocks_multiple_ors() { + let mut blocks = OrBlocks::new("foo | bar | baz"); + assert_eq!(blocks.next().unwrap(), ["foo", "bar", "baz"]); + assert_eq!(blocks.next(), None); + } + + #[test] + fn or_blocks_multiple_ands() { + let mut blocks = OrBlocks::new("foo bar baz"); + assert_eq!(blocks.next().unwrap(), ["foo"]); + assert_eq!(blocks.next().unwrap(), ["bar"]); + assert_eq!(blocks.next().unwrap(), ["baz"]); + assert_eq!(blocks.next(), None); + } + + #[test] + fn or_blocks_empty_between_ors() { + let mut blocks = OrBlocks::new("foo | | bar"); + assert_eq!(blocks.next().unwrap(), ["foo", "bar"]); + assert_eq!(blocks.next(), None); + } + + #[test] + fn or_blocks_multiple_ors_multiple_ands() { + let mut blocks = OrBlocks::new("foo | bar baz qux | quux | corge"); + assert_eq!(blocks.next().unwrap(), ["foo", "bar"]); + assert_eq!(blocks.next().unwrap(), ["baz"]); + assert_eq!(blocks.next().unwrap(), ["qux", "quux", "corge"]); + assert_eq!(blocks.next(), None); + } +} + #[cfg(test)] mod tests { use super::*; @@ -372,28 +523,4 @@ mod tests { assert_eq!(words.next().as_deref(), Some(" ")); assert_eq!(words.next(), None); } - - #[test] - fn parse_query_empty() { - assert!(parse("").is_empty()); - } - - #[test] - fn parse_query_single_fuzzy() { - let conditions = parse("foo"); - - assert_eq!(conditions.len(), 1); - - let condition = conditions.into_iter().next().unwrap(); - - let mut patterns = condition.or_patterns(); - - assert_eq!(patterns.len(), 1); - - let pattern = patterns.next().unwrap(); - - assert_eq!(pattern.into_string(), "foo"); - - assert_eq!(pattern.match_type, MatchType::Fuzzy); - } } diff --git a/src/algos/fzf/query.rs b/src/algos/fzf/query.rs index d13341f..060db13 100644 --- a/src/algos/fzf/query.rs +++ b/src/algos/fzf/query.rs @@ -164,10 +164,10 @@ pub(super) enum MatchType { SuffixExact, /// TODO: docs - InverseExact, + InverseFuzzy, /// TODO: docs - InverseFuzzy, + InverseExact, /// TODO: docs InversePrefixExact, diff --git a/src/algos/fzf/v2.rs b/src/algos/fzf/v2.rs index 41dd91a..3c1ef59 100644 --- a/src/algos/fzf/v2.rs +++ b/src/algos/fzf/v2.rs @@ -71,12 +71,12 @@ impl Metric for FzfV2 { return None; } - let mut score = 0; + let mut total_score = 0; let mut matched_ranges = MatchedRanges::default(); for condition in query.conditions() { - let (condition_score, ranges) = + let (score, ranges) = condition.or_patterns().find_map(|pattern| { match pattern.match_type { MatchType::Fuzzy => { @@ -86,14 +86,14 @@ impl Metric for FzfV2 { } })?; - score += condition_score; + total_score += score; if self.with_matched_ranges { matched_ranges.join(ranges); } } - let distance = FzfDistance::from_score(score); + let distance = FzfDistance::from_score(total_score); Some(Match::new(distance, matched_ranges)) }