Skip to content

Commit

Permalink
fzf: add OrBlocks iterator
Browse files Browse the repository at this point in the history
  • Loading branch information
noib3 committed Nov 4, 2023
1 parent 0aa9bbc commit 3d13422
Show file tree
Hide file tree
Showing 3 changed files with 157 additions and 30 deletions.
175 changes: 151 additions & 24 deletions src/algos/fzf/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,78 @@ fn parse(query: &str) -> Vec<Condition<'static>> {
conditions
}

const OR_BLOCK_SEPARATOR: &str = "|";

/// TODO: docs
struct OrBlocks<'a> {
/// TODO: docs
words: Words<'a>,

/// TODO: docs
next: Option<<Words<'a> 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<<Words<'a> as Iterator>::Item>;

#[inline]
fn next(&mut self) -> Option<Self::Item> {
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
Expand Down Expand Up @@ -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::*;
Expand Down Expand Up @@ -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);
}
}
4 changes: 2 additions & 2 deletions src/algos/fzf/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,10 +164,10 @@ pub(super) enum MatchType {
SuffixExact,

/// TODO: docs
InverseExact,
InverseFuzzy,

/// TODO: docs
InverseFuzzy,
InverseExact,

/// TODO: docs
InversePrefixExact,
Expand Down
8 changes: 4 additions & 4 deletions src/algos/fzf/v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 => {
Expand All @@ -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))
}
Expand Down

0 comments on commit 3d13422

Please sign in to comment.