reovim_plugin_completion/
state.rs1use std::sync::{
7 Arc, RwLock,
8 atomic::{AtomicU64, Ordering},
9};
10
11use crate::{
12 CompletionCache, CompletionRequest, CompletionSaturatorHandle, SourceRegistry,
13 cache::CompletionSnapshot, registry::SourceSupport, source::BufferWordsSource,
14};
15
16pub const AUTO_POPUP_DELAY_MS: u64 = 300;
18
19pub struct SharedCompletionManager {
24 pub registry: RwLock<SourceRegistry>,
26 pub cache: Arc<CompletionCache>,
28 pub saturator: RwLock<Option<CompletionSaturatorHandle>>,
30 pub debounce_generation: AtomicU64,
33 pub inner_event_tx: RwLock<Option<tokio::sync::mpsc::Sender<reovim_core::event::RuntimeEvent>>>,
35}
36
37impl SharedCompletionManager {
38 #[must_use]
40 pub fn new() -> Self {
41 let mut registry = SourceRegistry::new();
42 registry.register(Arc::new(BufferWordsSource::new()));
44
45 Self {
46 registry: RwLock::new(registry),
47 cache: Arc::new(CompletionCache::new()),
48 saturator: RwLock::new(None),
49 debounce_generation: AtomicU64::new(0),
50 inner_event_tx: RwLock::new(None),
51 }
52 }
53
54 pub fn set_inner_event_tx(
56 &self,
57 tx: tokio::sync::mpsc::Sender<reovim_core::event::RuntimeEvent>,
58 ) {
59 *self.inner_event_tx.write().unwrap() = Some(tx);
60 }
61
62 pub fn next_debounce_generation(&self) -> u64 {
67 self.debounce_generation.fetch_add(1, Ordering::SeqCst) + 1
68 }
69
70 pub fn current_debounce_generation(&self) -> u64 {
72 self.debounce_generation.load(Ordering::SeqCst)
73 }
74
75 pub fn request_render(&self) {
77 if let Some(tx) = self.inner_event_tx.read().unwrap().as_ref() {
78 let _ = tx.try_send(reovim_core::event::RuntimeEvent::render_signal());
79 }
80 }
81
82 pub fn send_command_event(&self, event: reovim_core::event::CommandEvent) {
87 if let Some(tx) = self.inner_event_tx.read().unwrap().as_ref() {
88 let _ = tx.try_send(reovim_core::event::RuntimeEvent::command(event));
89 }
90 }
91
92 pub fn register_source(&self, source: Arc<dyn SourceSupport>) {
94 self.registry.write().unwrap().register(source);
95 }
96
97 pub fn sources(&self) -> Arc<Vec<Arc<dyn SourceSupport>>> {
99 Arc::new(self.registry.read().unwrap().sources().to_vec())
100 }
101
102 pub fn set_saturator(&self, handle: CompletionSaturatorHandle) {
104 *self.saturator.write().unwrap() = Some(handle);
105 }
106
107 pub fn request_completion(&self, request: CompletionRequest) {
109 if let Some(handle) = self.saturator.read().unwrap().as_ref() {
110 handle.request_completion(request);
111 }
112 }
113
114 pub fn dismiss(&self) {
116 self.cache.dismiss();
117 }
118
119 pub fn select_next(&self) {
121 self.cache.select_next();
122 }
123
124 pub fn select_prev(&self) {
126 self.cache.select_prev();
127 }
128
129 #[must_use]
131 pub fn is_active(&self) -> bool {
132 self.cache.is_active()
133 }
134
135 #[must_use]
137 pub fn snapshot(&self) -> Arc<CompletionSnapshot> {
138 self.cache.load()
139 }
140}
141
142impl Default for SharedCompletionManager {
143 fn default() -> Self {
144 Self::new()
145 }
146}
147
148impl std::fmt::Debug for SharedCompletionManager {
149 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
150 f.debug_struct("SharedCompletionManager")
151 .field("cache", &self.cache)
152 .finish()
153 }
154}
155
156#[cfg(test)]
157mod tests {
158 use {
159 super::*,
160 reovim_core::completion::{CompletionContext, CompletionItem},
161 std::{future::Future, pin::Pin},
162 };
163
164 struct TestSource {
166 id: &'static str,
167 priority: u32,
168 }
169
170 impl TestSource {
171 fn new(id: &'static str) -> Self {
172 Self { id, priority: 100 }
173 }
174
175 fn with_priority(mut self, priority: u32) -> Self {
176 self.priority = priority;
177 self
178 }
179 }
180
181 impl SourceSupport for TestSource {
182 fn source_id(&self) -> &'static str {
183 self.id
184 }
185
186 fn priority(&self) -> u32 {
187 self.priority
188 }
189
190 fn complete<'a>(
191 &'a self,
192 _ctx: &'a CompletionContext,
193 _content: &'a str,
194 ) -> Pin<Box<dyn Future<Output = Vec<CompletionItem>> + Send + 'a>> {
195 Box::pin(async move { vec![CompletionItem::new("test", self.id)] })
196 }
197 }
198
199 #[test]
200 fn test_shared_manager_new() {
201 let manager = SharedCompletionManager::new();
202
203 let sources = manager.sources();
205 assert!(!sources.is_empty());
206
207 assert!(!manager.is_active());
209 }
210
211 #[test]
212 fn test_shared_manager_default() {
213 let manager = SharedCompletionManager::default();
214 assert!(!manager.is_active());
215 }
216
217 #[test]
218 fn test_shared_manager_register_source() {
219 let manager = SharedCompletionManager::new();
220 let initial_count = manager.sources().len();
221
222 manager.register_source(Arc::new(TestSource::new("test_source")));
223
224 let sources = manager.sources();
225 assert_eq!(sources.len(), initial_count + 1);
226 }
227
228 #[test]
229 fn test_shared_manager_dismiss() {
230 let manager = SharedCompletionManager::new();
231
232 manager.cache.store(CompletionSnapshot::new(
234 vec![CompletionItem::new("test", "test")],
235 "t".to_string(),
236 0,
237 0,
238 1,
239 0,
240 ));
241 assert!(manager.is_active());
242
243 manager.dismiss();
245 assert!(!manager.is_active());
246 }
247
248 #[test]
249 fn test_shared_manager_navigation() {
250 let manager = SharedCompletionManager::new();
251
252 manager.cache.store(CompletionSnapshot::new(
254 vec![
255 CompletionItem::new("a", "test"),
256 CompletionItem::new("b", "test"),
257 CompletionItem::new("c", "test"),
258 ],
259 "".to_string(),
260 0,
261 0,
262 0,
263 0,
264 ));
265
266 assert_eq!(manager.snapshot().selected_index, 0);
268
269 manager.select_next();
271 assert_eq!(manager.snapshot().selected_index, 1);
272
273 manager.select_next();
275 assert_eq!(manager.snapshot().selected_index, 2);
276
277 manager.select_next();
279 assert_eq!(manager.snapshot().selected_index, 0);
280
281 manager.select_prev();
283 assert_eq!(manager.snapshot().selected_index, 2);
284 }
285
286 #[test]
287 fn test_shared_manager_snapshot() {
288 let manager = SharedCompletionManager::new();
289
290 let items = vec![
291 CompletionItem::new("foo", "test"),
292 CompletionItem::new("bar", "test"),
293 ];
294 manager
295 .cache
296 .store(CompletionSnapshot::new(items, "prefix".to_string(), 42, 10, 15, 12));
297
298 let snapshot = manager.snapshot();
299 assert!(snapshot.active);
300 assert_eq!(snapshot.items.len(), 2);
301 assert_eq!(snapshot.prefix, "prefix");
302 assert_eq!(snapshot.buffer_id, 42);
303 assert_eq!(snapshot.cursor_row, 10);
304 assert_eq!(snapshot.cursor_col, 15);
305 assert_eq!(snapshot.word_start_col, 12);
306 }
307
308 #[test]
309 fn test_shared_manager_sources_priority_order() {
310 let manager = SharedCompletionManager::new();
311
312 manager.register_source(Arc::new(TestSource::new("low").with_priority(200)));
314 manager.register_source(Arc::new(TestSource::new("high").with_priority(10)));
315
316 let sources = manager.sources();
317 let ids: Vec<_> = sources.iter().map(|s| s.source_id()).collect();
319 let high_pos = ids.iter().position(|id| *id == "high").unwrap();
320 let low_pos = ids.iter().position(|id| *id == "low").unwrap();
321 assert!(high_pos < low_pos);
322 }
323
324 #[test]
325 fn test_shared_manager_debug_format() {
326 let manager = SharedCompletionManager::new();
327 let debug_str = format!("{:?}", manager);
328 assert!(debug_str.contains("SharedCompletionManager"));
329 assert!(debug_str.contains("cache"));
330 }
331
332 #[test]
333 fn test_shared_manager_request_without_saturator() {
334 let manager = SharedCompletionManager::new();
335
336 let request = CompletionRequest {
338 buffer_id: 1,
339 file_path: None,
340 content: "test".to_string(),
341 cursor_row: 0,
342 cursor_col: 0,
343 line: String::new(),
344 prefix: String::new(),
345 word_start_col: 0,
346 trigger_char: None,
347 };
348 manager.request_completion(request); }
350
351 #[test]
352 fn test_shared_manager_concurrent_source_registration() {
353 use std::thread;
354
355 let manager = Arc::new(SharedCompletionManager::new());
356
357 let handles: Vec<_> = (0..10)
358 .map(|i| {
359 let manager = Arc::clone(&manager);
360 thread::spawn(move || {
361 struct DynamicSource {
363 id: String,
364 }
365
366 impl SourceSupport for DynamicSource {
367 fn source_id(&self) -> &'static str {
368 Box::leak(self.id.clone().into_boxed_str())
371 }
372
373 fn priority(&self) -> u32 {
374 100
375 }
376
377 fn complete<'a>(
378 &'a self,
379 _ctx: &'a CompletionContext,
380 _content: &'a str,
381 ) -> Pin<Box<dyn Future<Output = Vec<CompletionItem>> + Send + 'a>>
382 {
383 Box::pin(async move { vec![] })
384 }
385 }
386
387 manager.register_source(Arc::new(DynamicSource {
388 id: format!("source_{}", i),
389 }));
390 })
391 })
392 .collect();
393
394 for handle in handles {
395 handle.join().expect("thread panicked");
396 }
397
398 let sources = manager.sources();
400 assert_eq!(sources.len(), 11);
401 }
402
403 #[test]
404 fn test_shared_manager_concurrent_cache_access() {
405 use std::{
406 sync::atomic::{AtomicBool, Ordering},
407 thread,
408 };
409
410 let manager = Arc::new(SharedCompletionManager::new());
411 let running = Arc::new(AtomicBool::new(true));
412
413 manager.cache.store(CompletionSnapshot::new(
415 vec![
416 CompletionItem::new("a", "test"),
417 CompletionItem::new("b", "test"),
418 CompletionItem::new("c", "test"),
419 ],
420 "".to_string(),
421 0,
422 0,
423 0,
424 0,
425 ));
426
427 let reader_handles: Vec<_> = (0..5)
429 .map(|_| {
430 let manager = Arc::clone(&manager);
431 let running = Arc::clone(&running);
432 thread::spawn(move || {
433 while running.load(Ordering::SeqCst) {
434 let _snapshot = manager.snapshot();
435 let _active = manager.is_active();
436 }
437 })
438 })
439 .collect();
440
441 let nav_handles: Vec<_> = (0..3)
443 .map(|_| {
444 let manager = Arc::clone(&manager);
445 let running = Arc::clone(&running);
446 thread::spawn(move || {
447 while running.load(Ordering::SeqCst) {
448 manager.select_next();
449 manager.select_prev();
450 }
451 })
452 })
453 .collect();
454
455 thread::sleep(std::time::Duration::from_millis(50));
457
458 running.store(false, Ordering::SeqCst);
460
461 for handle in reader_handles {
462 handle.join().expect("reader thread panicked");
463 }
464 for handle in nav_handles {
465 handle.join().expect("nav thread panicked");
466 }
467
468 let snapshot = manager.snapshot();
470 assert!(snapshot.selected_index < 3);
471 }
472
473 #[test]
474 fn test_shared_manager_concurrent_dismiss() {
475 use std::thread;
476
477 let manager = Arc::new(SharedCompletionManager::new());
478
479 manager.cache.store(CompletionSnapshot::new(
481 vec![CompletionItem::new("test", "test")],
482 "".to_string(),
483 0,
484 0,
485 0,
486 0,
487 ));
488
489 let handles: Vec<_> = (0..10)
490 .map(|_| {
491 let manager = Arc::clone(&manager);
492 thread::spawn(move || {
493 for _ in 0..100 {
494 if manager.is_active() {
495 manager.dismiss();
496 }
497 manager.cache.store(CompletionSnapshot::new(
499 vec![CompletionItem::new("test", "test")],
500 "".to_string(),
501 0,
502 0,
503 0,
504 0,
505 ));
506 }
507 })
508 })
509 .collect();
510
511 for handle in handles {
512 handle.join().expect("thread panicked");
513 }
514
515 }
517
518 #[test]
519 fn test_shared_manager_sources_thread_safe() {
520 use std::thread;
521
522 let manager = Arc::new(SharedCompletionManager::new());
523
524 let handles: Vec<_> = (0..10)
525 .map(|_| {
526 let manager = Arc::clone(&manager);
527 thread::spawn(move || {
528 for _ in 0..100 {
529 let _sources = manager.sources();
530 }
531 })
532 })
533 .collect();
534
535 for handle in handles {
536 handle.join().expect("thread panicked");
537 }
538 }
539
540 #[test]
541 fn test_shared_manager_empty_after_dismiss() {
542 let manager = SharedCompletionManager::new();
543
544 manager.cache.store(CompletionSnapshot::new(
546 vec![
547 CompletionItem::new("a", "test"),
548 CompletionItem::new("b", "test"),
549 ],
550 "prefix".to_string(),
551 42,
552 10,
553 15,
554 12,
555 ));
556
557 assert!(manager.is_active());
558 assert_eq!(manager.snapshot().items.len(), 2);
559
560 manager.dismiss();
561
562 assert!(!manager.is_active());
563 assert!(manager.snapshot().items.is_empty());
564 }
565
566 #[test]
567 fn test_shared_manager_navigation_wraps() {
568 let manager = SharedCompletionManager::new();
569
570 manager.cache.store(CompletionSnapshot::new(
571 vec![
572 CompletionItem::new("first", "test"),
573 CompletionItem::new("second", "test"),
574 ],
575 "".to_string(),
576 0,
577 0,
578 0,
579 0,
580 ));
581
582 assert_eq!(manager.snapshot().selected_index, 0);
584
585 manager.select_next();
587 assert_eq!(manager.snapshot().selected_index, 1);
588
589 manager.select_next();
591 assert_eq!(manager.snapshot().selected_index, 0);
592
593 manager.select_prev();
595 assert_eq!(manager.snapshot().selected_index, 1);
596 }
597}