Skip to main content

Module

Trait Module 

Source
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

  1. Module is loaded (by runner’s module loader)
  2. init() is called with ModuleContext
    • Returns ProbeResult::Success → module is ready
    • Returns ProbeResult::Defer → retry later
    • Returns ProbeResult::Failed → permanent failure
  3. Module is now Running
  4. 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() and exit() 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) and restore_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§

Source

fn id(&self) -> ModuleId

Unique module identifier.

Convention: kebab-case, e.g., “lang-rust”, “feat-completion”

Source

fn name(&self) -> &'static str

Human-readable name.

Source

fn version(&self) -> Version

Module version.

Source

fn init(&mut self, ctx: &ModuleContext) -> ProbeResult

Initialize module (Linux equivalent: probe() function).

Returns ProbeResult to support deferred probing:

  • Success - Module is ready
  • Defer(reason) - Retry later (like Linux -EPROBE_DEFER)
  • Failed(err) - Permanent failure
Source

fn exit(&mut self) -> Result<(), ModuleError>

Cleanup module (Linux equivalent: remove() function).

§Errors

Returns ModuleError if cleanup fails.

Provided Methods§

Source

fn api_version(&self) -> Version

Required kernel API version.

Defaults to current API version. Override to require specific version.

Source

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.

Source

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
    ]
}
Source

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.

Source

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.

Source

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.

Source

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.

Source

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);
    }
}
Source

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 buffer
  • ctx - 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);
    }
}
Source

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 fail
  • on_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.

Source

fn commands(&self) -> Vec<CommandRegistration>

Get command registrations.

Source

fn keybindings(&self) -> Vec<KeybindingRegistration>

Get keybinding registrations.

Source

fn event_handlers(&self) -> Vec<EventHandlerRegistration>

Get event handler registrations.

Source

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.

Source

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, or rmp-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)
}
Source

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(())
}

Implementors§