use crate::worktree::WorktreeInfo;
use nucleo_matcher::{
pattern::{CaseMatching, Normalization, Pattern},
Config as NucleoConfig, Matcher, Utf32Str,
};
pub fn fuzzy_match_indices(query: &str, worktrees: &[WorktreeInfo]) -> Vec<usize> {
if query.is_empty() {
return (0..worktrees.len()).collect();
}
let pattern = Pattern::parse(query, CaseMatching::Smart, Normalization::Smart);
let mut matcher = Matcher::new(NucleoConfig::DEFAULT);
let mut buf: Vec<char> = Vec::new();
let mut scored: Vec<(u32, usize)> = Vec::with_capacity(worktrees.len());
for (i, w) in worktrees.iter().enumerate() {
let hay = Utf32Str::new(&w.name, &mut buf);
if let Some(score) = pattern.score(hay, &mut matcher) {
scored.push((score, i));
}
}
scored.sort_by(|a, b| b.0.cmp(&a.0).then(a.1.cmp(&b.1)));
scored.into_iter().map(|(_, i)| i).collect()
}
#[derive(Debug, Default)]
pub struct FilterState {
pub active: bool,
query: String,
cached_indices: Option<Vec<usize>>,
cache_worktrees_len: usize,
}
impl FilterState {
pub fn new() -> Self {
Self::default()
}
pub fn query(&self) -> &str {
&self.query
}
pub fn push_char(&mut self, c: char) {
self.query.push(c);
self.cached_indices = None;
}
pub fn pop_char(&mut self) {
if self.query.pop().is_some() {
self.cached_indices = None;
}
}
pub fn set_query(&mut self, q: String) {
self.query = q;
self.cached_indices = None;
}
pub fn clear(&mut self) {
self.query.clear();
self.active = false;
self.cached_indices = None;
}
pub fn open(&mut self) {
self.active = true;
}
pub fn close_keep(&mut self) {
self.active = false;
}
pub fn close_cancel(&mut self) {
self.clear();
}
pub fn invalidate(&mut self) {
self.cached_indices = None;
}
pub fn filtered_indices<F>(&mut self, worktrees: &[WorktreeInfo], compute: F) -> &[usize]
where
F: FnOnce(&str, &[WorktreeInfo]) -> Vec<usize>,
{
let len_changed = self.cache_worktrees_len != worktrees.len();
let stale = self.cached_indices.is_none() || len_changed;
if stale {
let fresh = compute(&self.query, worktrees);
self.cached_indices = Some(fresh);
self.cache_worktrees_len = worktrees.len();
}
self
.cached_indices
.as_deref()
.expect("cached_indices populated above on cold/stale cache")
}
pub fn snapshot_indices<F>(&self, worktrees: &[WorktreeInfo], compute: F) -> Vec<usize>
where
F: FnOnce(&str, &[WorktreeInfo]) -> Vec<usize>,
{
let len_changed = self.cache_worktrees_len != worktrees.len();
match self.cached_indices.as_deref() {
Some(cached) if !len_changed => cached.to_vec(),
_ => compute(&self.query, worktrees),
}
}
}