reovim_driver_command/registry.rs
1//! Command handler registry for `ServiceRegistry`.
2//!
3//! This module provides a command handler store that can be stored in
4//! `ServiceRegistry` for module self-registration during `init()`.
5//!
6//! # Architecture
7//!
8//! Following the Epic #417 pattern:
9//! - **Mechanism (driver)**: This registry type
10//! - **Policy (modules)**: Register their handlers during `init()`
11//!
12//! # Example
13//!
14//! ```ignore
15//! // In module init():
16//! let store = ctx.services.get_or_create::<CommandHandlerStore>();
17//! for handler in self.command_handlers() {
18//! store.add(handler);
19//! }
20//!
21//! // In runner after all modules initialized:
22//! let store = services.get::<CommandHandlerStore>().unwrap();
23//! for handler in store.handlers() {
24//! command_registry.register(handler);
25//! }
26//! ```
27
28use std::sync::{Arc, RwLock};
29
30use reovim_kernel::api::v1::Service;
31
32use crate::CommandHandler;
33
34/// Store for command handlers registered by modules.
35///
36/// Modules register their handlers during `init()` by calling `add()`.
37/// After all modules are initialized, the runner extracts handlers
38/// via `take_handlers()`.
39///
40/// # Thread Safety
41///
42/// Uses `RwLock` for interior mutability, allowing modules to register
43/// handlers concurrently if needed.
44pub struct CommandHandlerStore {
45 handlers: RwLock<Vec<Arc<dyn CommandHandler>>>,
46}
47
48impl CommandHandlerStore {
49 /// Create a new empty handler store.
50 #[must_use]
51 #[allow(clippy::missing_const_for_fn)] // RwLock::new() is not const
52 pub fn new() -> Self {
53 Self {
54 handlers: RwLock::new(Vec::new()),
55 }
56 }
57
58 /// Add a command handler to the store.
59 ///
60 /// Called by modules during `init()`.
61 ///
62 /// # Panics
63 ///
64 /// Panics if the lock is poisoned.
65 pub fn add(&self, handler: Box<dyn CommandHandler>) {
66 self.handlers
67 .write()
68 .expect("CommandHandlerStore lock poisoned")
69 .push(handler.into());
70 }
71
72 /// Add an Arc-wrapped command handler to the store.
73 ///
74 /// # Panics
75 ///
76 /// Panics if the lock is poisoned.
77 pub fn add_arc(&self, handler: Arc<dyn CommandHandler>) {
78 self.handlers
79 .write()
80 .expect("CommandHandlerStore lock poisoned")
81 .push(handler);
82 }
83
84 /// Take all handlers, clearing the store.
85 ///
86 /// Called by runner after all modules are initialized.
87 ///
88 /// # Panics
89 ///
90 /// Panics if the lock is poisoned.
91 pub fn take_handlers(&self) -> Vec<Arc<dyn CommandHandler>> {
92 std::mem::take(
93 &mut *self
94 .handlers
95 .write()
96 .expect("CommandHandlerStore lock poisoned"),
97 )
98 }
99
100 /// Get the number of registered handlers.
101 ///
102 /// # Panics
103 ///
104 /// Panics if the lock is poisoned.
105 #[must_use]
106 pub fn len(&self) -> usize {
107 self.handlers
108 .read()
109 .expect("CommandHandlerStore lock poisoned")
110 .len()
111 }
112
113 /// Check if the store is empty.
114 ///
115 /// # Panics
116 ///
117 /// Panics if the lock is poisoned.
118 #[must_use]
119 pub fn is_empty(&self) -> bool {
120 self.handlers
121 .read()
122 .expect("CommandHandlerStore lock poisoned")
123 .is_empty()
124 }
125}
126
127impl Default for CommandHandlerStore {
128 fn default() -> Self {
129 Self::new()
130 }
131}
132
133// Implement Service so CommandHandlerStore can be stored in ServiceRegistry
134impl Service for CommandHandlerStore {}
135
136impl std::fmt::Debug for CommandHandlerStore {
137 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
138 f.debug_struct("CommandHandlerStore")
139 .field("count", &self.len())
140 .finish()
141 }
142}
143
144#[cfg(test)]
145#[path = "registry_tests.rs"]
146mod tests;