Skip to main content

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;