1use crate::master_layout::MasterLayoutKeyBindings;
4use super::{InteractionMode, PaneId, Tab};
5use crate::{MenuBar, MenuItem};
6use crossterm::event::{Event, KeyEvent, MouseButton, MouseEvent, MouseEventKind};
7use ratatui::layout::{Constraint, Direction, Layout, Rect};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum EventResult {
12 Consumed,
14 NotHandled,
16 Quit,
18}
19
20pub struct MasterLayout {
58 nav_bar: MenuBar,
59 tabs: Vec<Tab>,
60 active_tab_index: usize,
61 mode: InteractionMode,
62 global_area: Rect,
63 nav_bar_offset: u16,
64 keybindings: MasterLayoutKeyBindings,
65}
66
67impl MasterLayout {
68 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 keybindings: MasterLayoutKeyBindings::default(),
78 }
79 }
80
81 pub fn with_keybindings(mut self, keybindings: MasterLayoutKeyBindings) -> Self {
95 self.keybindings = keybindings;
96 self
97 }
98
99 pub fn keybindings(&self) -> &MasterLayoutKeyBindings {
101 &self.keybindings
102 }
103
104 pub fn set_keybindings(&mut self, keybindings: MasterLayoutKeyBindings) {
106 self.keybindings = keybindings;
107 }
108
109 pub fn set_nav_bar_offset(&mut self, offset: u16) {
111 self.nav_bar_offset = offset;
112 }
113
114 pub fn add_tab(&mut self, tab: Tab) {
116 self.tabs.push(tab);
117
118 let menu_items: Vec<MenuItem> = self
120 .tabs
121 .iter()
122 .enumerate()
123 .map(|(i, t)| MenuItem::new(t.name(), i))
124 .collect();
125 self.nav_bar = MenuBar::new(menu_items).with_selected(self.active_tab_index);
126
127 if self.tabs.len() == 1 {
129 self.select_first_pane_in_active_tab();
130 }
131 }
132
133 pub fn tab_count(&self) -> usize {
135 self.tabs.len()
136 }
137
138 pub fn active_tab_index(&self) -> usize {
140 self.active_tab_index
141 }
142
143 pub fn set_active_tab(&mut self, index: usize) {
145 if index < self.tabs.len() {
146 self.active_tab_index = index;
147 for (i, item) in self.nav_bar.items.iter_mut().enumerate() {
149 item.selected = i == index;
150 }
151
152 self.select_first_pane_in_active_tab();
154 }
155 }
156
157 pub fn active_tab(&self) -> Option<&Tab> {
159 self.tabs.get(self.active_tab_index)
160 }
161
162 pub fn active_tab_mut(&mut self) -> Option<&mut Tab> {
164 self.tabs.get_mut(self.active_tab_index)
165 }
166
167 pub fn mode(&self) -> &InteractionMode {
169 &self.mode
170 }
171
172 pub fn enter_layout_mode(&mut self) {
174 if let Some(focused_id) = self.mode.focused_pane() {
175 self.mode = InteractionMode::layout_with_selection(focused_id);
177 } else {
178 if !self.mode.is_layout() {
180 self.mode = InteractionMode::layout();
181 }
182 }
183 }
184
185 pub fn enter_focus_mode(&mut self, pane_id: PaneId) {
187 self.mode = InteractionMode::focus(pane_id);
188 }
189
190 pub fn exit_focus_mode(&mut self) {
192 self.mode.exit_focus();
193 }
194
195 fn select_first_pane_in_active_tab(&mut self) {
197 if let Some(tab) = self.active_tab() {
198 let container = tab.pane_container();
199 if let Some(first_pane) = container.select_next(None) {
200 self.mode = InteractionMode::layout_with_selection(first_pane);
201 }
202 }
203 }
204
205 pub fn select_next_pane(&mut self) {
207 if !self.mode.is_layout() {
208 return;
209 }
210
211 if let Some(tab) = self.active_tab() {
212 let current = self.mode.selected_pane();
213 if let Some(next) = tab.pane_container().select_next(current) {
214 self.mode.select_pane(next);
215 }
216 }
217 }
218
219 pub fn select_prev_pane(&mut self) {
221 if !self.mode.is_layout() {
222 return;
223 }
224
225 if let Some(tab) = self.active_tab() {
226 let current = self.mode.selected_pane();
227 if let Some(prev) = tab.pane_container().select_prev(current) {
228 self.mode.select_pane(prev);
229 }
230 }
231 }
232
233 pub fn select_left(&mut self) {
235 if !self.mode.is_layout() {
236 return;
237 }
238
239 if let Some(tab) = self.active_tab() {
240 if let Some(current) = self.mode.selected_pane() {
241 if let Some(left) = tab.pane_container().select_left(current) {
242 self.mode.select_pane(left);
243 }
244 }
245 }
246 }
247
248 pub fn select_right(&mut self) {
250 if !self.mode.is_layout() {
251 return;
252 }
253
254 if let Some(tab) = self.active_tab() {
255 if let Some(current) = self.mode.selected_pane() {
256 if let Some(right) = tab.pane_container().select_right(current) {
257 self.mode.select_pane(right);
258 }
259 }
260 }
261 }
262
263 pub fn select_up(&mut self) {
265 if !self.mode.is_layout() {
266 return;
267 }
268
269 if let Some(tab) = self.active_tab() {
270 if let Some(current) = self.mode.selected_pane() {
271 if let Some(up) = tab.pane_container().select_up(current) {
272 self.mode.select_pane(up);
273 }
274 }
275 }
276 }
277
278 pub fn select_down(&mut self) {
280 if !self.mode.is_layout() {
281 return;
282 }
283
284 if let Some(tab) = self.active_tab() {
285 if let Some(current) = self.mode.selected_pane() {
286 if let Some(down) = tab.pane_container().select_down(current) {
287 self.mode.select_pane(down);
288 }
289 }
290 }
291 }
292
293 pub fn focus_selected(&mut self) {
295 if !self.mode.is_layout() {
296 return;
297 }
298
299 if let Some(selected) = self.mode.selected_pane() {
300 self.enter_focus_mode(selected);
301 }
302 }
303
304 pub fn handle_event(&mut self, event: Event) -> EventResult {
306 match event {
307 Event::Key(key) => self.handle_key_event(key),
308 Event::Mouse(mouse) => self.handle_mouse_event(mouse),
309 Event::Resize(_, _) => EventResult::Consumed,
310 _ => EventResult::NotHandled,
311 }
312 }
313
314 fn handle_key_event(&mut self, key: KeyEvent) -> EventResult {
316 match self.mode.clone() {
318 InteractionMode::Layout { selected_pane } => {
319 if self.keybindings.is_quit(&key) {
321 return EventResult::Quit;
322 }
323
324 if let Some(pane_id) = selected_pane {
326 if self.keybindings.is_copy_selection(&key) {
328 if let Some(tab) = self.active_tab_mut() {
329 if let Some(pane) = tab.pane_container_mut().get_pane_mut(pane_id) {
330 if pane.has_selection() && pane.handle_key(key) {
332 return EventResult::Consumed;
333 }
334 }
335 }
336 }
337
338 if self.keybindings.is_clear_selection(&key) {
340 if let Some(tab) = self.active_tab_mut() {
341 if let Some(pane) = tab.pane_container_mut().get_pane_mut(pane_id) {
342 if pane.has_selection() && pane.handle_key(key) {
344 return EventResult::Consumed;
345 }
346 }
347 }
348 }
350 }
351
352 self.handle_layout_mode_key(key)
354 }
355 InteractionMode::Focus { focused_pane } => {
356 if self.keybindings.is_exit_focus_mode(&key) {
359 self.exit_focus_mode();
360 return EventResult::Consumed;
361 }
362
363 if let Some(tab) = self.active_tab_mut() {
365 if let Some(pane) = tab.pane_container_mut().get_pane_mut(focused_pane) {
366 if pane.handle_key(key) {
367 return EventResult::Consumed;
368 }
369 }
370 }
371
372 EventResult::NotHandled
373 }
374 }
375 }
376
377 fn handle_layout_mode_key(&mut self, key: KeyEvent) -> EventResult {
379 if self.keybindings.is_clear_selection(&key) {
381 self.mode = InteractionMode::Layout {
382 selected_pane: None,
383 };
384 return EventResult::Consumed;
385 }
386
387 if self.keybindings.is_deselect_pane(&key) {
389 self.mode = InteractionMode::Layout {
390 selected_pane: None,
391 };
392 return EventResult::Consumed;
393 }
394
395 if let Some(tab_index) = self.keybindings.get_tab_switch_index(&key) {
397 if tab_index < self.tab_count() {
398 self.set_active_tab(tab_index);
399 return EventResult::Consumed;
400 }
401 return EventResult::NotHandled;
402 }
403
404 if self.keybindings.is_navigate_left(&key) {
406 self.select_left();
407 return EventResult::Consumed;
408 }
409 if self.keybindings.is_navigate_down(&key) {
410 self.select_down();
411 return EventResult::Consumed;
412 }
413 if self.keybindings.is_navigate_up(&key) {
414 self.select_up();
415 return EventResult::Consumed;
416 }
417 if self.keybindings.is_navigate_right(&key) {
418 self.select_right();
419 return EventResult::Consumed;
420 }
421
422 if self.keybindings.is_focus_pane(&key) {
424 self.focus_selected();
425 return EventResult::Consumed;
426 }
427
428 EventResult::NotHandled
429 }
430
431 fn handle_mouse_event(&mut self, mouse: MouseEvent) -> EventResult {
433 if mouse.kind == MouseEventKind::Down(MouseButton::Left) {
435 if mouse.row < 3 {
437 if let Some(tab_index) = self.nav_bar.handle_click(mouse.column, mouse.row) {
438 self.set_active_tab(tab_index);
439 return EventResult::Consumed;
440 }
441 }
442 }
443
444 if matches!(self.mode, InteractionMode::Layout { .. }) {
446 if let Some(tab) = self.active_tab_mut() {
447 let container = tab.pane_container_mut();
448
449 match mouse.kind {
450 MouseEventKind::Down(MouseButton::Left) => {
451 if let Some(divider_idx) =
453 container.find_divider_at(mouse.column, mouse.row)
454 {
455 container.start_drag(divider_idx);
456 return EventResult::Consumed;
457 }
458 }
459 MouseEventKind::Drag(MouseButton::Left) => {
460 if container.is_dragging() {
462 container.update_drag(mouse.column, mouse.row);
463 return EventResult::Consumed;
464 }
465 }
466 MouseEventKind::Up(MouseButton::Left) => {
467 if container.is_dragging() {
469 container.stop_drag();
470 return EventResult::Consumed;
471 }
472 }
473 MouseEventKind::Moved => {
474 container.update_hover(mouse.column, mouse.row);
476 }
477 _ => {}
478 }
479 }
480 }
481
482 match mouse.kind {
484 MouseEventKind::Down(MouseButton::Left) if mouse.row >= 3 => {
485 let current_pane = match &self.mode {
487 InteractionMode::Layout { selected_pane } => *selected_pane,
488 InteractionMode::Focus { focused_pane } => Some(*focused_pane),
489 };
490
491 if let Some(tab) = self.active_tab_mut() {
493 if let Some(pane_id) =
494 tab.pane_container().find_pane_at(mouse.column, mouse.row)
495 {
496 if Some(pane_id) == current_pane {
498 if let Some(pane) = tab.pane_container_mut().get_pane_mut(pane_id) {
499 if pane.contains_point(mouse.column, mouse.row) {
500 let local_mouse = pane.translate_mouse(mouse);
501 pane.start_selection(local_mouse.column, local_mouse.row);
502 return EventResult::Consumed;
503 }
504 }
505 } else {
506 match &mut self.mode {
508 InteractionMode::Layout { .. } => {
509 self.mode.select_pane(pane_id);
510 }
511 InteractionMode::Focus { focused_pane } => {
512 if *focused_pane != pane_id {
513 self.enter_focus_mode(pane_id);
514 }
515 }
516 }
517 return EventResult::Consumed;
518 }
519 }
520 }
521 }
522 MouseEventKind::Drag(MouseButton::Left) if mouse.row >= 3 => {
523 let current_pane = match &self.mode {
525 InteractionMode::Layout { selected_pane } => *selected_pane,
526 InteractionMode::Focus { focused_pane } => Some(*focused_pane),
527 };
528
529 if let Some(pane_id) = current_pane {
530 if let Some(tab) = self.active_tab_mut() {
531 if let Some(pane) = tab.pane_container_mut().get_pane_mut(pane_id) {
532 if pane.contains_point(mouse.column, mouse.row) {
533 let local_mouse = pane.translate_mouse(mouse);
534 pane.update_selection(local_mouse.column, local_mouse.row);
535 return EventResult::Consumed;
536 }
537 }
538 }
539 }
540 }
541 MouseEventKind::Up(MouseButton::Left) if mouse.row >= 3 => {
542 let current_pane = match &self.mode {
544 InteractionMode::Layout { selected_pane } => *selected_pane,
545 InteractionMode::Focus { focused_pane } => Some(*focused_pane),
546 };
547
548 if let Some(pane_id) = current_pane {
549 if let Some(tab) = self.active_tab_mut() {
550 if let Some(pane) = tab.pane_container_mut().get_pane_mut(pane_id) {
551 pane.end_selection();
552 return EventResult::Consumed;
553 }
554 }
555 }
556 }
557 _ => {}
558 }
559
560 if let InteractionMode::Focus { focused_pane } = self.mode.clone() {
562 if let Some(tab) = self.active_tab_mut() {
563 if let Some(pane) = tab.pane_container_mut().get_pane_mut(focused_pane) {
564 if pane.contains_point(mouse.column, mouse.row) {
566 let local_mouse = pane.translate_mouse(mouse);
568 if pane.handle_mouse(local_mouse) {
569 return EventResult::Consumed;
570 }
571 } else {
572 if mouse.kind == MouseEventKind::Down(MouseButton::Left) {
574 if let Some(new_pane) =
575 tab.pane_container().find_pane_at(mouse.column, mouse.row)
576 {
577 self.enter_focus_mode(new_pane);
578 return EventResult::Consumed;
579 }
580 }
581 }
582 }
583 }
584 }
585
586 EventResult::NotHandled
587 }
588
589 pub fn render(&mut self, frame: &mut ratatui::Frame) {
591 self.global_area = frame.area();
592
593 let chunks = Layout::default()
595 .direction(Direction::Vertical)
596 .constraints([
597 Constraint::Length(3), Constraint::Min(5), ])
600 .split(self.global_area);
601
602 let nav_area = chunks[0];
603 let tab_area = chunks[1];
604
605 self.nav_bar
607 .render_with_offset(frame, nav_area, self.nav_bar_offset);
608
609 let mode = self.mode.clone();
611 if let Some(tab) = self.active_tab_mut() {
612 tab.render(frame, tab_area, &mode);
613 }
614 }
615}
616
617impl Default for MasterLayout {
618 fn default() -> Self {
619 Self::new()
620 }
621}
622
623#[cfg(test)]
624mod tests {
625 use super::*;
626 use crate::master_layout::{Pane, PaneContent};
627 use crossterm::event::{KeyCode, KeyModifiers, MouseButton, MouseEventKind};
628 use ratatui::{backend::TestBackend, buffer::Buffer, widgets::Widget, Terminal};
629
630 struct MockContent {
632 title: String,
633 key_count: usize,
634 mouse_count: usize,
635 last_key: Option<KeyEvent>,
636 }
637
638 impl MockContent {
639 fn new(title: &str) -> Self {
640 Self {
641 title: title.to_string(),
642 key_count: 0,
643 mouse_count: 0,
644 last_key: None,
645 }
646 }
647 }
648
649 impl Widget for MockContent {
650 fn render(self, _area: Rect, _buf: &mut Buffer) {}
651 }
652
653 impl PaneContent for MockContent {
654 fn handle_key(&mut self, key: KeyEvent) -> bool {
655 self.key_count += 1;
656 self.last_key = Some(key);
657 true
658 }
659
660 fn handle_mouse(&mut self, _mouse: MouseEvent) -> bool {
661 self.mouse_count += 1;
662 true
663 }
664
665 fn title(&self) -> String {
666 self.title.clone()
667 }
668
669 fn render_content(&mut self, _area: Rect, _frame: &mut ratatui::Frame) {
670 }
672 }
673
674 fn create_test_layout() -> MasterLayout {
675 let mut layout = MasterLayout::new();
676
677 let mut tab = Tab::new("Test Tab");
679 let pane1 = Pane::new(PaneId::new("pane1"), Box::new(MockContent::new("Pane 1")));
680 let pane2 = Pane::new(PaneId::new("pane2"), Box::new(MockContent::new("Pane 2")));
681 tab.add_pane(pane1);
682 tab.add_pane(pane2);
683
684 layout.add_tab(tab);
685 layout
686 }
687
688 #[test]
689 fn test_master_layout_creation() {
690 let layout = MasterLayout::new();
691 assert_eq!(layout.tab_count(), 0);
692 assert_eq!(layout.active_tab_index(), 0);
693 assert!(layout.mode().is_layout());
694 }
695
696 #[test]
697 fn test_add_tab() {
698 let mut layout = MasterLayout::new();
699 let tab = Tab::new("Tab 1");
700
701 layout.add_tab(tab);
702
703 assert_eq!(layout.tab_count(), 1);
704 assert!(layout.active_tab().is_some());
705 assert_eq!(layout.active_tab().unwrap().name(), "Tab 1");
706 }
707
708 #[test]
709 fn test_add_multiple_tabs() {
710 let mut layout = MasterLayout::new();
711
712 layout.add_tab(Tab::new("Tab 1"));
713 layout.add_tab(Tab::new("Tab 2"));
714 layout.add_tab(Tab::new("Tab 3"));
715
716 assert_eq!(layout.tab_count(), 3);
717 assert_eq!(layout.active_tab_index(), 0);
718 }
719
720 #[test]
721 fn test_set_active_tab() {
722 let mut layout = MasterLayout::new();
723 layout.add_tab(Tab::new("Tab 1"));
724 layout.add_tab(Tab::new("Tab 2"));
725 layout.add_tab(Tab::new("Tab 3"));
726
727 layout.set_active_tab(1);
728 assert_eq!(layout.active_tab_index(), 1);
729 assert_eq!(layout.active_tab().unwrap().name(), "Tab 2");
730
731 layout.set_active_tab(2);
732 assert_eq!(layout.active_tab_index(), 2);
733 assert_eq!(layout.active_tab().unwrap().name(), "Tab 3");
734 }
735
736 #[test]
737 fn test_set_active_tab_invalid_index() {
738 let mut layout = MasterLayout::new();
739 layout.add_tab(Tab::new("Tab 1"));
740
741 layout.set_active_tab(10);
742 assert_eq!(layout.active_tab_index(), 0);
744 }
745
746 #[test]
747 fn test_mode_transitions() {
748 let mut layout = create_test_layout();
749
750 assert!(layout.mode().is_layout());
752
753 let pane_id = layout
755 .active_tab()
756 .unwrap()
757 .pane_container()
758 .get_pane_by_index(0)
759 .unwrap()
760 .id();
761
762 layout.enter_focus_mode(pane_id);
764 assert!(layout.mode().is_focus());
765 assert_eq!(layout.mode().focused_pane(), Some(pane_id));
766
767 layout.exit_focus_mode();
769 assert!(layout.mode().is_layout());
770 assert_eq!(layout.mode().selected_pane(), Some(pane_id));
771 }
772
773 #[test]
774 fn test_enter_key_focuses_selected_pane() {
775 let mut layout = create_test_layout();
776
777 let selected = layout.mode().selected_pane();
779 assert!(selected.is_some());
780
781 let key = KeyEvent::new(KeyCode::Enter, KeyModifiers::empty());
783 let result = layout.handle_key_event(key);
784
785 assert_eq!(result, EventResult::Consumed);
786 assert!(layout.mode().is_focus());
787 assert_eq!(layout.mode().focused_pane(), selected);
788 }
789
790 #[test]
791 fn test_ctrl_a_exits_focus_mode() {
792 let mut layout = create_test_layout();
793
794 let pane_id = layout
795 .active_tab()
796 .unwrap()
797 .pane_container()
798 .get_pane_by_index(0)
799 .unwrap()
800 .id();
801
802 layout.enter_focus_mode(pane_id);
804 assert!(layout.mode().is_focus());
805
806 let key = KeyEvent::new(KeyCode::Char('a'), KeyModifiers::CONTROL);
808 let result = layout.handle_key_event(key);
809
810 assert_eq!(result, EventResult::Consumed);
811 assert!(layout.mode().is_layout());
812 assert_eq!(layout.mode().selected_pane(), Some(pane_id));
813 }
814
815 #[test]
816 fn test_q_quits() {
817 let mut layout = create_test_layout();
818
819 let key = KeyEvent::new(KeyCode::Char('q'), KeyModifiers::empty());
820 let result = layout.handle_key_event(key);
821
822 assert_eq!(result, EventResult::Quit);
823 }
824
825 #[test]
826 fn test_tab_key_not_handled_in_layout_mode() {
827 let mut layout = create_test_layout();
829
830 let first_selected = layout.mode().selected_pane();
831 assert!(first_selected.is_some());
832
833 let key = KeyEvent::new(KeyCode::Tab, KeyModifiers::empty());
835 let result = layout.handle_key_event(key);
836
837 assert_eq!(result, EventResult::NotHandled);
839
840 let still_selected = layout.mode().selected_pane();
842 assert_eq!(first_selected, still_selected);
843 }
844
845 #[test]
846 fn test_shift_tab_not_handled_in_layout_mode() {
847 let mut layout = create_test_layout();
849
850 let first_selected = layout.mode().selected_pane();
851
852 let key = KeyEvent::new(KeyCode::BackTab, KeyModifiers::SHIFT);
854 let result = layout.handle_key_event(key);
855
856 assert_eq!(result, EventResult::NotHandled);
858
859 let still_selected = layout.mode().selected_pane();
861 assert_eq!(first_selected, still_selected);
862 }
863
864 #[test]
865 fn test_digit_keys_switch_tabs_in_layout_mode() {
866 let mut layout = MasterLayout::new();
868
869 let mut tab1 = Tab::new("Tab 1");
870 tab1.add_pane(Pane::new(
871 PaneId::new("p1"),
872 Box::new(MockContent::new("P1")),
873 ));
874 layout.add_tab(tab1);
875
876 let mut tab2 = Tab::new("Tab 2");
877 tab2.add_pane(Pane::new(
878 PaneId::new("p2"),
879 Box::new(MockContent::new("P2")),
880 ));
881 layout.add_tab(tab2);
882
883 let mut tab3 = Tab::new("Tab 3");
884 tab3.add_pane(Pane::new(
885 PaneId::new("p3"),
886 Box::new(MockContent::new("P3")),
887 ));
888 layout.add_tab(tab3);
889
890 assert!(layout.mode().is_layout());
892
893 let key = KeyEvent::new(KeyCode::Char('2'), KeyModifiers::empty());
895 let result = layout.handle_key_event(key);
896
897 assert_eq!(result, EventResult::Consumed);
898 assert_eq!(layout.active_tab_index(), 1);
899
900 let key = KeyEvent::new(KeyCode::Char('3'), KeyModifiers::empty());
902 layout.handle_key_event(key);
903 assert_eq!(layout.active_tab_index(), 2);
904 }
905
906 #[test]
907 fn test_hjkl_navigation_in_layout_mode() {
908 let mut layout = create_test_layout();
909
910 assert!(layout.mode().is_layout());
912 assert!(layout.mode().selected_pane().is_some());
913
914 let key = KeyEvent::new(KeyCode::Char('h'), KeyModifiers::empty());
916 let result = layout.handle_key_event(key);
917 assert_eq!(result, EventResult::Consumed);
918
919 let key = KeyEvent::new(KeyCode::Char('j'), KeyModifiers::empty());
921 let result = layout.handle_key_event(key);
922 assert_eq!(result, EventResult::Consumed);
923
924 let key = KeyEvent::new(KeyCode::Char('k'), KeyModifiers::empty());
926 let result = layout.handle_key_event(key);
927 assert_eq!(result, EventResult::Consumed);
928
929 let key = KeyEvent::new(KeyCode::Char('l'), KeyModifiers::empty());
931 let result = layout.handle_key_event(key);
932 assert_eq!(result, EventResult::Consumed);
933 }
934
935 #[test]
936 fn test_hjkl_ignored_in_focus_mode() {
937 let mut layout = create_test_layout();
938
939 let pane_id = layout
940 .active_tab()
941 .unwrap()
942 .pane_container()
943 .get_pane_by_index(0)
944 .unwrap()
945 .id();
946
947 layout.enter_focus_mode(pane_id);
949
950 let initial_mode = layout.mode().clone();
951
952 let key = KeyEvent::new(KeyCode::Char('h'), KeyModifiers::empty());
954 layout.handle_key_event(key);
955
956 assert!(layout.mode().is_focus());
958 assert_eq!(layout.mode(), &initial_mode);
959 }
960
961 #[test]
962 fn test_mouse_click_on_nav_bar() {
963 let mut layout = MasterLayout::new();
964 layout.add_tab(Tab::new("Tab 1"));
965 layout.add_tab(Tab::new("Tab 2"));
966
967 assert_eq!(layout.active_tab_index(), 0);
969
970 let backend = TestBackend::new(80, 24);
972 let mut terminal = Terminal::new(backend).unwrap();
973 terminal
974 .draw(|frame| {
975 layout.render(frame);
976 })
977 .unwrap();
978
979 let mouse = MouseEvent {
986 kind: MouseEventKind::Down(MouseButton::Left),
987 column: 5,
988 row: 1,
989 modifiers: KeyModifiers::empty(),
990 };
991
992 let result = layout.handle_mouse_event(mouse);
993 assert!(result == EventResult::Consumed || result == EventResult::NotHandled);
996 }
997
998 #[test]
999 fn test_mouse_click_on_pane_focuses() {
1000 let mut layout = create_test_layout();
1001
1002 let backend = TestBackend::new(80, 24);
1004 let mut terminal = Terminal::new(backend).unwrap();
1005 terminal
1006 .draw(|frame| {
1007 layout.render(frame);
1008 })
1009 .unwrap();
1010
1011 let mouse = MouseEvent {
1013 kind: MouseEventKind::Down(MouseButton::Left),
1014 column: 10,
1015 row: 5,
1016 modifiers: KeyModifiers::empty(),
1017 };
1018
1019 layout.handle_mouse_event(mouse);
1020
1021 }
1024
1025 #[test]
1026 fn test_keys_routed_to_focused_pane() {
1027 let mut layout = create_test_layout();
1028
1029 let pane_id = layout
1030 .active_tab()
1031 .unwrap()
1032 .pane_container()
1033 .get_pane_by_index(0)
1034 .unwrap()
1035 .id();
1036
1037 layout.enter_focus_mode(pane_id);
1039
1040 let key = KeyEvent::new(KeyCode::Char('x'), KeyModifiers::empty());
1042 let result = layout.handle_key_event(key);
1043
1044 assert_eq!(result, EventResult::Consumed);
1045 }
1047
1048 #[test]
1049 fn test_tab_key_routed_to_pane_in_focus_mode() {
1050 let mut layout = create_test_layout();
1052
1053 let pane_id = layout
1054 .active_tab()
1055 .unwrap()
1056 .pane_container()
1057 .get_pane_by_index(0)
1058 .unwrap()
1059 .id();
1060
1061 layout.enter_focus_mode(pane_id);
1063 assert!(layout.mode().is_focus());
1064
1065 let key = KeyEvent::new(KeyCode::Tab, KeyModifiers::empty());
1067 let result = layout.handle_key_event(key);
1068
1069 assert_eq!(result, EventResult::Consumed);
1072
1073 assert!(layout.mode().is_focus());
1075 assert_eq!(layout.mode().focused_pane(), Some(pane_id));
1076 }
1077
1078 #[test]
1079 fn test_number_keys_routed_to_pane_in_focus_mode() {
1080 let mut layout = MasterLayout::new();
1082
1083 let mut tab1 = Tab::new("Tab 1");
1084 tab1.add_pane(Pane::new(
1085 PaneId::new("p1"),
1086 Box::new(MockContent::new("P1")),
1087 ));
1088 layout.add_tab(tab1);
1089
1090 let mut tab2 = Tab::new("Tab 2");
1091 tab2.add_pane(Pane::new(
1092 PaneId::new("p2"),
1093 Box::new(MockContent::new("P2")),
1094 ));
1095 layout.add_tab(tab2);
1096
1097 assert_eq!(layout.active_tab_index(), 0);
1099
1100 let pane_id = layout
1101 .active_tab()
1102 .unwrap()
1103 .pane_container()
1104 .get_pane_by_index(0)
1105 .unwrap()
1106 .id();
1107
1108 layout.enter_focus_mode(pane_id);
1110 assert!(layout.mode().is_focus());
1111
1112 let key = KeyEvent::new(KeyCode::Char('2'), KeyModifiers::empty());
1114 let result = layout.handle_key_event(key);
1115
1116 assert_eq!(result, EventResult::Consumed);
1118
1119 assert_eq!(layout.active_tab_index(), 0);
1121
1122 assert!(layout.mode().is_focus());
1124 assert_eq!(layout.mode().focused_pane(), Some(pane_id));
1125 }
1126
1127 #[test]
1128 fn test_only_ctrl_a_exits_focus_mode() {
1129 let mut layout = create_test_layout();
1131
1132 let pane_id = layout
1133 .active_tab()
1134 .unwrap()
1135 .pane_container()
1136 .get_pane_by_index(0)
1137 .unwrap()
1138 .id();
1139
1140 layout.enter_focus_mode(pane_id);
1142 assert!(layout.mode().is_focus());
1143
1144 let test_keys = vec![
1146 KeyEvent::new(KeyCode::Esc, KeyModifiers::empty()),
1147 KeyEvent::new(KeyCode::Enter, KeyModifiers::empty()),
1148 KeyEvent::new(KeyCode::Char('h'), KeyModifiers::empty()),
1149 KeyEvent::new(KeyCode::Tab, KeyModifiers::empty()),
1150 KeyEvent::new(KeyCode::Char('x'), KeyModifiers::empty()),
1151 ];
1152
1153 for key in test_keys {
1154 layout.handle_key_event(key);
1155 assert!(
1157 layout.mode().is_focus(),
1158 "Key {:?} should not exit focus mode",
1159 key
1160 );
1161 }
1162
1163 let key = KeyEvent::new(KeyCode::Char('a'), KeyModifiers::CONTROL);
1165 let result = layout.handle_key_event(key);
1166
1167 assert_eq!(result, EventResult::Consumed);
1168 assert!(layout.mode().is_layout());
1169 }
1170
1171 #[test]
1172 fn test_all_keys_passed_to_focused_pane() {
1173 let mut layout = create_test_layout();
1175
1176 let pane_id = layout
1177 .active_tab()
1178 .unwrap()
1179 .pane_container()
1180 .get_pane_by_index(0)
1181 .unwrap()
1182 .id();
1183
1184 layout.enter_focus_mode(pane_id);
1186 assert!(layout.mode().is_focus());
1187
1188 let test_keys = vec![
1190 KeyEvent::new(KeyCode::Char('w'), KeyModifiers::CONTROL), KeyEvent::new(KeyCode::Char('c'), KeyModifiers::CONTROL), KeyEvent::new(KeyCode::Char('d'), KeyModifiers::CONTROL), KeyEvent::new(KeyCode::Esc, KeyModifiers::empty()), KeyEvent::new(KeyCode::Char('r'), KeyModifiers::CONTROL), ];
1196
1197 for key in test_keys {
1198 let result = layout.handle_key_event(key);
1199
1200 assert_eq!(
1202 result,
1203 EventResult::Consumed,
1204 "Key {:?} should be passed to pane",
1205 key
1206 );
1207
1208 assert!(
1210 layout.mode().is_focus(),
1211 "Key {:?} should not exit focus mode",
1212 key
1213 );
1214
1215 let _pane = layout
1217 .active_tab()
1218 .unwrap()
1219 .pane_container()
1220 .get_pane(pane_id)
1221 .unwrap();
1222
1223 }
1227 }
1228
1229 #[test]
1230 fn test_q_quits_only_in_layout_mode() {
1231 let mut layout = create_test_layout();
1233
1234 assert!(layout.mode().is_layout());
1236 let key = KeyEvent::new(KeyCode::Char('q'), KeyModifiers::empty());
1237 let result = layout.handle_key_event(key);
1238 assert_eq!(result, EventResult::Quit, "'q' should quit in Layout Mode");
1239
1240 let mut layout = create_test_layout();
1242 let pane_id = layout
1243 .active_tab()
1244 .unwrap()
1245 .pane_container()
1246 .get_pane_by_index(0)
1247 .unwrap()
1248 .id();
1249
1250 layout.enter_focus_mode(pane_id);
1251 assert!(layout.mode().is_focus());
1252
1253 let key = KeyEvent::new(KeyCode::Char('q'), KeyModifiers::empty());
1254 let result = layout.handle_key_event(key);
1255 assert_eq!(
1256 result,
1257 EventResult::Consumed,
1258 "'q' should be consumed by pane in Focus Mode, NOT quit app"
1259 );
1260
1261 assert!(
1263 layout.mode().is_focus(),
1264 "Should still be in Focus Mode after 'q'"
1265 );
1266 }
1267
1268 #[test]
1269 fn test_q_in_focus_mode_regression() {
1270 let mut layout = create_test_layout();
1274
1275 let pane_id = layout
1277 .active_tab()
1278 .unwrap()
1279 .pane_container()
1280 .get_pane_by_index(0)
1281 .unwrap()
1282 .id();
1283 layout.enter_focus_mode(pane_id);
1284
1285 assert!(layout.mode().is_focus(), "Should be in Focus Mode");
1287
1288 let q_key = KeyEvent::new(KeyCode::Char('q'), KeyModifiers::empty());
1290 let result = layout.handle_key_event(q_key);
1291
1292 assert_ne!(
1294 result,
1295 EventResult::Quit,
1296 "REGRESSION: 'q' in Focus Mode should NOT quit the application"
1297 );
1298 assert_eq!(
1299 result,
1300 EventResult::Consumed,
1301 "'q' should be consumed by the focused pane"
1302 );
1303
1304 assert!(
1306 layout.mode().is_focus(),
1307 "Should still be in Focus Mode after pressing 'q'"
1308 );
1309
1310 for _ in 0..5 {
1312 let result = layout.handle_key_event(q_key);
1313 assert_eq!(
1314 result,
1315 EventResult::Consumed,
1316 "Multiple 'q' presses should all be consumed"
1317 );
1318 assert!(layout.mode().is_focus(), "Should remain in Focus Mode");
1319 }
1320
1321 assert!(
1323 layout.mode().is_focus(),
1324 "Should remain in Focus Mode after all 'q' presses"
1325 );
1326 }
1327
1328 #[test]
1329 fn test_uppercase_q_in_focus_mode() {
1330 let mut layout = create_test_layout();
1334
1335 let pane_id = layout
1337 .active_tab()
1338 .unwrap()
1339 .pane_container()
1340 .get_pane_by_index(0)
1341 .unwrap()
1342 .id();
1343 layout.enter_focus_mode(pane_id);
1344 assert!(layout.mode().is_focus());
1345
1346 let uppercase_q = KeyEvent::new(KeyCode::Char('Q'), KeyModifiers::empty());
1348 let result = layout.handle_key_event(uppercase_q);
1349
1350 assert_ne!(
1352 result,
1353 EventResult::Quit,
1354 "REGRESSION: Uppercase 'Q' (Shift+Q) in Focus Mode should NOT quit the application"
1355 );
1356 assert_eq!(
1357 result,
1358 EventResult::Consumed,
1359 "Uppercase 'Q' should be consumed by the focused pane"
1360 );
1361
1362 assert!(
1364 layout.mode().is_focus(),
1365 "Should still be in Focus Mode after pressing 'Q'"
1366 );
1367 }
1368
1369 #[test]
1370 fn test_uppercase_q_quits_in_layout_mode() {
1371 let mut layout = create_test_layout();
1373 assert!(layout.mode().is_layout());
1374
1375 let lowercase_q = KeyEvent::new(KeyCode::Char('q'), KeyModifiers::empty());
1377 let result = layout.handle_key_event(lowercase_q);
1378 assert_eq!(
1379 result,
1380 EventResult::Quit,
1381 "Lowercase 'q' should quit in Layout Mode"
1382 );
1383
1384 let mut layout = create_test_layout();
1386 let uppercase_q = KeyEvent::new(KeyCode::Char('Q'), KeyModifiers::empty());
1387 let result = layout.handle_key_event(uppercase_q);
1388 assert_eq!(
1389 result,
1390 EventResult::Quit,
1391 "Uppercase 'Q' should quit in Layout Mode"
1392 );
1393 }
1394
1395 #[test]
1396 fn test_render_does_not_panic() {
1397 let mut layout = create_test_layout();
1398
1399 let backend = TestBackend::new(80, 24);
1400 let mut terminal = Terminal::new(backend).unwrap();
1401
1402 terminal
1403 .draw(|frame| {
1404 layout.render(frame);
1405 })
1406 .unwrap();
1407 }
1408
1409 #[test]
1410 fn test_empty_layout_renders() {
1411 let mut layout = MasterLayout::new();
1412
1413 let backend = TestBackend::new(80, 24);
1414 let mut terminal = Terminal::new(backend).unwrap();
1415
1416 terminal
1417 .draw(|frame| {
1418 layout.render(frame);
1419 })
1420 .unwrap();
1421 }
1422
1423 #[test]
1424 fn test_tab_switching_preserves_mode() {
1425 let mut layout = MasterLayout::new();
1426
1427 let mut tab1 = Tab::new("Tab 1");
1429 tab1.add_pane(Pane::new(
1430 PaneId::new("t1p1"),
1431 Box::new(MockContent::new("T1P1")),
1432 ));
1433 layout.add_tab(tab1);
1434
1435 let mut tab2 = Tab::new("Tab 2");
1436 tab2.add_pane(Pane::new(
1437 PaneId::new("t2p1"),
1438 Box::new(MockContent::new("T2P1")),
1439 ));
1440 layout.add_tab(tab2);
1441
1442 layout.set_active_tab(1);
1444
1445 assert!(layout.mode().is_layout());
1447 assert!(layout.mode().selected_pane().is_some());
1448 }
1449
1450 #[test]
1451 fn test_no_panes_no_selection() {
1452 let mut layout = MasterLayout::new();
1453 let tab = Tab::new("Empty Tab");
1454 layout.add_tab(tab);
1455
1456 assert!(layout.mode().is_layout());
1458 }
1459
1460 #[test]
1461 fn test_event_handling_resize() {
1462 let mut layout = create_test_layout();
1463
1464 let event = Event::Resize(100, 50);
1465 let result = layout.handle_event(event);
1466
1467 assert_eq!(result, EventResult::Consumed);
1468 }
1469
1470 #[test]
1471 fn test_select_next_with_no_tabs() {
1472 let mut layout = MasterLayout::new();
1473 layout.select_next_pane();
1474 }
1476
1477 #[test]
1478 fn test_focus_selected_with_no_selection() {
1479 let mut layout = MasterLayout::new();
1480 let tab = Tab::new("Empty Tab");
1481 layout.add_tab(tab);
1482
1483 layout.focus_selected();
1485 assert!(layout.mode().is_layout());
1487 }
1488
1489 #[test]
1490 fn test_esc_deselects_in_layout_mode() {
1491 let mut layout = create_test_layout();
1492
1493 assert!(layout.mode().is_layout());
1495 assert!(layout.mode().selected_pane().is_some());
1496
1497 let key = KeyEvent::new(KeyCode::Esc, KeyModifiers::empty());
1499 let result = layout.handle_key_event(key);
1500
1501 assert_eq!(result, EventResult::Consumed);
1503
1504 assert!(layout.mode().is_layout());
1506
1507 assert_eq!(layout.mode().selected_pane(), None);
1509 }
1510
1511 #[test]
1512 fn test_mouse_click_selects_pane_in_layout_mode() {
1513 let mut layout = create_test_layout();
1514
1515 assert!(layout.mode().is_layout());
1517 let initial_selection = layout.mode().selected_pane();
1518 assert!(initial_selection.is_some());
1519
1520 let _pane2 = layout
1522 .active_tab()
1523 .unwrap()
1524 .pane_container()
1525 .get_pane_by_index(1)
1526 .unwrap()
1527 .id();
1528
1529 let backend = TestBackend::new(80, 24);
1531 let mut terminal = Terminal::new(backend).unwrap();
1532 terminal
1533 .draw(|frame| {
1534 layout.render(frame);
1535 })
1536 .unwrap();
1537
1538 let mouse = MouseEvent {
1540 kind: MouseEventKind::Down(MouseButton::Left),
1541 column: 10,
1542 row: 5,
1543 modifiers: KeyModifiers::empty(),
1544 };
1545
1546 layout.handle_mouse_event(mouse);
1547
1548 assert!(
1551 layout.mode().is_layout(),
1552 "Mode should still be Layout Mode after mouse click in Layout Mode"
1553 );
1554
1555 assert!(
1557 layout.mode().focused_pane().is_none(),
1558 "No pane should be focused in Layout Mode"
1559 );
1560
1561 let current_selection = layout.mode().selected_pane();
1563 assert!(
1564 current_selection.is_some(),
1565 "A pane should be selected after mouse click"
1566 );
1567 }
1568
1569 #[test]
1570 fn test_keyboard_navigation_then_enter_focuses() {
1571 let mut layout = create_test_layout();
1572
1573 assert!(layout.mode().is_layout());
1575 let first_pane = layout.mode().selected_pane();
1576 assert!(first_pane.is_some());
1577
1578 let backend = TestBackend::new(80, 24);
1580 let mut terminal = Terminal::new(backend).unwrap();
1581 terminal
1582 .draw(|frame| {
1583 layout.render(frame);
1584 })
1585 .unwrap();
1586
1587 let key = KeyEvent::new(KeyCode::Char('l'), KeyModifiers::empty());
1589 let result = layout.handle_key_event(key);
1590 assert_eq!(result, EventResult::Consumed);
1591
1592 let second_pane = layout.mode().selected_pane();
1594 assert!(second_pane.is_some());
1595 assert_ne!(
1596 first_pane, second_pane,
1597 "Second pane should be different from first"
1598 );
1599 assert!(layout.mode().is_layout(), "Should still be in Layout Mode");
1600
1601 let key = KeyEvent::new(KeyCode::Enter, KeyModifiers::empty());
1603 let result = layout.handle_key_event(key);
1604 assert_eq!(result, EventResult::Consumed);
1605
1606 assert!(
1608 layout.mode().is_focus(),
1609 "Should be in Focus Mode after Enter"
1610 );
1611 assert_eq!(
1612 layout.mode().focused_pane(),
1613 second_pane,
1614 "Second pane should be focused"
1615 );
1616 }
1617
1618 #[test]
1619 fn test_ctrl_a_deselects_in_layout_mode() {
1620 let mut layout = create_test_layout();
1621
1622 assert!(layout.mode().is_layout());
1624 assert!(layout.mode().selected_pane().is_some());
1625
1626 let key = KeyEvent::new(KeyCode::Char('a'), KeyModifiers::CONTROL);
1628 let result = layout.handle_key_event(key);
1629
1630 assert_eq!(result, EventResult::Consumed);
1632
1633 assert!(layout.mode().is_layout());
1635
1636 assert_eq!(layout.mode().selected_pane(), None);
1638 }
1639
1640 #[test]
1641 fn test_click_different_pane_in_focus_mode_switches() {
1642 let mut layout = create_test_layout();
1643
1644 let pane1_id = layout
1646 .active_tab()
1647 .unwrap()
1648 .pane_container()
1649 .get_pane_by_index(0)
1650 .unwrap()
1651 .id();
1652
1653 let pane2_id = layout
1654 .active_tab()
1655 .unwrap()
1656 .pane_container()
1657 .get_pane_by_index(1)
1658 .unwrap()
1659 .id();
1660
1661 layout.enter_focus_mode(pane1_id);
1663 assert!(layout.mode().is_focus());
1664 assert_eq!(layout.mode().focused_pane(), Some(pane1_id));
1665
1666 let backend = TestBackend::new(80, 24);
1668 let mut terminal = Terminal::new(backend).unwrap();
1669 terminal
1670 .draw(|frame| {
1671 layout.render(frame);
1672 })
1673 .unwrap();
1674
1675 let mouse = MouseEvent {
1677 kind: MouseEventKind::Down(MouseButton::Left),
1678 column: 50,
1679 row: 12,
1680 modifiers: KeyModifiers::empty(),
1681 };
1682
1683 let result = layout.handle_mouse_event(mouse);
1684 assert_eq!(result, EventResult::Consumed);
1685
1686 assert!(layout.mode().is_focus());
1688
1689 assert_eq!(layout.mode().focused_pane(), Some(pane2_id));
1691 }
1692
1693 #[test]
1694 fn test_click_same_pane_in_focus_mode_maintains() {
1695 let mut layout = create_test_layout();
1696
1697 let pane1_id = layout
1699 .active_tab()
1700 .unwrap()
1701 .pane_container()
1702 .get_pane_by_index(0)
1703 .unwrap()
1704 .id();
1705
1706 layout.enter_focus_mode(pane1_id);
1708 assert!(layout.mode().is_focus());
1709 assert_eq!(layout.mode().focused_pane(), Some(pane1_id));
1710
1711 let backend = TestBackend::new(80, 24);
1713 let mut terminal = Terminal::new(backend).unwrap();
1714 terminal
1715 .draw(|frame| {
1716 layout.render(frame);
1717 })
1718 .unwrap();
1719
1720 let mouse = MouseEvent {
1722 kind: MouseEventKind::Down(MouseButton::Left),
1723 column: 10,
1724 row: 8,
1725 modifiers: KeyModifiers::empty(),
1726 };
1727
1728 let result = layout.handle_mouse_event(mouse);
1729 assert_eq!(result, EventResult::Consumed);
1730
1731 assert!(layout.mode().is_focus());
1733
1734 assert_eq!(layout.mode().focused_pane(), Some(pane1_id));
1736 }
1737
1738 #[test]
1739 fn test_hjkl_navigation_blocked_in_focus_mode() {
1740 let mut layout = create_test_layout();
1741
1742 let pane1_id = layout
1743 .active_tab()
1744 .unwrap()
1745 .pane_container()
1746 .get_pane_by_index(0)
1747 .unwrap()
1748 .id();
1749
1750 let pane2_id = layout
1751 .active_tab()
1752 .unwrap()
1753 .pane_container()
1754 .get_pane_by_index(1)
1755 .unwrap()
1756 .id();
1757
1758 assert_ne!(pane1_id, pane2_id);
1760
1761 layout.enter_focus_mode(pane1_id);
1763 assert!(layout.mode().is_focus());
1764 assert_eq!(layout.mode().focused_pane(), Some(pane1_id));
1765
1766 let key = KeyEvent::new(KeyCode::Char('l'), KeyModifiers::empty());
1768 let result = layout.handle_key_event(key);
1769
1770 assert_eq!(result, EventResult::Consumed);
1772
1773 assert!(layout.mode().is_focus(), "Should still be in Focus Mode");
1775 assert_eq!(
1776 layout.mode().focused_pane(),
1777 Some(pane1_id),
1778 "Should still be focused on first pane (navigation not allowed in Focus Mode)"
1779 );
1780 }
1781}