Skip to main content

scarab_plugin_api/object_model/
pane.rs

1//! Pane object proxy for Fusabi scripts
2
3use super::{ObjectError, ObjectHandle, ObjectRegistry, ObjectType, Result};
4
5/// Proxy for a terminal pane
6#[derive(Debug, Clone)]
7pub struct PaneProxy {
8    handle: ObjectHandle,
9}
10
11impl PaneProxy {
12    pub fn new(handle: ObjectHandle) -> Result<Self> {
13        if handle.object_type() != ObjectType::Pane {
14            return Err(ObjectError::type_mismatch(
15                handle,
16                ObjectType::Pane,
17                handle.object_type(),
18            ));
19        }
20        Ok(Self { handle })
21    }
22
23    pub fn handle(&self) -> ObjectHandle {
24        self.handle
25    }
26
27    pub fn id(&self) -> u64 {
28        self.handle.id()
29    }
30
31    /// Send text to the pane's PTY
32    pub fn send_text(&self, _text: &str) -> Result<()> {
33        Err(ObjectError::method_not_found(self.handle, "send_text"))
34    }
35
36    /// Send text as paste (bracketed paste mode)
37    pub fn send_paste(&self, _text: &str) -> Result<()> {
38        Err(ObjectError::method_not_found(self.handle, "send_paste"))
39    }
40
41    /// Get the pane title
42    pub fn get_title(&self) -> Result<String> {
43        Err(ObjectError::method_not_found(self.handle, "get_title"))
44    }
45
46    /// Get current working directory
47    pub fn get_current_working_dir(&self) -> Result<Option<String>> {
48        Err(ObjectError::method_not_found(
49            self.handle,
50            "get_current_working_dir",
51        ))
52    }
53
54    /// Get cursor position
55    pub fn get_cursor_position(&self) -> Result<(u16, u16)> {
56        Err(ObjectError::method_not_found(
57            self.handle,
58            "get_cursor_position",
59        ))
60    }
61
62    /// Get pane dimensions in cells
63    pub fn get_dimensions(&self) -> Result<(u16, u16)> {
64        Err(ObjectError::method_not_found(self.handle, "get_dimensions"))
65    }
66
67    /// Get foreground process name
68    pub fn get_foreground_process_name(&self) -> Result<String> {
69        Err(ObjectError::method_not_found(
70            self.handle,
71            "get_foreground_process_name",
72        ))
73    }
74
75    /// Get text from scrollback
76    pub fn get_lines_as_text(&self, _start: i32, _end: i32) -> Result<String> {
77        Err(ObjectError::method_not_found(
78            self.handle,
79            "get_lines_as_text",
80        ))
81    }
82
83    /// Check if alternate screen buffer is active
84    pub fn is_alt_screen_active(&self) -> Result<bool> {
85        Err(ObjectError::method_not_found(
86            self.handle,
87            "is_alt_screen_active",
88        ))
89    }
90
91    /// Check for unseen output
92    pub fn has_unseen_output(&self) -> Result<bool> {
93        Err(ObjectError::method_not_found(
94            self.handle,
95            "has_unseen_output",
96        ))
97    }
98
99    /// Focus this pane
100    pub fn activate(&self) -> Result<()> {
101        Err(ObjectError::method_not_found(self.handle, "activate"))
102    }
103
104    /// Get parent tab
105    ///
106    /// # Arguments
107    ///
108    /// * `registry` - The object registry to use for navigation
109    ///
110    /// # Returns
111    ///
112    /// * `Ok(TabProxy)` - The tab containing this pane
113    /// * `Err(ObjectError)` - If the pane has no parent or navigation fails
114    pub fn tab<T>(&self, registry: &impl ObjectRegistry<T>) -> Result<TabProxy> {
115        let parent_handle = registry
116            .get_parent(&self.handle)?
117            .ok_or_else(|| ObjectError::method_not_found(self.handle, "tab: no parent"))?;
118
119        if parent_handle.object_type() != ObjectType::Tab {
120            return Err(ObjectError::type_mismatch(
121                parent_handle,
122                ObjectType::Tab,
123                parent_handle.object_type(),
124            ));
125        }
126
127        TabProxy::new(parent_handle)
128    }
129
130    /// Get parent window
131    ///
132    /// # Arguments
133    ///
134    /// * `registry` - The object registry to use for navigation
135    ///
136    /// # Returns
137    ///
138    /// * `Ok(WindowProxy)` - The window containing this pane
139    /// * `Err(ObjectError)` - If navigation fails
140    pub fn window<T>(&self, registry: &impl ObjectRegistry<T>) -> Result<WindowProxy> {
141        // Navigate up through tab to window
142        let tab = self.tab(registry)?;
143        tab.window(registry)
144    }
145}
146
147// Forward declarations for cross-references
148use super::TabProxy;
149use super::WindowProxy;
150
151#[cfg(test)]
152mod tests {
153    use super::*;
154
155    #[test]
156    fn test_pane_proxy_creation() {
157        let handle = ObjectHandle::new(ObjectType::Pane, 1, 0);
158        let proxy = PaneProxy::new(handle);
159        assert!(proxy.is_ok());
160
161        let proxy = proxy.unwrap();
162        assert_eq!(proxy.id(), 1);
163        assert_eq!(proxy.handle(), handle);
164    }
165
166    #[test]
167    fn test_pane_proxy_type_validation() {
168        let window_handle = ObjectHandle::new(ObjectType::Window, 1, 0);
169        let result = PaneProxy::new(window_handle);
170
171        assert!(result.is_err());
172        match result {
173            Err(ObjectError::TypeMismatch {
174                expected, actual, ..
175            }) => {
176                assert_eq!(expected, ObjectType::Pane);
177                assert_eq!(actual, ObjectType::Window);
178            }
179            _ => panic!("Expected TypeMismatch error"),
180        }
181    }
182
183    #[test]
184    fn test_pane_proxy_methods_not_implemented() {
185        let handle = ObjectHandle::new(ObjectType::Pane, 1, 0);
186        let proxy = PaneProxy::new(handle).unwrap();
187
188        // All methods should return MethodNotFound errors
189        assert!(matches!(
190            proxy.send_text("test"),
191            Err(ObjectError::MethodNotFound { .. })
192        ));
193        assert!(matches!(
194            proxy.send_paste("test"),
195            Err(ObjectError::MethodNotFound { .. })
196        ));
197        assert!(matches!(
198            proxy.get_title(),
199            Err(ObjectError::MethodNotFound { .. })
200        ));
201        assert!(matches!(
202            proxy.get_current_working_dir(),
203            Err(ObjectError::MethodNotFound { .. })
204        ));
205        assert!(matches!(
206            proxy.get_cursor_position(),
207            Err(ObjectError::MethodNotFound { .. })
208        ));
209        assert!(matches!(
210            proxy.get_dimensions(),
211            Err(ObjectError::MethodNotFound { .. })
212        ));
213        assert!(matches!(
214            proxy.get_foreground_process_name(),
215            Err(ObjectError::MethodNotFound { .. })
216        ));
217        assert!(matches!(
218            proxy.get_lines_as_text(0, 10),
219            Err(ObjectError::MethodNotFound { .. })
220        ));
221        assert!(matches!(
222            proxy.is_alt_screen_active(),
223            Err(ObjectError::MethodNotFound { .. })
224        ));
225        assert!(matches!(
226            proxy.has_unseen_output(),
227            Err(ObjectError::MethodNotFound { .. })
228        ));
229        assert!(matches!(
230            proxy.activate(),
231            Err(ObjectError::MethodNotFound { .. })
232        ));
233    }
234
235    #[test]
236    fn test_pane_proxy_clone() {
237        let handle = ObjectHandle::new(ObjectType::Pane, 1, 0);
238        let proxy1 = PaneProxy::new(handle).unwrap();
239        let proxy2 = proxy1.clone();
240
241        assert_eq!(proxy1.handle(), proxy2.handle());
242        assert_eq!(proxy1.id(), proxy2.id());
243    }
244
245    // Test navigation methods with mock registry
246    use super::super::registry::{ObjectRegistry, RegistryEntry};
247    use std::collections::HashMap;
248
249    #[derive(Clone)]
250    struct TestObject;
251
252    struct TestRegistry {
253        objects: HashMap<u64, RegistryEntry<TestObject>>,
254        parents: HashMap<u64, ObjectHandle>,
255        children: HashMap<u64, Vec<ObjectHandle>>,
256        generations: HashMap<u64, u32>,
257    }
258
259    impl TestRegistry {
260        fn new() -> Self {
261            Self {
262                objects: HashMap::new(),
263                parents: HashMap::new(),
264                children: HashMap::new(),
265                generations: HashMap::new(),
266            }
267        }
268
269        fn set_parent(&mut self, child: ObjectHandle, parent: ObjectHandle) {
270            self.parents.insert(child.id(), parent);
271            self.children
272                .entry(parent.id())
273                .or_insert_with(Vec::new)
274                .push(child);
275        }
276    }
277
278    impl ObjectRegistry<TestObject> for TestRegistry {
279        fn register(&mut self, object: TestObject) -> ObjectHandle {
280            let id = self.objects.len() as u64 + 1;
281            let generation = self.current_generation(id);
282            let handle = ObjectHandle::new(ObjectType::Pane, id, generation);
283            self.objects.insert(id, RegistryEntry::new(handle, object));
284            handle
285        }
286
287        fn unregister(&mut self, handle: ObjectHandle) -> Result<TestObject> {
288            self.increment_generation(handle.id());
289            self.objects
290                .remove(&handle.id())
291                .map(|entry| entry.into_object())
292                .ok_or_else(|| ObjectError::not_found(handle))
293        }
294
295        fn get(&self, handle: ObjectHandle) -> Result<&TestObject> {
296            let current_gen = self.current_generation(handle.id());
297            if !handle.is_valid(current_gen) {
298                return Err(ObjectError::stale_handle(handle, current_gen));
299            }
300            self.objects
301                .get(&handle.id())
302                .map(|entry| entry.object())
303                .ok_or_else(|| ObjectError::not_found(handle))
304        }
305
306        fn get_mut(&mut self, handle: ObjectHandle) -> Result<&mut TestObject> {
307            let current_gen = self.current_generation(handle.id());
308            if !handle.is_valid(current_gen) {
309                return Err(ObjectError::stale_handle(handle, current_gen));
310            }
311            self.objects
312                .get_mut(&handle.id())
313                .map(|entry| entry.object_mut())
314                .ok_or_else(|| ObjectError::not_found(handle))
315        }
316
317        fn get_by_id(&self, id: u64) -> Option<&TestObject> {
318            self.objects.get(&id).map(|entry| entry.object())
319        }
320
321        fn next_id(&mut self) -> u64 {
322            self.objects.len() as u64 + 1
323        }
324
325        fn increment_generation(&mut self, id: u64) {
326            let gen = self.generations.entry(id).or_insert(0);
327            *gen = gen.wrapping_add(1);
328        }
329
330        fn current_generation(&self, id: u64) -> u32 {
331            self.generations.get(&id).copied().unwrap_or(0)
332        }
333
334        fn all_ids(&self) -> Vec<u64> {
335            self.objects.keys().copied().collect()
336        }
337
338        fn len(&self) -> usize {
339            self.objects.len()
340        }
341
342        fn get_parent(&self, handle: &ObjectHandle) -> Result<Option<ObjectHandle>> {
343            let current_gen = self.current_generation(handle.id());
344            if !handle.is_valid(current_gen) {
345                return Err(ObjectError::stale_handle(*handle, current_gen));
346            }
347            if !self.objects.contains_key(&handle.id()) {
348                return Err(ObjectError::not_found(*handle));
349            }
350            Ok(self.parents.get(&handle.id()).copied())
351        }
352
353        fn get_children(&self, handle: &ObjectHandle) -> Result<Vec<ObjectHandle>> {
354            let current_gen = self.current_generation(handle.id());
355            if !handle.is_valid(current_gen) {
356                return Err(ObjectError::stale_handle(*handle, current_gen));
357            }
358            if !self.objects.contains_key(&handle.id()) {
359                return Err(ObjectError::not_found(*handle));
360            }
361            Ok(self
362                .children
363                .get(&handle.id())
364                .cloned()
365                .unwrap_or_else(Vec::new))
366        }
367    }
368
369    #[test]
370    fn test_pane_navigation_to_tab() {
371        let mut registry = TestRegistry::new();
372
373        // Create a pane and a tab
374        let pane_handle = ObjectHandle::new(ObjectType::Pane, 1, 0);
375        let tab_handle = ObjectHandle::new(ObjectType::Tab, 2, 0);
376
377        registry
378            .objects
379            .insert(1, RegistryEntry::new(pane_handle, TestObject));
380        registry
381            .objects
382            .insert(2, RegistryEntry::new(tab_handle, TestObject));
383
384        // Set tab as parent of pane
385        registry.set_parent(pane_handle, tab_handle);
386
387        let pane_proxy = PaneProxy::new(pane_handle).unwrap();
388        let tab_proxy = pane_proxy.tab(&registry).unwrap();
389
390        assert_eq!(tab_proxy.handle(), tab_handle);
391        assert_eq!(tab_proxy.id(), 2);
392    }
393
394    #[test]
395    fn test_pane_navigation_to_window() {
396        let mut registry = TestRegistry::new();
397
398        // Create a pane, tab, and window
399        let pane_handle = ObjectHandle::new(ObjectType::Pane, 1, 0);
400        let tab_handle = ObjectHandle::new(ObjectType::Tab, 2, 0);
401        let window_handle = ObjectHandle::new(ObjectType::Window, 3, 0);
402
403        registry
404            .objects
405            .insert(1, RegistryEntry::new(pane_handle, TestObject));
406        registry
407            .objects
408            .insert(2, RegistryEntry::new(tab_handle, TestObject));
409        registry
410            .objects
411            .insert(3, RegistryEntry::new(window_handle, TestObject));
412
413        // Set hierarchy: pane -> tab -> window
414        registry.set_parent(pane_handle, tab_handle);
415        registry.set_parent(tab_handle, window_handle);
416
417        let pane_proxy = PaneProxy::new(pane_handle).unwrap();
418        let window_proxy = pane_proxy.window(&registry).unwrap();
419
420        assert_eq!(window_proxy.handle(), window_handle);
421        assert_eq!(window_proxy.id(), 3);
422    }
423
424    #[test]
425    fn test_pane_navigation_no_parent() {
426        let registry = TestRegistry::new();
427
428        let pane_handle = ObjectHandle::new(ObjectType::Pane, 1, 0);
429        let pane_proxy = PaneProxy::new(pane_handle).unwrap();
430
431        // Pane has no parent
432        let result = pane_proxy.tab(&registry);
433        assert!(result.is_err());
434    }
435
436    #[test]
437    fn test_pane_navigation_wrong_parent_type() {
438        let mut registry = TestRegistry::new();
439
440        // Create a pane with a window as direct parent (wrong!)
441        let pane_handle = ObjectHandle::new(ObjectType::Pane, 1, 0);
442        let window_handle = ObjectHandle::new(ObjectType::Window, 2, 0);
443
444        registry
445            .objects
446            .insert(1, RegistryEntry::new(pane_handle, TestObject));
447        registry
448            .objects
449            .insert(2, RegistryEntry::new(window_handle, TestObject));
450
451        // Set window as parent of pane (should be tab)
452        registry.set_parent(pane_handle, window_handle);
453
454        let pane_proxy = PaneProxy::new(pane_handle).unwrap();
455        let result = pane_proxy.tab(&registry);
456
457        // Should fail with type mismatch
458        assert!(matches!(result, Err(ObjectError::TypeMismatch { .. })));
459    }
460}