pub trait Module:
Send
+ Sync
+ 'static {
Show 21 methods
// Required methods
fn id(&self) -> ModuleId;
fn name(&self) -> &'static str;
fn version(&self) -> Version;
fn init(&mut self, ctx: &ModuleContext) -> ProbeResult;
fn exit(&mut self) -> Result<(), ModuleError>;
// Provided methods
fn api_version(&self) -> Version { ... }
fn dependencies(&self) -> Vec<ModuleId> { ... }
fn optional_dependencies(&self) -> Vec<ModuleId> { ... }
fn extension_kinds(&self) -> &[&'static str] { ... }
fn provides(&self) -> &[&'static str] { ... }
fn requires(&self) -> &[&'static str] { ... }
fn version_constraints(&self) -> Vec<(ModuleId, &'static str)> { ... }
fn on_all_loaded(&mut self, _ctx: &ModuleContext) { ... }
fn on_buffer_focus(&mut self, _buffer_id: BufferId, _ctx: &ModuleContext) { ... }
fn on_unload(&mut self) -> Result<(), ModuleError> { ... }
fn commands(&self) -> Vec<CommandRegistration> { ... }
fn keybindings(&self) -> Vec<KeybindingRegistration> { ... }
fn event_handlers(&self) -> Vec<EventHandlerRegistration> { ... }
fn supports_hot_reload(&self) -> bool { ... }
fn save_state(&self) -> Option<Box<[u8]>> { ... }
fn restore_state(&mut self, _state: &[u8]) -> Result<(), ModuleError> { ... }
}Expand description
Trait for all loadable modules.
Linux equivalent: struct module with module_init/module_exit.
Modules encapsulate functionality that can be loaded dynamically at runtime. The kernel defines this trait (mechanism); the runner implements loading (policy).
§Lifecycle
- Module is loaded (by runner’s module loader)
init()is called withModuleContext- Returns
ProbeResult::Success→ module is ready - Returns
ProbeResult::Defer→ retry later - Returns
ProbeResult::Failed→ permanent failure
- Returns
- Module is now
Running exit()is called before unload
§Thread Safety
Modules must be Send + Sync as they may be accessed from multiple threads.
The threading model is:
- Exclusive access methods (
&mut self):init()andexit()are guaranteed to be called with exclusive access. The runner/kernel ensures no concurrent calls. - Shared access methods (
&self):id(),name(),version(),commands(),keybindings(),event_handlers(), etc. may be called concurrently from multiple threads. Implementations should return const data or use internal synchronization. - Hot reload methods:
save_state()(&self) andrestore_state()(&mut self) follow the same exclusive/shared access patterns.
§Example
use reovim_kernel::api::v1::*;
pub struct MyModule;
impl Module for MyModule {
fn id(&self) -> ModuleId { ModuleId::new("my-module") }
fn name(&self) -> &'static str { "My Module" }
fn version(&self) -> Version { Version::new(1, 0, 0) }
fn init(&mut self, _ctx: &ModuleContext) -> ProbeResult {
pr_info!("MyModule initialized");
ProbeResult::Success
}
fn exit(&mut self) -> Result<(), ModuleError> {
pr_info!("MyModule exiting");
Ok(())
}
}Required Methods§
Sourcefn id(&self) -> ModuleId
fn id(&self) -> ModuleId
Unique module identifier.
Convention: kebab-case, e.g., “lang-rust”, “feat-completion”
Sourcefn init(&mut self, ctx: &ModuleContext) -> ProbeResult
fn init(&mut self, ctx: &ModuleContext) -> ProbeResult
Initialize module (Linux equivalent: probe() function).
Returns ProbeResult to support deferred probing:
Success- Module is readyDefer(reason)- Retry later (like Linux-EPROBE_DEFER)Failed(err)- Permanent failure
Provided Methods§
Sourcefn api_version(&self) -> Version
fn api_version(&self) -> Version
Required kernel API version.
Defaults to current API version. Override to require specific version.
Sourcefn dependencies(&self) -> Vec<ModuleId>
fn dependencies(&self) -> Vec<ModuleId>
Required dependencies (must be loaded before this module).
The runner will ensure all required dependencies are loaded and initialized
before calling init() on this module. If any dependency fails to load,
this module will not be initialized.
Sourcefn optional_dependencies(&self) -> Vec<ModuleId>
fn optional_dependencies(&self) -> Vec<ModuleId>
Optional dependencies (load before if available, but not required).
§Semantics
- Optional dependencies are loaded before this module if they exist
- If an optional dependency fails to load, this module can still initialize
- Unlike
dependencies(), missing optional dependencies don’t cause init failure - Useful for feature detection and graceful degradation
§Use Cases
- LSP enhancement: Load LSP plugin if available for better completions
- Syntax integration: Use treesitter if available, fall back to regex
- Theme support: Load icon theme if available
§Example
fn optional_dependencies(&self) -> Vec<ModuleId> {
vec![
ModuleId::new("feat-lsp"), // Enhance with LSP if available
ModuleId::new("feat-treesitter"), // Better syntax if available
]
}Sourcefn extension_kinds(&self) -> &[&'static str]
fn extension_kinds(&self) -> &[&'static str]
Extension kinds pushed by this module via ExtensionStateBridge (#584).
Returns the extension kind identifiers that this module registers bridges for. Used by server startup validation to detect orphaned bridges (bridge registered but no module claims to push that kind).
Modules that register bridges in init() should override this to
declare the kinds they push. Define kind constants locally in each
module crate.
§Kernel Purity
The kernel defines this with &[&'static str] return type. Modules
define their own kind constants locally — no shared crate needed.
Sourcefn provides(&self) -> &[&'static str]
fn provides(&self) -> &[&'static str]
Capabilities provided by this module (#618).
Returns capability identifiers that this module provides (e.g.,
"syntax-highlighting", "undo-provider"). Used for abstract
dependency matching: a module that requires a capability is
satisfied by any module that provides it.
Use constants from reovim-capabilities.
§Kernel Purity
Same as extension_kinds(): the kernel defines the method with
&[&'static str] return type. Capability constants live in
shared/capabilities/, not in the kernel.
Sourcefn requires(&self) -> &[&'static str]
fn requires(&self) -> &[&'static str]
Capabilities required by this module (#618).
Returns capability identifiers that this module requires (e.g.,
"lsp-provider"). During dependency resolution, the depgraph
resolver matches these against provides() from other modules
and adds implicit ordering edges.
If no module provides a required capability, the module may fail to initialize or operate in degraded mode.
Use constants from reovim-capabilities.
Sourcefn version_constraints(&self) -> Vec<(ModuleId, &'static str)>
fn version_constraints(&self) -> Vec<(ModuleId, &'static str)>
Version constraints on dependencies (#619).
Returns (module_id, version_range_string) pairs declaring minimum
version requirements on specific dependencies. The version range
follows Cargo semver syntax:
"^1.2.3"— compatible (same major,>=1.2.3, <2.0.0)"=1.2.3"— exact match only">=1.2.3"— at least this version"1.2.3"— shorthand for"^1.2.3"
Constraints are checked after dependency resolution but before init. Violations are logged and the constrained module may be skipped.
§Kernel Purity
The kernel carries opaque (ModuleId, &'static str) pairs.
The depgraph driver parses and evaluates the version range strings.
Sourcefn on_all_loaded(&mut self, _ctx: &ModuleContext)
fn on_all_loaded(&mut self, _ctx: &ModuleContext)
Called after ALL modules are loaded (post-init phase).
Use for cross-module discovery via ServiceRegistry. At this point,
all modules have completed their init() and registered their services.
§Use Cases
- Query services registered by other modules
- Set up inter-module communication channels
- Perform late initialization that depends on other modules
§Example
fn on_all_loaded(&mut self, ctx: &ModuleContext) {
// Now safe to query services from other modules
if let Some(lsp) = ctx.services.get::<dyn LspProvider>() {
self.lsp_client = Some(lsp);
}
}Sourcefn on_buffer_focus(&mut self, _buffer_id: BufferId, _ctx: &ModuleContext)
fn on_buffer_focus(&mut self, _buffer_id: BufferId, _ctx: &ModuleContext)
Called when session focuses on a buffer.
Use for lazy initialization of buffer-specific state. This hook is called whenever the active buffer changes, allowing modules to:
- Load buffer-specific configuration
- Initialize syntax highlighting
- Set up LSP connections for the buffer’s language
§Arguments
buffer_id- The newly focused bufferctx- Module context for accessing kernel services
§Example
fn on_buffer_focus(&mut self, buffer_id: BufferId, ctx: &ModuleContext) {
// Load undo history for this buffer
if let Some(undo_provider) = ctx.services.get::<dyn UndoProvider>() {
undo_provider.load_graceful(buffer_id, &self.buffer_path);
}
}Sourcefn on_unload(&mut self) -> Result<(), ModuleError>
fn on_unload(&mut self) -> Result<(), ModuleError>
Called before module unload for cleanup.
Unlike exit(), this hook is specifically for releasing resources
registered with the kernel (services, event handlers, etc.).
§Difference from exit()
exit(): General cleanup, may failon_unload(): Release kernel resources, should not fail
§Example
fn on_unload(&mut self) -> Result<(), ModuleError> {
// Unregister services
if let Some(registry) = self.service_registry.take() {
registry.unregister_all();
}
Ok(())
}§Errors
Returns ModuleError if resource release fails.
Sourcefn commands(&self) -> Vec<CommandRegistration>
fn commands(&self) -> Vec<CommandRegistration>
Get command registrations.
Sourcefn keybindings(&self) -> Vec<KeybindingRegistration>
fn keybindings(&self) -> Vec<KeybindingRegistration>
Get keybinding registrations.
Sourcefn event_handlers(&self) -> Vec<EventHandlerRegistration>
fn event_handlers(&self) -> Vec<EventHandlerRegistration>
Get event handler registrations.
Sourcefn supports_hot_reload(&self) -> bool
fn supports_hot_reload(&self) -> bool
Whether this module supports hot reload.
If true, save_state() and restore_state() should be implemented.
Hot reload allows updating module code without restarting the editor.
Sourcefn save_state(&self) -> Option<Box<[u8]>>
fn save_state(&self) -> Option<Box<[u8]>>
Save module state for hot reload.
Returns opaque bytes that restore_state() can use to restore state.
Return None if no state to save.
§Serialization Format
The binary format is module-specific. Recommended practices:
- Include a version header for forward compatibility (e.g., first 4 bytes)
- Use a stable serialization format like
bincode,postcard, orrmp-serde - Keep state minimal - only save what’s necessary to restore user experience
- Handle missing fields gracefully in
restore_state()
§Example
fn save_state(&self) -> Option<Box<[u8]>> {
let state = MyModuleState {
version: 1,
cursor_history: self.cursor_history.clone(),
settings: self.settings.clone(),
};
bincode::serialize(&state).ok().map(Vec::into_boxed_slice)
}Sourcefn restore_state(&mut self, _state: &[u8]) -> Result<(), ModuleError>
fn restore_state(&mut self, _state: &[u8]) -> Result<(), ModuleError>
Restore module state after hot reload.
Called with bytes from previous save_state() call.
§State Version Compatibility
Modules should handle version mismatches gracefully:
- If the saved state version is newer than current, return an error
- If the saved state version is older, migrate or use defaults
- If deserialization fails, return an error (module will re-initialize)
§Errors
Returns ModuleError::InitFailed if:
- Hot reload is not supported
- State version is incompatible
- Deserialization fails
§Example
fn restore_state(&mut self, state: &[u8]) -> Result<(), ModuleError> {
let saved: MyModuleState = bincode::deserialize(state)
.map_err(|e| ModuleError::InitFailed(format!("deserialize: {e}")))?;
if saved.version > CURRENT_VERSION {
return Err(ModuleError::InitFailed("state version too new".into()));
}
self.cursor_history = saved.cursor_history;
self.settings = saved.settings;
Ok(())
}