use nucleo_matcher::{Config, Matcher};
use crate::config::CaseMatching;
use crate::models::{AliasEntry, SearchResult};
#[derive(Debug, Clone)]
pub struct SearchOptions {
pub case_matching: CaseMatching,
pub normalize: bool,
}
impl Default for SearchOptions {
fn default() -> Self {
Self {
case_matching: CaseMatching::Smart,
normalize: true,
}
}
}
pub fn fuzzy_search(
entries: &[AliasEntry],
query: &str,
opts: &SearchOptions,
) -> Vec<SearchResult> {
if query.is_empty() {
return entries
.iter()
.map(|entry| SearchResult {
entry: entry.clone(),
score: 0,
})
.collect();
}
let mut matcher = create_matcher(opts);
let mut results = Vec::new();
let query_haystack = nucleo_matcher::Utf32Str::Ascii(query.as_bytes());
for entry in entries {
let name_haystack = nucleo_matcher::Utf32Str::Ascii(entry.name.as_bytes());
let name_score = matcher.fuzzy_match(name_haystack, query_haystack);
let comment_score = entry.comments.as_ref().and_then(|comments| {
let comment_text = comments.join(" ");
let comment_haystack = nucleo_matcher::Utf32Str::Ascii(comment_text.as_bytes());
matcher.fuzzy_match(comment_haystack, query_haystack)
});
if let Some(score) = name_score.or(comment_score) {
results.push(SearchResult {
entry: entry.clone(),
score: score as u32,
});
}
}
results.sort_by_key(|r| std::cmp::Reverse(r.score));
results
}
fn create_matcher(_opts: &SearchOptions) -> Matcher {
Matcher::new(Config::DEFAULT)
}
#[cfg(test)]
mod search_tests;