use fuzzy_matcher::skim::SkimMatcherV2;
use fuzzy_matcher::FuzzyMatcher;
#[derive(Debug, Clone)]
pub struct FuzzyMatch {
pub score: i64,
pub indices: Vec<usize>,
}
pub fn fuzzy_match(pattern: &str, text: &str) -> Option<FuzzyMatch> {
if pattern.is_empty() {
return None;
}
let matcher = SkimMatcherV2::default();
matcher
.fuzzy_indices(text, pattern)
.map(|(score, indices)| FuzzyMatch { score, indices })
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_fuzzy_match_basic() {
let result = fuzzy_match("fauth", "feature/auth");
assert!(result.is_some());
let m = result.unwrap();
assert!(m.score > 0);
assert!(!m.indices.is_empty());
}
#[test]
fn test_fuzzy_match_no_match() {
let result = fuzzy_match("xyz", "feature/auth");
assert!(result.is_none());
}
#[test]
fn test_fuzzy_match_exact() {
let result = fuzzy_match("main", "main");
assert!(result.is_some());
let m = result.unwrap();
assert_eq!(m.indices, vec![0, 1, 2, 3]);
}
#[test]
fn test_fuzzy_match_empty_pattern() {
let result = fuzzy_match("", "feature/auth");
assert!(result.is_none());
}
#[test]
fn test_fuzzy_match_case_insensitive() {
let result = fuzzy_match("main", "MAIN");
assert!(result.is_some());
}
#[test]
fn test_fuzzy_match_word_boundary() {
let result1 = fuzzy_match("auth", "feature/auth");
let result2 = fuzzy_match("auth", "featureauth");
assert!(result1.is_some());
assert!(result2.is_some());
assert!(result1.unwrap().score >= result2.unwrap().score);
}
#[test]
fn test_fuzzy_match_continuous() {
let result_continuous = fuzzy_match("feat", "feature/auth");
let result_spread = fuzzy_match("fea", "feature/auth");
assert!(result_continuous.is_some());
assert!(result_spread.is_some());
assert!(result_continuous.unwrap().score > result_spread.unwrap().score);
}
#[test]
fn test_fuzzy_match_multibyte() {
let result = fuzzy_match("日本", "日本語テスト");
assert!(result.is_some());
let m = result.unwrap();
assert_eq!(m.indices, vec![0, 1]);
}
}