reovim_kernel/ipc/events/kernel.rs
1//! Kernel-level events (mechanism layer).
2//!
3//! Linux equivalent: Events similar to `struct file_operations` callbacks
4//!
5//! These events represent fundamental state changes that occurred in the kernel.
6//! They are pure **notifications** - they tell you what happened, not what to do about it.
7//!
8//! # Design Philosophy
9//!
10//! Following "mechanism, not policy":
11//! - Kernel events = notifications (something happened)
12//! - Request events = policy (stay in modules)
13//!
14//! # Priority Constants
15//!
16//! ```ignore
17//! CRITICAL (0): Lifecycle events (Shutdown)
18//! CORE (10): System state changes (Buffer/Window lifecycle)
19//! NORMAL (50): Content changes (BufferModified, CursorMoved)
20//! PLUGIN (100): Default for plugin handlers
21//! LOW (200): Cleanup/finalization handlers
22//! ```
23//!
24//! # Example
25//!
26//! ```
27//! use reovim_kernel::api::v1::{Event, EventBus, EventResult};
28//! use reovim_kernel::api::v1::events::kernel::{BufferCreated, BufferModified, Modification};
29//!
30//! let bus = EventBus::new();
31//!
32//! // Subscribe to buffer creation
33//! let _sub = bus.subscribe::<BufferCreated, _>(100, |event| {
34//! println!("Buffer {} created", event.buffer_id);
35//! EventResult::Handled
36//! });
37//!
38//! // Emit when a buffer is created
39//! bus.emit(BufferCreated { buffer_id: 1 });
40//! ```
41
42use crate::{
43 core::{ModeId, OptionScopeId},
44 ipc::Event,
45};
46
47/// Priority constants for event handlers.
48///
49/// Lower numbers = higher priority (run first).
50pub mod priority {
51 /// Highest priority - for critical handlers (lifecycle events)
52 pub const CRITICAL: u32 = 0;
53 /// High priority - for core system handlers
54 pub const CORE: u32 = 10;
55 /// Normal priority - for standard handlers
56 pub const NORMAL: u32 = 50;
57 /// Default priority for plugins
58 pub const PLUGIN: u32 = 100;
59 /// Low priority - for cleanup/finalization handlers
60 pub const LOW: u32 = 200;
61}
62
63// =============================================================================
64// Buffer Events
65// =============================================================================
66
67/// A buffer was created in the kernel.
68///
69/// Emitted when `mm::Buffer` is allocated and registered.
70/// This is a pure notification - no action is expected from handlers.
71#[derive(Debug, Clone, Copy, PartialEq, Eq)]
72pub struct BufferCreated {
73 /// ID of the created buffer
74 pub buffer_id: u64,
75}
76
77impl Event for BufferCreated {}
78
79/// A buffer was closed/destroyed.
80///
81/// Emitted when a buffer is deallocated from the kernel.
82/// Handlers should clean up any state associated with this buffer.
83#[derive(Debug, Clone, Copy, PartialEq, Eq)]
84pub struct BufferClosed {
85 /// ID of the closed buffer
86 pub buffer_id: u64,
87}
88
89impl Event for BufferClosed {}
90
91/// A buffer's content was modified.
92///
93/// Emitted after any change to buffer content (insert, delete, replace).
94/// Handlers can use this to trigger re-parsing, update highlights, etc.
95#[derive(Debug, Clone, PartialEq, Eq)]
96pub struct BufferModified {
97 /// ID of the modified buffer
98 pub buffer_id: u64,
99 /// Type of modification that occurred
100 pub modification: Modification,
101}
102
103impl Event for BufferModified {}
104
105/// Type of buffer modification.
106///
107/// Describes what kind of change was made to the buffer.
108#[derive(Debug, Clone, PartialEq, Eq)]
109pub enum Modification {
110 /// Text was inserted at a position.
111 Insert {
112 /// Start position (line, column) - 0-indexed
113 start: (u32, u32),
114 /// The inserted text
115 text: String,
116 /// Byte offset where the insertion begins (for incremental parsing).
117 start_byte: usize,
118 },
119 /// Text was deleted from a range.
120 Delete {
121 /// Start position (line, column) - 0-indexed
122 start: (u32, u32),
123 /// End position (line, column) - 0-indexed
124 end: (u32, u32),
125 /// The deleted text
126 text: String,
127 /// Byte offset where the deletion begins (for incremental parsing).
128 start_byte: usize,
129 },
130 /// Text was replaced (delete + insert combined).
131 Replace {
132 /// Start position (line, column) - 0-indexed
133 start: (u32, u32),
134 /// End position (line, column) - 0-indexed
135 end: (u32, u32),
136 /// Old text that was replaced
137 old_text: String,
138 /// New text
139 new_text: String,
140 /// Byte offset where the replacement begins (for incremental parsing).
141 start_byte: usize,
142 },
143 /// Entire buffer content was replaced (e.g., file reload).
144 FullReplace,
145}
146
147/// Active buffer changed.
148///
149/// Emitted when the kernel switches focus to a different buffer.
150#[derive(Debug, Clone, Copy, PartialEq, Eq)]
151pub struct BufferSwitched {
152 /// Previous buffer ID (if any)
153 pub from: Option<u64>,
154 /// New active buffer ID
155 pub to: u64,
156}
157
158impl Event for BufferSwitched {}
159
160/// A buffer is about to be saved to disk.
161///
162/// Emitted before buffer content is persisted. Subscribers can modify
163/// the buffer content (e.g., format-on-save) and the write command will
164/// pick up the formatted content. Dispatch is synchronous.
165#[derive(Debug, Clone, PartialEq, Eq)]
166pub struct BufferWillSave {
167 /// ID of the buffer about to be saved
168 pub buffer_id: u64,
169 /// Path the buffer will be saved to
170 pub path: String,
171}
172
173impl Event for BufferWillSave {}
174
175/// A buffer was saved to disk.
176///
177/// Emitted after buffer content is persisted to the filesystem.
178#[derive(Debug, Clone, PartialEq, Eq)]
179pub struct BufferSaved {
180 /// ID of the saved buffer
181 pub buffer_id: u64,
182 /// Path the buffer was saved to
183 pub path: String,
184}
185
186impl Event for BufferSaved {}
187
188// =============================================================================
189// Cursor Events
190// =============================================================================
191
192/// Cursor position changed in a buffer.
193///
194/// Emitted after the cursor moves to a new position.
195/// Note: This is a notification only - it doesn't request movement.
196#[derive(Debug, Clone, Copy, PartialEq, Eq)]
197pub struct CursorMoved {
198 /// Buffer ID where cursor moved
199 pub buffer_id: u64,
200 /// Previous position (line, column) - 0-indexed
201 pub from: (u32, u32),
202 /// New position (line, column) - 0-indexed
203 pub to: (u32, u32),
204}
205
206impl Event for CursorMoved {}
207
208// =============================================================================
209// Mode Events
210// =============================================================================
211
212/// Editor mode changed.
213///
214/// Emitted after a mode transition occurs (e.g., Normal → Insert).
215/// The mode strings are intentionally generic - policy (specific modes)
216/// is defined by the runtime and modules.
217///
218/// # Type-Safe Mode Transitions
219///
220/// When `target_mode` is set, it provides the exact `ModeId` to transition to.
221/// This enables event-driven mode transitions without hardcoding command names
222/// in the runner layer.
223///
224/// # Example
225///
226/// ```ignore
227/// // Commands emit with target_mode for type-safe transitions
228/// ctx.event_bus.emit(ModeChanged::with_mode_id("normal", editor_visual_mode));
229///
230/// // Runner subscribes and updates mode_stack from the event
231/// bus.subscribe::<ModeChanged, _>(priority::CORE, |event| {
232/// if let Some(mode_id) = event.target_mode() {
233/// app.mode_stack.set(mode_id.clone());
234/// }
235/// EventResult::Handled
236/// });
237/// ```
238#[derive(Debug, Clone, PartialEq, Eq)]
239pub struct ModeChanged {
240 /// Previous mode description (for display/logging).
241 pub from: String,
242 /// New mode description (for display/logging).
243 pub to: String,
244 /// Target mode ID for type-safe transitions.
245 ///
246 /// When set, the runner uses this to update the mode stack instead of
247 /// predicting mode transitions based on command names.
248 target_mode: Option<ModeId>,
249}
250
251impl ModeChanged {
252 /// Create a new mode change event with string descriptions only.
253 ///
254 /// Use this for backward compatibility or when the target mode is
255 /// implicit (e.g., commands that emit events for logging only).
256 #[must_use]
257 pub fn new(from: impl Into<String>, to: impl Into<String>) -> Self {
258 Self {
259 from: from.into(),
260 to: to.into(),
261 target_mode: None,
262 }
263 }
264
265 /// Create a mode change event with a specific target mode ID.
266 ///
267 /// The runner subscribes to these events and updates the mode stack
268 /// using the provided `ModeId`, eliminating the need for hardcoded
269 /// command name mappings.
270 #[must_use]
271 pub fn with_mode_id(from: impl Into<String>, target: ModeId) -> Self {
272 let to = target.name().to_string();
273 Self {
274 from: from.into(),
275 to,
276 target_mode: Some(target),
277 }
278 }
279
280 /// Get the target mode ID, if set.
281 ///
282 /// Returns `Some(&ModeId)` when the event was created with `with_mode_id()`,
283 /// `None` for legacy events created with just string descriptions.
284 #[must_use]
285 pub const fn target_mode(&self) -> Option<&ModeId> {
286 self.target_mode.as_ref()
287 }
288
289 /// Check if this event has a type-safe target mode.
290 #[must_use]
291 pub const fn has_target_mode(&self) -> bool {
292 self.target_mode.is_some()
293 }
294}
295
296impl Event for ModeChanged {}
297
298// =============================================================================
299// Window Events
300// =============================================================================
301
302/// A window was created.
303///
304/// Emitted when a new window is added to the layout.
305#[derive(Debug, Clone, Copy, PartialEq, Eq)]
306pub struct WindowCreated {
307 /// ID of the created window
308 pub window_id: u64,
309}
310
311impl Event for WindowCreated {}
312
313/// A window was closed.
314///
315/// Emitted when a window is removed from the layout.
316#[derive(Debug, Clone, Copy, PartialEq, Eq)]
317pub struct WindowClosed {
318 /// ID of the closed window
319 pub window_id: u64,
320}
321
322impl Event for WindowClosed {}
323
324/// Active window changed.
325///
326/// Emitted when focus moves to a different window.
327#[derive(Debug, Clone, Copy, PartialEq, Eq)]
328pub struct WindowFocused {
329 /// Previous window ID (if any)
330 pub from: Option<u64>,
331 /// New active window ID
332 pub to: u64,
333}
334
335impl Event for WindowFocused {}
336
337/// Viewport scrolled within a window.
338///
339/// Emitted when the visible portion of a buffer changes due to scrolling.
340#[derive(Debug, Clone, Copy, PartialEq, Eq)]
341pub struct ViewportScrolled {
342 /// ID of the window that scrolled
343 pub window_id: u64,
344 /// ID of the buffer being viewed
345 pub buffer_id: u64,
346 /// First visible line (0-indexed)
347 pub top_line: u32,
348 /// Last visible line (0-indexed)
349 pub bottom_line: u32,
350}
351
352impl Event for ViewportScrolled {}
353
354// =============================================================================
355// Layout Events
356// =============================================================================
357
358/// Direction of a window split.
359#[derive(Debug, Clone, Copy, PartialEq, Eq)]
360pub enum SplitDirection {
361 /// Horizontal split (windows stacked top/bottom).
362 Horizontal,
363 /// Vertical split (windows side by side).
364 Vertical,
365}
366
367/// Type of layout change that occurred.
368#[derive(Debug, Clone, PartialEq, Eq)]
369pub enum LayoutChangeKind {
370 /// Window was split.
371 Split {
372 /// ID of the new window created by the split.
373 new_window: u64,
374 /// Direction of the split.
375 direction: SplitDirection,
376 },
377 /// Window was closed.
378 Close {
379 /// ID of the closed window.
380 closed_window: u64,
381 /// ID of the window that received focus (if any).
382 new_focus: Option<u64>,
383 },
384 /// Focus changed to a different window.
385 Focus {
386 /// Previous focused window (if any).
387 from: Option<u64>,
388 /// New focused window.
389 to: u64,
390 },
391 /// Window was resized.
392 Resize {
393 /// ID of the resized window.
394 window: u64,
395 },
396 /// All windows were equalized in size.
397 Equalize,
398}
399
400/// Layout changed event.
401///
402/// Emitted after any layout operation (split, close, focus, resize, equalize).
403/// This is the primary event for notifying clients of layout changes.
404///
405/// The runner subscribes to this event and converts it to an RPC notification
406/// for connected clients.
407///
408/// # Example
409///
410/// ```ignore
411/// use reovim_kernel::api::v1::{EventBus, EventResult, events::kernel::LayoutChanged};
412///
413/// let bus = EventBus::new();
414/// let _sub = bus.subscribe::<LayoutChanged, _>(100, |event| {
415/// println!("Layout changed: {:?}", event.kind);
416/// EventResult::Handled
417/// });
418/// ```
419#[derive(Debug, Clone)]
420pub struct LayoutChanged {
421 /// Type of layout change that occurred.
422 pub kind: LayoutChangeKind,
423 /// Total window count after the change.
424 pub window_count: usize,
425 /// Currently focused window (if any).
426 pub focused_window: Option<u64>,
427}
428
429impl Event for LayoutChanged {}
430
431// =============================================================================
432// File Events
433// =============================================================================
434
435/// A file was opened into a buffer.
436///
437/// Emitted after file content is loaded into a buffer.
438#[derive(Debug, Clone, PartialEq, Eq)]
439pub struct FileOpened {
440 /// Buffer ID the file was loaded into
441 pub buffer_id: u64,
442 /// Path of the opened file
443 pub path: String,
444}
445
446impl Event for FileOpened {}
447
448/// File type was detected or changed.
449///
450/// Emitted when the file type (language) is determined or updated.
451#[derive(Debug, Clone, PartialEq, Eq)]
452pub struct FileTypeChanged {
453 /// Buffer ID
454 pub buffer_id: u64,
455 /// Detected file type (e.g., "rust", "python", "markdown")
456 pub file_type: String,
457}
458
459impl Event for FileTypeChanged {}
460
461// =============================================================================
462// Option Events
463// =============================================================================
464
465/// Source of an option value change.
466///
467/// Indicates how an option was modified, useful for handlers
468/// that need to distinguish user commands from programmatic changes.
469#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
470pub enum ChangeSource {
471 /// Changed via `:set` command by the user.
472 #[default]
473 UserCommand,
474 /// Changed programmatically by a plugin/module.
475 Plugin,
476 /// Loaded from configuration file.
477 Config,
478 /// Set to default during initialization.
479 Default,
480 /// Changed via settings menu/UI.
481 SettingsMenu,
482 /// Changed via RPC (server mode).
483 Rpc,
484}
485
486/// An option value changed.
487///
488/// Emitted after any option value is modified via `OptionRegistry::set()`.
489/// Handlers can react to option changes (e.g., re-render, update state).
490///
491/// # Example
492///
493/// ```ignore
494/// use reovim_kernel::api::v1::{EventBus, EventResult, events::kernel::OptionChanged};
495///
496/// let bus = EventBus::new();
497/// bus.subscribe::<OptionChanged, _>(100, |event| {
498/// println!("Option '{}' changed from {} to {}",
499/// event.name, event.old_value, event.new_value);
500/// EventResult::Handled
501/// });
502/// ```
503#[derive(Debug, Clone, PartialEq, Eq)]
504pub struct OptionChanged {
505 /// Full option name that changed.
506 pub name: String,
507 /// Previous value.
508 pub old_value: String,
509 /// New value.
510 pub new_value: String,
511 /// Source of the change.
512 pub source: ChangeSource,
513 /// Runtime scope where the change was applied (which specific buffer or window).
514 ///
515 /// This is the runtime context, NOT the option's static scope declaration
516 /// (see `OptionSpec::scope` for the capability declaration).
517 pub scope: OptionScopeId,
518}
519
520impl Event for OptionChanged {}
521
522/// An option was reset to its default value.
523///
524/// Emitted when an option is reset via `:set option&` or `OptionRegistry::reset()`.
525#[derive(Debug, Clone, PartialEq, Eq)]
526pub struct OptionReset {
527 /// Option name that was reset.
528 pub name: String,
529 /// Previous value (before reset).
530 pub old_value: String,
531 /// Default value (after reset).
532 pub default_value: String,
533 /// Runtime scope where the reset was applied (which specific buffer or window).
534 ///
535 /// This is the runtime context, NOT the option's static scope declaration
536 /// (see `OptionSpec::scope` for the capability declaration).
537 pub scope: OptionScopeId,
538}
539
540impl Event for OptionReset {}
541
542// =============================================================================
543// Lifecycle Events
544// =============================================================================
545
546/// Kernel is shutting down.
547///
548/// Emitted before the kernel terminates. Handlers should perform cleanup.
549/// This is the last event handlers will receive before termination.
550#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
551pub struct Shutdown;
552
553impl Event for Shutdown {}