Skip to main content

ratatui_toolkit/master_layout/
layout_manager.rs

1//! Master Layout - Top-level orchestrator for tabs, navigation, and modes
2
3use super::{InteractionMode, PaneId, Tab};
4use crate::{MenuBar, MenuItem};
5use crossterm::event::{
6    Event, KeyCode, KeyEvent, KeyModifiers, MouseButton, MouseEvent, MouseEventKind,
7};
8use ratatui::layout::{Constraint, Direction, Layout, Rect};
9
10/// Result of event handling
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum EventResult {
13    /// Event was consumed by the master layout or a pane
14    Consumed,
15    /// Event was not handled
16    NotHandled,
17    /// Application should quit
18    Quit,
19}
20
21/// Master Layout - orchestrates the entire TUI application
22///
23/// # Architecture
24///
25/// ```text
26/// ┌─────────────────────────────────┐
27/// │ Navigation Bar (1 row + border) │ ← 3 rows total
28/// ├─────────────────────────────────┤
29/// │                                 │
30/// │         Active Tab              │ ← Remaining space
31/// │  (panes + footer)               │
32/// │                                 │
33/// └─────────────────────────────────┘
34/// ```
35///
36/// # Modes
37///
38/// - **Layout Mode**: Navigate panes with hjkl, Tab/Shift+Tab, Enter to focus
39/// - **Focus Mode**: All input goes to focused pane, Ctrl-A to exit
40///
41/// # Key Bindings
42///
43/// **Global (always work)**:
44/// - Ctrl+Q: Quit
45/// - 1-9: Switch tabs
46///
47/// **Layout Mode**:
48/// - h/j/k/l: Directional navigation
49/// - Tab: Next pane
50/// - Shift+Tab: Previous pane
51/// - Enter: Focus selected pane
52///
53/// **Focus Mode**:
54/// - Ctrl-A: Exit to Layout Mode
55/// - All other keys: Route to focused pane
56/// - Mouse on same pane: Route to pane
57/// - Mouse on different pane: Change focus
58pub struct MasterLayout {
59    nav_bar: MenuBar,
60    tabs: Vec<Tab>,
61    active_tab_index: usize,
62    mode: InteractionMode,
63    global_area: Rect,
64    nav_bar_offset: u16,
65}
66
67impl MasterLayout {
68    /// Create a new empty master layout
69    pub fn new() -> Self {
70        Self {
71            nav_bar: MenuBar::new(Vec::new()),
72            tabs: Vec::new(),
73            active_tab_index: 0,
74            mode: InteractionMode::default(),
75            global_area: Rect::default(),
76            nav_bar_offset: 0,
77        }
78    }
79
80    /// Set the navigation bar left offset (to make room for other components like IconNavBar)
81    pub fn set_nav_bar_offset(&mut self, offset: u16) {
82        self.nav_bar_offset = offset;
83    }
84
85    /// Add a tab to the master layout
86    pub fn add_tab(&mut self, tab: Tab) {
87        self.tabs.push(tab);
88
89        // Rebuild nav bar with updated tab names as menu items
90        let menu_items: Vec<MenuItem> = self
91            .tabs
92            .iter()
93            .enumerate()
94            .map(|(i, t)| MenuItem::new(t.name(), i))
95            .collect();
96        self.nav_bar = MenuBar::new(menu_items).with_selected(self.active_tab_index);
97
98        // If this is the first tab and we have panes, select the first pane
99        if self.tabs.len() == 1 {
100            self.select_first_pane_in_active_tab();
101        }
102    }
103
104    /// Get number of tabs
105    pub fn tab_count(&self) -> usize {
106        self.tabs.len()
107    }
108
109    /// Get active tab index
110    pub fn active_tab_index(&self) -> usize {
111        self.active_tab_index
112    }
113
114    /// Set active tab by index
115    pub fn set_active_tab(&mut self, index: usize) {
116        if index < self.tabs.len() {
117            self.active_tab_index = index;
118            // Update menu bar selection
119            for (i, item) in self.nav_bar.items.iter_mut().enumerate() {
120                item.selected = i == index;
121            }
122
123            // When switching tabs, select first pane in new tab
124            self.select_first_pane_in_active_tab();
125        }
126    }
127
128    /// Get reference to active tab
129    pub fn active_tab(&self) -> Option<&Tab> {
130        self.tabs.get(self.active_tab_index)
131    }
132
133    /// Get mutable reference to active tab
134    pub fn active_tab_mut(&mut self) -> Option<&mut Tab> {
135        self.tabs.get_mut(self.active_tab_index)
136    }
137
138    /// Get current interaction mode
139    pub fn mode(&self) -> &InteractionMode {
140        &self.mode
141    }
142
143    /// Enter Layout Mode
144    pub fn enter_layout_mode(&mut self) {
145        if let Some(focused_id) = self.mode.focused_pane() {
146            // If we're in focus mode, transition to layout mode with selection
147            self.mode = InteractionMode::layout_with_selection(focused_id);
148        } else {
149            // Just ensure we're in layout mode
150            if !self.mode.is_layout() {
151                self.mode = InteractionMode::layout();
152            }
153        }
154    }
155
156    /// Enter Focus Mode with a specific pane
157    pub fn enter_focus_mode(&mut self, pane_id: PaneId) {
158        self.mode = InteractionMode::focus(pane_id);
159    }
160
161    /// Exit Focus Mode (Ctrl-A) - returns to Layout Mode
162    pub fn exit_focus_mode(&mut self) {
163        self.mode.exit_focus();
164    }
165
166    /// Select the first focusable pane in the active tab
167    fn select_first_pane_in_active_tab(&mut self) {
168        if let Some(tab) = self.active_tab() {
169            let container = tab.pane_container();
170            if let Some(first_pane) = container.select_next(None) {
171                self.mode = InteractionMode::layout_with_selection(first_pane);
172            }
173        }
174    }
175
176    /// Select next pane (Tab key in Layout Mode)
177    pub fn select_next_pane(&mut self) {
178        if !self.mode.is_layout() {
179            return;
180        }
181
182        if let Some(tab) = self.active_tab() {
183            let current = self.mode.selected_pane();
184            if let Some(next) = tab.pane_container().select_next(current) {
185                self.mode.select_pane(next);
186            }
187        }
188    }
189
190    /// Select previous pane (Shift+Tab key in Layout Mode)
191    pub fn select_prev_pane(&mut self) {
192        if !self.mode.is_layout() {
193            return;
194        }
195
196        if let Some(tab) = self.active_tab() {
197            let current = self.mode.selected_pane();
198            if let Some(prev) = tab.pane_container().select_prev(current) {
199                self.mode.select_pane(prev);
200            }
201        }
202    }
203
204    /// Select pane to the left (h key in Layout Mode)
205    pub fn select_left(&mut self) {
206        if !self.mode.is_layout() {
207            return;
208        }
209
210        if let Some(tab) = self.active_tab() {
211            if let Some(current) = self.mode.selected_pane() {
212                if let Some(left) = tab.pane_container().select_left(current) {
213                    self.mode.select_pane(left);
214                }
215            }
216        }
217    }
218
219    /// Select pane to the right (l key in Layout Mode)
220    pub fn select_right(&mut self) {
221        if !self.mode.is_layout() {
222            return;
223        }
224
225        if let Some(tab) = self.active_tab() {
226            if let Some(current) = self.mode.selected_pane() {
227                if let Some(right) = tab.pane_container().select_right(current) {
228                    self.mode.select_pane(right);
229                }
230            }
231        }
232    }
233
234    /// Select pane above (k key in Layout Mode)
235    pub fn select_up(&mut self) {
236        if !self.mode.is_layout() {
237            return;
238        }
239
240        if let Some(tab) = self.active_tab() {
241            if let Some(current) = self.mode.selected_pane() {
242                if let Some(up) = tab.pane_container().select_up(current) {
243                    self.mode.select_pane(up);
244                }
245            }
246        }
247    }
248
249    /// Select pane below (j key in Layout Mode)
250    pub fn select_down(&mut self) {
251        if !self.mode.is_layout() {
252            return;
253        }
254
255        if let Some(tab) = self.active_tab() {
256            if let Some(current) = self.mode.selected_pane() {
257                if let Some(down) = tab.pane_container().select_down(current) {
258                    self.mode.select_pane(down);
259                }
260            }
261        }
262    }
263
264    /// Focus the currently selected pane (Enter key in Layout Mode)
265    pub fn focus_selected(&mut self) {
266        if !self.mode.is_layout() {
267            return;
268        }
269
270        if let Some(selected) = self.mode.selected_pane() {
271            self.enter_focus_mode(selected);
272        }
273    }
274
275    /// Handle a crossterm event
276    pub fn handle_event(&mut self, event: Event) -> EventResult {
277        match event {
278            Event::Key(key) => self.handle_key_event(key),
279            Event::Mouse(mouse) => self.handle_mouse_event(mouse),
280            Event::Resize(_, _) => EventResult::Consumed,
281            _ => EventResult::NotHandled,
282        }
283    }
284
285    /// Handle keyboard events
286    fn handle_key_event(&mut self, key: KeyEvent) -> EventResult {
287        // Mode-specific handling
288        match self.mode.clone() {
289            InteractionMode::Layout { selected_pane } => {
290                // In Layout Mode: Q quits the application (case-insensitive: both 'q' and 'Q')
291                if matches!(key.code, KeyCode::Char('q') | KeyCode::Char('Q'))
292                    && key.modifiers.is_empty()
293                {
294                    return EventResult::Quit;
295                }
296
297                // In Layout Mode: Handle Ctrl+Shift+C and Esc for text selection if pane has selection
298                if let Some(pane_id) = selected_pane {
299                    // Handle Ctrl+Shift+C (copy selection) - only if pane has selection
300                    if key.code == KeyCode::Char('c')
301                        && key.modifiers.contains(KeyModifiers::CONTROL)
302                        && key.modifiers.contains(KeyModifiers::SHIFT)
303                    {
304                        if let Some(tab) = self.active_tab_mut() {
305                            if let Some(pane) = tab.pane_container_mut().get_pane_mut(pane_id) {
306                                // Only route to pane if it has a selection
307                                if pane.has_selection() && pane.handle_key(key) {
308                                    return EventResult::Consumed;
309                                }
310                            }
311                        }
312                    }
313
314                    // Handle Esc (clear selection) - only if pane has selection
315                    if key.code == KeyCode::Esc {
316                        if let Some(tab) = self.active_tab_mut() {
317                            if let Some(pane) = tab.pane_container_mut().get_pane_mut(pane_id) {
318                                // Only route to pane if it has a selection to clear
319                                if pane.has_selection() && pane.handle_key(key) {
320                                    return EventResult::Consumed;
321                                }
322                            }
323                        }
324                        // If no selection to clear, fall through to let Layout Mode handle Esc
325                    }
326                }
327
328                // Handle other Layout Mode keys (hjkl navigation, Enter, tab switching, etc.)
329                self.handle_layout_mode_key(key)
330            }
331            InteractionMode::Focus { focused_pane } => {
332                // ONLY Ctrl-A exits focus mode - EVERYTHING else goes to the pane
333                // This includes 'q', 'h', 'j', 'k', 'l', numbers, etc.
334                if key.code == KeyCode::Char('a') && key.modifiers.contains(KeyModifiers::CONTROL) {
335                    self.exit_focus_mode();
336                    return EventResult::Consumed;
337                }
338
339                // ALL other keys (including 'q', Ctrl+C, Ctrl+W, Esc, etc.) go to focused pane
340                if let Some(tab) = self.active_tab_mut() {
341                    if let Some(pane) = tab.pane_container_mut().get_pane_mut(focused_pane) {
342                        if pane.handle_key(key) {
343                            return EventResult::Consumed;
344                        }
345                    }
346                }
347
348                EventResult::NotHandled
349            }
350        }
351    }
352
353    /// Handle keyboard events in Layout Mode
354    fn handle_layout_mode_key(&mut self, key: KeyEvent) -> EventResult {
355        match key.code {
356            // Esc: Clear selection (exit command mode)
357            KeyCode::Esc => {
358                self.mode = InteractionMode::Layout {
359                    selected_pane: None,
360                };
361                EventResult::Consumed
362            }
363            // Ctrl-A: Clear selection in Layout Mode (deselect pane)
364            KeyCode::Char('a') if key.modifiers.contains(KeyModifiers::CONTROL) => {
365                self.mode = InteractionMode::Layout {
366                    selected_pane: None,
367                };
368                EventResult::Consumed
369            }
370            // 1-9: Switch tabs (only in Layout Mode)
371            KeyCode::Char(c) if c.is_ascii_digit() => {
372                let digit = c.to_digit(10).unwrap() as usize;
373                if digit >= 1 && digit <= self.tab_count() {
374                    self.set_active_tab(digit - 1);
375                    return EventResult::Consumed;
376                }
377                EventResult::NotHandled
378            }
379            // hjkl: Directional navigation (ONLY way to navigate panes)
380            KeyCode::Char('h') => {
381                self.select_left();
382                EventResult::Consumed
383            }
384            KeyCode::Char('j') => {
385                self.select_down();
386                EventResult::Consumed
387            }
388            KeyCode::Char('k') => {
389                self.select_up();
390                EventResult::Consumed
391            }
392            KeyCode::Char('l') => {
393                self.select_right();
394                EventResult::Consumed
395            }
396            // Enter: Focus selected pane
397            KeyCode::Enter => {
398                self.focus_selected();
399                EventResult::Consumed
400            }
401            _ => EventResult::NotHandled,
402        }
403    }
404
405    /// Handle mouse events
406    fn handle_mouse_event(&mut self, mouse: MouseEvent) -> EventResult {
407        // Check if click is on navigation bar
408        if mouse.kind == MouseEventKind::Down(MouseButton::Left) {
409            // Nav bar is in top 3 rows
410            if mouse.row < 3 {
411                if let Some(tab_index) = self.nav_bar.handle_click(mouse.column, mouse.row) {
412                    self.set_active_tab(tab_index);
413                    return EventResult::Consumed;
414                }
415            }
416        }
417
418        // Handle pane resizing (only in Layout Mode)
419        if matches!(self.mode, InteractionMode::Layout { .. }) {
420            if let Some(tab) = self.active_tab_mut() {
421                let container = tab.pane_container_mut();
422
423                match mouse.kind {
424                    MouseEventKind::Down(MouseButton::Left) => {
425                        // Check if clicking on a divider
426                        if let Some(divider_idx) =
427                            container.find_divider_at(mouse.column, mouse.row)
428                        {
429                            container.start_drag(divider_idx);
430                            return EventResult::Consumed;
431                        }
432                    }
433                    MouseEventKind::Drag(MouseButton::Left) => {
434                        // Update drag if dragging
435                        if container.is_dragging() {
436                            container.update_drag(mouse.column, mouse.row);
437                            return EventResult::Consumed;
438                        }
439                    }
440                    MouseEventKind::Up(MouseButton::Left) => {
441                        // Stop dragging
442                        if container.is_dragging() {
443                            container.stop_drag();
444                            return EventResult::Consumed;
445                        }
446                    }
447                    MouseEventKind::Moved => {
448                        // Update hover state
449                        container.update_hover(mouse.column, mouse.row);
450                    }
451                    _ => {}
452                }
453            }
454        }
455
456        // Handle text selection in focused/selected pane
457        match mouse.kind {
458            MouseEventKind::Down(MouseButton::Left) if mouse.row >= 3 => {
459                // Get the currently selected/focused pane BEFORE borrowing
460                let current_pane = match &self.mode {
461                    InteractionMode::Layout { selected_pane } => *selected_pane,
462                    InteractionMode::Focus { focused_pane } => Some(*focused_pane),
463                };
464
465                // Check if clicking on a pane
466                if let Some(tab) = self.active_tab_mut() {
467                    if let Some(pane_id) =
468                        tab.pane_container().find_pane_at(mouse.column, mouse.row)
469                    {
470                        // If clicking on the current pane, start text selection
471                        if Some(pane_id) == current_pane {
472                            if let Some(pane) = tab.pane_container_mut().get_pane_mut(pane_id) {
473                                if pane.contains_point(mouse.column, mouse.row) {
474                                    let local_mouse = pane.translate_mouse(mouse);
475                                    pane.start_selection(local_mouse.column, local_mouse.row);
476                                    return EventResult::Consumed;
477                                }
478                            }
479                        } else {
480                            // Clicking on a different pane: select it
481                            match &mut self.mode {
482                                InteractionMode::Layout { .. } => {
483                                    self.mode.select_pane(pane_id);
484                                }
485                                InteractionMode::Focus { focused_pane } => {
486                                    if *focused_pane != pane_id {
487                                        self.enter_focus_mode(pane_id);
488                                    }
489                                }
490                            }
491                            return EventResult::Consumed;
492                        }
493                    }
494                }
495            }
496            MouseEventKind::Drag(MouseButton::Left) if mouse.row >= 3 => {
497                // Update selection if dragging in current pane
498                let current_pane = match &self.mode {
499                    InteractionMode::Layout { selected_pane } => *selected_pane,
500                    InteractionMode::Focus { focused_pane } => Some(*focused_pane),
501                };
502
503                if let Some(pane_id) = current_pane {
504                    if let Some(tab) = self.active_tab_mut() {
505                        if let Some(pane) = tab.pane_container_mut().get_pane_mut(pane_id) {
506                            if pane.contains_point(mouse.column, mouse.row) {
507                                let local_mouse = pane.translate_mouse(mouse);
508                                pane.update_selection(local_mouse.column, local_mouse.row);
509                                return EventResult::Consumed;
510                            }
511                        }
512                    }
513                }
514            }
515            MouseEventKind::Up(MouseButton::Left) if mouse.row >= 3 => {
516                // End selection
517                let current_pane = match &self.mode {
518                    InteractionMode::Layout { selected_pane } => *selected_pane,
519                    InteractionMode::Focus { focused_pane } => Some(*focused_pane),
520                };
521
522                if let Some(pane_id) = current_pane {
523                    if let Some(tab) = self.active_tab_mut() {
524                        if let Some(pane) = tab.pane_container_mut().get_pane_mut(pane_id) {
525                            pane.end_selection();
526                            return EventResult::Consumed;
527                        }
528                    }
529                }
530            }
531            _ => {}
532        }
533
534        // If in Focus Mode, route mouse events to focused pane
535        if let InteractionMode::Focus { focused_pane } = self.mode.clone() {
536            if let Some(tab) = self.active_tab_mut() {
537                if let Some(pane) = tab.pane_container_mut().get_pane_mut(focused_pane) {
538                    // Check if mouse is within focused pane
539                    if pane.contains_point(mouse.column, mouse.row) {
540                        // Translate to pane-local coordinates and route
541                        let local_mouse = pane.translate_mouse(mouse);
542                        if pane.handle_mouse(local_mouse) {
543                            return EventResult::Consumed;
544                        }
545                    } else {
546                        // Mouse clicked outside focused pane - change focus
547                        if mouse.kind == MouseEventKind::Down(MouseButton::Left) {
548                            if let Some(new_pane) =
549                                tab.pane_container().find_pane_at(mouse.column, mouse.row)
550                            {
551                                self.enter_focus_mode(new_pane);
552                                return EventResult::Consumed;
553                            }
554                        }
555                    }
556                }
557            }
558        }
559
560        EventResult::NotHandled
561    }
562
563    /// Render the master layout
564    pub fn render(&mut self, frame: &mut ratatui::Frame) {
565        self.global_area = frame.area();
566
567        // Calculate layout: nav bar (3 rows) + active tab (remaining)
568        let chunks = Layout::default()
569            .direction(Direction::Vertical)
570            .constraints([
571                Constraint::Length(3), // Nav bar (1 row content + 2 border)
572                Constraint::Min(5),    // Tab content (minimum 5 rows)
573            ])
574            .split(self.global_area);
575
576        let nav_area = chunks[0];
577        let tab_area = chunks[1];
578
579        // Render navigation bar as menu bar with offset
580        self.nav_bar
581            .render_with_offset(frame, nav_area, self.nav_bar_offset);
582
583        // Render active tab
584        let mode = self.mode.clone();
585        if let Some(tab) = self.active_tab_mut() {
586            tab.render(frame, tab_area, &mode);
587        }
588    }
589}
590
591impl Default for MasterLayout {
592    fn default() -> Self {
593        Self::new()
594    }
595}
596
597#[cfg(test)]
598mod tests {
599    use super::*;
600    use crate::master_layout::{Pane, PaneContent};
601    use crossterm::event::{KeyModifiers, MouseButton, MouseEventKind};
602    use ratatui::{backend::TestBackend, buffer::Buffer, widgets::Widget, Terminal};
603
604    // Mock PaneContent for testing
605    struct MockContent {
606        title: String,
607        key_count: usize,
608        mouse_count: usize,
609        last_key: Option<KeyEvent>,
610    }
611
612    impl MockContent {
613        fn new(title: &str) -> Self {
614            Self {
615                title: title.to_string(),
616                key_count: 0,
617                mouse_count: 0,
618                last_key: None,
619            }
620        }
621    }
622
623    impl Widget for MockContent {
624        fn render(self, _area: Rect, _buf: &mut Buffer) {}
625    }
626
627    impl PaneContent for MockContent {
628        fn handle_key(&mut self, key: KeyEvent) -> bool {
629            self.key_count += 1;
630            self.last_key = Some(key);
631            true
632        }
633
634        fn handle_mouse(&mut self, _mouse: MouseEvent) -> bool {
635            self.mouse_count += 1;
636            true
637        }
638
639        fn title(&self) -> String {
640            self.title.clone()
641        }
642
643        fn render_content(&mut self, _area: Rect, _frame: &mut ratatui::Frame) {
644            // Mock implementation - do nothing
645        }
646    }
647
648    fn create_test_layout() -> MasterLayout {
649        let mut layout = MasterLayout::new();
650
651        // Create a tab with two panes
652        let mut tab = Tab::new("Test Tab");
653        let pane1 = Pane::new(PaneId::new("pane1"), Box::new(MockContent::new("Pane 1")));
654        let pane2 = Pane::new(PaneId::new("pane2"), Box::new(MockContent::new("Pane 2")));
655        tab.add_pane(pane1);
656        tab.add_pane(pane2);
657
658        layout.add_tab(tab);
659        layout
660    }
661
662    #[test]
663    fn test_master_layout_creation() {
664        let layout = MasterLayout::new();
665        assert_eq!(layout.tab_count(), 0);
666        assert_eq!(layout.active_tab_index(), 0);
667        assert!(layout.mode().is_layout());
668    }
669
670    #[test]
671    fn test_add_tab() {
672        let mut layout = MasterLayout::new();
673        let tab = Tab::new("Tab 1");
674
675        layout.add_tab(tab);
676
677        assert_eq!(layout.tab_count(), 1);
678        assert!(layout.active_tab().is_some());
679        assert_eq!(layout.active_tab().unwrap().name(), "Tab 1");
680    }
681
682    #[test]
683    fn test_add_multiple_tabs() {
684        let mut layout = MasterLayout::new();
685
686        layout.add_tab(Tab::new("Tab 1"));
687        layout.add_tab(Tab::new("Tab 2"));
688        layout.add_tab(Tab::new("Tab 3"));
689
690        assert_eq!(layout.tab_count(), 3);
691        assert_eq!(layout.active_tab_index(), 0);
692    }
693
694    #[test]
695    fn test_set_active_tab() {
696        let mut layout = MasterLayout::new();
697        layout.add_tab(Tab::new("Tab 1"));
698        layout.add_tab(Tab::new("Tab 2"));
699        layout.add_tab(Tab::new("Tab 3"));
700
701        layout.set_active_tab(1);
702        assert_eq!(layout.active_tab_index(), 1);
703        assert_eq!(layout.active_tab().unwrap().name(), "Tab 2");
704
705        layout.set_active_tab(2);
706        assert_eq!(layout.active_tab_index(), 2);
707        assert_eq!(layout.active_tab().unwrap().name(), "Tab 3");
708    }
709
710    #[test]
711    fn test_set_active_tab_invalid_index() {
712        let mut layout = MasterLayout::new();
713        layout.add_tab(Tab::new("Tab 1"));
714
715        layout.set_active_tab(10);
716        // Should remain at 0
717        assert_eq!(layout.active_tab_index(), 0);
718    }
719
720    #[test]
721    fn test_mode_transitions() {
722        let mut layout = create_test_layout();
723
724        // Should start in Layout Mode
725        assert!(layout.mode().is_layout());
726
727        // Get a pane ID to focus
728        let pane_id = layout
729            .active_tab()
730            .unwrap()
731            .pane_container()
732            .get_pane_by_index(0)
733            .unwrap()
734            .id();
735
736        // Enter Focus Mode
737        layout.enter_focus_mode(pane_id);
738        assert!(layout.mode().is_focus());
739        assert_eq!(layout.mode().focused_pane(), Some(pane_id));
740
741        // Exit Focus Mode
742        layout.exit_focus_mode();
743        assert!(layout.mode().is_layout());
744        assert_eq!(layout.mode().selected_pane(), Some(pane_id));
745    }
746
747    #[test]
748    fn test_enter_key_focuses_selected_pane() {
749        let mut layout = create_test_layout();
750
751        // Should have a pane selected (first pane auto-selected on tab add)
752        let selected = layout.mode().selected_pane();
753        assert!(selected.is_some());
754
755        // Press Enter
756        let key = KeyEvent::new(KeyCode::Enter, KeyModifiers::empty());
757        let result = layout.handle_key_event(key);
758
759        assert_eq!(result, EventResult::Consumed);
760        assert!(layout.mode().is_focus());
761        assert_eq!(layout.mode().focused_pane(), selected);
762    }
763
764    #[test]
765    fn test_ctrl_a_exits_focus_mode() {
766        let mut layout = create_test_layout();
767
768        let pane_id = layout
769            .active_tab()
770            .unwrap()
771            .pane_container()
772            .get_pane_by_index(0)
773            .unwrap()
774            .id();
775
776        // Enter focus mode
777        layout.enter_focus_mode(pane_id);
778        assert!(layout.mode().is_focus());
779
780        // Press Ctrl-A
781        let key = KeyEvent::new(KeyCode::Char('a'), KeyModifiers::CONTROL);
782        let result = layout.handle_key_event(key);
783
784        assert_eq!(result, EventResult::Consumed);
785        assert!(layout.mode().is_layout());
786        assert_eq!(layout.mode().selected_pane(), Some(pane_id));
787    }
788
789    #[test]
790    fn test_q_quits() {
791        let mut layout = create_test_layout();
792
793        let key = KeyEvent::new(KeyCode::Char('q'), KeyModifiers::empty());
794        let result = layout.handle_key_event(key);
795
796        assert_eq!(result, EventResult::Quit);
797    }
798
799    #[test]
800    fn test_tab_key_not_handled_in_layout_mode() {
801        // Verify that Tab does NOT cycle panes in Layout Mode
802        let mut layout = create_test_layout();
803
804        let first_selected = layout.mode().selected_pane();
805        assert!(first_selected.is_some());
806
807        // Press Tab - should NOT be handled in Layout Mode
808        let key = KeyEvent::new(KeyCode::Tab, KeyModifiers::empty());
809        let result = layout.handle_key_event(key);
810
811        // Tab should not be consumed by Layout Mode
812        assert_eq!(result, EventResult::NotHandled);
813
814        // Selection should not change
815        let still_selected = layout.mode().selected_pane();
816        assert_eq!(first_selected, still_selected);
817    }
818
819    #[test]
820    fn test_shift_tab_not_handled_in_layout_mode() {
821        // Verify that Shift+Tab does NOT cycle panes in Layout Mode
822        let mut layout = create_test_layout();
823
824        let first_selected = layout.mode().selected_pane();
825
826        // Press Shift+Tab - should NOT be handled in Layout Mode
827        let key = KeyEvent::new(KeyCode::BackTab, KeyModifiers::SHIFT);
828        let result = layout.handle_key_event(key);
829
830        // Shift+Tab should not be consumed
831        assert_eq!(result, EventResult::NotHandled);
832
833        // Selection should not change
834        let still_selected = layout.mode().selected_pane();
835        assert_eq!(first_selected, still_selected);
836    }
837
838    #[test]
839    fn test_digit_keys_switch_tabs_in_layout_mode() {
840        // Verify that number keys ONLY switch tabs in Layout Mode
841        let mut layout = MasterLayout::new();
842
843        let mut tab1 = Tab::new("Tab 1");
844        tab1.add_pane(Pane::new(
845            PaneId::new("p1"),
846            Box::new(MockContent::new("P1")),
847        ));
848        layout.add_tab(tab1);
849
850        let mut tab2 = Tab::new("Tab 2");
851        tab2.add_pane(Pane::new(
852            PaneId::new("p2"),
853            Box::new(MockContent::new("P2")),
854        ));
855        layout.add_tab(tab2);
856
857        let mut tab3 = Tab::new("Tab 3");
858        tab3.add_pane(Pane::new(
859            PaneId::new("p3"),
860            Box::new(MockContent::new("P3")),
861        ));
862        layout.add_tab(tab3);
863
864        // Verify we're in Layout Mode
865        assert!(layout.mode().is_layout());
866
867        // Press '2' to switch to second tab
868        let key = KeyEvent::new(KeyCode::Char('2'), KeyModifiers::empty());
869        let result = layout.handle_key_event(key);
870
871        assert_eq!(result, EventResult::Consumed);
872        assert_eq!(layout.active_tab_index(), 1);
873
874        // Press '3' to switch to third tab
875        let key = KeyEvent::new(KeyCode::Char('3'), KeyModifiers::empty());
876        layout.handle_key_event(key);
877        assert_eq!(layout.active_tab_index(), 2);
878    }
879
880    #[test]
881    fn test_hjkl_navigation_in_layout_mode() {
882        let mut layout = create_test_layout();
883
884        // Setup: ensure we're in layout mode with a selection
885        assert!(layout.mode().is_layout());
886        assert!(layout.mode().selected_pane().is_some());
887
888        // Test h key (left)
889        let key = KeyEvent::new(KeyCode::Char('h'), KeyModifiers::empty());
890        let result = layout.handle_key_event(key);
891        assert_eq!(result, EventResult::Consumed);
892
893        // Test j key (down)
894        let key = KeyEvent::new(KeyCode::Char('j'), KeyModifiers::empty());
895        let result = layout.handle_key_event(key);
896        assert_eq!(result, EventResult::Consumed);
897
898        // Test k key (up)
899        let key = KeyEvent::new(KeyCode::Char('k'), KeyModifiers::empty());
900        let result = layout.handle_key_event(key);
901        assert_eq!(result, EventResult::Consumed);
902
903        // Test l key (right)
904        let key = KeyEvent::new(KeyCode::Char('l'), KeyModifiers::empty());
905        let result = layout.handle_key_event(key);
906        assert_eq!(result, EventResult::Consumed);
907    }
908
909    #[test]
910    fn test_hjkl_ignored_in_focus_mode() {
911        let mut layout = create_test_layout();
912
913        let pane_id = layout
914            .active_tab()
915            .unwrap()
916            .pane_container()
917            .get_pane_by_index(0)
918            .unwrap()
919            .id();
920
921        // Enter focus mode
922        layout.enter_focus_mode(pane_id);
923
924        let initial_mode = layout.mode().clone();
925
926        // Press h - should be routed to pane, not change selection
927        let key = KeyEvent::new(KeyCode::Char('h'), KeyModifiers::empty());
928        layout.handle_key_event(key);
929
930        // Should still be in focus mode with same pane
931        assert!(layout.mode().is_focus());
932        assert_eq!(layout.mode(), &initial_mode);
933    }
934
935    #[test]
936    fn test_mouse_click_on_nav_bar() {
937        let mut layout = MasterLayout::new();
938        layout.add_tab(Tab::new("Tab 1"));
939        layout.add_tab(Tab::new("Tab 2"));
940
941        // Start on first tab
942        assert_eq!(layout.active_tab_index(), 0);
943
944        // Render to calculate nav bar button areas
945        let backend = TestBackend::new(80, 24);
946        let mut terminal = Terminal::new(backend).unwrap();
947        terminal
948            .draw(|frame| {
949                layout.render(frame);
950            })
951            .unwrap();
952
953        // Get the actual button area for the second tab after rendering
954        // We need to access nav_bar to find the button position
955        // For now, we'll test by switching tabs using the API directly
956        // The click handling is tested indirectly through other tests
957
958        // Alternative: Test that clicking in nav bar area is handled
959        let mouse = MouseEvent {
960            kind: MouseEventKind::Down(MouseButton::Left),
961            column: 5,
962            row: 1,
963            modifiers: KeyModifiers::empty(),
964        };
965
966        let result = layout.handle_mouse_event(mouse);
967        // Should be consumed if click hits a button, or NotHandled if it misses
968        // Since button layout is calculated during render, we just verify it doesn't panic
969        assert!(result == EventResult::Consumed || result == EventResult::NotHandled);
970    }
971
972    #[test]
973    fn test_mouse_click_on_pane_focuses() {
974        let mut layout = create_test_layout();
975
976        // Render to calculate pane areas
977        let backend = TestBackend::new(80, 24);
978        let mut terminal = Terminal::new(backend).unwrap();
979        terminal
980            .draw(|frame| {
981                layout.render(frame);
982            })
983            .unwrap();
984
985        // Click on pane area (below nav bar)
986        let mouse = MouseEvent {
987            kind: MouseEventKind::Down(MouseButton::Left),
988            column: 10,
989            row: 5,
990            modifiers: KeyModifiers::empty(),
991        };
992
993        layout.handle_mouse_event(mouse);
994
995        // Should enter focus mode (if click hit a pane)
996        // Note: This test is simplified; in real usage, pane areas are calculated during render
997    }
998
999    #[test]
1000    fn test_keys_routed_to_focused_pane() {
1001        let mut layout = create_test_layout();
1002
1003        let pane_id = layout
1004            .active_tab()
1005            .unwrap()
1006            .pane_container()
1007            .get_pane_by_index(0)
1008            .unwrap()
1009            .id();
1010
1011        // Enter focus mode
1012        layout.enter_focus_mode(pane_id);
1013
1014        // Send a key event
1015        let key = KeyEvent::new(KeyCode::Char('x'), KeyModifiers::empty());
1016        let result = layout.handle_key_event(key);
1017
1018        assert_eq!(result, EventResult::Consumed);
1019        // Pane should have received the key (verified by MockContent.key_count in real test)
1020    }
1021
1022    #[test]
1023    fn test_tab_key_routed_to_pane_in_focus_mode() {
1024        // Verify that Tab key goes to the pane in Focus Mode, not navigation
1025        let mut layout = create_test_layout();
1026
1027        let pane_id = layout
1028            .active_tab()
1029            .unwrap()
1030            .pane_container()
1031            .get_pane_by_index(0)
1032            .unwrap()
1033            .id();
1034
1035        // Enter focus mode
1036        layout.enter_focus_mode(pane_id);
1037        assert!(layout.mode().is_focus());
1038
1039        // Press Tab - should be routed to pane, not used for navigation
1040        let key = KeyEvent::new(KeyCode::Tab, KeyModifiers::empty());
1041        let result = layout.handle_key_event(key);
1042
1043        // Tab should be consumed (by pane or by layout attempting to route it)
1044        // The key point is it should NOT switch panes
1045        assert_eq!(result, EventResult::Consumed);
1046
1047        // Should still be in focus mode with the same pane
1048        assert!(layout.mode().is_focus());
1049        assert_eq!(layout.mode().focused_pane(), Some(pane_id));
1050    }
1051
1052    #[test]
1053    fn test_number_keys_routed_to_pane_in_focus_mode() {
1054        // Verify that number keys go to pane in Focus Mode, NOT switch tabs
1055        let mut layout = MasterLayout::new();
1056
1057        let mut tab1 = Tab::new("Tab 1");
1058        tab1.add_pane(Pane::new(
1059            PaneId::new("p1"),
1060            Box::new(MockContent::new("P1")),
1061        ));
1062        layout.add_tab(tab1);
1063
1064        let mut tab2 = Tab::new("Tab 2");
1065        tab2.add_pane(Pane::new(
1066            PaneId::new("p2"),
1067            Box::new(MockContent::new("P2")),
1068        ));
1069        layout.add_tab(tab2);
1070
1071        // Start on first tab
1072        assert_eq!(layout.active_tab_index(), 0);
1073
1074        let pane_id = layout
1075            .active_tab()
1076            .unwrap()
1077            .pane_container()
1078            .get_pane_by_index(0)
1079            .unwrap()
1080            .id();
1081
1082        // Enter focus mode on first tab
1083        layout.enter_focus_mode(pane_id);
1084        assert!(layout.mode().is_focus());
1085
1086        // Press '2' - should go to pane, NOT switch to tab 2
1087        let key = KeyEvent::new(KeyCode::Char('2'), KeyModifiers::empty());
1088        let result = layout.handle_key_event(key);
1089
1090        // Should be consumed by pane
1091        assert_eq!(result, EventResult::Consumed);
1092
1093        // Should still be on first tab (NOT switched to tab 2)
1094        assert_eq!(layout.active_tab_index(), 0);
1095
1096        // Should still be in focus mode with the same pane
1097        assert!(layout.mode().is_focus());
1098        assert_eq!(layout.mode().focused_pane(), Some(pane_id));
1099    }
1100
1101    #[test]
1102    fn test_only_ctrl_a_exits_focus_mode() {
1103        // Verify that ONLY Ctrl-A exits Focus Mode, other keys don't
1104        let mut layout = create_test_layout();
1105
1106        let pane_id = layout
1107            .active_tab()
1108            .unwrap()
1109            .pane_container()
1110            .get_pane_by_index(0)
1111            .unwrap()
1112            .id();
1113
1114        // Enter focus mode
1115        layout.enter_focus_mode(pane_id);
1116        assert!(layout.mode().is_focus());
1117
1118        // Try various keys - none should exit focus mode (except 'q' which quits the app)
1119        let test_keys = vec![
1120            KeyEvent::new(KeyCode::Esc, KeyModifiers::empty()),
1121            KeyEvent::new(KeyCode::Enter, KeyModifiers::empty()),
1122            KeyEvent::new(KeyCode::Char('h'), KeyModifiers::empty()),
1123            KeyEvent::new(KeyCode::Tab, KeyModifiers::empty()),
1124            KeyEvent::new(KeyCode::Char('x'), KeyModifiers::empty()),
1125        ];
1126
1127        for key in test_keys {
1128            layout.handle_key_event(key);
1129            // Should still be in focus mode
1130            assert!(
1131                layout.mode().is_focus(),
1132                "Key {:?} should not exit focus mode",
1133                key
1134            );
1135        }
1136
1137        // Now press Ctrl-A - should exit focus mode
1138        let key = KeyEvent::new(KeyCode::Char('a'), KeyModifiers::CONTROL);
1139        let result = layout.handle_key_event(key);
1140
1141        assert_eq!(result, EventResult::Consumed);
1142        assert!(layout.mode().is_layout());
1143    }
1144
1145    #[test]
1146    fn test_all_keys_passed_to_focused_pane() {
1147        // Verify that ALL keys (including Ctrl+W, Ctrl+C, etc.) are passed to focused pane
1148        let mut layout = create_test_layout();
1149
1150        let pane_id = layout
1151            .active_tab()
1152            .unwrap()
1153            .pane_container()
1154            .get_pane_by_index(0)
1155            .unwrap()
1156            .id();
1157
1158        // Enter focus mode
1159        layout.enter_focus_mode(pane_id);
1160        assert!(layout.mode().is_focus());
1161
1162        // Test various control sequences that should be passed through
1163        let test_keys = vec![
1164            KeyEvent::new(KeyCode::Char('w'), KeyModifiers::CONTROL), // Ctrl+W
1165            KeyEvent::new(KeyCode::Char('c'), KeyModifiers::CONTROL), // Ctrl+C
1166            KeyEvent::new(KeyCode::Char('d'), KeyModifiers::CONTROL), // Ctrl+D
1167            KeyEvent::new(KeyCode::Esc, KeyModifiers::empty()),       // Esc
1168            KeyEvent::new(KeyCode::Char('r'), KeyModifiers::CONTROL), // Ctrl+R
1169        ];
1170
1171        for key in test_keys {
1172            let result = layout.handle_key_event(key);
1173
1174            // Key should be consumed (passed to pane)
1175            assert_eq!(
1176                result,
1177                EventResult::Consumed,
1178                "Key {:?} should be passed to pane",
1179                key
1180            );
1181
1182            // Should still be in focus mode
1183            assert!(
1184                layout.mode().is_focus(),
1185                "Key {:?} should not exit focus mode",
1186                key
1187            );
1188
1189            // Verify the pane received the key by checking last_key
1190            let _pane = layout
1191                .active_tab()
1192                .unwrap()
1193                .pane_container()
1194                .get_pane(pane_id)
1195                .unwrap();
1196
1197            // We need to access the MockContent's last_key, but it's boxed
1198            // For now, just verify we're still in focus mode
1199            // The key_count verification in other tests confirms keys are being passed
1200        }
1201    }
1202
1203    #[test]
1204    fn test_q_quits_only_in_layout_mode() {
1205        // Verify Q (without modifiers) quits ONLY in Layout Mode, NOT in Focus Mode
1206        let mut layout = create_test_layout();
1207
1208        // Test in Layout Mode - 'q' should quit
1209        assert!(layout.mode().is_layout());
1210        let key = KeyEvent::new(KeyCode::Char('q'), KeyModifiers::empty());
1211        let result = layout.handle_key_event(key);
1212        assert_eq!(result, EventResult::Quit, "'q' should quit in Layout Mode");
1213
1214        // Reset layout and test in Focus Mode - 'q' should be sent to pane
1215        let mut layout = create_test_layout();
1216        let pane_id = layout
1217            .active_tab()
1218            .unwrap()
1219            .pane_container()
1220            .get_pane_by_index(0)
1221            .unwrap()
1222            .id();
1223
1224        layout.enter_focus_mode(pane_id);
1225        assert!(layout.mode().is_focus());
1226
1227        let key = KeyEvent::new(KeyCode::Char('q'), KeyModifiers::empty());
1228        let result = layout.handle_key_event(key);
1229        assert_eq!(
1230            result,
1231            EventResult::Consumed,
1232            "'q' should be consumed by pane in Focus Mode, NOT quit app"
1233        );
1234
1235        // Verify still in Focus Mode (key was consumed, not used for app control)
1236        assert!(
1237            layout.mode().is_focus(),
1238            "Should still be in Focus Mode after 'q'"
1239        );
1240    }
1241
1242    #[test]
1243    fn test_q_in_focus_mode_regression() {
1244        // Regression test: Ensure 'q' key in Focus Mode does NOT quit the application
1245        // Bug history: Previously, 'q' was checked globally before mode handling,
1246        // causing it to quit even when a terminal was focused
1247        let mut layout = create_test_layout();
1248
1249        // Enter Focus Mode on the first pane
1250        let pane_id = layout
1251            .active_tab()
1252            .unwrap()
1253            .pane_container()
1254            .get_pane_by_index(0)
1255            .unwrap()
1256            .id();
1257        layout.enter_focus_mode(pane_id);
1258
1259        // Verify we're in Focus Mode
1260        assert!(layout.mode().is_focus(), "Should be in Focus Mode");
1261
1262        // Press 'q' - this should go to the pane, NOT quit
1263        let q_key = KeyEvent::new(KeyCode::Char('q'), KeyModifiers::empty());
1264        let result = layout.handle_key_event(q_key);
1265
1266        // Critical assertion: 'q' should be consumed by pane, not trigger quit
1267        assert_ne!(
1268            result,
1269            EventResult::Quit,
1270            "REGRESSION: 'q' in Focus Mode should NOT quit the application"
1271        );
1272        assert_eq!(
1273            result,
1274            EventResult::Consumed,
1275            "'q' should be consumed by the focused pane"
1276        );
1277
1278        // Verify we're still in Focus Mode (didn't quit or exit Focus Mode)
1279        assert!(
1280            layout.mode().is_focus(),
1281            "Should still be in Focus Mode after pressing 'q'"
1282        );
1283
1284        // Press multiple 'q' keys to ensure consistent behavior
1285        for _ in 0..5 {
1286            let result = layout.handle_key_event(q_key);
1287            assert_eq!(
1288                result,
1289                EventResult::Consumed,
1290                "Multiple 'q' presses should all be consumed"
1291            );
1292            assert!(layout.mode().is_focus(), "Should remain in Focus Mode");
1293        }
1294
1295        // Final verification: still in Focus Mode after multiple 'q' presses
1296        assert!(
1297            layout.mode().is_focus(),
1298            "Should remain in Focus Mode after all 'q' presses"
1299        );
1300    }
1301
1302    #[test]
1303    fn test_uppercase_q_in_focus_mode() {
1304        // Regression test: Ensure uppercase 'Q' (Shift+Q) in Focus Mode does NOT quit
1305        // Bug: When pressing Shift+Q, it generates KeyCode::Char('Q') which wasn't being
1306        // handled properly and could slip through to quit the app
1307        let mut layout = create_test_layout();
1308
1309        // Enter Focus Mode
1310        let pane_id = layout
1311            .active_tab()
1312            .unwrap()
1313            .pane_container()
1314            .get_pane_by_index(0)
1315            .unwrap()
1316            .id();
1317        layout.enter_focus_mode(pane_id);
1318        assert!(layout.mode().is_focus());
1319
1320        // Press uppercase 'Q' (Shift+Q) - should go to pane, NOT quit
1321        let uppercase_q = KeyEvent::new(KeyCode::Char('Q'), KeyModifiers::empty());
1322        let result = layout.handle_key_event(uppercase_q);
1323
1324        // Should NOT quit the application
1325        assert_ne!(
1326            result,
1327            EventResult::Quit,
1328            "REGRESSION: Uppercase 'Q' (Shift+Q) in Focus Mode should NOT quit the application"
1329        );
1330        assert_eq!(
1331            result,
1332            EventResult::Consumed,
1333            "Uppercase 'Q' should be consumed by the focused pane"
1334        );
1335
1336        // Should still be in Focus Mode
1337        assert!(
1338            layout.mode().is_focus(),
1339            "Should still be in Focus Mode after pressing 'Q'"
1340        );
1341    }
1342
1343    #[test]
1344    fn test_uppercase_q_quits_in_layout_mode() {
1345        // Both 'q' and 'Q' should quit when in Layout Mode (case-insensitive)
1346        let mut layout = create_test_layout();
1347        assert!(layout.mode().is_layout());
1348
1349        // Test lowercase 'q'
1350        let lowercase_q = KeyEvent::new(KeyCode::Char('q'), KeyModifiers::empty());
1351        let result = layout.handle_key_event(lowercase_q);
1352        assert_eq!(
1353            result,
1354            EventResult::Quit,
1355            "Lowercase 'q' should quit in Layout Mode"
1356        );
1357
1358        // Reset and test uppercase 'Q'
1359        let mut layout = create_test_layout();
1360        let uppercase_q = KeyEvent::new(KeyCode::Char('Q'), KeyModifiers::empty());
1361        let result = layout.handle_key_event(uppercase_q);
1362        assert_eq!(
1363            result,
1364            EventResult::Quit,
1365            "Uppercase 'Q' should quit in Layout Mode"
1366        );
1367    }
1368
1369    #[test]
1370    fn test_render_does_not_panic() {
1371        let mut layout = create_test_layout();
1372
1373        let backend = TestBackend::new(80, 24);
1374        let mut terminal = Terminal::new(backend).unwrap();
1375
1376        terminal
1377            .draw(|frame| {
1378                layout.render(frame);
1379            })
1380            .unwrap();
1381    }
1382
1383    #[test]
1384    fn test_empty_layout_renders() {
1385        let mut layout = MasterLayout::new();
1386
1387        let backend = TestBackend::new(80, 24);
1388        let mut terminal = Terminal::new(backend).unwrap();
1389
1390        terminal
1391            .draw(|frame| {
1392                layout.render(frame);
1393            })
1394            .unwrap();
1395    }
1396
1397    #[test]
1398    fn test_tab_switching_preserves_mode() {
1399        let mut layout = MasterLayout::new();
1400
1401        // Create two tabs with panes
1402        let mut tab1 = Tab::new("Tab 1");
1403        tab1.add_pane(Pane::new(
1404            PaneId::new("t1p1"),
1405            Box::new(MockContent::new("T1P1")),
1406        ));
1407        layout.add_tab(tab1);
1408
1409        let mut tab2 = Tab::new("Tab 2");
1410        tab2.add_pane(Pane::new(
1411            PaneId::new("t2p1"),
1412            Box::new(MockContent::new("T2P1")),
1413        ));
1414        layout.add_tab(tab2);
1415
1416        // Switch to tab 2
1417        layout.set_active_tab(1);
1418
1419        // Mode should still be layout mode with first pane selected
1420        assert!(layout.mode().is_layout());
1421        assert!(layout.mode().selected_pane().is_some());
1422    }
1423
1424    #[test]
1425    fn test_no_panes_no_selection() {
1426        let mut layout = MasterLayout::new();
1427        let tab = Tab::new("Empty Tab");
1428        layout.add_tab(tab);
1429
1430        // No panes, so no selection should be made
1431        assert!(layout.mode().is_layout());
1432    }
1433
1434    #[test]
1435    fn test_event_handling_resize() {
1436        let mut layout = create_test_layout();
1437
1438        let event = Event::Resize(100, 50);
1439        let result = layout.handle_event(event);
1440
1441        assert_eq!(result, EventResult::Consumed);
1442    }
1443
1444    #[test]
1445    fn test_select_next_with_no_tabs() {
1446        let mut layout = MasterLayout::new();
1447        layout.select_next_pane();
1448        // Should not panic
1449    }
1450
1451    #[test]
1452    fn test_focus_selected_with_no_selection() {
1453        let mut layout = MasterLayout::new();
1454        let tab = Tab::new("Empty Tab");
1455        layout.add_tab(tab);
1456
1457        // No panes to select
1458        layout.focus_selected();
1459        // Should remain in layout mode
1460        assert!(layout.mode().is_layout());
1461    }
1462
1463    #[test]
1464    fn test_esc_deselects_in_layout_mode() {
1465        let mut layout = create_test_layout();
1466
1467        // Verify we start with a pane selected
1468        assert!(layout.mode().is_layout());
1469        assert!(layout.mode().selected_pane().is_some());
1470
1471        // Press ESC to clear selection
1472        let key = KeyEvent::new(KeyCode::Esc, KeyModifiers::empty());
1473        let result = layout.handle_key_event(key);
1474
1475        // Verify the ESC key was consumed
1476        assert_eq!(result, EventResult::Consumed);
1477
1478        // Verify we're still in Layout Mode
1479        assert!(layout.mode().is_layout());
1480
1481        // Verify no pane is selected
1482        assert_eq!(layout.mode().selected_pane(), None);
1483    }
1484
1485    #[test]
1486    fn test_mouse_click_selects_pane_in_layout_mode() {
1487        let mut layout = create_test_layout();
1488
1489        // Verify initial state: Layout Mode with first pane selected
1490        assert!(layout.mode().is_layout());
1491        let initial_selection = layout.mode().selected_pane();
1492        assert!(initial_selection.is_some());
1493
1494        // Get the second pane ID for comparison
1495        let _pane2 = layout
1496            .active_tab()
1497            .unwrap()
1498            .pane_container()
1499            .get_pane_by_index(1)
1500            .unwrap()
1501            .id();
1502
1503        // Render to calculate pane areas
1504        let backend = TestBackend::new(80, 24);
1505        let mut terminal = Terminal::new(backend).unwrap();
1506        terminal
1507            .draw(|frame| {
1508                layout.render(frame);
1509            })
1510            .unwrap();
1511
1512        // Simulate mouse click on second pane (below nav bar)
1513        let mouse = MouseEvent {
1514            kind: MouseEventKind::Down(MouseButton::Left),
1515            column: 10,
1516            row: 5,
1517            modifiers: KeyModifiers::empty(),
1518        };
1519
1520        layout.handle_mouse_event(mouse);
1521
1522        // Assert that:
1523        // 1. Mode is still Layout Mode (not Focus Mode)
1524        assert!(
1525            layout.mode().is_layout(),
1526            "Mode should still be Layout Mode after mouse click in Layout Mode"
1527        );
1528
1529        // 2. No pane is focused (verified by checking it's not Focus Mode)
1530        assert!(
1531            layout.mode().focused_pane().is_none(),
1532            "No pane should be focused in Layout Mode"
1533        );
1534
1535        // 3. A pane is selected
1536        let current_selection = layout.mode().selected_pane();
1537        assert!(
1538            current_selection.is_some(),
1539            "A pane should be selected after mouse click"
1540        );
1541    }
1542
1543    #[test]
1544    fn test_keyboard_navigation_then_enter_focuses() {
1545        let mut layout = create_test_layout();
1546
1547        // Step 1: Verify first pane is selected in Layout Mode
1548        assert!(layout.mode().is_layout());
1549        let first_pane = layout.mode().selected_pane();
1550        assert!(first_pane.is_some());
1551
1552        // Render to calculate pane areas (required for directional navigation)
1553        let backend = TestBackend::new(80, 24);
1554        let mut terminal = Terminal::new(backend).unwrap();
1555        terminal
1556            .draw(|frame| {
1557                layout.render(frame);
1558            })
1559            .unwrap();
1560
1561        // Step 2: Press 'l' key to select right pane
1562        let key = KeyEvent::new(KeyCode::Char('l'), KeyModifiers::empty());
1563        let result = layout.handle_key_event(key);
1564        assert_eq!(result, EventResult::Consumed);
1565
1566        // Step 3: Assert second pane is now selected (still in Layout Mode)
1567        let second_pane = layout.mode().selected_pane();
1568        assert!(second_pane.is_some());
1569        assert_ne!(
1570            first_pane, second_pane,
1571            "Second pane should be different from first"
1572        );
1573        assert!(layout.mode().is_layout(), "Should still be in Layout Mode");
1574
1575        // Step 4: Press Enter key
1576        let key = KeyEvent::new(KeyCode::Enter, KeyModifiers::empty());
1577        let result = layout.handle_key_event(key);
1578        assert_eq!(result, EventResult::Consumed);
1579
1580        // Step 5: Assert Mode is now Focus Mode and second pane is focused
1581        assert!(
1582            layout.mode().is_focus(),
1583            "Should be in Focus Mode after Enter"
1584        );
1585        assert_eq!(
1586            layout.mode().focused_pane(),
1587            second_pane,
1588            "Second pane should be focused"
1589        );
1590    }
1591
1592    #[test]
1593    fn test_ctrl_a_deselects_in_layout_mode() {
1594        let mut layout = create_test_layout();
1595
1596        // Verify we start in Layout Mode with a pane selected
1597        assert!(layout.mode().is_layout());
1598        assert!(layout.mode().selected_pane().is_some());
1599
1600        // Press Ctrl-A to deselect
1601        let key = KeyEvent::new(KeyCode::Char('a'), KeyModifiers::CONTROL);
1602        let result = layout.handle_key_event(key);
1603
1604        // Verify the Ctrl-A key was consumed
1605        assert_eq!(result, EventResult::Consumed);
1606
1607        // Verify we're still in Layout Mode
1608        assert!(layout.mode().is_layout());
1609
1610        // Verify no pane is selected
1611        assert_eq!(layout.mode().selected_pane(), None);
1612    }
1613
1614    #[test]
1615    fn test_click_different_pane_in_focus_mode_switches() {
1616        let mut layout = create_test_layout();
1617
1618        // Get pane IDs from the layout
1619        let pane1_id = layout
1620            .active_tab()
1621            .unwrap()
1622            .pane_container()
1623            .get_pane_by_index(0)
1624            .unwrap()
1625            .id();
1626
1627        let pane2_id = layout
1628            .active_tab()
1629            .unwrap()
1630            .pane_container()
1631            .get_pane_by_index(1)
1632            .unwrap()
1633            .id();
1634
1635        // Enter Focus Mode on first pane
1636        layout.enter_focus_mode(pane1_id);
1637        assert!(layout.mode().is_focus());
1638        assert_eq!(layout.mode().focused_pane(), Some(pane1_id));
1639
1640        // Render to calculate pane areas
1641        let backend = TestBackend::new(80, 24);
1642        let mut terminal = Terminal::new(backend).unwrap();
1643        terminal
1644            .draw(|frame| {
1645                layout.render(frame);
1646            })
1647            .unwrap();
1648
1649        // Simulate clicking on the second pane (somewhere in the pane area)
1650        let mouse = MouseEvent {
1651            kind: MouseEventKind::Down(MouseButton::Left),
1652            column: 50,
1653            row: 12,
1654            modifiers: KeyModifiers::empty(),
1655        };
1656
1657        let result = layout.handle_mouse_event(mouse);
1658        assert_eq!(result, EventResult::Consumed);
1659
1660        // Assert: Still in Focus Mode
1661        assert!(layout.mode().is_focus());
1662
1663        // Assert: Now focused on second pane
1664        assert_eq!(layout.mode().focused_pane(), Some(pane2_id));
1665    }
1666
1667    #[test]
1668    fn test_click_same_pane_in_focus_mode_maintains() {
1669        let mut layout = create_test_layout();
1670
1671        // Get first pane ID
1672        let pane1_id = layout
1673            .active_tab()
1674            .unwrap()
1675            .pane_container()
1676            .get_pane_by_index(0)
1677            .unwrap()
1678            .id();
1679
1680        // Enter Focus Mode on first pane
1681        layout.enter_focus_mode(pane1_id);
1682        assert!(layout.mode().is_focus());
1683        assert_eq!(layout.mode().focused_pane(), Some(pane1_id));
1684
1685        // Render to calculate pane areas
1686        let backend = TestBackend::new(80, 24);
1687        let mut terminal = Terminal::new(backend).unwrap();
1688        terminal
1689            .draw(|frame| {
1690                layout.render(frame);
1691            })
1692            .unwrap();
1693
1694        // Simulate clicking on the same first pane
1695        let mouse = MouseEvent {
1696            kind: MouseEventKind::Down(MouseButton::Left),
1697            column: 10,
1698            row: 8,
1699            modifiers: KeyModifiers::empty(),
1700        };
1701
1702        let result = layout.handle_mouse_event(mouse);
1703        assert_eq!(result, EventResult::Consumed);
1704
1705        // Assert: Still in Focus Mode
1706        assert!(layout.mode().is_focus());
1707
1708        // Assert: Still focused on first pane
1709        assert_eq!(layout.mode().focused_pane(), Some(pane1_id));
1710    }
1711
1712    #[test]
1713    fn test_hjkl_navigation_blocked_in_focus_mode() {
1714        let mut layout = create_test_layout();
1715
1716        let pane1_id = layout
1717            .active_tab()
1718            .unwrap()
1719            .pane_container()
1720            .get_pane_by_index(0)
1721            .unwrap()
1722            .id();
1723
1724        let pane2_id = layout
1725            .active_tab()
1726            .unwrap()
1727            .pane_container()
1728            .get_pane_by_index(1)
1729            .unwrap()
1730            .id();
1731
1732        // Verify we have 2 panes
1733        assert_ne!(pane1_id, pane2_id);
1734
1735        // Enter focus mode on first pane
1736        layout.enter_focus_mode(pane1_id);
1737        assert!(layout.mode().is_focus());
1738        assert_eq!(layout.mode().focused_pane(), Some(pane1_id));
1739
1740        // Press 'l' key (should navigate to second pane in Layout Mode, but not in Focus Mode)
1741        let key = KeyEvent::new(KeyCode::Char('l'), KeyModifiers::empty());
1742        let result = layout.handle_key_event(key);
1743
1744        // Key should be consumed (routed to pane, not processed by layout navigation)
1745        assert_eq!(result, EventResult::Consumed);
1746
1747        // IMPORTANT: Verify navigation is BLOCKED - still in Focus Mode with same pane
1748        assert!(layout.mode().is_focus(), "Should still be in Focus Mode");
1749        assert_eq!(
1750            layout.mode().focused_pane(),
1751            Some(pane1_id),
1752            "Should still be focused on first pane (navigation not allowed in Focus Mode)"
1753        );
1754    }
1755}