reovim-driver-session 0.14.4

Session driver for reovim - provides traits for session management
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
//! Compositor API for window layout operations.
//!
//! This module provides the `CompositorApi` trait, which offers high-level
//! window management operations that delegate to the nested compositor.
//!
//! # Architecture
//!
//! ```text
//! Command (e.g., FocusLeft)
//!//!     ↓ calls
//! CompositorApi.navigate(Left)
//!//!     ↓ delegates to
//! RootCompositor.layer_compositor(active_layer)
//!//!     ↓ then
//! WindowLayerCompositor.navigate_tiled(from, Left)
//! ```
//!
//! # Overlay Operations (#399)
//!
//! Overlays are temporary UI elements (popups, menus, tooltips) that appear
//! above all other windows. They do NOT auto-focus when shown - focus remains
//! on the underlying tiled/float window.
//!
use {
    reovim_driver_layout::{
        LayerId, NavigateDirection, OverlayConstraints, Rect, SplitDirection, WindowId,
        WindowPlacement,
    },
    reovim_kernel::api::v1::TabId,
};

/// Errors from compositor operations.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CompositorError {
    /// Cannot close the last window.
    CannotCloseLastWindow,
    /// No window in the specified direction.
    NoNeighbor(NavigateDirection),
    /// Not enough room to split.
    NotEnoughRoom,
    /// Cannot resize at screen edge.
    CannotResizeAtEdge,
    /// No active layer to operate on.
    NoActiveLayer,
    /// No focused window in the active layer.
    NoFocusedWindow,
    /// Window not found.
    WindowNotFound(WindowId),
    /// Layer not found.
    LayerNotFound(LayerId),
    /// No tab pages available.
    NoTabPages,
    /// Cannot close the last tab page.
    CannotCloseLastTab,
    /// Tab page not found.
    TabNotFound(TabId),
}

impl std::fmt::Display for CompositorError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::CannotCloseLastWindow => write!(f, "cannot close last window"),
            Self::NoNeighbor(dir) => write!(f, "no window {dir:?}"),
            Self::NotEnoughRoom => write!(f, "not enough room to split"),
            Self::CannotResizeAtEdge => write!(f, "cannot resize at edge"),
            Self::NoActiveLayer => write!(f, "no active layer"),
            Self::NoFocusedWindow => write!(f, "no focused window"),
            Self::WindowNotFound(id) => write!(f, "window {} not found", id.as_usize()),
            Self::LayerNotFound(id) => write!(f, "layer {} not found", id.as_u16()),
            Self::NoTabPages => write!(f, "no tab pages"),
            Self::CannotCloseLastTab => write!(f, "cannot close last tab"),
            Self::TabNotFound(id) => write!(f, "tab {} not found", id.as_usize()),
        }
    }
}

impl std::error::Error for CompositorError {}

/// Compositor operations for window management.
///
/// This trait provides high-level operations that commands use to manage
/// windows. It abstracts over the compositor implementation, allowing
/// commands to work with any compositor.
///
/// # Design
///
/// Operations work with the **currently focused window** in the **active layer**.
/// This matches vim's window model where `<C-w>` commands operate on the
/// current window without requiring explicit window arguments.
///
/// # Examples
///
/// ```ignore
/// // In a command handler:
/// fn execute(&self, runtime: &mut SessionRuntime<'_>, _ctx: &CommandContext) -> CommandResult {
///     match runtime.navigate(NavigateDirection::Left) {
///         Ok(window) => {
///             runtime.focus(window)?;
///             CommandResult::Success
///         }
///         Err(e) => CommandResult::Error(e.to_string()),
///     }
/// }
/// ```
pub trait CompositorApi {
    /// Navigate in direction from the current window.
    ///
    /// Returns the window ID of the neighbor in that direction, or an error
    /// if there is no neighbor (at screen edge).
    ///
    /// # Errors
    ///
    /// - `NoActiveLayer` - No layer is active
    /// - `NoFocusedWindow` - No window is focused
    /// - `NoNeighbor` - No window in the specified direction
    fn navigate(&self, direction: NavigateDirection) -> Result<WindowId, CompositorError>;

    /// Split the current window.
    ///
    /// Creates a new window adjacent to the current window in the specified
    /// direction. The new window is focused after creation.
    ///
    /// # Errors
    ///
    /// - `NoActiveLayer` - No layer is active
    /// - `NoFocusedWindow` - No window is focused
    /// - `NotEnoughRoom` - Window is too small to split
    fn split(&mut self, direction: SplitDirection) -> Result<WindowId, CompositorError>;

    /// Close the current window.
    ///
    /// Returns the window that will receive focus after closing.
    /// Named `close_current_window` to avoid conflict with `WindowApi::close_window`.
    ///
    /// # Errors
    ///
    /// - `CannotCloseLastWindow` - This is the only window
    /// - `NoActiveLayer` - No layer is active
    /// - `NoFocusedWindow` - No window is focused
    fn close_current_window(&mut self) -> Result<WindowId, CompositorError>;

    /// Close all windows except the current one.
    ///
    /// # Errors
    ///
    /// - `NoActiveLayer` - No layer is active
    /// - `NoFocusedWindow` - No window is focused
    fn close_others(&mut self) -> Result<(), CompositorError>;

    /// Resize the current window.
    ///
    /// Moves the edge of the current window in the specified direction.
    /// Positive delta expands in that direction, negative contracts.
    ///
    /// # Arguments
    ///
    /// * `direction` - Which edge to move (Left/Right/Up/Down)
    /// * `delta` - Amount to move (positive = expand, negative = contract)
    ///
    /// # Errors
    ///
    /// - `NoActiveLayer` - No layer is active
    /// - `NoFocusedWindow` - No window is focused
    /// - `CannotResizeAtEdge` - Window is at screen edge in that direction
    fn resize(&mut self, direction: NavigateDirection, delta: i16) -> Result<(), CompositorError>;

    /// Equalize all windows in the current layer.
    ///
    /// Distributes space equally among all tiled windows.
    ///
    /// # Errors
    ///
    /// - `NoActiveLayer` - No layer is active
    fn equalize(&mut self) -> Result<(), CompositorError>;

    /// Cycle to next/previous window.
    ///
    /// Windows are ordered by winnr (top-to-bottom, left-to-right).
    ///
    /// # Arguments
    ///
    /// * `forward` - `true` for next window, `false` for previous
    ///
    /// # Errors
    ///
    /// - `NoActiveLayer` - No layer is active
    /// - `NoFocusedWindow` - No window is focused
    fn cycle(&self, forward: bool) -> Result<WindowId, CompositorError>;

    /// Set focus to a specific window.
    ///
    /// Also activates the layer containing the window.
    ///
    /// # Errors
    ///
    /// - `WindowNotFound` - Window does not exist
    fn focus(&mut self, window: WindowId) -> Result<(), CompositorError>;

    /// Get the currently focused window.
    ///
    /// Returns `None` if no window is focused.
    fn focused_window(&self) -> Option<WindowId>;

    /// Get window count in the active layer.
    fn compositor_window_count(&self) -> usize;

    /// Get all window placements (for rendering).
    ///
    /// Returns windows in z-order (lowest first) for proper rendering.
    fn arrange(&self, screen: Rect) -> Vec<WindowPlacement>;

    /// Get the active layer ID.
    fn active_layer(&self) -> Option<LayerId>;

    /// Update screen size (on terminal resize).
    fn set_screen(&mut self, screen: Rect);

    // =========================================================================
    // Float Zone Operations (#398)
    // =========================================================================

    /// Toggle the current window between tiled and float zones.
    ///
    /// If the window is tiled, it becomes a floating window (80% centered).
    /// If the window is floating, it returns to the tiled zone.
    ///
    /// # Errors
    ///
    /// - `NoActiveLayer` - No layer is active
    /// - `NoFocusedWindow` - No window is focused
    fn toggle_float(&mut self) -> Result<(), CompositorError>;

    /// Raise the current float window to the front.
    ///
    /// No-op if the current window is not a float.
    ///
    /// # Errors
    ///
    /// - `NoActiveLayer` - No layer is active
    /// - `NoFocusedWindow` - No window is focused
    fn raise_float(&mut self) -> Result<(), CompositorError>;

    /// Lower the current float window to the back.
    ///
    /// No-op if the current window is not a float.
    ///
    /// # Errors
    ///
    /// - `NoActiveLayer` - No layer is active
    /// - `NoFocusedWindow` - No window is focused
    fn lower_float(&mut self) -> Result<(), CompositorError>;

    // =========================================================================
    // Opacity Operations (#400)
    // =========================================================================

    /// Set the opacity of the active layer.
    ///
    /// # Arguments
    ///
    /// * `opacity` - Value clamped to 0.0..=1.0
    ///
    /// # Errors
    ///
    /// - `NoActiveLayer` - No layer is active
    fn set_active_layer_opacity(&mut self, opacity: f32) -> Result<(), CompositorError>;

    /// Get the opacity of the active layer.
    ///
    /// # Errors
    ///
    /// - `NoActiveLayer` - No layer is active
    fn active_layer_opacity(&self) -> Result<f32, CompositorError>;

    /// Adjust the active layer's opacity by a delta.
    ///
    /// The result is clamped to 0.0..=1.0.
    ///
    /// # Arguments
    ///
    /// * `delta` - Amount to add (positive = more opaque, negative = more transparent)
    ///
    /// # Returns
    ///
    /// The new opacity value after adjustment.
    ///
    /// # Errors
    ///
    /// - `NoActiveLayer` - No layer is active
    fn adjust_active_layer_opacity(&mut self, delta: f32) -> Result<f32, CompositorError>;

    // =========================================================================
    // Overlay Zone Operations (#399)
    // =========================================================================

    /// Show an overlay with constraints.
    ///
    /// Creates a new overlay positioned according to the constraints.
    /// Overlays do NOT auto-focus - they are temporary UI elements that
    /// appear above content without stealing keyboard input.
    ///
    /// # Arguments
    ///
    /// * `constraints` - Positioning constraints (anchor, preferred size, max size)
    ///
    /// # Returns
    ///
    /// The window ID of the new overlay.
    ///
    /// # Errors
    ///
    /// - `NoActiveLayer` - No layer is active
    fn show_overlay(
        &mut self,
        constraints: OverlayConstraints,
    ) -> Result<WindowId, CompositorError>;

    /// Hide (remove) an overlay.
    ///
    /// # Arguments
    ///
    /// * `window` - The overlay window ID to hide
    ///
    /// # Errors
    ///
    /// - `NoActiveLayer` - No layer is active
    fn hide_overlay(&mut self, window: WindowId) -> Result<(), CompositorError>;

    /// Resize an overlay.
    ///
    /// Updates the preferred size of the overlay.
    ///
    /// # Errors
    ///
    /// - `NoActiveLayer` - No layer is active
    fn resize_overlay(
        &mut self,
        window: WindowId,
        width: u16,
        height: u16,
    ) -> Result<(), CompositorError>;

    /// Hide all overlays in the active layer.
    ///
    /// Useful for commands like "dismiss all popups" (Escape key behavior).
    ///
    /// # Errors
    ///
    /// - `NoActiveLayer` - No layer is active
    fn hide_all_overlays(&mut self) -> Result<(), CompositorError>;

    // =========================================================================
    // Tab Page Operations (#401)
    // =========================================================================

    /// Create a new tab page after the active tab.
    ///
    /// The new tab becomes active.
    ///
    /// # Errors
    ///
    /// - `NoTabPages` - Tab system not initialized
    fn tab_new(&mut self) -> Result<TabId, CompositorError>;

    /// Close the active tab page.
    ///
    /// # Errors
    ///
    /// - `NoTabPages` - Tab system not initialized
    /// - `CannotCloseLastTab` - Cannot close the only tab
    fn tab_close(&mut self) -> Result<(), CompositorError>;

    /// Switch to the next tab page (wraps around).
    ///
    /// # Errors
    ///
    /// - `NoTabPages` - Tab system not initialized
    fn tab_next(&mut self) -> Result<TabId, CompositorError>;

    /// Switch to the previous tab page (wraps around).
    ///
    /// # Errors
    ///
    /// - `NoTabPages` - Tab system not initialized
    fn tab_prev(&mut self) -> Result<TabId, CompositorError>;

    /// Switch to a tab page by 0-based index.
    ///
    /// # Errors
    ///
    /// - `NoTabPages` - Tab system not initialized
    /// - `TabNotFound` - Index out of bounds
    fn tab_goto(&mut self, index: usize) -> Result<TabId, CompositorError>;

    /// Get the number of tab pages.
    fn tab_count(&self) -> usize;

    /// Get the active tab page's ID.
    fn active_tab_id(&self) -> Option<TabId>;
}
#[cfg(test)]
#[path = "tests/compositor.rs"]
mod tests;