1use std::sync::Arc;
6
7use {arc_swap::ArcSwap, reovim_core::context_provider::ContextHierarchy};
8
9#[derive(Debug, Clone)]
11pub struct CachedContext {
12 pub buffer_id: usize,
14 pub line: u32,
16 pub col: u32,
18 pub context: Option<ContextHierarchy>,
20}
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub struct LastCursorPosition {
25 pub buffer_id: usize,
26 pub line: u32,
27 pub col: u32,
28}
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub struct LastViewportPosition {
33 pub buffer_id: usize,
34 pub top_line: u32,
35}
36
37pub struct ContextManager {
41 cursor_context: ArcSwap<Option<CachedContext>>,
43 viewport_context: ArcSwap<Option<CachedContext>>,
45 last_cursor: std::sync::RwLock<Option<LastCursorPosition>>,
47 last_viewport: std::sync::RwLock<Option<LastViewportPosition>>,
49 buffer_versions: std::sync::RwLock<std::collections::HashMap<usize, u64>>,
51}
52
53impl Default for ContextManager {
54 fn default() -> Self {
55 Self::new()
56 }
57}
58
59impl ContextManager {
60 #[must_use]
62 pub fn new() -> Self {
63 Self {
64 cursor_context: ArcSwap::new(Arc::new(None)),
65 viewport_context: ArcSwap::new(Arc::new(None)),
66 last_cursor: std::sync::RwLock::new(None),
67 last_viewport: std::sync::RwLock::new(None),
68 buffer_versions: std::sync::RwLock::new(std::collections::HashMap::new()),
69 }
70 }
71
72 #[must_use]
80 pub fn cursor_changed(&self, buffer_id: usize, line: u32, col: u32) -> bool {
81 let last = self.last_cursor.read().unwrap();
82 last.is_none_or(|pos| {
83 pos != LastCursorPosition {
84 buffer_id,
85 line,
86 col,
87 }
88 })
89 }
90
91 pub fn update_cursor(&self, buffer_id: usize, line: u32, col: u32) {
97 let mut last = self.last_cursor.write().unwrap();
98 *last = Some(LastCursorPosition {
99 buffer_id,
100 line,
101 col,
102 });
103 }
104
105 #[must_use]
113 pub fn viewport_changed(&self, buffer_id: usize, top_line: u32) -> bool {
114 let last = self.last_viewport.read().unwrap();
115 last.is_none_or(|pos| {
116 pos != LastViewportPosition {
117 buffer_id,
118 top_line,
119 }
120 })
121 }
122
123 pub fn update_viewport(&self, buffer_id: usize, top_line: u32) {
129 let mut last = self.last_viewport.write().unwrap();
130 *last = Some(LastViewportPosition {
131 buffer_id,
132 top_line,
133 });
134 }
135
136 pub fn set_cursor_context(&self, context: CachedContext) {
138 self.cursor_context.store(Arc::new(Some(context)));
139 }
140
141 #[must_use]
143 pub fn get_cursor_context(&self) -> Arc<Option<CachedContext>> {
144 self.cursor_context.load_full()
145 }
146
147 pub fn set_viewport_context(&self, context: CachedContext) {
149 self.viewport_context.store(Arc::new(Some(context)));
150 }
151
152 #[must_use]
154 pub fn get_viewport_context(&self) -> Arc<Option<CachedContext>> {
155 self.viewport_context.load_full()
156 }
157
158 pub fn invalidate_buffer(&self, buffer_id: usize) {
164 *self
166 .buffer_versions
167 .write()
168 .unwrap()
169 .entry(buffer_id)
170 .or_insert(0) += 1;
171
172 if let Some(ref ctx) = **self.cursor_context.load()
174 && ctx.buffer_id == buffer_id
175 {
176 self.cursor_context.store(Arc::new(None));
177 }
178 if let Some(ref ctx) = **self.viewport_context.load()
179 && ctx.buffer_id == buffer_id
180 {
181 self.viewport_context.store(Arc::new(None));
182 }
183 }
184
185 #[must_use]
191 pub fn buffer_version(&self, buffer_id: usize) -> u64 {
192 self.buffer_versions
193 .read()
194 .unwrap()
195 .get(&buffer_id)
196 .copied()
197 .unwrap_or(0)
198 }
199}
200
201pub type SharedContextManager = Arc<ContextManager>;
203
204#[cfg(test)]
205mod tests {
206 use {super::*, reovim_core::context_provider::ContextItem};
207
208 fn create_test_hierarchy(buffer_id: usize, line: u32, col: u32) -> ContextHierarchy {
209 ContextHierarchy::with_items(
210 buffer_id,
211 line,
212 col,
213 vec![ContextItem {
214 text: "test_function".to_string(),
215 start_line: 0,
216 end_line: 100,
217 kind: "function".to_string(),
218 level: 0,
219 }],
220 )
221 }
222
223 #[test]
224 fn test_cursor_change_detection() {
225 let manager = ContextManager::new();
226
227 assert!(manager.cursor_changed(0, 0, 0));
229 assert!(manager.cursor_changed(1, 0, 0));
230
231 manager.update_cursor(0, 0, 0);
233 assert!(!manager.cursor_changed(0, 0, 0));
234 assert!(manager.cursor_changed(1, 0, 0)); assert!(manager.cursor_changed(0, 1, 0)); assert!(manager.cursor_changed(0, 0, 1)); }
238
239 #[test]
240 fn test_cursor_update() {
241 let manager = ContextManager::new();
242
243 manager.update_cursor(5, 10, 20);
245
246 assert!(!manager.cursor_changed(5, 10, 20));
248 assert!(manager.cursor_changed(5, 11, 20)); }
250
251 #[test]
252 fn test_viewport_change_detection() {
253 let manager = ContextManager::new();
254
255 assert!(manager.viewport_changed(0, 0));
257 assert!(manager.viewport_changed(1, 0));
258
259 manager.update_viewport(0, 0);
261 assert!(!manager.viewport_changed(0, 0));
262 assert!(manager.viewport_changed(1, 0)); assert!(manager.viewport_changed(0, 1)); }
265
266 #[test]
267 fn test_viewport_update() {
268 let manager = ContextManager::new();
269
270 manager.update_viewport(3, 50);
272
273 assert!(!manager.viewport_changed(3, 50));
275 assert!(manager.viewport_changed(3, 51)); }
277
278 #[test]
279 fn test_cursor_context_caching() {
280 let manager = ContextManager::new();
281
282 assert!((*manager.get_cursor_context()).is_none());
284
285 let hierarchy = create_test_hierarchy(1, 5, 0);
287 manager.set_cursor_context(CachedContext {
288 buffer_id: 1,
289 line: 5,
290 col: 0,
291 context: Some(hierarchy),
292 });
293
294 let cached = manager.get_cursor_context();
296 assert!((*cached).is_some());
297 let ctx = cached.as_ref().as_ref().unwrap();
298 assert_eq!(ctx.buffer_id, 1);
299 assert_eq!(ctx.line, 5);
300 }
301
302 #[test]
303 fn test_viewport_context_caching() {
304 let manager = ContextManager::new();
305
306 assert!((*manager.get_viewport_context()).is_none());
308
309 let hierarchy = create_test_hierarchy(2, 100, 0);
311 manager.set_viewport_context(CachedContext {
312 buffer_id: 2,
313 line: 100,
314 col: 0,
315 context: Some(hierarchy),
316 });
317
318 let cached = manager.get_viewport_context();
320 assert!((*cached).is_some());
321 let ctx = cached.as_ref().as_ref().unwrap();
322 assert_eq!(ctx.buffer_id, 2);
323 assert_eq!(ctx.line, 100);
324 }
325
326 #[test]
327 fn test_buffer_invalidation() {
328 let manager = ContextManager::new();
329
330 manager.set_cursor_context(CachedContext {
332 buffer_id: 1,
333 line: 5,
334 col: 0,
335 context: Some(create_test_hierarchy(1, 5, 0)),
336 });
337 manager.set_viewport_context(CachedContext {
338 buffer_id: 1,
339 line: 0,
340 col: 0,
341 context: Some(create_test_hierarchy(1, 0, 0)),
342 });
343
344 assert!((*manager.get_cursor_context()).is_some());
346 assert!((*manager.get_viewport_context()).is_some());
347
348 manager.invalidate_buffer(1);
350
351 assert!((*manager.get_cursor_context()).is_none());
353 assert!((*manager.get_viewport_context()).is_none());
354 }
355
356 #[test]
357 fn test_buffer_invalidation_different_buffer() {
358 let manager = ContextManager::new();
359
360 manager.set_cursor_context(CachedContext {
362 buffer_id: 1,
363 line: 5,
364 col: 0,
365 context: Some(create_test_hierarchy(1, 5, 0)),
366 });
367
368 manager.invalidate_buffer(2);
370
371 assert!((*manager.get_cursor_context()).is_some());
373 }
374
375 #[test]
376 fn test_buffer_version() {
377 let manager = ContextManager::new();
378
379 assert_eq!(manager.buffer_version(1), 0);
381
382 manager.invalidate_buffer(1);
384 assert_eq!(manager.buffer_version(1), 1);
385
386 manager.invalidate_buffer(1);
387 assert_eq!(manager.buffer_version(1), 2);
388
389 assert_eq!(manager.buffer_version(2), 0);
391 }
392}