reovim_driver_session/mode.rs
1//! Mode trait with lifecycle hooks.
2//!
3//! This module provides the [`SessionMode`] trait that modules implement
4//! for their modes. Unlike the kernel's `Mode` trait (which is for compile-time
5//! mode enums), this trait is object-safe and supports runtime lifecycle hooks.
6//!
7//! # Design
8//!
9//! - **Kernel `Mode` trait**: Compile-time mode enums with type safety
10//! - **Session `SessionMode` trait**: Runtime mode instances with lifecycle
11//!
12//! The kernel's `ModeStack` manages `ModeId` values. The session driver's
13//! `SessionMode` trait adds runtime behavior (`on_enter`, `on_exit`, resolver).
14//!
15//! # Lifecycle
16//!
17//! ```text
18//! push_mode(mode_id)
19//! └─> SessionMode::on_enter() ─┐
20//! ├─> Ok(()) ─────────────>│ Mode pushed to stack
21//! └─> Err(e) ─────────────>│ Transition aborted, error logged
22//!
23//! pop_mode()
24//! └─> SessionMode::on_exit() ──> Mode popped from stack
25//! (always succeeds - best-effort cleanup)
26//! ```
27
28use std::fmt;
29
30use reovim_kernel::api::v1::ModeId;
31
32/// Error from mode lifecycle hooks.
33///
34/// Returned by [`SessionMode::on_enter()`] when mode transition should be aborted.
35#[derive(Debug, Clone)]
36pub struct ModeError {
37 /// Human-readable error message.
38 pub message: String,
39}
40
41impl ModeError {
42 /// Create a new mode error.
43 #[must_use]
44 pub fn new(message: impl Into<String>) -> Self {
45 Self {
46 message: message.into(),
47 }
48 }
49}
50
51impl fmt::Display for ModeError {
52 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53 write!(f, "mode error: {}", self.message)
54 }
55}
56
57impl std::error::Error for ModeError {}
58
59/// Mode with lifecycle hooks.
60///
61/// Modules implement this trait for their modes. The runner calls lifecycle
62/// hooks when modes are pushed/popped, allowing modes to initialize and
63/// cleanup state.
64///
65/// # Object Safety
66///
67/// This trait is intentionally object-safe (`dyn SessionMode`) to support
68/// runtime mode registration. Use the kernel's `Mode` trait for compile-time
69/// mode enums.
70///
71/// # Example
72///
73/// ```ignore
74/// use reovim_driver_session::{SessionMode, ModeError};
75/// use reovim_kernel::api::v1::ModeId;
76///
77/// pub struct VimInsertMode;
78///
79/// impl SessionMode for VimInsertMode {
80/// fn id(&self) -> ModeId {
81/// ModeId::new(ModuleId::new("vim"), "insert")
82/// }
83///
84/// fn on_enter(&self) -> Result<(), ModeError> {
85/// // Initialize insert mode state
86/// Ok(())
87/// }
88///
89/// fn on_exit(&self) {
90/// // Cleanup insert mode state
91/// }
92/// }
93/// ```
94pub trait SessionMode: Send + Sync + 'static {
95 /// Mode identifier.
96 ///
97 /// Must match the `ModeId` used in the kernel's `ModeStack`.
98 fn id(&self) -> ModeId;
99
100 /// Whether this is an entry point mode (can be home mode).
101 ///
102 /// Entry modes are candidates for the session's home mode.
103 /// Only one mode per module should return `true`.
104 fn is_entry(&self) -> bool {
105 false
106 }
107
108 /// Called when this mode is pushed onto the stack.
109 ///
110 /// Return `Err` to abort the mode transition. The error is logged
111 /// and the mode is not pushed.
112 ///
113 /// # Errors
114 ///
115 /// Return `Err(ModeError)` if the mode cannot be entered.
116 fn on_enter(&self) -> Result<(), ModeError> {
117 Ok(())
118 }
119
120 /// Called when this mode is popped from the stack.
121 ///
122 /// This method is infallible (like a destructor). Errors are logged
123 /// but cannot prevent the pop. Use for best-effort cleanup.
124 fn on_exit(&self) {}
125}
126#[cfg(test)]
127#[path = "mode_tests.rs"]
128mod tests;