Lines
100 %
Functions
62.5 %
Branches
use fuzzy_matcher::skim::SkimMatcherV2;
/// Encapsulate SkimMatcherV2 to extend it with custom methods.
pub struct Matcher {
matcher: SkimMatcherV2,
}
impl Matcher {
/// Find a best match for a pattern from choices and return its index.
pub fn best_match(&self, choices: &[&str], pattern: &str) -> Option<usize> {
choices
.iter()
.enumerate()
// Calculate fuzzy score for each choice and store it as Option<f64>.
.map(|(index, &choice)| {
(
index,
self.matcher
.fuzzy(choice, pattern, false)
.map(|(score, _)| score),
)
})
// Filter out None scores and map Some(score) to (index, score) tuple.
.filter_map(|(index, score)| score.map(|s| (index, s)))
.max_by(|(_, score1), (_, score2)| score1.partial_cmp(score2).unwrap())
.map(|(index, _)| index)
impl Default for Matcher {
/// Return the default Matcher.
fn default() -> Self {
Matcher {
matcher: SkimMatcherV2::default(),
#[cfg(test)]
macro_rules! best_match {
($($name: ident: $value:expr,)*) => {
$(
#[test]
fn $name() {
// GIVEN an array of choices, search pattern & expected index
let (choices, pattern, expected) = $value;
let matcher = Matcher::default();
// WHEN getting the best match for the pattern
let actual = matcher.best_match(&choices, &pattern);
// THEN the returned index should match the expected choice's index
assert_eq!(expected, actual);
)*
mod tests {
use super::*;
best_match! {
best_match_exact: (["Atrox", "Opifex", "Solitus"], "Atrox", Some(0)),
best_match_partial: (["Atrox", "Opifex", "Solitus"], "Opi", Some(1)),
best_match_fuzzy: (["Atrox", "Opifex", "Solitus"], "soli", Some(2)),
best_match_none: (["Atrox", "Opifex", "Solitus"], "Nanomage", None),