Skip to main content

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;