sql_cli/ui/traits/
navigation.rs

1use crate::app_state_container::AppStateContainer;
2use crate::buffer::{AppMode, BufferAPI};
3use crate::ui::viewport_manager::{RowNavigationResult, ViewportManager};
4use std::cell::RefCell;
5// Arc import removed - no longer needed
6
7/// Trait that provides navigation behavior for TUI components
8/// This extracts navigation methods from EnhancedTui to reduce coupling
9pub trait NavigationBehavior {
10    // Required methods - these provide access to TUI internals
11    fn viewport_manager(&self) -> &RefCell<Option<ViewportManager>>;
12    fn buffer_mut(&mut self) -> &mut dyn BufferAPI;
13    fn buffer(&self) -> &dyn BufferAPI;
14    fn state_container(&self) -> &AppStateContainer;
15    fn state_container_mut(&mut self) -> &mut AppStateContainer; // Added for mutable access
16    fn get_row_count(&self) -> usize;
17
18    // Mode switching method that needs to be implemented by EnhancedTui to handle shadow_state
19    fn set_mode_with_sync(&mut self, mode: AppMode, trigger: &str);
20
21    // Helper method that stays in the trait
22    fn apply_row_navigation_result(&mut self, result: RowNavigationResult) {
23        // Use centralized sync method
24        self.sync_row_state(result.row_position);
25
26        // Update scroll offset if viewport changed
27        if result.viewport_changed {
28            let mut offset = self.buffer().get_scroll_offset();
29            offset.0 = result.row_scroll_offset;
30            self.buffer_mut().set_scroll_offset(offset);
31            self.state_container().navigation_mut().scroll_offset.0 = result.row_scroll_offset;
32        }
33    }
34
35    /// Centralized method to sync row state across all components
36    /// This ensures Buffer, AppStateContainer, and any other row tracking stays in sync
37    fn sync_row_state(&mut self, row: usize) {
38        // 1. Update Buffer's selected row
39        self.buffer_mut().set_selected_row(Some(row));
40
41        // 2. Update AppStateContainer navigation state
42        self.state_container().navigation_mut().selected_row = row;
43
44        // 3. Also update via set_table_selected_row for consistency
45        // This ensures any internal bookkeeping in AppStateContainer is maintained
46        self.state_container().set_table_selected_row(Some(row));
47    }
48
49    // ========== Row Navigation Methods ==========
50
51    fn next_row(&mut self) {
52        let nav_result = {
53            let mut viewport_borrow = self.viewport_manager().borrow_mut();
54            viewport_borrow.as_mut().map(|vm| vm.navigate_row_down())
55        };
56
57        if let Some(nav_result) = nav_result {
58            self.apply_row_navigation_result(nav_result);
59        }
60    }
61
62    fn previous_row(&mut self) {
63        let nav_result = {
64            let mut viewport_borrow = self.viewport_manager().borrow_mut();
65            viewport_borrow.as_mut().map(|vm| vm.navigate_row_up())
66        };
67
68        if let Some(nav_result) = nav_result {
69            self.apply_row_navigation_result(nav_result);
70        }
71    }
72
73    fn goto_first_row(&mut self) {
74        let total_rows = self.get_row_count();
75        if total_rows > 0 {
76            let nav_result = {
77                let mut viewport_borrow = self.viewport_manager().borrow_mut();
78                viewport_borrow
79                    .as_mut()
80                    .map(|vm| vm.navigate_to_first_row(total_rows))
81            };
82
83            if let Some(nav_result) = nav_result {
84                self.apply_row_navigation_result(nav_result);
85            }
86        }
87    }
88
89    fn goto_last_row(&mut self) {
90        let total_rows = self.get_row_count();
91        if total_rows > 0 {
92            let nav_result = {
93                let mut viewport_borrow = self.viewport_manager().borrow_mut();
94                viewport_borrow
95                    .as_mut()
96                    .map(|vm| vm.navigate_to_last_row(total_rows))
97            };
98
99            if let Some(nav_result) = nav_result {
100                self.apply_row_navigation_result(nav_result);
101            }
102        }
103    }
104
105    fn page_down(&mut self) {
106        let nav_result = {
107            let mut viewport_borrow = self.viewport_manager().borrow_mut();
108            viewport_borrow.as_mut().map(|vm| vm.page_down())
109        };
110
111        if let Some(nav_result) = nav_result {
112            self.apply_row_navigation_result(nav_result);
113        }
114    }
115
116    fn page_up(&mut self) {
117        let nav_result = {
118            let mut viewport_borrow = self.viewport_manager().borrow_mut();
119            viewport_borrow.as_mut().map(|vm| vm.page_up())
120        };
121
122        if let Some(nav_result) = nav_result {
123            self.apply_row_navigation_result(nav_result);
124        }
125    }
126
127    fn half_page_down(&mut self) {
128        let nav_result = {
129            let mut viewport_borrow = self.viewport_manager().borrow_mut();
130            viewport_borrow.as_mut().map(|vm| vm.half_page_down())
131        };
132
133        if let Some(nav_result) = nav_result {
134            self.apply_row_navigation_result(nav_result);
135        }
136    }
137
138    fn half_page_up(&mut self) {
139        let nav_result = {
140            let mut viewport_borrow = self.viewport_manager().borrow_mut();
141            viewport_borrow.as_mut().map(|vm| vm.half_page_up())
142        };
143
144        if let Some(nav_result) = nav_result {
145            self.apply_row_navigation_result(nav_result);
146        }
147    }
148
149    fn goto_line(&mut self, line_number: usize) {
150        let total_rows = self.get_row_count();
151        if line_number > 0 && line_number <= total_rows {
152            let target_row = line_number - 1; // Convert to 0-indexed
153            let nav_result = {
154                let mut viewport_borrow = self.viewport_manager().borrow_mut();
155                viewport_borrow.as_mut().map(|vm| vm.goto_line(target_row))
156            };
157
158            if let Some(nav_result) = nav_result {
159                self.apply_row_navigation_result(nav_result);
160                self.state_container_mut()
161                    .set_status_message(format!("Jumped to row {} (centered)", line_number));
162            }
163        } else {
164            self.state_container_mut().set_status_message(format!(
165                "Row {} out of range (max: {})",
166                line_number, total_rows
167            ));
168        }
169    }
170
171    /// Navigate to the top of the current viewport
172    fn goto_viewport_top(&mut self) {
173        let nav_result = {
174            let mut viewport_borrow = self.viewport_manager().borrow_mut();
175            viewport_borrow
176                .as_mut()
177                .map(|vm| vm.navigate_to_viewport_top())
178        };
179
180        if let Some(nav_result) = nav_result {
181            self.apply_row_navigation_result(nav_result);
182        }
183    }
184
185    /// Navigate to the middle of the current viewport
186    fn goto_viewport_middle(&mut self) {
187        let nav_result = {
188            let mut viewport_borrow = self.viewport_manager().borrow_mut();
189            viewport_borrow
190                .as_mut()
191                .map(|vm| vm.navigate_to_viewport_middle())
192        };
193
194        if let Some(nav_result) = nav_result {
195            self.apply_row_navigation_result(nav_result);
196        }
197    }
198
199    /// Navigate to the bottom of the current viewport
200    fn goto_viewport_bottom(&mut self) {
201        let nav_result = {
202            let mut viewport_borrow = self.viewport_manager().borrow_mut();
203            viewport_borrow
204                .as_mut()
205                .map(|vm| vm.navigate_to_viewport_bottom())
206        };
207
208        if let Some(nav_result) = nav_result {
209            self.apply_row_navigation_result(nav_result);
210        }
211    }
212
213    /// Complete jump-to-row operation (called on Enter key)
214    fn complete_jump_to_row(&mut self, input: &str) {
215        if let Ok(row_num) = input.parse::<usize>() {
216            self.goto_line(row_num);
217        } else {
218            self.state_container_mut()
219                .set_status_message("Invalid row number".to_string());
220        }
221
222        // Use proper mode synchronization that updates both buffer and shadow_state
223        self.set_mode_with_sync(AppMode::Results, "jump_to_row_completed");
224
225        // Clear jump-to-row state
226        let jump_state = self.state_container_mut().jump_to_row_mut();
227        jump_state.input.clear();
228        jump_state.is_active = false;
229    }
230}