reovim_kernel/api/module/mod.rs
1//! Module identity, traits, and registration types.
2//!
3//! Linux equivalent: `include/linux/module.h`
4//!
5//! This module provides the core module mechanism for the kernel. Modules are
6//! loadable components that can extend the editor's functionality. The kernel
7//! defines the `Module` trait (mechanism); the runner implements loading (policy).
8//!
9//! # Linux Kernel Patterns
10//!
11//! The design follows Linux kernel patterns:
12//! - `Module` trait ≈ `struct module` + `module_init`/`module_exit`
13//! - `ProbeResult` ≈ `probe()` return with `-EPROBE_DEFER` support
14//! - `RegistrationFlags` ≈ module flags and capabilities
15//! - Registration types ≈ `struct platform_driver`, `struct notifier_block`
16
17mod error;
18mod flags;
19mod id;
20mod probe;
21mod registration;
22mod state;
23
24pub use {
25 error::{ModuleError, ProbeResult},
26 flags::RegistrationFlags,
27 id::ModuleId,
28 probe::ModuleProbe,
29 registration::{CommandRegistration, EventHandlerRegistration, KeybindingRegistration},
30 state::{ModuleInfo, ModuleState},
31};
32
33use super::{
34 context::ModuleContext,
35 version::{API_VERSION, Version},
36};
37
38// Import BufferId for on_buffer_focus hook
39use crate::mm::BufferId;
40
41/// Trait for all loadable modules.
42///
43/// Linux equivalent: `struct module` with `module_init`/`module_exit`.
44///
45/// Modules encapsulate functionality that can be loaded dynamically at runtime.
46/// The kernel defines this trait (mechanism); the runner implements loading (policy).
47///
48/// # Lifecycle
49///
50/// 1. Module is loaded (by runner's module loader)
51/// 2. `init()` is called with `ModuleContext`
52/// - Returns `ProbeResult::Success` → module is ready
53/// - Returns `ProbeResult::Defer` → retry later
54/// - Returns `ProbeResult::Failed` → permanent failure
55/// 3. Module is now `Running`
56/// 4. `exit()` is called before unload
57///
58/// # Thread Safety
59///
60/// Modules must be `Send + Sync` as they may be accessed from multiple threads.
61/// The threading model is:
62///
63/// - **Exclusive access methods** (`&mut self`): `init()` and `exit()` are guaranteed
64/// to be called with exclusive access. The runner/kernel ensures no concurrent calls.
65/// - **Shared access methods** (`&self`): `id()`, `name()`, `version()`, `commands()`,
66/// `keybindings()`, `event_handlers()`, etc. may be called concurrently from multiple
67/// threads. Implementations should return const data or use internal synchronization.
68/// - **Hot reload methods**: `save_state()` (`&self`) and `restore_state()` (`&mut self`)
69/// follow the same exclusive/shared access patterns.
70///
71/// # Example
72///
73/// ```ignore
74/// use reovim_kernel::api::v1::*;
75///
76/// pub struct MyModule;
77///
78/// impl Module for MyModule {
79/// fn id(&self) -> ModuleId { ModuleId::new("my-module") }
80/// fn name(&self) -> &'static str { "My Module" }
81/// fn version(&self) -> Version { Version::new(1, 0, 0) }
82///
83/// fn init(&mut self, _ctx: &ModuleContext) -> ProbeResult {
84/// pr_info!("MyModule initialized");
85/// ProbeResult::Success
86/// }
87///
88/// fn exit(&mut self) -> Result<(), ModuleError> {
89/// pr_info!("MyModule exiting");
90/// Ok(())
91/// }
92/// }
93/// ```
94pub trait Module: Send + Sync + 'static {
95 // ========================================================================
96 // Identity
97 // ========================================================================
98
99 /// Unique module identifier.
100 ///
101 /// Convention: kebab-case, e.g., "lang-rust", "feat-completion"
102 fn id(&self) -> ModuleId;
103
104 /// Human-readable name.
105 fn name(&self) -> &'static str;
106
107 /// Module version.
108 fn version(&self) -> Version;
109
110 /// Required kernel API version.
111 ///
112 /// Defaults to current API version. Override to require specific version.
113 fn api_version(&self) -> Version {
114 API_VERSION
115 }
116
117 // ========================================================================
118 // Dependencies
119 // ========================================================================
120
121 /// Required dependencies (must be loaded before this module).
122 ///
123 /// The runner will ensure all required dependencies are loaded and initialized
124 /// before calling `init()` on this module. If any dependency fails to load,
125 /// this module will not be initialized.
126 fn dependencies(&self) -> Vec<ModuleId> {
127 Vec::new()
128 }
129
130 /// Optional dependencies (load before if available, but not required).
131 ///
132 /// # Semantics
133 ///
134 /// - Optional dependencies are loaded before this module **if they exist**
135 /// - If an optional dependency fails to load, this module can still initialize
136 /// - Unlike `dependencies()`, missing optional dependencies don't cause init failure
137 /// - Useful for feature detection and graceful degradation
138 ///
139 /// # Use Cases
140 ///
141 /// - LSP enhancement: Load LSP plugin if available for better completions
142 /// - Syntax integration: Use treesitter if available, fall back to regex
143 /// - Theme support: Load icon theme if available
144 ///
145 /// # Example
146 ///
147 /// ```ignore
148 /// fn optional_dependencies(&self) -> Vec<ModuleId> {
149 /// vec![
150 /// ModuleId::new("feat-lsp"), // Enhance with LSP if available
151 /// ModuleId::new("feat-treesitter"), // Better syntax if available
152 /// ]
153 /// }
154 /// ```
155 fn optional_dependencies(&self) -> Vec<ModuleId> {
156 Vec::new()
157 }
158
159 /// Extension kinds pushed by this module via `ExtensionStateBridge` (#584).
160 ///
161 /// Returns the extension kind identifiers that this module registers
162 /// bridges for. Used by server startup validation to detect orphaned
163 /// bridges (bridge registered but no module claims to push that kind).
164 ///
165 /// Modules that register bridges in `init()` should override this to
166 /// declare the kinds they push. Define kind constants locally in each
167 /// module crate.
168 ///
169 /// # Kernel Purity
170 ///
171 /// The kernel defines this with `&[&'static str]` return type. Modules
172 /// define their own kind constants locally — no shared crate needed.
173 fn extension_kinds(&self) -> &[&'static str] {
174 &[]
175 }
176
177 /// Capabilities provided by this module (#618).
178 ///
179 /// Returns capability identifiers that this module provides (e.g.,
180 /// `"syntax-highlighting"`, `"undo-provider"`). Used for abstract
181 /// dependency matching: a module that `requires` a capability is
182 /// satisfied by any module that `provides` it.
183 ///
184 /// Use constants from `reovim-capabilities`.
185 ///
186 /// # Kernel Purity
187 ///
188 /// Same as `extension_kinds()`: the kernel defines the method with
189 /// `&[&'static str]` return type. Capability constants live in
190 /// `shared/capabilities/`, not in the kernel.
191 fn provides(&self) -> &[&'static str] {
192 &[]
193 }
194
195 /// Capabilities required by this module (#618).
196 ///
197 /// Returns capability identifiers that this module requires (e.g.,
198 /// `"lsp-provider"`). During dependency resolution, the depgraph
199 /// resolver matches these against `provides()` from other modules
200 /// and adds implicit ordering edges.
201 ///
202 /// If no module provides a required capability, the module may fail
203 /// to initialize or operate in degraded mode.
204 ///
205 /// Use constants from `reovim-capabilities`.
206 fn requires(&self) -> &[&'static str] {
207 &[]
208 }
209
210 /// Version constraints on dependencies (#619).
211 ///
212 /// Returns `(module_id, version_range_string)` pairs declaring minimum
213 /// version requirements on specific dependencies. The version range
214 /// follows Cargo semver syntax:
215 ///
216 /// - `"^1.2.3"` — compatible (same major, `>=1.2.3, <2.0.0`)
217 /// - `"=1.2.3"` — exact match only
218 /// - `">=1.2.3"` — at least this version
219 /// - `"1.2.3"` — shorthand for `"^1.2.3"`
220 ///
221 /// Constraints are checked after dependency resolution but before init.
222 /// Violations are logged and the constrained module may be skipped.
223 ///
224 /// # Kernel Purity
225 ///
226 /// The kernel carries opaque `(ModuleId, &'static str)` pairs.
227 /// The depgraph driver parses and evaluates the version range strings.
228 fn version_constraints(&self) -> Vec<(ModuleId, &'static str)> {
229 Vec::new()
230 }
231
232 // ========================================================================
233 // Lifecycle (Linux: module_init / module_exit)
234 // ========================================================================
235
236 /// Initialize module (Linux equivalent: `probe()` function).
237 ///
238 /// Returns `ProbeResult` to support deferred probing:
239 /// - `Success` - Module is ready
240 /// - `Defer(reason)` - Retry later (like Linux `-EPROBE_DEFER`)
241 /// - `Failed(err)` - Permanent failure
242 fn init(&mut self, ctx: &ModuleContext) -> ProbeResult;
243
244 /// Cleanup module (Linux equivalent: `remove()` function).
245 ///
246 /// # Errors
247 ///
248 /// Returns `ModuleError` if cleanup fails.
249 fn exit(&mut self) -> Result<(), ModuleError>;
250
251 // ========================================================================
252 // Lifecycle Hooks (Epic #417 Part 2)
253 // ========================================================================
254
255 /// Called after ALL modules are loaded (post-init phase).
256 ///
257 /// Use for cross-module discovery via `ServiceRegistry`. At this point,
258 /// all modules have completed their `init()` and registered their services.
259 ///
260 /// # Use Cases
261 ///
262 /// - Query services registered by other modules
263 /// - Set up inter-module communication channels
264 /// - Perform late initialization that depends on other modules
265 ///
266 /// # Example
267 ///
268 /// ```ignore
269 /// fn on_all_loaded(&mut self, ctx: &ModuleContext) {
270 /// // Now safe to query services from other modules
271 /// if let Some(lsp) = ctx.services.get::<dyn LspProvider>() {
272 /// self.lsp_client = Some(lsp);
273 /// }
274 /// }
275 /// ```
276 fn on_all_loaded(&mut self, _ctx: &ModuleContext) {
277 // Default: no-op
278 }
279
280 /// Called when session focuses on a buffer.
281 ///
282 /// Use for lazy initialization of buffer-specific state. This hook is
283 /// called whenever the active buffer changes, allowing modules to:
284 ///
285 /// - Load buffer-specific configuration
286 /// - Initialize syntax highlighting
287 /// - Set up LSP connections for the buffer's language
288 ///
289 /// # Arguments
290 ///
291 /// * `buffer_id` - The newly focused buffer
292 /// * `ctx` - Module context for accessing kernel services
293 ///
294 /// # Example
295 ///
296 /// ```ignore
297 /// fn on_buffer_focus(&mut self, buffer_id: BufferId, ctx: &ModuleContext) {
298 /// // Load undo history for this buffer
299 /// if let Some(undo_provider) = ctx.services.get::<dyn UndoProvider>() {
300 /// undo_provider.load_graceful(buffer_id, &self.buffer_path);
301 /// }
302 /// }
303 /// ```
304 fn on_buffer_focus(&mut self, _buffer_id: BufferId, _ctx: &ModuleContext) {
305 // Default: no-op
306 }
307
308 /// Called before module unload for cleanup.
309 ///
310 /// Unlike `exit()`, this hook is specifically for releasing resources
311 /// registered with the kernel (services, event handlers, etc.).
312 ///
313 /// # Difference from `exit()`
314 ///
315 /// - `exit()`: General cleanup, may fail
316 /// - `on_unload()`: Release kernel resources, should not fail
317 ///
318 /// # Example
319 ///
320 /// ```ignore
321 /// fn on_unload(&mut self) -> Result<(), ModuleError> {
322 /// // Unregister services
323 /// if let Some(registry) = self.service_registry.take() {
324 /// registry.unregister_all();
325 /// }
326 /// Ok(())
327 /// }
328 /// ```
329 ///
330 /// # Errors
331 ///
332 /// Returns `ModuleError` if resource release fails.
333 fn on_unload(&mut self) -> Result<(), ModuleError> {
334 // Default: no-op
335 Ok(())
336 }
337
338 // ========================================================================
339 // Registration (method-based per Clean Arch Proposal)
340 // ========================================================================
341
342 /// Get command registrations.
343 fn commands(&self) -> Vec<CommandRegistration> {
344 Vec::new()
345 }
346
347 /// Get keybinding registrations.
348 fn keybindings(&self) -> Vec<KeybindingRegistration> {
349 Vec::new()
350 }
351
352 /// Get event handler registrations.
353 fn event_handlers(&self) -> Vec<EventHandlerRegistration> {
354 Vec::new()
355 }
356
357 // ========================================================================
358 // Hot Reload Support
359 // ========================================================================
360
361 /// Whether this module supports hot reload.
362 ///
363 /// If true, `save_state()` and `restore_state()` should be implemented.
364 /// Hot reload allows updating module code without restarting the editor.
365 fn supports_hot_reload(&self) -> bool {
366 false
367 }
368
369 /// Save module state for hot reload.
370 ///
371 /// Returns opaque bytes that `restore_state()` can use to restore state.
372 /// Return `None` if no state to save.
373 ///
374 /// # Serialization Format
375 ///
376 /// The binary format is module-specific. Recommended practices:
377 ///
378 /// - **Include a version header** for forward compatibility (e.g., first 4 bytes)
379 /// - **Use a stable serialization format** like `bincode`, `postcard`, or `rmp-serde`
380 /// - **Keep state minimal** - only save what's necessary to restore user experience
381 /// - **Handle missing fields gracefully** in `restore_state()`
382 ///
383 /// # Example
384 ///
385 /// ```ignore
386 /// fn save_state(&self) -> Option<Box<[u8]>> {
387 /// let state = MyModuleState {
388 /// version: 1,
389 /// cursor_history: self.cursor_history.clone(),
390 /// settings: self.settings.clone(),
391 /// };
392 /// bincode::serialize(&state).ok().map(Vec::into_boxed_slice)
393 /// }
394 /// ```
395 fn save_state(&self) -> Option<Box<[u8]>> {
396 None
397 }
398
399 /// Restore module state after hot reload.
400 ///
401 /// Called with bytes from previous `save_state()` call.
402 ///
403 /// # State Version Compatibility
404 ///
405 /// Modules should handle version mismatches gracefully:
406 /// - If the saved state version is **newer** than current, return an error
407 /// - If the saved state version is **older**, migrate or use defaults
408 /// - If deserialization fails, return an error (module will re-initialize)
409 ///
410 /// # Errors
411 ///
412 /// Returns `ModuleError::InitFailed` if:
413 /// - Hot reload is not supported
414 /// - State version is incompatible
415 /// - Deserialization fails
416 ///
417 /// # Example
418 ///
419 /// ```ignore
420 /// fn restore_state(&mut self, state: &[u8]) -> Result<(), ModuleError> {
421 /// let saved: MyModuleState = bincode::deserialize(state)
422 /// .map_err(|e| ModuleError::InitFailed(format!("deserialize: {e}")))?;
423 ///
424 /// if saved.version > CURRENT_VERSION {
425 /// return Err(ModuleError::InitFailed("state version too new".into()));
426 /// }
427 ///
428 /// self.cursor_history = saved.cursor_history;
429 /// self.settings = saved.settings;
430 /// Ok(())
431 /// }
432 /// ```
433 fn restore_state(&mut self, _state: &[u8]) -> Result<(), ModuleError> {
434 Err(ModuleError::InitFailed("hot reload not supported".into()))
435 }
436}
437
438#[cfg(test)]
439#[path = "mod_tests.rs"]
440mod tests;