1use std::{sync::Arc, time::Instant};
6
7use arc_swap::ArcSwap;
8
9use reovim_core::completion::CompletionItem;
10
11#[derive(Debug, Clone)]
13pub struct CompletionSnapshot {
14 pub items: Vec<CompletionItem>,
16 pub prefix: String,
18 pub buffer_id: usize,
20 pub cursor_row: u32,
22 pub cursor_col: u32,
23 pub word_start_col: u32,
25 pub timestamp: Instant,
27 pub active: bool,
29 pub selected_index: usize,
31}
32
33impl Default for CompletionSnapshot {
34 fn default() -> Self {
35 Self {
36 items: Vec::new(),
37 prefix: String::new(),
38 buffer_id: 0,
39 cursor_row: 0,
40 cursor_col: 0,
41 word_start_col: 0,
42 timestamp: Instant::now(),
43 active: false,
44 selected_index: 0,
45 }
46 }
47}
48
49impl CompletionSnapshot {
50 #[must_use]
52 pub fn new(
53 items: Vec<CompletionItem>,
54 prefix: String,
55 buffer_id: usize,
56 cursor_row: u32,
57 cursor_col: u32,
58 word_start_col: u32,
59 ) -> Self {
60 Self {
61 items,
62 prefix,
63 buffer_id,
64 cursor_row,
65 cursor_col,
66 word_start_col,
67 timestamp: Instant::now(),
68 active: true,
69 selected_index: 0,
70 }
71 }
72
73 #[must_use]
75 pub fn dismissed() -> Self {
76 Self {
77 active: false,
78 ..Self::default()
79 }
80 }
81
82 #[must_use]
84 pub fn selected_item(&self) -> Option<&CompletionItem> {
85 if self.items.is_empty() {
86 None
87 } else {
88 self.items.get(self.selected_index)
89 }
90 }
91
92 #[must_use]
94 pub fn has_items(&self) -> bool {
95 !self.items.is_empty()
96 }
97
98 #[must_use]
100 pub fn item_count(&self) -> usize {
101 self.items.len()
102 }
103}
104
105pub struct CompletionCache {
110 current: ArcSwap<CompletionSnapshot>,
111}
112
113impl CompletionCache {
114 #[must_use]
116 pub fn new() -> Self {
117 Self {
118 current: ArcSwap::from_pointee(CompletionSnapshot::default()),
119 }
120 }
121
122 pub fn store(&self, snapshot: CompletionSnapshot) {
126 self.current.store(Arc::new(snapshot));
127 }
128
129 #[must_use]
133 pub fn load(&self) -> Arc<CompletionSnapshot> {
134 self.current.load_full()
135 }
136
137 pub fn update_selection(&self, new_index: usize) {
141 let current = self.load();
142 if current.active && new_index < current.items.len() {
143 let mut new_snapshot = (*current).clone();
144 new_snapshot.selected_index = new_index;
145 self.store(new_snapshot);
146 }
147 }
148
149 pub fn select_next(&self) {
151 let current = self.load();
152 if current.active && !current.items.is_empty() {
153 let new_index = (current.selected_index + 1) % current.items.len();
154 self.update_selection(new_index);
155 }
156 }
157
158 pub fn select_prev(&self) {
160 let current = self.load();
161 if current.active && !current.items.is_empty() {
162 let new_index = if current.selected_index == 0 {
163 current.items.len() - 1
164 } else {
165 current.selected_index - 1
166 };
167 self.update_selection(new_index);
168 }
169 }
170
171 pub fn dismiss(&self) {
173 self.store(CompletionSnapshot::dismissed());
174 }
175
176 #[must_use]
178 pub fn is_active(&self) -> bool {
179 self.load().active
180 }
181}
182
183impl Default for CompletionCache {
184 fn default() -> Self {
185 Self::new()
186 }
187}
188
189impl std::fmt::Debug for CompletionCache {
190 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
191 let snapshot = self.load();
192 f.debug_struct("CompletionCache")
193 .field("active", &snapshot.active)
194 .field("item_count", &snapshot.items.len())
195 .field("selected_index", &snapshot.selected_index)
196 .finish()
197 }
198}
199
200#[cfg(test)]
201mod tests {
202 use super::*;
203
204 #[test]
205 fn test_cache_store_load() {
206 let cache = CompletionCache::new();
207
208 assert!(!cache.is_active());
210
211 let items = vec![
213 CompletionItem::new("foo", "test"),
214 CompletionItem::new("bar", "test"),
215 ];
216 let snapshot = CompletionSnapshot::new(items, "f".to_string(), 1, 0, 1, 0);
217 cache.store(snapshot);
218
219 assert!(cache.is_active());
221 let loaded = cache.load();
222 assert_eq!(loaded.items.len(), 2);
223 assert_eq!(loaded.prefix, "f");
224 }
225
226 #[test]
227 fn test_selection_navigation() {
228 let cache = CompletionCache::new();
229
230 let items = vec![
231 CompletionItem::new("a", "test"),
232 CompletionItem::new("b", "test"),
233 CompletionItem::new("c", "test"),
234 ];
235 let snapshot = CompletionSnapshot::new(items, "".to_string(), 1, 0, 0, 0);
236 cache.store(snapshot);
237
238 assert_eq!(cache.load().selected_index, 0);
240
241 cache.select_next();
243 assert_eq!(cache.load().selected_index, 1);
244 cache.select_next();
245 assert_eq!(cache.load().selected_index, 2);
246 cache.select_next();
247 assert_eq!(cache.load().selected_index, 0); cache.select_prev();
251 assert_eq!(cache.load().selected_index, 2); }
253
254 #[test]
255 fn test_dismiss() {
256 let cache = CompletionCache::new();
257
258 let items = vec![CompletionItem::new("foo", "test")];
259 let snapshot = CompletionSnapshot::new(items, "f".to_string(), 1, 0, 1, 0);
260 cache.store(snapshot);
261
262 assert!(cache.is_active());
263
264 cache.dismiss();
265
266 assert!(!cache.is_active());
267 assert!(cache.load().items.is_empty());
268 }
269
270 #[test]
271 fn test_snapshot_default() {
272 let snapshot = CompletionSnapshot::default();
273
274 assert!(!snapshot.active);
275 assert!(snapshot.items.is_empty());
276 assert_eq!(snapshot.selected_index, 0);
277 assert_eq!(snapshot.buffer_id, 0);
278 assert!(snapshot.prefix.is_empty());
279 }
280
281 #[test]
282 fn test_snapshot_new_is_active() {
283 let items = vec![CompletionItem::new("test", "source")];
284 let snapshot = CompletionSnapshot::new(items, "te".to_string(), 1, 5, 7, 5);
285
286 assert!(snapshot.active);
287 assert_eq!(snapshot.items.len(), 1);
288 assert_eq!(snapshot.prefix, "te");
289 assert_eq!(snapshot.buffer_id, 1);
290 assert_eq!(snapshot.cursor_row, 5);
291 assert_eq!(snapshot.cursor_col, 7);
292 assert_eq!(snapshot.word_start_col, 5);
293 assert_eq!(snapshot.selected_index, 0);
294 }
295
296 #[test]
297 fn test_snapshot_dismissed() {
298 let dismissed = CompletionSnapshot::dismissed();
299
300 assert!(!dismissed.active);
301 assert!(dismissed.items.is_empty());
302 }
303
304 #[test]
305 fn test_snapshot_selected_item() {
306 let items = vec![
307 CompletionItem::new("first", "test"),
308 CompletionItem::new("second", "test"),
309 CompletionItem::new("third", "test"),
310 ];
311 let mut snapshot = CompletionSnapshot::new(items, "".to_string(), 0, 0, 0, 0);
312
313 let selected = snapshot.selected_item();
315 assert!(selected.is_some());
316 assert_eq!(selected.unwrap().label, "first");
317
318 snapshot.selected_index = 2;
320 let selected = snapshot.selected_item();
321 assert_eq!(selected.unwrap().label, "third");
322 }
323
324 #[test]
325 fn test_snapshot_selected_item_empty() {
326 let snapshot = CompletionSnapshot::default();
327 assert!(snapshot.selected_item().is_none());
328 }
329
330 #[test]
331 fn test_snapshot_has_items() {
332 let empty = CompletionSnapshot::default();
333 assert!(!empty.has_items());
334
335 let with_items = CompletionSnapshot::new(
336 vec![CompletionItem::new("a", "test")],
337 "".to_string(),
338 0,
339 0,
340 0,
341 0,
342 );
343 assert!(with_items.has_items());
344 }
345
346 #[test]
347 fn test_snapshot_item_count() {
348 let snapshot = CompletionSnapshot::new(
349 vec![
350 CompletionItem::new("a", "test"),
351 CompletionItem::new("b", "test"),
352 ],
353 "".to_string(),
354 0,
355 0,
356 0,
357 0,
358 );
359 assert_eq!(snapshot.item_count(), 2);
360 }
361
362 #[test]
363 fn test_update_selection() {
364 let cache = CompletionCache::new();
365 let items = vec![
366 CompletionItem::new("a", "test"),
367 CompletionItem::new("b", "test"),
368 CompletionItem::new("c", "test"),
369 ];
370 cache.store(CompletionSnapshot::new(items, "".to_string(), 0, 0, 0, 0));
371
372 cache.update_selection(2);
373 assert_eq!(cache.load().selected_index, 2);
374
375 cache.update_selection(10);
377 assert_eq!(cache.load().selected_index, 2); }
379
380 #[test]
381 fn test_select_on_empty_cache() {
382 let cache = CompletionCache::new();
383
384 cache.select_next();
386 cache.select_prev();
387
388 assert_eq!(cache.load().selected_index, 0);
389 }
390
391 #[test]
392 fn test_select_on_single_item() {
393 let cache = CompletionCache::new();
394 cache.store(CompletionSnapshot::new(
395 vec![CompletionItem::new("only", "test")],
396 "".to_string(),
397 0,
398 0,
399 0,
400 0,
401 ));
402
403 cache.select_next();
405 assert_eq!(cache.load().selected_index, 0);
406
407 cache.select_prev();
408 assert_eq!(cache.load().selected_index, 0);
409 }
410
411 #[test]
412 fn test_cache_debug_format() {
413 let cache = CompletionCache::new();
414 cache.store(CompletionSnapshot::new(
415 vec![
416 CompletionItem::new("a", "test"),
417 CompletionItem::new("b", "test"),
418 ],
419 "".to_string(),
420 0,
421 0,
422 0,
423 0,
424 ));
425
426 let debug_str = format!("{:?}", cache);
427 assert!(debug_str.contains("CompletionCache"));
428 assert!(debug_str.contains("active"));
429 assert!(debug_str.contains("item_count"));
430 }
431
432 #[test]
433 fn test_cache_default() {
434 let cache = CompletionCache::default();
435 assert!(!cache.is_active());
436 }
437
438 #[test]
439 fn test_concurrent_reads() {
440 use std::{sync::Arc, thread};
441
442 let cache = Arc::new(CompletionCache::new());
443 let items = vec![
444 CompletionItem::new("a", "test"),
445 CompletionItem::new("b", "test"),
446 CompletionItem::new("c", "test"),
447 ];
448 cache.store(CompletionSnapshot::new(items, "".to_string(), 0, 0, 0, 0));
449
450 let handles: Vec<_> = (0..10)
451 .map(|_| {
452 let cache = Arc::clone(&cache);
453 thread::spawn(move || {
454 for _ in 0..100 {
455 let snapshot = cache.load();
456 assert!(snapshot.active);
457 assert_eq!(snapshot.items.len(), 3);
458 }
459 })
460 })
461 .collect();
462
463 for handle in handles {
464 handle.join().expect("thread panicked");
465 }
466 }
467
468 #[test]
469 fn test_concurrent_read_write() {
470 use std::{
471 sync::{
472 Arc,
473 atomic::{AtomicBool, Ordering},
474 },
475 thread,
476 };
477
478 let cache = Arc::new(CompletionCache::new());
479 let running = Arc::new(AtomicBool::new(true));
480
481 let reader_handles: Vec<_> = (0..5)
483 .map(|_| {
484 let cache = Arc::clone(&cache);
485 let running = Arc::clone(&running);
486 thread::spawn(move || {
487 while running.load(Ordering::SeqCst) {
488 let snapshot = cache.load();
489 let _ = snapshot.items.len();
491 let _ = snapshot.active;
492 }
493 })
494 })
495 .collect();
496
497 let writer_handles: Vec<_> = (0..3)
499 .map(|i| {
500 let cache = Arc::clone(&cache);
501 thread::spawn(move || {
502 for j in 0..50 {
503 let items = vec![CompletionItem::new(format!("item_{i}_{j}"), "test")];
504 cache.store(CompletionSnapshot::new(
505 items,
506 format!("prefix_{j}"),
507 i,
508 0,
509 0,
510 0,
511 ));
512 }
513 })
514 })
515 .collect();
516
517 for handle in writer_handles {
519 handle.join().expect("writer thread panicked");
520 }
521
522 running.store(false, Ordering::SeqCst);
524 for handle in reader_handles {
525 handle.join().expect("reader thread panicked");
526 }
527
528 let snapshot = cache.load();
530 assert!(!snapshot.items.is_empty() || !snapshot.active);
531 }
532
533 #[test]
534 fn test_concurrent_selection_update() {
535 use std::{sync::Arc, thread};
536
537 let cache = Arc::new(CompletionCache::new());
538 let items: Vec<_> = (0..100)
539 .map(|i| CompletionItem::new(format!("item_{i}"), "test"))
540 .collect();
541 cache.store(CompletionSnapshot::new(items, "".to_string(), 0, 0, 0, 0));
542
543 let handles: Vec<_> = (0..10)
544 .map(|_| {
545 let cache = Arc::clone(&cache);
546 thread::spawn(move || {
547 for _ in 0..100 {
548 cache.select_next();
549 cache.select_prev();
550 }
551 })
552 })
553 .collect();
554
555 for handle in handles {
556 handle.join().expect("thread panicked");
557 }
558
559 let snapshot = cache.load();
561 assert!(snapshot.selected_index < 100);
562 }
563
564 #[test]
565 fn test_rapid_store_load_cycle() {
566 let cache = CompletionCache::new();
567
568 for i in 0..1000 {
569 let items = vec![CompletionItem::new(format!("item_{i}"), "test")];
570 cache.store(CompletionSnapshot::new(items, format!("{i}"), i, 0, 0, 0));
571
572 let snapshot = cache.load();
573 assert!(snapshot.active);
574 assert_eq!(snapshot.buffer_id, i);
575 }
576 }
577
578 #[test]
579 fn test_selection_bounds_after_new_store() {
580 let cache = CompletionCache::new();
581
582 let items: Vec<_> = (0..10)
584 .map(|i| CompletionItem::new(format!("item_{i}"), "test"))
585 .collect();
586 cache.store(CompletionSnapshot::new(items, "".to_string(), 0, 0, 0, 0));
587 cache.update_selection(5);
588 assert_eq!(cache.load().selected_index, 5);
589
590 let new_items: Vec<_> = (0..3)
592 .map(|i| CompletionItem::new(format!("new_{i}"), "test"))
593 .collect();
594 cache.store(CompletionSnapshot::new(new_items, "".to_string(), 0, 0, 0, 0));
595
596 assert_eq!(cache.load().selected_index, 0);
598 }
599
600 #[test]
601 fn test_dismiss_clears_selection() {
602 let cache = CompletionCache::new();
603 let items = vec![
604 CompletionItem::new("a", "test"),
605 CompletionItem::new("b", "test"),
606 ];
607 cache.store(CompletionSnapshot::new(items, "".to_string(), 0, 0, 0, 0));
608 cache.update_selection(1);
609
610 cache.dismiss();
611
612 let snapshot = cache.load();
613 assert!(!snapshot.active);
614 assert_eq!(snapshot.selected_index, 0);
615 }
616
617 #[test]
618 fn test_multiple_store_overwrites() {
619 let cache = CompletionCache::new();
620
621 cache.store(CompletionSnapshot::new(
623 vec![CompletionItem::new("first", "test")],
624 "f".to_string(),
625 0,
626 0,
627 0,
628 0,
629 ));
630 assert_eq!(cache.load().items[0].label, "first");
631
632 cache.store(CompletionSnapshot::new(
634 vec![CompletionItem::new("second", "test")],
635 "s".to_string(),
636 0,
637 0,
638 0,
639 0,
640 ));
641 assert_eq!(cache.load().items[0].label, "second");
642 assert_eq!(cache.load().prefix, "s");
643 }
644}