steer_tui/tui/state/
file_cache.rs

1use fuzzy_matcher::{FuzzyMatcher, skim::SkimMatcherV2};
2use std::sync::Arc;
3use tokio::sync::RwLock;
4
5/// Cache for workspace files to enable fast fuzzy searching
6#[derive(Clone, Debug)]
7pub struct FileCache {
8    /// Cached file paths from the workspace
9    files: Arc<RwLock<Vec<String>>>,
10    /// Session ID this cache belongs to
11    session_id: String,
12}
13
14impl FileCache {
15    /// Create a new empty file cache
16    pub fn new(session_id: String) -> Self {
17        Self {
18            files: Arc::new(RwLock::new(Vec::new())),
19            session_id,
20        }
21    }
22
23    /// Update the cache with new file paths
24    pub async fn update(&self, files: Vec<String>) {
25        let mut cache = self.files.write().await;
26        *cache = files;
27    }
28
29    /// Clear the cache
30    pub async fn clear(&self) {
31        let mut cache = self.files.write().await;
32        cache.clear();
33    }
34
35    /// Check if the cache is empty
36    pub async fn is_empty(&self) -> bool {
37        let cache = self.files.read().await;
38        cache.is_empty()
39    }
40
41    /// Get the number of files in the cache
42    pub async fn len(&self) -> usize {
43        let cache = self.files.read().await;
44        cache.len()
45    }
46
47    /// Search files with fuzzy matching
48    pub async fn fuzzy_search(&self, query: &str, max_results: Option<usize>) -> Vec<String> {
49        if query.is_empty() {
50            // If no query, return all files up to limit
51            let cache = self.files.read().await;
52            let max = max_results.unwrap_or(cache.len());
53            return cache.iter().take(max).cloned().collect();
54        }
55
56        let cache = self.files.read().await;
57        let matcher = SkimMatcherV2::default();
58
59        let mut scored_files: Vec<(i64, String)> = cache
60            .iter()
61            .filter_map(|file| {
62                matcher
63                    .fuzzy_match(file, query)
64                    .map(|score| (score, file.clone()))
65            })
66            .collect();
67
68        // Sort by score (highest first)
69        scored_files.sort_by(|a, b| b.0.cmp(&a.0));
70
71        // Apply limit if specified
72
73        if let Some(max) = max_results {
74            scored_files
75                .into_iter()
76                .take(max)
77                .map(|(_, file)| file)
78                .collect()
79        } else {
80            scored_files.into_iter().map(|(_, file)| file).collect()
81        }
82    }
83
84    /// Get the session ID
85    pub fn session_id(&self) -> &str {
86        &self.session_id
87    }
88}