Skip to main content

scarab_plugin_api/events/
args.rs

1//! Event argument types
2//!
3//! Defines the data structures passed to event handlers, including context
4//! about the terminal objects involved and event-specific data.
5
6use super::EventType;
7use crate::object_model::ObjectHandle;
8use std::time::Instant;
9
10/// Event arguments passed to handlers
11///
12/// Contains all contextual information about an event, including:
13/// - The event type
14/// - Handles to affected terminal objects (window, pane, tab)
15/// - Event-specific data payload
16/// - Timestamp when the event occurred
17#[derive(Clone, Debug)]
18pub struct EventArgs {
19    /// Type of event being fired
20    pub event_type: EventType,
21
22    /// Handle to the window associated with this event (if any)
23    pub window: Option<ObjectHandle>,
24
25    /// Handle to the pane associated with this event (if any)
26    pub pane: Option<ObjectHandle>,
27
28    /// Handle to the tab associated with this event (if any)
29    pub tab: Option<ObjectHandle>,
30
31    /// Event-specific data payload
32    pub data: EventData,
33
34    /// When this event was created
35    pub timestamp: Instant,
36}
37
38impl EventArgs {
39    /// Create new event arguments
40    pub fn new(event_type: EventType) -> Self {
41        Self {
42            event_type,
43            window: None,
44            pane: None,
45            tab: None,
46            data: EventData::None,
47            timestamp: Instant::now(),
48        }
49    }
50
51    /// Set the window handle (builder pattern)
52    pub fn with_window(mut self, handle: ObjectHandle) -> Self {
53        self.window = Some(handle);
54        self
55    }
56
57    /// Set the pane handle (builder pattern)
58    pub fn with_pane(mut self, handle: ObjectHandle) -> Self {
59        self.pane = Some(handle);
60        self
61    }
62
63    /// Set the tab handle (builder pattern)
64    pub fn with_tab(mut self, handle: ObjectHandle) -> Self {
65        self.tab = Some(handle);
66        self
67    }
68
69    /// Set the event data (builder pattern)
70    pub fn with_data(mut self, data: EventData) -> Self {
71        self.data = data;
72        self
73    }
74
75    /// Get a reference to the event data
76    pub fn data(&self) -> &EventData {
77        &self.data
78    }
79
80    /// Check if this event has window context
81    pub fn has_window(&self) -> bool {
82        self.window.is_some()
83    }
84
85    /// Check if this event has pane context
86    pub fn has_pane(&self) -> bool {
87        self.pane.is_some()
88    }
89
90    /// Check if this event has tab context
91    pub fn has_tab(&self) -> bool {
92        self.tab.is_some()
93    }
94
95    /// Get the age of this event
96    pub fn age(&self) -> std::time::Duration {
97        self.timestamp.elapsed()
98    }
99}
100
101/// Event-specific data payload
102///
103/// Different events carry different types of data. This enum provides
104/// type-safe access to event payloads.
105#[derive(Clone, Debug, Default)]
106pub enum EventData {
107    /// No data
108    #[default]
109    None,
110
111    /// Plain text data (for output, input, etc.)
112    Text(String),
113
114    /// URI/URL data (for OpenUri events)
115    Uri(String),
116
117    /// Focus state change
118    FocusState {
119        /// Whether the object now has focus
120        is_focused: bool,
121    },
122
123    /// Dimension change (for resize events)
124    Dimensions {
125        /// New width in columns
126        cols: u16,
127        /// New height in rows
128        rows: u16,
129    },
130
131    /// Title change event
132    TitleChange {
133        /// Previous title
134        old: String,
135        /// New title
136        new: String,
137    },
138
139    /// User variable change (OSC 1337)
140    UserVar {
141        /// Variable name
142        name: String,
143        /// Variable value
144        value: String,
145    },
146
147    /// Text selection data
148    Selection {
149        /// Selected text content
150        text: String,
151        /// Selection start position (col, row)
152        start: (u16, u16),
153        /// Selection end position (col, row)
154        end: (u16, u16),
155    },
156
157    /// Process exit code
158    ExitCode(i32),
159
160    /// Raw binary data (for legacy hooks)
161    Binary(Vec<u8>),
162}
163
164impl EventData {
165    /// Try to extract text data
166    pub fn as_text(&self) -> Option<&str> {
167        match self {
168            EventData::Text(s) => Some(s),
169            _ => None,
170        }
171    }
172
173    /// Try to extract URI data
174    pub fn as_uri(&self) -> Option<&str> {
175        match self {
176            EventData::Uri(s) => Some(s),
177            _ => None,
178        }
179    }
180
181    /// Try to extract focus state
182    pub fn as_focus_state(&self) -> Option<bool> {
183        match self {
184            EventData::FocusState { is_focused } => Some(*is_focused),
185            _ => None,
186        }
187    }
188
189    /// Try to extract dimensions
190    pub fn as_dimensions(&self) -> Option<(u16, u16)> {
191        match self {
192            EventData::Dimensions { cols, rows } => Some((*cols, *rows)),
193            _ => None,
194        }
195    }
196
197    /// Try to extract title change data
198    pub fn as_title_change(&self) -> Option<(&str, &str)> {
199        match self {
200            EventData::TitleChange { old, new } => Some((old, new)),
201            _ => None,
202        }
203    }
204
205    /// Try to extract user variable data
206    pub fn as_user_var(&self) -> Option<(&str, &str)> {
207        match self {
208            EventData::UserVar { name, value } => Some((name, value)),
209            _ => None,
210        }
211    }
212
213    /// Try to extract selection data
214    pub fn as_selection(&self) -> Option<(&str, (u16, u16), (u16, u16))> {
215        match self {
216            EventData::Selection { text, start, end } => Some((text, *start, *end)),
217            _ => None,
218        }
219    }
220
221    /// Try to extract exit code
222    pub fn as_exit_code(&self) -> Option<i32> {
223        match self {
224            EventData::ExitCode(code) => Some(*code),
225            _ => None,
226        }
227    }
228
229    /// Try to extract binary data
230    pub fn as_binary(&self) -> Option<&[u8]> {
231        match self {
232            EventData::Binary(data) => Some(data),
233            _ => None,
234        }
235    }
236
237    /// Check if this is empty/none data
238    pub fn is_none(&self) -> bool {
239        matches!(self, EventData::None)
240    }
241}
242
243#[cfg(test)]
244mod tests {
245    use super::*;
246    use crate::object_model::ObjectType;
247
248    #[test]
249    fn test_event_args_builder() {
250        let window = ObjectHandle::new(ObjectType::Window, 1, 0);
251        let pane = ObjectHandle::new(ObjectType::Pane, 2, 0);
252
253        let args = EventArgs::new(EventType::PaneFocused)
254            .with_window(window)
255            .with_pane(pane)
256            .with_data(EventData::FocusState { is_focused: true });
257
258        assert_eq!(args.event_type, EventType::PaneFocused);
259        assert_eq!(args.window, Some(window));
260        assert_eq!(args.pane, Some(pane));
261        assert!(args.has_window());
262        assert!(args.has_pane());
263        assert!(!args.has_tab());
264        assert!(args.data.as_focus_state().unwrap());
265    }
266
267    #[test]
268    fn test_event_data_text() {
269        let data = EventData::Text("hello".to_string());
270        assert_eq!(data.as_text(), Some("hello"));
271        assert!(data.as_uri().is_none());
272        assert!(!data.is_none());
273    }
274
275    #[test]
276    fn test_event_data_dimensions() {
277        let data = EventData::Dimensions { cols: 80, rows: 24 };
278        assert_eq!(data.as_dimensions(), Some((80, 24)));
279        assert!(data.as_text().is_none());
280    }
281
282    #[test]
283    fn test_event_data_selection() {
284        let data = EventData::Selection {
285            text: "selected".to_string(),
286            start: (0, 0),
287            end: (8, 0),
288        };
289
290        let (text, start, end) = data.as_selection().unwrap();
291        assert_eq!(text, "selected");
292        assert_eq!(start, (0, 0));
293        assert_eq!(end, (8, 0));
294    }
295
296    #[test]
297    fn test_event_data_user_var() {
298        let data = EventData::UserVar {
299            name: "CWD".to_string(),
300            value: "/home/user".to_string(),
301        };
302
303        let (name, value) = data.as_user_var().unwrap();
304        assert_eq!(name, "CWD");
305        assert_eq!(value, "/home/user");
306    }
307
308    #[test]
309    fn test_event_data_title_change() {
310        let data = EventData::TitleChange {
311            old: "old title".to_string(),
312            new: "new title".to_string(),
313        };
314
315        let (old, new) = data.as_title_change().unwrap();
316        assert_eq!(old, "old title");
317        assert_eq!(new, "new title");
318    }
319
320    #[test]
321    fn test_event_data_exit_code() {
322        let data = EventData::ExitCode(42);
323        assert_eq!(data.as_exit_code(), Some(42));
324    }
325
326    #[test]
327    fn test_event_data_binary() {
328        let bytes = vec![1, 2, 3, 4];
329        let data = EventData::Binary(bytes.clone());
330        assert_eq!(data.as_binary(), Some(bytes.as_slice()));
331    }
332
333    #[test]
334    fn test_event_args_age() {
335        let args = EventArgs::new(EventType::Bell);
336        std::thread::sleep(std::time::Duration::from_millis(10));
337        assert!(args.age().as_millis() >= 10);
338    }
339}