use crate::types::{EntryType, FileEntry, SearchResult};
use fuzzy_matcher::skim::SkimMatcherV2;
use fuzzy_matcher::FuzzyMatcher;
pub struct SearchEngine {
matcher: SkimMatcherV2,
}
impl SearchEngine {
pub fn new() -> Self {
Self {
matcher: SkimMatcherV2::default(),
}
}
pub fn search(&self, entries: &[FileEntry], query: &str, limit: usize) -> Vec<SearchResult> {
if query.is_empty() {
return Vec::new();
}
let mut results = Vec::new();
for entry in entries {
if let Some((score, indices)) = self.matcher.fuzzy_indices(&entry.name, query) {
results.push(SearchResult {
entry: entry.clone(),
score,
matched_indices: indices,
});
}
}
results.sort_by(|a, b| b.score.cmp(&a.score));
results.truncate(limit);
results
}
pub fn is_exact_match(&self, entry: &FileEntry, query: &str) -> bool {
let entry_name = entry.name.to_lowercase();
let query_lower = query.to_lowercase();
entry_name == query_lower ||
entry.path.file_stem()
.and_then(|stem| stem.to_str())
.map(|stem| stem.to_lowercase() == query_lower)
.unwrap_or(false)
}
pub fn find_single_application_match<'a>(&self, entries: &'a [FileEntry], query: &str) -> Option<&'a FileEntry> {
let app_matches: Vec<&FileEntry> = entries
.iter()
.filter(|entry| matches!(entry.entry_type, EntryType::Application))
.filter(|entry| self.matcher.fuzzy_match(&entry.name, query).is_some())
.collect();
if app_matches.len() == 1 {
Some(app_matches[0])
} else {
None
}
}
pub fn find_exact_application_match<'a>(&self, entries: &'a [FileEntry], query: &str) -> Option<&'a FileEntry> {
let exact_matches: Vec<&FileEntry> = entries
.iter()
.filter(|entry| matches!(entry.entry_type, EntryType::Application))
.filter(|entry| self.is_exact_match(entry, query))
.collect();
if exact_matches.len() == 1 {
Some(exact_matches[0])
} else {
None
}
}
}