vtcode_core/tools/
grep_cache.rs1use super::grep_file::{GrepSearchInput, GrepSearchResult};
9use crate::cache::{CacheKey, DEFAULT_CACHE_TTL, EvictionPolicy, UnifiedCache, estimate_json_size};
10use std::sync::Arc;
11
12#[derive(Debug, Clone, Hash, PartialEq, Eq)]
14struct SearchCacheKey {
15 pattern: String,
16 path: String,
17 case_sensitive: bool,
18 max_results: usize,
19 glob_pattern: Option<String>,
20 type_pattern: Option<String>,
21 max_result_bytes: Option<usize>,
22 respect_ignore_files: bool,
23 search_hidden: bool,
24 search_binary: bool,
25 literal: bool,
26}
27
28impl CacheKey for SearchCacheKey {
29 fn to_cache_key(&self) -> String {
30 format!(
31 "grep:{}:{}:{}:{}",
32 self.pattern, self.path, self.case_sensitive, self.max_results
33 )
34 }
35}
36
37impl From<&GrepSearchInput> for SearchCacheKey {
38 fn from(input: &GrepSearchInput) -> Self {
39 Self {
40 pattern: input.pattern.clone(),
41 path: input.path.clone(),
42 case_sensitive: input.case_sensitive.unwrap_or(false),
43 max_results: input.max_results.unwrap_or(5), glob_pattern: input.glob_pattern.clone(),
45 type_pattern: input.type_pattern.clone(),
46 max_result_bytes: input.max_result_bytes,
47 respect_ignore_files: input.respect_ignore_files.unwrap_or(true),
48 search_hidden: input.search_hidden.unwrap_or(false),
49 search_binary: input.search_binary.unwrap_or(false),
50 literal: input.literal.unwrap_or(false),
51 }
52 }
53}
54
55pub struct GrepSearchCache {
57 cache: UnifiedCache<SearchCacheKey, GrepSearchResult>,
58}
59
60impl GrepSearchCache {
61 pub fn new(capacity: usize) -> Self {
63 Self {
64 cache: UnifiedCache::new(capacity, DEFAULT_CACHE_TTL, EvictionPolicy::Lru),
65 }
66 }
67
68 pub fn get(&self, input: &GrepSearchInput) -> Option<Arc<GrepSearchResult>> {
70 let key = SearchCacheKey::from(input);
71 self.cache.get(&key)
72 }
73
74 pub fn put(&self, input: &GrepSearchInput, result: GrepSearchResult) {
76 let key = SearchCacheKey::from(input);
77 let size_bytes = size_of::<GrepSearchResult>() as u64
78 + result.query.len() as u64
79 + result.matches.iter().map(estimate_json_size).sum::<u64>();
80 self.cache.insert(key, result, size_bytes);
81 }
82
83 pub fn should_cache(result: &GrepSearchResult) -> bool {
85 !result.matches.is_empty()
86 }
87
88 pub fn clear(&self) {
90 self.cache.clear();
91 }
92
93 pub fn stats(&self) -> (usize, usize) {
95 let stats = self.cache.stats();
96 (stats.current_size, stats.max_size)
97 }
98}
99
100impl Default for GrepSearchCache {
101 fn default() -> Self {
102 Self::new(100) }
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109
110 fn make_test_input(pattern: &str, path: &str) -> GrepSearchInput {
111 GrepSearchInput {
112 pattern: pattern.to_string(),
113 path: path.to_string(),
114 case_sensitive: Some(true),
115 literal: None,
116 glob_pattern: Some("*.rs".to_string()),
117 context_lines: None,
118 include_hidden: None,
119 max_results: Some(5), respect_ignore_files: None,
121 max_file_size: None,
122 search_hidden: None,
123 search_binary: None,
124 files_with_matches: None,
125 type_pattern: None,
126 invert_match: None,
127 word_boundaries: None,
128 line_number: None,
129 column: None,
130 only_matching: None,
131 trim: None,
132 max_result_bytes: None,
133 timeout: None,
134 extra_ignore_globs: None,
135 }
136 }
137
138 #[test]
139 fn test_cache_key_equality() {
140 let input1 = make_test_input("test", "/path");
141 let input2 = make_test_input("test", "/path");
142
143 let key1 = SearchCacheKey::from(&input1);
144 let key2 = SearchCacheKey::from(&input2);
145 assert_eq!(key1, key2);
146 }
147
148 #[test]
149 fn test_cache_operations() {
150 let cache = GrepSearchCache::new(10);
151
152 let input = make_test_input("test", "/path");
153
154 let result = GrepSearchResult {
155 query: "test".to_string(),
156 matches: vec![serde_json::json!({"file": "test.rs", "line": 1})],
157 truncated: false,
158 total_matches: None,
159 };
160
161 assert!(cache.get(&input).is_none());
163
164 cache.put(&input, result.clone());
166
167 let cached = cache.get(&input).unwrap();
169 assert_eq!(cached.query, result.query);
170 assert_eq!(cached.matches.len(), result.matches.len());
171 assert_eq!(cached.truncated, result.truncated);
172 }
173}