Skip to main content

scarab_plugin_api/object_model/
tab.rs

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