Skip to main content

reovim_module_window_ops/
lib.rs

1#![cfg_attr(coverage_nightly, allow(unused_features))]
2#![cfg_attr(coverage_nightly, feature(coverage_attribute))]
3//! Window Operations Module
4//!
5//! This module handles window lifecycle and viewport events from the kernel `EventBus`.
6//! It subscribes to `WindowCreated`, `WindowClosed`, `WindowFocused`, and `ViewportScrolled`
7//! events and provides coordinated window state management.
8//!
9//! # Architecture
10//!
11//! Following the kernel's "mechanism vs policy" principle:
12//! - Kernel provides the window events (mechanism)
13//! - This module decides how to react (policy)
14//!
15//! # Command IDs
16//!
17//! This module also provides command ID constants for window operations
18//! (focus, split, resize, etc.) used by keybinding modules like `vim`.
19//!
20//! # Commands
21//!
22//! This module implements command handlers for window operations:
23//! - Focus navigation: `<C-w>h/j/k/l` - move focus directionally
24//! - Focus cycling: `<C-w>w/W` - cycle through windows
25//! - Splitting: `<C-w>s/v` - horizontal/vertical splits
26//! - Closing: `<C-w>c/o` - close current/close others
27//! - Float zone: Toggle, raise, lower floating windows
28//!
29//! # Event Subscriptions
30//!
31//! - `WindowCreated`: Initialize window-specific state
32//! - `WindowClosed`: Cleanup window-specific resources
33//! - `WindowFocused`: Update active window tracking, trigger highlights
34//! - `ViewportScrolled`: Handle lazy loading, update visible ranges
35
36pub mod command;
37pub mod ids;
38
39use std::sync::Arc;
40
41use {
42    reovim_driver_command::{CommandHandler, CommandHandlerStore, CommandProvider},
43    reovim_kernel::api::v1::{
44        EventResult, Module, ModuleContext, ModuleError, ModuleId, ProbeResult, Subscription,
45        Version,
46        events::kernel::{ViewportScrolled, WindowClosed, WindowCreated, WindowFocused, priority},
47        pr_info,
48    },
49};
50
51/// Window operations module.
52///
53/// Manages window lifecycle events and viewport tracking across the editor.
54/// Subscriptions are stored to keep handlers active until module exit.
55pub struct WindowOps {
56    /// Active subscriptions - MUST be stored to prevent immediate drop (RAII pattern)
57    subscriptions: Vec<Subscription>,
58}
59
60impl WindowOps {
61    /// Create a new `WindowOps` module instance.
62    #[must_use]
63    pub const fn new() -> Self {
64        Self {
65            subscriptions: Vec::new(),
66        }
67    }
68}
69
70impl Default for WindowOps {
71    fn default() -> Self {
72        Self::new()
73    }
74}
75
76impl Module for WindowOps {
77    fn id(&self) -> ModuleId {
78        ModuleId::new("window-ops")
79    }
80
81    fn name(&self) -> &'static str {
82        "Window Operations"
83    }
84
85    fn version(&self) -> Version {
86        Version::new(0, 1, 0)
87    }
88
89    #[cfg_attr(coverage_nightly, coverage(off))]
90    fn init(&mut self, ctx: &ModuleContext) -> ProbeResult {
91        // Register command handlers (Epic #417 Part 3)
92        let command_store = ctx.services.get_or_create::<CommandHandlerStore>();
93        for handler in self.command_handlers() {
94            command_store.add(handler);
95        }
96
97        let bus = Arc::clone(&ctx.kernel.event_bus);
98
99        // Subscribe to window creation events
100        // Priority: CORE (10) - system state change, needs early handling
101        let sub_created =
102            bus.subscribe_with_context::<WindowCreated, _>(priority::CORE, |event, _ctx| {
103                pr_info!("Window {} created", event.window_id);
104                // Future: Initialize window-specific module state (status line, borders)
105                // _ctx.request_render() available when needed
106                EventResult::Handled
107            });
108        // Detach so handlers survive module drop (bootstrap drops modules after init).
109        sub_created.detach();
110        self.subscriptions.push(sub_created);
111
112        // Subscribe to window close events
113        // Priority: LOW (200) - cleanup, run after other handlers
114        let sub_closed =
115            bus.subscribe_with_context::<WindowClosed, _>(priority::LOW, |event, _ctx| {
116                pr_info!("Window {} closed", event.window_id);
117                // Future: Cleanup window-specific module state
118                EventResult::Handled
119            });
120        sub_closed.detach();
121        self.subscriptions.push(sub_closed);
122
123        // Subscribe to window focus events
124        // Priority: CORE (10) - affects active window state
125        let sub_focused =
126            bus.subscribe_with_context::<WindowFocused, _>(priority::CORE, |event, _ctx| {
127                pr_info!("Window focus: {:?} -> {}", event.from, event.to);
128                // Future: Update status line, trigger cursor highlight
129                // _ctx.request_render() available when needed
130                EventResult::Handled
131            });
132        sub_focused.detach();
133        self.subscriptions.push(sub_focused);
134
135        // Subscribe to viewport scroll events
136        // Priority: NORMAL (50) - standard priority for viewport updates
137        let sub_scrolled =
138            bus.subscribe_with_context::<ViewportScrolled, _>(priority::NORMAL, |event, _ctx| {
139                pr_info!(
140                    "Window {} viewport scrolled: buffer={}, lines {}..{}",
141                    event.window_id,
142                    event.buffer_id,
143                    event.top_line,
144                    event.bottom_line
145                );
146                // Future: Trigger lazy syntax highlighting for visible range
147                // _ctx.request_render() available when needed
148                EventResult::Handled
149            });
150        sub_scrolled.detach();
151        self.subscriptions.push(sub_scrolled);
152
153        pr_info!(
154            "WindowOps module initialized with {} commands and {} subscriptions",
155            command::all_commands().len(),
156            self.subscriptions.len()
157        );
158        ProbeResult::Success
159    }
160
161    fn exit(&mut self) -> Result<(), ModuleError> {
162        // Subscriptions auto-unsubscribe when dropped (RAII)
163        let count = self.subscriptions.len();
164        self.subscriptions.clear();
165        pr_info!("WindowOps module exiting, cleared {} subscriptions", count);
166        Ok(())
167    }
168}
169
170impl CommandProvider for WindowOps {
171    fn command_handlers(&self) -> Vec<Box<dyn CommandHandler>> {
172        command::all_commands()
173    }
174}
175
176// Generate FFI entry points for dynamic loading (only when building standalone cdylib)
177#[cfg(feature = "dynamic")]
178reovim_module_macros::declare_module!(WindowOps);
179
180#[cfg(test)]
181#[path = "lib_tests.rs"]
182mod tests;