Skip to main content

scarab_plugin_api/events/
types.rs

1//! Event type definitions
2//!
3//! Defines all available event types in the Scarab event system, matching
4//! WezTerm's event capabilities with 20+ granular events.
5
6use serde::{Deserialize, Serialize};
7
8/// Event type enumeration
9///
10/// Defines all events that plugins can subscribe to. Events are organized into
11/// categories: window, tab, pane, terminal, and status events.
12#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
13pub enum EventType {
14    // ==================== Window Events ====================
15    /// Fired when a new window is created
16    WindowCreated,
17
18    /// Fired when a window is closed
19    WindowClosed,
20
21    /// Fired when window focus changes (gained or lost)
22    WindowFocusChanged,
23
24    /// Fired when a window is resized
25    WindowResized,
26
27    /// Fired when configuration is reloaded
28    WindowConfigReloaded,
29
30    /// Fired once when the GUI starts up (before first window)
31    GuiStartup,
32
33    // ==================== Tab Events ====================
34    /// Fired when a new tab is created
35    TabCreated,
36
37    /// Fired when a tab is closed
38    TabClosed,
39
40    /// Fired when the active tab changes
41    TabSwitched,
42
43    /// Fired when the user clicks the "new tab" button
44    NewTabButtonClick,
45
46    // ==================== Pane Events ====================
47    /// Fired when a new pane is created (split)
48    PaneCreated,
49
50    /// Fired when a pane is closed
51    PaneClosed,
52
53    /// Fired when a pane gains focus
54    PaneFocused,
55
56    /// Fired when a pane's title changes
57    PaneTitleChanged,
58
59    // ==================== Terminal Events ====================
60    /// Fired when the terminal bell is rung
61    Bell,
62
63    /// Fired when the text selection changes
64    SelectionChanged,
65
66    /// Fired when a user-defined variable changes (OSC 1337)
67    UserVarChanged,
68
69    /// Fired when a clickable URI is detected
70    OpenUri,
71
72    /// Fired when the scrollback buffer is cleared
73    ScrollbackCleared,
74
75    // ==================== Status Events ====================
76    /// Request to update the status bar
77    UpdateStatus,
78
79    /// Request to update the right status section
80    UpdateRightStatus,
81
82    /// Request to update the left status section
83    UpdateLeftStatus,
84
85    /// Request to format a tab title (return formatted string)
86    FormatTabTitle,
87
88    /// Request to format the window title (return formatted string)
89    FormatWindowTitle,
90
91    // ==================== Legacy Hook Events ====================
92    /// Terminal output (maps to HookType::PreOutput)
93    Output,
94
95    /// User input (maps to HookType::PostInput)
96    Input,
97
98    /// Before command execution (maps to HookType::PreCommand)
99    PreCommand,
100
101    /// After command execution (maps to HookType::PostCommand)
102    PostCommand,
103
104    /// Terminal resize (maps to HookType::OnResize)
105    Resize,
106
107    /// Client attached (maps to HookType::OnAttach)
108    Attach,
109
110    /// Client detached (maps to HookType::OnDetach)
111    Detach,
112
113    // ==================== Custom Events ====================
114    /// User-defined custom event
115    ///
116    /// Allows plugins to emit and listen for custom events with arbitrary names.
117    /// For example, `Custom("git-status-changed")` or `Custom("test-completed")`.
118    Custom(String),
119}
120
121impl EventType {
122    /// Check if this is a window-related event
123    pub fn is_window_event(&self) -> bool {
124        matches!(
125            self,
126            EventType::WindowCreated
127                | EventType::WindowClosed
128                | EventType::WindowFocusChanged
129                | EventType::WindowResized
130                | EventType::WindowConfigReloaded
131                | EventType::GuiStartup
132        )
133    }
134
135    /// Check if this is a tab-related event
136    pub fn is_tab_event(&self) -> bool {
137        matches!(
138            self,
139            EventType::TabCreated
140                | EventType::TabClosed
141                | EventType::TabSwitched
142                | EventType::NewTabButtonClick
143        )
144    }
145
146    /// Check if this is a pane-related event
147    pub fn is_pane_event(&self) -> bool {
148        matches!(
149            self,
150            EventType::PaneCreated
151                | EventType::PaneClosed
152                | EventType::PaneFocused
153                | EventType::PaneTitleChanged
154        )
155    }
156
157    /// Check if this is a terminal interaction event
158    pub fn is_terminal_event(&self) -> bool {
159        matches!(
160            self,
161            EventType::Bell
162                | EventType::SelectionChanged
163                | EventType::UserVarChanged
164                | EventType::OpenUri
165                | EventType::ScrollbackCleared
166        )
167    }
168
169    /// Check if this is a status/formatting event
170    pub fn is_status_event(&self) -> bool {
171        matches!(
172            self,
173            EventType::UpdateStatus
174                | EventType::UpdateRightStatus
175                | EventType::UpdateLeftStatus
176                | EventType::FormatTabTitle
177                | EventType::FormatWindowTitle
178        )
179    }
180
181    /// Check if this is a legacy hook event
182    pub fn is_legacy_event(&self) -> bool {
183        matches!(
184            self,
185            EventType::Output
186                | EventType::Input
187                | EventType::PreCommand
188                | EventType::PostCommand
189                | EventType::Resize
190                | EventType::Attach
191                | EventType::Detach
192        )
193    }
194
195    /// Check if this is a custom event
196    pub fn is_custom_event(&self) -> bool {
197        matches!(self, EventType::Custom(_))
198    }
199
200    /// Get a human-readable name for this event
201    pub fn name(&self) -> String {
202        match self {
203            EventType::WindowCreated => "window-created".to_string(),
204            EventType::WindowClosed => "window-closed".to_string(),
205            EventType::WindowFocusChanged => "window-focus-changed".to_string(),
206            EventType::WindowResized => "window-resized".to_string(),
207            EventType::WindowConfigReloaded => "window-config-reloaded".to_string(),
208            EventType::GuiStartup => "gui-startup".to_string(),
209            EventType::TabCreated => "tab-created".to_string(),
210            EventType::TabClosed => "tab-closed".to_string(),
211            EventType::TabSwitched => "tab-switched".to_string(),
212            EventType::NewTabButtonClick => "new-tab-button-click".to_string(),
213            EventType::PaneCreated => "pane-created".to_string(),
214            EventType::PaneClosed => "pane-closed".to_string(),
215            EventType::PaneFocused => "pane-focused".to_string(),
216            EventType::PaneTitleChanged => "pane-title-changed".to_string(),
217            EventType::Bell => "bell".to_string(),
218            EventType::SelectionChanged => "selection-changed".to_string(),
219            EventType::UserVarChanged => "user-var-changed".to_string(),
220            EventType::OpenUri => "open-uri".to_string(),
221            EventType::ScrollbackCleared => "scrollback-cleared".to_string(),
222            EventType::UpdateStatus => "update-status".to_string(),
223            EventType::UpdateRightStatus => "update-right-status".to_string(),
224            EventType::UpdateLeftStatus => "update-left-status".to_string(),
225            EventType::FormatTabTitle => "format-tab-title".to_string(),
226            EventType::FormatWindowTitle => "format-window-title".to_string(),
227            EventType::Output => "output".to_string(),
228            EventType::Input => "input".to_string(),
229            EventType::PreCommand => "pre-command".to_string(),
230            EventType::PostCommand => "post-command".to_string(),
231            EventType::Resize => "resize".to_string(),
232            EventType::Attach => "attach".to_string(),
233            EventType::Detach => "detach".to_string(),
234            EventType::Custom(name) => format!("custom:{}", name),
235        }
236    }
237
238    /// Get all standard (non-custom) event types
239    pub fn all_standard() -> Vec<EventType> {
240        vec![
241            // Window events
242            EventType::WindowCreated,
243            EventType::WindowClosed,
244            EventType::WindowFocusChanged,
245            EventType::WindowResized,
246            EventType::WindowConfigReloaded,
247            EventType::GuiStartup,
248            // Tab events
249            EventType::TabCreated,
250            EventType::TabClosed,
251            EventType::TabSwitched,
252            EventType::NewTabButtonClick,
253            // Pane events
254            EventType::PaneCreated,
255            EventType::PaneClosed,
256            EventType::PaneFocused,
257            EventType::PaneTitleChanged,
258            // Terminal events
259            EventType::Bell,
260            EventType::SelectionChanged,
261            EventType::UserVarChanged,
262            EventType::OpenUri,
263            EventType::ScrollbackCleared,
264            // Status events
265            EventType::UpdateStatus,
266            EventType::UpdateRightStatus,
267            EventType::UpdateLeftStatus,
268            EventType::FormatTabTitle,
269            EventType::FormatWindowTitle,
270            // Legacy events
271            EventType::Output,
272            EventType::Input,
273            EventType::PreCommand,
274            EventType::PostCommand,
275            EventType::Resize,
276            EventType::Attach,
277            EventType::Detach,
278        ]
279    }
280
281    /// Convert from legacy HookType to EventType
282    pub fn from_hook_type(hook_type: crate::types::HookType) -> Self {
283        match hook_type {
284            crate::types::HookType::PreOutput => EventType::Output,
285            crate::types::HookType::PostInput => EventType::Input,
286            crate::types::HookType::PreCommand => EventType::PreCommand,
287            crate::types::HookType::PostCommand => EventType::PostCommand,
288            crate::types::HookType::OnResize => EventType::Resize,
289            crate::types::HookType::OnAttach => EventType::Attach,
290            crate::types::HookType::OnDetach => EventType::Detach,
291        }
292    }
293
294    /// Try to convert EventType to legacy HookType (returns None for new events)
295    pub fn to_hook_type(&self) -> Option<crate::types::HookType> {
296        match self {
297            EventType::Output => Some(crate::types::HookType::PreOutput),
298            EventType::Input => Some(crate::types::HookType::PostInput),
299            EventType::PreCommand => Some(crate::types::HookType::PreCommand),
300            EventType::PostCommand => Some(crate::types::HookType::PostCommand),
301            EventType::Resize => Some(crate::types::HookType::OnResize),
302            EventType::Attach => Some(crate::types::HookType::OnAttach),
303            EventType::Detach => Some(crate::types::HookType::OnDetach),
304            _ => None,
305        }
306    }
307}
308
309#[cfg(test)]
310mod tests {
311    use super::*;
312
313    #[test]
314    fn test_event_categorization() {
315        assert!(EventType::WindowCreated.is_window_event());
316        assert!(!EventType::WindowCreated.is_tab_event());
317
318        assert!(EventType::TabSwitched.is_tab_event());
319        assert!(!EventType::TabSwitched.is_pane_event());
320
321        assert!(EventType::PaneFocused.is_pane_event());
322        assert!(!EventType::PaneFocused.is_terminal_event());
323
324        assert!(EventType::Bell.is_terminal_event());
325        assert!(!EventType::Bell.is_status_event());
326
327        assert!(EventType::UpdateStatus.is_status_event());
328        assert!(!EventType::UpdateStatus.is_legacy_event());
329
330        assert!(EventType::Output.is_legacy_event());
331    }
332
333    #[test]
334    fn test_custom_event() {
335        let custom = EventType::Custom("my-event".to_string());
336        assert!(custom.is_custom_event());
337        assert_eq!(custom.name(), "custom:my-event");
338    }
339
340    #[test]
341    fn test_event_names() {
342        assert_eq!(EventType::WindowCreated.name(), "window-created");
343        assert_eq!(EventType::Bell.name(), "bell");
344        assert_eq!(EventType::FormatTabTitle.name(), "format-tab-title");
345    }
346
347    #[test]
348    fn test_hook_type_conversion() {
349        use crate::types::HookType;
350
351        assert_eq!(
352            EventType::from_hook_type(HookType::PreOutput),
353            EventType::Output
354        );
355        assert_eq!(
356            EventType::from_hook_type(HookType::OnResize),
357            EventType::Resize
358        );
359
360        assert_eq!(EventType::Output.to_hook_type(), Some(HookType::PreOutput));
361        assert_eq!(EventType::Bell.to_hook_type(), None);
362    }
363
364    #[test]
365    fn test_all_standard_events() {
366        let events = EventType::all_standard();
367        assert_eq!(events.len(), 31); // 6 window + 4 tab + 4 pane + 5 terminal + 5 status + 7 legacy + 2 custom
368
369        // Verify no duplicates
370        use std::collections::HashSet;
371        let unique: HashSet<_> = events.iter().collect();
372        assert_eq!(unique.len(), events.len());
373    }
374}