1const TABLE_BORDER_WIDTH: u16 = 4; const INPUT_AREA_HEIGHT: u16 = 3; const STATUS_BAR_HEIGHT: u16 = 3; const TOTAL_UI_CHROME: u16 = INPUT_AREA_HEIGHT + STATUS_BAR_HEIGHT; const TABLE_CHROME_ROWS: u16 = 3; use crate::app_state_container::{AppStateContainer, SelectionMode};
8use crate::buffer::{
9 AppMode, Buffer, BufferAPI, BufferManager, ColumnStatistics, ColumnType, EditMode,
10};
11use crate::buffer_handler::BufferHandler;
12use crate::config::config::Config;
13use crate::core::search_manager::{SearchConfig, SearchManager};
14use crate::cursor_manager::CursorManager;
15use crate::data::adapters::BufferAdapter;
16use crate::data::data_analyzer::DataAnalyzer;
17use crate::data::data_provider::DataProvider;
18use crate::data::data_view::DataView;
19use crate::debug::{DebugRegistry, MemoryTracker};
20use crate::debug_service::DebugService;
21use crate::help_text::HelpText;
22use crate::services::QueryOrchestrator;
23use crate::sql::hybrid_parser::HybridParser;
24use crate::sql_highlighter::SqlHighlighter;
25use crate::state::StateDispatcher;
26use crate::ui::debug::DebugContext;
27use crate::ui::input::action_handlers::ActionHandlerContext;
28use crate::ui::input::actions::{Action, ActionContext, ActionResult};
29use crate::ui::key_handling::{
30 format_key_for_display, ChordResult, KeyChordHandler, KeyDispatcher, KeyMapper,
31 KeyPressIndicator, KeySequenceRenderer,
32};
33use crate::ui::rendering::table_widget_manager::TableWidgetManager;
34use crate::ui::search::vim_search_adapter::VimSearchAdapter;
35use crate::ui::state::shadow_state::ShadowStateManager;
36use crate::ui::traits::{
37 BufferManagementBehavior, ColumnBehavior, InputBehavior, NavigationBehavior, YankBehavior,
38};
39use crate::ui::viewport::ColumnPackingMode;
40use crate::ui::viewport_manager::{ViewportEfficiency, ViewportManager};
41use crate::utils::logging::LogRingBuffer;
42use crate::widget_traits::DebugInfoProvider;
43use crate::widgets::debug_widget::DebugWidget;
44use crate::widgets::editor_widget::{BufferAction, EditorAction, EditorWidget};
45use crate::widgets::help_widget::HelpWidget;
46use crate::widgets::search_modes_widget::{SearchMode, SearchModesAction, SearchModesWidget};
47use crate::widgets::stats_widget::StatsWidget;
48use crate::widgets::tab_bar_widget::TabBarWidget;
49use crate::{buffer, data_analyzer, dual_logging};
50use anyhow::Result;
51use crossterm::{
52 event::{
53 self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEvent, KeyModifiers,
54 },
55 execute,
56 terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
57};
58use std::cell::RefCell;
59
60use ratatui::{
61 backend::{Backend, CrosstermBackend},
62 layout::{Constraint, Direction, Layout, Rect},
63 style::{Color, Modifier, Style},
64 text::{Line, Span, Text},
65 widgets::{Block, Borders, Paragraph},
66 Frame, Terminal,
67};
68use std::io;
69use std::sync::Arc;
70use tracing::{debug, error, info, trace, warn};
71use tui_input::{backend::crossterm::EventHandler, Input};
72
73struct CommandEditor {
76 input: Input,
78
79 scroll_offset: usize,
81 last_cursor_position: usize,
82 history_search_term: Option<String>,
83}
84
85impl CommandEditor {
86 fn new() -> Self {
87 Self {
88 input: Input::default(),
89 scroll_offset: 0,
90 last_cursor_position: 0,
91 history_search_term: None,
92 }
93 }
94
95 fn handle_input(
97 &mut self,
98 key: KeyEvent,
99 state_container: &mut AppStateContainer,
100 shadow_state: &RefCell<crate::ui::state::shadow_state::ShadowStateManager>,
101 ) -> Result<bool> {
102 debug!(
103 "CommandEditor::handle_input - key: {:?}, current text: '{}', cursor: {}",
104 key,
105 self.input.value(),
106 self.input.cursor()
107 );
108
109 match key.code {
111 KeyCode::Char(c) => {
113 if key.modifiers.is_empty() || key.modifiers == KeyModifiers::SHIFT {
114 let before = self.input.value().to_string();
116 let before_cursor = self.input.cursor();
117 self.input.handle_event(&Event::Key(key));
118 let after = self.input.value().to_string();
119 let after_cursor = self.input.cursor();
120 debug!(
121 "CommandEditor processed char '{}': text '{}' -> '{}', cursor {} -> {}",
122 c, before, after, before_cursor, after_cursor
123 );
124 return Ok(false);
125 }
126
127 if key.modifiers.contains(KeyModifiers::CONTROL) {
129 match c {
130 'a' | 'A' => {
131 self.input = self.input.clone().with_cursor(0);
133 return Ok(false);
134 }
135 'e' | 'E' => {
136 let len = self.input.value().len();
138 self.input = self.input.clone().with_cursor(len);
139 return Ok(false);
140 }
141 'k' | 'K' => {
142 let cursor = self.input.cursor();
144 let text = self.input.value();
145 if cursor < text.len() {
146 let new_text = text[..cursor].to_string();
147 self.input = Input::from(new_text).with_cursor(cursor);
148 }
149 return Ok(false);
150 }
151 'u' | 'U' => {
152 let cursor = self.input.cursor();
154 let text = self.input.value();
155 if cursor > 0 {
156 let new_text = text[cursor..].to_string();
157 self.input = Input::from(new_text).with_cursor(0);
158 }
159 return Ok(false);
160 }
161 'w' | 'W' => {
162 self.delete_word_backward();
164 return Ok(false);
165 }
166 'd' | 'D' => {
167 self.delete_word_forward();
169 return Ok(false);
170 }
171 'v' | 'V' => {
172 debug!("CommandEditor: Ctrl+V detected, letting parent handle clipboard paste");
177 }
178 'y' | 'Y' => {
179 debug!("CommandEditor: Ctrl+Y detected, letting parent handle yank");
182 }
183 _ => {}
184 }
185 }
186
187 if key.modifiers.contains(KeyModifiers::ALT) {
189 match c {
190 'b' | 'B' => {
191 self.move_word_backward();
193 return Ok(false);
194 }
195 'f' | 'F' => {
196 debug!("CommandEditor: Alt+F - Move word forward");
197 self.move_word_forward();
199 return Ok(false);
200 }
201 'd' | 'D' => {
202 self.delete_word_forward();
204 return Ok(false);
205 }
206 _ => {}
207 }
208 }
209 }
210
211 KeyCode::Backspace => {
213 self.input.handle_event(&Event::Key(key));
214 return Ok(false);
215 }
216 KeyCode::Delete => {
217 self.input.handle_event(&Event::Key(key));
218 return Ok(false);
219 }
220 KeyCode::Left => {
221 if key.modifiers.contains(KeyModifiers::CONTROL) {
222 self.move_word_backward();
223 } else {
224 self.input.handle_event(&Event::Key(key));
225 }
226 return Ok(false);
227 }
228 KeyCode::Right => {
229 if key.modifiers.contains(KeyModifiers::CONTROL) {
230 self.move_word_forward();
231 } else {
232 self.input.handle_event(&Event::Key(key));
233 }
234 return Ok(false);
235 }
236 KeyCode::Home => {
237 self.input = self.input.clone().with_cursor(0);
238 return Ok(false);
239 }
240 KeyCode::End => {
241 let len = self.input.value().len();
242 self.input = self.input.clone().with_cursor(len);
243 return Ok(false);
244 }
245
246 KeyCode::Tab => {
248 }
251
252 _ => {}
253 }
254
255 Ok(false)
257 }
258
259 fn delete_word_backward(&mut self) {
262 let cursor = self.input.cursor();
263 let text = self.input.value();
264
265 if cursor == 0 {
266 return;
267 }
268
269 let chars: Vec<char> = text.chars().collect();
271 let mut pos = cursor;
272
273 while pos > 0 && chars[pos - 1].is_whitespace() {
275 pos -= 1;
276 }
277
278 while pos > 0 && !chars[pos - 1].is_whitespace() {
280 pos -= 1;
281 }
282
283 let new_text = format!("{}{}", &text[..pos], &text[cursor..]);
285 self.input = Input::from(new_text).with_cursor(pos);
286 }
287
288 fn delete_word_forward(&mut self) {
289 let cursor = self.input.cursor();
290 let text = self.input.value();
291
292 if cursor >= text.len() {
293 return;
294 }
295
296 let chars: Vec<char> = text.chars().collect();
298 let mut pos = cursor;
299
300 while pos < chars.len() && !chars[pos].is_whitespace() {
302 pos += 1;
303 }
304
305 while pos < chars.len() && chars[pos].is_whitespace() {
307 pos += 1;
308 }
309
310 let new_text = format!("{}{}", &text[..cursor], &text[pos..]);
312 self.input = Input::from(new_text).with_cursor(cursor);
313 }
314
315 fn move_word_backward(&mut self) {
316 let cursor = self.input.cursor();
317 let text = self.input.value();
318
319 if cursor == 0 {
320 return;
321 }
322
323 let chars: Vec<char> = text.chars().collect();
324 let mut pos = cursor;
325
326 while pos > 0 && chars[pos - 1].is_whitespace() {
328 pos -= 1;
329 }
330
331 while pos > 0 && !chars[pos - 1].is_whitespace() {
333 pos -= 1;
334 }
335
336 self.input = self.input.clone().with_cursor(pos);
337 }
338
339 fn move_word_forward(&mut self) {
340 let cursor = self.input.cursor();
341 let text = self.input.value();
342
343 if cursor >= text.len() {
344 return;
345 }
346
347 let chars: Vec<char> = text.chars().collect();
348 let mut pos = cursor;
349
350 while pos < chars.len() && !chars[pos].is_whitespace() {
352 pos += 1;
353 }
354
355 while pos < chars.len() && chars[pos].is_whitespace() {
357 pos += 1;
358 }
359
360 self.input = self.input.clone().with_cursor(pos);
361 }
362
363 fn get_text(&self) -> String {
365 self.input.value().to_string()
366 }
367
368 fn set_text(&mut self, text: String) {
370 self.input = Input::from(text);
371 }
372
373 fn get_cursor(&self) -> usize {
375 self.input.cursor()
376 }
377
378 fn set_cursor(&mut self, pos: usize) {
380 let text = self.input.value().to_string();
381 self.input = tui_input::Input::new(text).with_cursor(pos);
382 }
383}
384
385pub struct EnhancedTuiApp {
386 state_container: AppStateContainer,
388 debug_service: Option<DebugService>,
390
391 input: Input,
392 command_editor: CommandEditor, cursor_manager: CursorManager, data_analyzer: DataAnalyzer, hybrid_parser: HybridParser,
396
397 config: Config,
399
400 sql_highlighter: SqlHighlighter,
401 pub(crate) debug_widget: DebugWidget,
402 editor_widget: EditorWidget,
403 stats_widget: StatsWidget,
404 help_widget: HelpWidget,
405 search_modes_widget: SearchModesWidget,
406 vim_search_adapter: RefCell<VimSearchAdapter>, search_manager: RefCell<SearchManager>, state_dispatcher: RefCell<StateDispatcher>, key_chord_handler: KeyChordHandler, key_dispatcher: KeyDispatcher, key_mapper: KeyMapper, buffer_handler: BufferHandler, pub(crate) navigation_timings: Vec<String>, pub(crate) render_timings: Vec<String>, log_buffer: Option<LogRingBuffer>, data_source: Option<String>, key_indicator: KeyPressIndicator,
428 key_sequence_renderer: KeySequenceRenderer,
429
430 pub(crate) viewport_manager: RefCell<Option<ViewportManager>>,
432 viewport_efficiency: RefCell<Option<ViewportEfficiency>>,
433
434 shadow_state: RefCell<crate::ui::state::shadow_state::ShadowStateManager>,
436
437 table_widget_manager: RefCell<TableWidgetManager>,
439
440 query_orchestrator: QueryOrchestrator,
442
443 pub(crate) debug_registry: DebugRegistry,
445 pub(crate) memory_tracker: MemoryTracker,
446}
447
448impl DebugContext for EnhancedTuiApp {
449 fn buffer(&self) -> &dyn BufferAPI {
450 self.state_container
451 .current_buffer()
452 .expect("Buffer should exist")
453 }
454
455 fn buffer_mut(&mut self) -> &mut dyn BufferAPI {
456 self.state_container
457 .current_buffer_mut()
458 .expect("Buffer should exist")
459 }
460
461 fn get_debug_widget(&self) -> &DebugWidget {
462 &self.debug_widget
463 }
464
465 fn get_debug_widget_mut(&mut self) -> &mut DebugWidget {
466 &mut self.debug_widget
467 }
468
469 fn get_shadow_state(&self) -> &RefCell<ShadowStateManager> {
470 &self.shadow_state
471 }
472
473 fn get_buffer_manager(&self) -> &BufferManager {
474 self.state_container.buffers()
475 }
476
477 fn get_viewport_manager(&self) -> &RefCell<Option<ViewportManager>> {
478 &self.viewport_manager
479 }
480
481 fn get_state_container(&self) -> &AppStateContainer {
482 &self.state_container
483 }
484
485 fn get_state_container_mut(&mut self) -> &mut AppStateContainer {
486 &mut self.state_container
487 }
488
489 fn get_navigation_timings(&self) -> &Vec<String> {
490 &self.navigation_timings
491 }
492
493 fn get_render_timings(&self) -> &Vec<String> {
494 &self.render_timings
495 }
496
497 fn debug_current_buffer(&mut self) {
498 }
501
502 fn get_input_cursor(&self) -> usize {
503 EnhancedTuiApp::get_input_cursor(self)
504 }
505
506 fn get_visual_cursor(&self) -> (usize, usize) {
507 EnhancedTuiApp::get_visual_cursor(self)
508 }
509
510 fn get_input_text(&self) -> String {
511 EnhancedTuiApp::get_input_text(self)
512 }
513
514 fn get_buffer_mut_if_available(&mut self) -> Option<&mut Buffer> {
515 self.state_container.buffers_mut().current_mut()
516 }
517
518 fn set_mode_via_shadow_state(&mut self, mode: AppMode, trigger: &str) {
519 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
520 debug!(
521 "set_mode_via_shadow_state: Setting mode to {:?} with trigger '{}'",
522 mode, trigger
523 );
524 self.shadow_state
525 .borrow_mut()
526 .set_mode(mode, buffer, trigger);
527 } else {
528 error!(
529 "set_mode_via_shadow_state: No buffer available! Cannot set mode to {:?}",
530 mode
531 );
532 }
533 }
534
535 fn collect_current_state(
536 &self,
537 ) -> (AppMode, String, String, Option<usize>, usize, usize, usize) {
538 if let Some(buffer) = self.state_container.buffers().current() {
539 let mode = buffer.get_mode();
540 if mode == AppMode::Debug {
541 (mode, String::new(), String::new(), None, 0, 0, 0)
542 } else {
543 (
544 mode,
545 buffer.get_last_query(),
546 buffer.get_input_text(),
547 buffer.get_selected_row(),
548 self.state_container.get_current_column(),
549 buffer.get_dataview().map_or(0, |v| v.source().row_count()),
550 buffer
551 .get_dataview()
552 .map_or(0, super::super::data::data_view::DataView::row_count),
553 )
554 }
555 } else {
556 (
557 AppMode::Command,
558 String::new(),
559 String::new(),
560 None,
561 0,
562 0,
563 0,
564 )
565 }
566 }
567
568 fn format_buffer_manager_state(&self) -> String {
569 let buffer_names: Vec<String> = self
570 .state_container
571 .buffers()
572 .all_buffers()
573 .iter()
574 .map(super::super::buffer::BufferAPI::get_name)
575 .collect();
576 let buffer_count = self.state_container.buffers().all_buffers().len();
577 let buffer_index = self.state_container.buffers().current_index();
578
579 format!(
580 "\n========== BUFFER MANAGER STATE ==========\n\
581 Number of Buffers: {}\n\
582 Current Buffer Index: {}\n\
583 Buffer Names: {}\n",
584 buffer_count,
585 buffer_index,
586 buffer_names.join(", ")
587 )
588 }
589
590 fn debug_generate_viewport_efficiency(&self) -> String {
591 if let Some(ref efficiency) = *self.viewport_efficiency.borrow() {
592 let mut result = String::from("\n========== VIEWPORT EFFICIENCY ==========\n");
593 result.push_str(&efficiency.to_debug_string());
594 result.push_str("\n==========================================\n");
595 result
596 } else {
597 String::new()
598 }
599 }
600
601 fn debug_generate_key_chord_info(&self) -> String {
602 let mut result = String::from("\n");
603 result.push_str(&self.key_chord_handler.format_debug_info());
604 result.push_str("========================================\n");
605 result
606 }
607
608 fn debug_generate_search_modes_info(&self) -> String {
609 let mut result = String::from("\n");
610 result.push_str(&self.search_modes_widget.debug_info());
611 result
612 }
613
614 fn debug_generate_state_container_info(&self) -> String {
615 let mut result = String::from("\n");
616 result.push_str(&self.state_container.debug_dump());
617 result.push('\n');
618 result
619 }
620
621 fn collect_debug_info(&self) -> String {
622 let mut debug_info = String::new();
624 debug_info
625 .push_str(&self.debug_generate_parser_info(&self.state_container.get_input_text()));
626 debug_info.push_str(&self.debug_generate_memory_info());
627 debug_info
628 }
629
630 fn debug_generate_parser_info(&self, query: &str) -> String {
632 EnhancedTuiApp::debug_generate_parser_info(self, query)
633 }
634
635 fn debug_generate_navigation_state(&self) -> String {
636 Self::debug_generate_navigation_state(self)
638 }
639
640 fn debug_generate_column_search_state(&self) -> String {
641 Self::debug_generate_column_search_state(self)
643 }
644
645 fn debug_generate_trace_logs(&self) -> String {
646 Self::debug_generate_trace_logs(self)
648 }
649
650 fn debug_generate_state_logs(&self) -> String {
651 Self::debug_generate_state_logs(self)
653 }
654}
655
656impl EnhancedTuiApp {
657 pub(crate) fn state_container(&self) -> &AppStateContainer {
660 &self.state_container
661 }
662
663 pub(crate) fn state_container_mut(&mut self) -> &mut AppStateContainer {
665 &mut self.state_container
666 }
667
668 pub(crate) fn sync_navigation_with_viewport(&self) {
673 debug!(target: "column_search_sync", "sync_navigation_with_viewport: ENTRY");
674 let viewport_borrow = self.viewport_manager.borrow();
675
676 if let Some(viewport) = viewport_borrow.as_ref() {
677 let mut nav = self.state_container.navigation_mut();
678
679 debug!(target: "column_search_sync", "sync_navigation_with_viewport: BEFORE - nav.selected_column: {}, viewport.crosshair_col: {}",
681 nav.selected_column, viewport.get_crosshair_col());
682 debug!(target: "column_search_sync", "sync_navigation_with_viewport: BEFORE - nav.selected_row: {}, viewport.crosshair_row: {}",
683 nav.selected_row, viewport.get_crosshair_row());
684
685 nav.selected_row = viewport.get_selected_row();
687 nav.selected_column = viewport.get_selected_column();
688 nav.scroll_offset = viewport.get_scroll_offset();
689
690 debug!(target: "column_search_sync", "sync_navigation_with_viewport: AFTER - nav.selected_column: {}, nav.selected_row: {}",
692 nav.selected_column, nav.selected_row);
693 debug!(target: "column_search_sync", "sync_navigation_with_viewport: Successfully synced NavigationState with ViewportManager");
694 } else {
695 debug!(target: "column_search_sync", "sync_navigation_with_viewport: No ViewportManager available to sync with");
696 }
697 debug!(target: "column_search_sync", "sync_navigation_with_viewport: EXIT");
698 }
699
700 fn sync_mode(&mut self, mode: AppMode, trigger: &str) {
703 debug!(target: "column_search_sync", "sync_mode: ENTRY - mode: {:?}, trigger: '{}'", mode, trigger);
704 use crate::ui::state::state_coordinator::StateCoordinator;
706 StateCoordinator::sync_mode_with_refs(
707 &mut self.state_container,
708 &self.shadow_state,
709 mode,
710 trigger,
711 );
712 debug!(target: "column_search_sync", "sync_mode: EXIT - StateCoordinator::sync_mode_with_refs completed");
713 }
714
715 fn save_viewport_to_current_buffer(&mut self) {
717 let viewport_borrow = self.viewport_manager.borrow();
718
719 if let Some(viewport) = viewport_borrow.as_ref() {
720 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
721 buffer.set_selected_row(Some(viewport.get_selected_row()));
723 buffer.set_current_column(viewport.get_selected_column());
724
725 buffer.set_scroll_offset(viewport.get_scroll_offset());
727 }
728 }
729 }
730
731 fn restore_viewport_from_current_buffer(&mut self) {
733 if let Some(buffer) = self.state_container.buffers().current() {
734 if let Some(dataview) = buffer.get_dataview() {
736 let needs_new_viewport = {
738 let viewport_borrow = self.viewport_manager.borrow();
739 viewport_borrow.is_none()
740 };
741
742 if needs_new_viewport {
743 self.viewport_manager =
745 RefCell::new(Some(ViewportManager::new(Arc::new(dataview.clone()))));
746 } else {
747 let mut viewport_borrow = self.viewport_manager.borrow_mut();
749 if let Some(ref mut viewport) = viewport_borrow.as_mut() {
750 viewport.set_dataview(Arc::new(dataview.clone()));
751 }
752 }
753
754 let mut viewport_borrow = self.viewport_manager.borrow_mut();
756 if let Some(ref mut viewport) = viewport_borrow.as_mut() {
757 let row = buffer.get_selected_row().unwrap_or(0);
762 let col = buffer.get_current_column();
763 viewport.set_crosshair(row, col);
764
765 let scroll_offset = buffer.get_scroll_offset();
767 viewport.set_scroll_offset(scroll_offset.0, scroll_offset.1);
768
769 let term_width = viewport.get_terminal_width();
771 let term_height = viewport.get_terminal_height() as u16;
772 viewport.update_terminal_size(term_width, term_height);
773
774 drop(viewport_borrow);
776 self.sync_navigation_with_viewport();
777 }
778
779 let mut table_manager = self.table_widget_manager.borrow_mut();
781 table_manager.set_dataview(Arc::new(dataview.clone()));
782 table_manager.force_render(); }
784 }
785 }
786
787 fn calculate_available_data_rows(terminal_height: u16) -> u16 {
792 crate::ui::rendering::ui_layout_utils::calculate_available_data_rows(terminal_height)
793 }
794
795 fn calculate_table_data_rows(table_area_height: u16) -> u16 {
798 crate::ui::rendering::ui_layout_utils::calculate_table_data_rows(table_area_height)
799 }
800
801 fn current_buffer(&self) -> Option<&dyn buffer::BufferAPI> {
805 self.state_container
806 .buffers()
807 .current()
808 .map(|b| b as &dyn buffer::BufferAPI)
809 }
810
811 fn buffer(&self) -> &dyn buffer::BufferAPI {
814 self.current_buffer()
815 .expect("No buffer available - this should not happen")
816 }
817
818 fn build_action_context(&self) -> ActionContext {
822 let nav = self.state_container.navigation();
823 let dataview = self.state_container.get_buffer_dataview();
824
825 ActionContext {
826 mode: self.state_container.get_mode(),
827 selection_mode: self.state_container.get_selection_mode(),
828 has_results: dataview.is_some(),
829 has_filter: !self.state_container.get_filter_pattern().is_empty()
830 || !self.state_container.get_fuzzy_filter_pattern().is_empty(),
831 has_search: !self.state_container.get_search_pattern().is_empty()
832 || self
833 .vim_search_adapter
834 .borrow()
835 .should_handle_key(&self.state_container)
836 || self.state_container.column_search().is_active,
837 row_count: dataview.as_ref().map_or(0, |v| v.row_count()),
838 column_count: dataview.as_ref().map_or(0, |v| v.column_count()),
839 current_row: nav.selected_row,
840 current_column: nav.selected_column,
841 }
842 }
843
844 fn try_handle_action(
848 &mut self,
849 action: Action,
850 context: &ActionContext,
851 ) -> Result<ActionResult> {
852 let temp_dispatcher = crate::ui::input::action_handlers::ActionDispatcher::new();
855 match temp_dispatcher.dispatch(&action, context, self) {
856 Ok(ActionResult::NotHandled) => {
857 }
860 result => {
861 return result;
863 }
864 }
865
866 use Action::{
867 Backspace, CycleColumnPacking, Delete, ExecuteQuery, ExitCurrentMode, HideEmptyColumns,
868 InsertChar, MoveCursorEnd, MoveCursorHome, MoveCursorLeft, MoveCursorRight,
869 NextSearchMatch, PreviousSearchMatch, Redo, ShowColumnStatistics, Sort,
870 StartHistorySearch, SwitchMode, SwitchModeWithCursor, Undo,
871 };
872
873 match action {
875 Sort(_column_idx) => {
888 self.toggle_sort_current_column();
890 Ok(ActionResult::Handled)
891 }
892 HideEmptyColumns => {
894 tracing::info!("HideEmptyColumns action triggered");
895
896 let (count, updated_dataview) = {
898 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
899 if let Some(ref mut viewport_manager) = *viewport_manager_borrow {
900 tracing::debug!("ViewportManager available, checking for empty columns");
901 let count = viewport_manager.hide_empty_columns();
902 if count > 0 {
903 (count, Some(viewport_manager.clone_dataview()))
904 } else {
905 (count, None)
906 }
907 } else {
908 tracing::warn!("No ViewportManager available to hide columns");
909 (0, None)
910 }
911 };
912
913 if let Some(updated_dataview) = updated_dataview {
915 self.state_container.set_dataview(Some(updated_dataview));
916 }
917
918 tracing::info!("Hidden {} empty columns", count);
919 let message = if count > 0 {
920 format!("Hidden {count} empty columns (press Ctrl+Shift+H to unhide)")
921 } else {
922 "No empty columns found".to_string()
923 };
924 self.state_container.set_status_message(message);
925 Ok(ActionResult::Handled)
926 }
927 ExitCurrentMode => {
930 match context.mode {
932 AppMode::Results => {
933 if let Some(selected) = self.state_container.get_table_selected_row() {
937 self.state_container.set_last_results_row(Some(selected));
938 let scroll_offset = self.state_container.get_scroll_offset();
939 self.state_container.set_last_scroll_offset(scroll_offset);
940 }
941
942 let last_query = self.state_container.get_last_query();
944 let current_input = self.state_container.get_input_text();
945 debug!(target: "mode", "Exiting Results mode: current input_text='{}', last_query='{}'", current_input, last_query);
946
947 if !last_query.is_empty() {
948 debug!(target: "buffer", "Restoring last_query to input_text: '{}'", last_query);
949 self.set_input_text(last_query.clone());
951 } else if !current_input.is_empty() {
952 debug!(target: "buffer", "No last_query but input_text has content, keeping: '{}'", current_input);
953 } else {
954 debug!(target: "buffer", "No last_query to restore when exiting Results mode");
955 }
956
957 debug!(target: "mode", "Switching from Results to Command mode");
958 self.state_container.set_mode(AppMode::Command);
959 self.shadow_state
960 .borrow_mut()
961 .observe_mode_change(AppMode::Command, "escape_from_results");
962 self.state_container.set_table_selected_row(None);
963 }
964 AppMode::Help => {
965 self.set_mode_via_shadow_state(AppMode::Results, "escape_from_help");
968 self.state_container.set_help_visible(false);
969 }
970 AppMode::Debug => {
971 self.set_mode_via_shadow_state(AppMode::Results, "escape_from_debug");
974 }
975 _ => {
976 self.set_mode_via_shadow_state(AppMode::Command, "escape_to_command");
979 }
980 }
981 Ok(ActionResult::Handled)
982 }
983 SwitchMode(target_mode) => {
984 if target_mode == AppMode::Results && !context.has_results {
987 self.state_container.set_status_message(
989 "No results to display. Run a query first.".to_string(),
990 );
991 Ok(ActionResult::Handled)
992 } else {
993 self.state_container.set_mode(target_mode.clone());
994
995 let trigger = match target_mode {
997 AppMode::Command => "switch_to_command",
998 AppMode::Results => "switch_to_results",
999 AppMode::Help => "switch_to_help",
1000 AppMode::History => "switch_to_history",
1001 _ => "switch_mode",
1002 };
1003 self.shadow_state
1004 .borrow_mut()
1005 .observe_mode_change(target_mode.clone(), trigger);
1006
1007 let msg = match target_mode {
1008 AppMode::Command => "Command mode - Enter SQL queries",
1009 AppMode::Results => {
1010 "Results mode - Navigate with arrows/hjkl, Tab for command"
1011 }
1012 _ => "",
1013 };
1014 if !msg.is_empty() {
1015 self.state_container.set_status_message(msg.to_string());
1016 }
1017 Ok(ActionResult::Handled)
1018 }
1019 }
1020 SwitchModeWithCursor(target_mode, cursor_position) => {
1021 use crate::ui::input::actions::{CursorPosition, SqlClause};
1022
1023 self.state_container.set_mode(target_mode.clone());
1025
1026 let trigger = match cursor_position {
1028 CursorPosition::End => "a_key_pressed",
1029 CursorPosition::Current => "i_key_pressed",
1030 CursorPosition::AfterClause(_) => "clause_navigation",
1031 };
1032 self.shadow_state
1033 .borrow_mut()
1034 .observe_mode_change(target_mode.clone(), trigger);
1035
1036 match cursor_position {
1038 CursorPosition::Current => {
1039 }
1041 CursorPosition::End => {
1042 let text = self.state_container.get_buffer_input_text();
1044 let text_len = text.len();
1045 self.state_container.set_input_cursor_position(text_len);
1046 self.input = tui_input::Input::new(text).with_cursor(text_len);
1048 }
1049 CursorPosition::AfterClause(clause) => {
1050 let input_text = self.state_container.get_input_text();
1052
1053 use crate::sql::recursive_parser::Lexer;
1055 let mut lexer = Lexer::new(&input_text);
1056 let tokens = lexer.tokenize_all_with_positions();
1057
1058 let mut cursor_pos = None;
1060 for i in 0..tokens.len() {
1061 let (_, end_pos, ref token) = tokens[i];
1062
1063 use crate::sql::recursive_parser::Token;
1065 let clause_matched = match (&clause, token) {
1066 (SqlClause::Select, Token::Select) => true,
1067 (SqlClause::From, Token::From) => true,
1068 (SqlClause::Where, Token::Where) => true,
1069 (SqlClause::OrderBy, Token::OrderBy) => true,
1070 (SqlClause::GroupBy, Token::GroupBy) => true,
1071 (SqlClause::Having, Token::Having) => true,
1072 (SqlClause::Limit, Token::Limit) => true,
1073 _ => false,
1074 };
1075
1076 if clause_matched {
1077 let mut clause_end = end_pos;
1079
1080 for j in (i + 1)..tokens.len() {
1082 let (_, token_end, ref next_token) = tokens[j];
1083
1084 match next_token {
1086 Token::Select
1087 | Token::From
1088 | Token::Where
1089 | Token::OrderBy
1090 | Token::GroupBy
1091 | Token::Having
1092 | Token::Limit => break,
1093 _ => clause_end = token_end,
1094 }
1095 }
1096
1097 cursor_pos = Some(clause_end);
1098 break;
1099 }
1100 }
1101
1102 if let Some(pos) = cursor_pos {
1105 self.state_container.set_input_cursor_position(pos);
1106 let text = self.state_container.get_buffer_input_text();
1108 self.input = tui_input::Input::new(text).with_cursor(pos);
1109 } else {
1110 let clause_text = match clause {
1112 SqlClause::Where => " WHERE ",
1113 SqlClause::OrderBy => " ORDER BY ",
1114 SqlClause::GroupBy => " GROUP BY ",
1115 SqlClause::Having => " HAVING ",
1116 SqlClause::Limit => " LIMIT ",
1117 SqlClause::Select => "SELECT ",
1118 SqlClause::From => " FROM ",
1119 };
1120
1121 let mut new_text = self.state_container.get_input_text();
1122 new_text.push_str(clause_text);
1123 let cursor_pos = new_text.len();
1124 self.set_input_text_with_cursor(new_text, cursor_pos);
1126 }
1127 }
1128 }
1129
1130 let msg = match target_mode {
1132 AppMode::Command => "Command mode - Enter SQL queries",
1133 _ => "",
1134 };
1135 if !msg.is_empty() {
1136 self.state_container.set_status_message(msg.to_string());
1137 }
1138
1139 Ok(ActionResult::Handled)
1140 }
1141
1142 MoveCursorLeft => Ok(ActionResult::NotHandled),
1145 MoveCursorRight => Ok(ActionResult::NotHandled),
1147 MoveCursorHome => Ok(ActionResult::NotHandled),
1149 MoveCursorEnd => Ok(ActionResult::NotHandled),
1151 Backspace => Ok(ActionResult::NotHandled),
1153 Delete => Ok(ActionResult::NotHandled),
1155 Undo => Ok(ActionResult::NotHandled),
1158 Redo => Ok(ActionResult::NotHandled),
1160 ExecuteQuery => {
1161 if context.mode == AppMode::Command {
1162 self.handle_execute_query()?;
1164 Ok(ActionResult::Handled)
1165 } else {
1166 Ok(ActionResult::NotHandled)
1167 }
1168 }
1169 InsertChar(c) => {
1170 if context.mode == AppMode::Command {
1171 self.state_container.insert_char_at_cursor(c);
1172
1173 self.state_container.clear_completion();
1175
1176 self.handle_completion();
1178
1179 Ok(ActionResult::Handled)
1180 } else {
1181 Ok(ActionResult::NotHandled)
1182 }
1183 }
1184
1185 NextSearchMatch => {
1186 debug!(target: "column_search_sync", "NextSearchMatch: 'n' key pressed, checking if search navigation should be handled");
1188 use crate::ui::state::state_coordinator::StateCoordinator;
1190 let should_handle = StateCoordinator::should_handle_next_match(
1191 &self.state_container,
1192 Some(&self.vim_search_adapter),
1193 );
1194 debug!(target: "column_search_sync", "NextSearchMatch: StateCoordinator::should_handle_next_match returned: {}", should_handle);
1195 if should_handle {
1196 debug!(target: "column_search_sync", "NextSearchMatch: Calling vim_search_next()");
1197 self.vim_search_next();
1198 debug!(target: "column_search_sync", "NextSearchMatch: vim_search_next() completed");
1199 } else {
1200 debug!(target: "column_search_sync", "NextSearchMatch: No active search (or cancelled with Escape), ignoring 'n' key");
1201 }
1202 Ok(ActionResult::Handled)
1203 }
1204 PreviousSearchMatch => {
1205 use crate::ui::state::state_coordinator::StateCoordinator;
1208 if StateCoordinator::should_handle_previous_match(
1209 &self.state_container,
1210 Some(&self.vim_search_adapter),
1211 ) {
1212 self.vim_search_previous();
1213 } else {
1214 return self.try_handle_action(Action::ToggleRowNumbers, context);
1216 }
1217 Ok(ActionResult::Handled)
1218 }
1219 ShowColumnStatistics => {
1220 self.calculate_column_statistics();
1221 Ok(ActionResult::Handled)
1222 }
1223 StartHistorySearch => {
1224 use crate::ui::state::state_coordinator::StateCoordinator;
1225
1226 let current_input = self.get_input_text();
1228
1229 let (input_to_use, _match_count) = StateCoordinator::start_history_search_with_refs(
1231 &mut self.state_container,
1232 &self.shadow_state,
1233 current_input,
1234 );
1235
1236 if input_to_use != self.get_input_text() {
1238 self.set_input_text(input_to_use);
1239 }
1240
1241 self.update_history_matches_in_container();
1243
1244 Ok(ActionResult::Handled)
1245 }
1246 CycleColumnPacking => {
1247 let message = {
1248 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
1249 let viewport_manager = viewport_manager_borrow
1250 .as_mut()
1251 .expect("ViewportManager must exist");
1252 let new_mode = viewport_manager.cycle_packing_mode();
1253 format!("Column packing: {}", new_mode.display_name())
1254 };
1255 self.state_container.set_status_message(message);
1256 Ok(ActionResult::Handled)
1257 }
1258 _ => {
1259 Ok(ActionResult::NotHandled)
1261 }
1262 }
1263 }
1264
1265 fn get_data_provider(&self) -> Option<Box<dyn DataProvider + '_>> {
1270 if let Some(buffer) = self.state_container.buffers().current() {
1273 if buffer.has_dataview() {
1275 return Some(Box::new(BufferAdapter::new(buffer)));
1276 }
1277 }
1278 None
1279 }
1280
1281 fn get_input_text(&self) -> String {
1287 if self.shadow_state.borrow().is_in_search_mode() {
1289 self.input.value().to_string() } else {
1292 self.state_container.get_buffer_input_text()
1294 }
1295 }
1296
1297 fn get_input_cursor(&self) -> usize {
1299 if self.shadow_state.borrow().is_in_search_mode() {
1301 self.input.cursor()
1303 } else {
1304 self.state_container.get_input_cursor_position()
1306 }
1307 }
1308
1309 fn set_input_text(&mut self, text: String) {
1311 let old_text = self.state_container.get_input_text();
1312 let mode = self.shadow_state.borrow().get_mode();
1313
1314 info!(target: "input", "SET_INPUT_TEXT: '{}' -> '{}' (mode: {:?})",
1316 if old_text.len() > 50 { format!("{}...", &old_text[..50]) } else { old_text.clone() },
1317 if text.len() > 50 { format!("{}...", &text[..50]) } else { text.clone() },
1318 mode);
1319
1320 self.state_container.set_buffer_input_text(text.clone());
1322
1323 self.input = tui_input::Input::new(text.clone()).with_cursor(text.len());
1326 }
1327
1328 fn set_input_text_with_cursor(&mut self, text: String, cursor_pos: usize) {
1330 let old_text = self.state_container.get_buffer_input_text();
1331 let old_cursor = self.state_container.get_input_cursor_position();
1332 let mode = self.state_container.get_mode();
1333
1334 info!(target: "input", "SET_INPUT_TEXT_WITH_CURSOR: '{}' (cursor {}) -> '{}' (cursor {}) (mode: {:?})",
1336 if old_text.len() > 50 { format!("{}...", &old_text[..50]) } else { old_text.clone() },
1337 old_cursor,
1338 if text.len() > 50 { format!("{}...", &text[..50]) } else { text.clone() },
1339 cursor_pos,
1340 mode);
1341
1342 self.state_container
1344 .set_buffer_input_text_with_cursor(text.clone(), cursor_pos);
1345
1346 self.input = tui_input::Input::new(text.clone()).with_cursor(cursor_pos);
1349 }
1350
1351 fn sync_all_input_states(&mut self) {
1357 let text = self.state_container.get_input_text();
1358 let cursor = self.state_container.get_input_cursor_position();
1359 let mode = self.state_container.get_mode();
1360
1361 let backtrace_str = std::backtrace::Backtrace::capture().to_string();
1363 let caller = backtrace_str
1364 .lines()
1365 .skip(3) .find(|line| line.contains("enhanced_tui") && !line.contains("sync_all_input_states"))
1367 .and_then(|line| line.split("::").last())
1368 .unwrap_or("unknown");
1369
1370 self.input = tui_input::Input::new(text.clone()).with_cursor(cursor);
1372
1373 self.state_container
1375 .set_input_text_with_cursor(text.clone(), cursor);
1376
1377 self.cursor_manager.reset_horizontal_scroll();
1381 self.state_container.scroll_mut().input_scroll_offset = 0;
1382
1383 self.update_horizontal_scroll(120); info!(target: "input", "SYNC_ALL [{}]: text='{}', cursor={}, mode={:?}, scroll_reset",
1387 caller,
1388 if text.len() > 50 { format!("{}...", &text[..50]) } else { text.clone() },
1389 cursor,
1390 mode);
1391 }
1392
1393 fn handle_input_key(&mut self, key: KeyEvent) -> bool {
1395 let mode = self.shadow_state.borrow().get_mode();
1397 match mode {
1398 AppMode::Search | AppMode::Filter | AppMode::FuzzyFilter | AppMode::ColumnSearch => {
1399 self.input.handle_event(&Event::Key(key));
1400 false
1401 }
1402 _ => {
1403 self.state_container.handle_input_key(key)
1405 }
1406 }
1407 }
1408 fn get_visual_cursor(&self) -> (usize, usize) {
1412 let mode = self.state_container.get_mode();
1414 let (text, cursor) = match mode {
1415 AppMode::Search | AppMode::Filter | AppMode::FuzzyFilter | AppMode::ColumnSearch => {
1416 (self.input.value().to_string(), self.input.cursor())
1418 }
1419 _ => {
1420 (
1422 self.state_container.get_input_text(),
1423 self.state_container.get_input_cursor_position(),
1424 )
1425 }
1426 };
1427
1428 let lines: Vec<&str> = text.split('\n').collect();
1429 let mut current_pos = 0;
1430 for (row, line) in lines.iter().enumerate() {
1431 if current_pos + line.len() >= cursor {
1432 return (row, cursor - current_pos);
1433 }
1434 current_pos += line.len() + 1; }
1436 (0, cursor)
1437 }
1438
1439 fn get_selection_mode(&self) -> SelectionMode {
1440 self.state_container.get_selection_mode()
1441 }
1443
1444 #[must_use]
1445 pub fn new(api_url: &str) -> Self {
1446 let config = Config::load().unwrap_or_else(|_e| {
1448 Config::default()
1450 });
1451
1452 crate::config::global::init_config(config.clone());
1454
1455 let data_source = if api_url.is_empty() {
1457 None
1458 } else {
1459 Some(api_url.to_string())
1460 };
1461
1462 if let Some(logger) = dual_logging::get_dual_logger() {
1464 logger.log(
1465 "INFO",
1466 "EnhancedTuiApp",
1467 &format!("Initializing TUI with API URL: {api_url}"),
1468 );
1469 }
1470
1471 let mut buffer_manager = BufferManager::new();
1473 let mut buffer = buffer::Buffer::new(1);
1474 buffer.set_case_insensitive(config.behavior.case_insensitive_default);
1476 buffer.set_compact_mode(config.display.compact_mode);
1477 buffer.set_show_row_numbers(config.display.show_row_numbers);
1478 buffer_manager.add_buffer(buffer);
1479
1480 let state_container = match AppStateContainer::new(buffer_manager) {
1482 Ok(container) => container,
1483 Err(e) => {
1484 panic!("Failed to initialize AppStateContainer: {e}");
1485 }
1486 };
1487
1488 let debug_service = DebugService::new(1000); debug_service.set_enabled(true); state_container.set_debug_service(debug_service.clone_service());
1492
1493 let help_widget = HelpWidget::new();
1495 let mut app = Self {
1498 state_container,
1499 debug_service: Some(debug_service),
1500 input: Input::default(),
1501 command_editor: CommandEditor::new(),
1502 cursor_manager: CursorManager::new(),
1503 data_analyzer: DataAnalyzer::new(),
1504 hybrid_parser: HybridParser::new(),
1505 config: config.clone(),
1506 sql_highlighter: SqlHighlighter::new(),
1507 debug_widget: DebugWidget::new(),
1508 editor_widget: EditorWidget::new(),
1509 stats_widget: StatsWidget::new(),
1510 help_widget,
1511 search_modes_widget: SearchModesWidget::new(),
1512 vim_search_adapter: RefCell::new(VimSearchAdapter::new()),
1513 state_dispatcher: RefCell::new(StateDispatcher::new()),
1514 search_manager: RefCell::new({
1515 let mut search_config = SearchConfig::default();
1516 search_config.case_sensitive = !config.behavior.case_insensitive_default;
1517 SearchManager::with_config(search_config)
1518 }),
1519 key_chord_handler: KeyChordHandler::new(),
1520 key_dispatcher: KeyDispatcher::new(),
1521 key_mapper: KeyMapper::new(),
1522 buffer_handler: BufferHandler::new(),
1525 navigation_timings: Vec::new(),
1526 render_timings: Vec::new(),
1527 log_buffer: dual_logging::get_dual_logger().map(|logger| logger.ring_buffer().clone()),
1528 data_source,
1529 key_indicator: {
1530 let mut indicator = KeyPressIndicator::new();
1531 if config.display.show_key_indicator {
1532 indicator.set_enabled(true);
1533 }
1534 indicator
1535 },
1536 key_sequence_renderer: {
1537 let mut renderer = KeySequenceRenderer::new();
1538 if config.display.show_key_indicator {
1539 renderer.set_enabled(true);
1540 }
1541 renderer
1542 },
1543 viewport_manager: RefCell::new(None), viewport_efficiency: RefCell::new(None),
1545 shadow_state: RefCell::new(crate::ui::state::shadow_state::ShadowStateManager::new()),
1546 table_widget_manager: RefCell::new(TableWidgetManager::new()),
1547 query_orchestrator: QueryOrchestrator::with_behavior_config(config.behavior.clone()),
1548 debug_registry: DebugRegistry::new(),
1549 memory_tracker: MemoryTracker::new(100),
1550 };
1551
1552 app.setup_state_coordination();
1554
1555 app
1556 }
1557
1558 fn setup_state_coordination(&mut self) {
1560 if let Some(current_buffer) = self.state_container.buffers().current() {
1562 let buffer_rc = std::rc::Rc::new(std::cell::RefCell::new(current_buffer.clone()));
1563 self.state_dispatcher.borrow_mut().set_buffer(buffer_rc);
1564 }
1565
1566 info!("State coordination setup complete");
1571 }
1572
1573 pub fn new_with_dataview(dataview: DataView, source_name: &str) -> Result<Self> {
1576 let mut app = Self::new("");
1578
1579 app.data_source = Some(source_name.to_string());
1581
1582 app.state_container.buffers_mut().clear_all();
1584 let mut buffer = buffer::Buffer::new(1);
1585
1586 let source_table = dataview.source_arc();
1589
1590 use crate::utils::memory_audit;
1592 use crate::utils::memory_tracker;
1593
1594 memory_tracker::track_memory("before_arc_share");
1595
1596 buffer.set_datatable(Some(source_table));
1598
1599 memory_tracker::track_memory("after_arc_share");
1600
1601 let audits = memory_audit::perform_memory_audit(
1603 buffer.get_datatable(),
1604 buffer.get_original_source(),
1605 None,
1606 );
1607 memory_audit::log_memory_audit(&audits);
1608
1609 buffer.set_dataview(Some(dataview.clone()));
1611 let buffer_name = std::path::Path::new(source_name)
1613 .file_name()
1614 .and_then(|s| s.to_str())
1615 .unwrap_or(source_name)
1616 .to_string();
1617 buffer.set_name(buffer_name);
1618
1619 buffer.set_case_insensitive(app.config.behavior.case_insensitive_default);
1621 buffer.set_compact_mode(app.config.display.compact_mode);
1622 buffer.set_show_row_numbers(app.config.display.show_row_numbers);
1623
1624 app.state_container.buffers_mut().add_buffer(buffer);
1626
1627 app.state_container.set_dataview(Some(dataview.clone()));
1629
1630 app.viewport_manager = RefCell::new(Some(ViewportManager::new(Arc::new(dataview.clone()))));
1632
1633 app.calculate_optimal_column_widths();
1635
1636 let row_count = dataview.row_count();
1638 let column_count = dataview.column_count();
1639 app.state_container
1640 .update_data_size(row_count, column_count);
1641
1642 Ok(app)
1643 }
1644
1645 pub fn add_dataview(&mut self, dataview: DataView, source_name: &str) -> Result<()> {
1647 use crate::ui::state::state_coordinator::StateCoordinator;
1649
1650 StateCoordinator::add_dataview_with_refs(
1651 &mut self.state_container,
1652 &self.viewport_manager,
1653 dataview,
1654 source_name,
1655 &self.config,
1656 )?;
1657
1658 self.calculate_optimal_column_widths();
1660
1661 Ok(())
1662 }
1663
1664 pub fn update_viewport_with_dataview(&mut self, dataview: DataView) {
1666 self.viewport_manager = RefCell::new(Some(ViewportManager::new(Arc::new(dataview))));
1667 }
1668
1669 pub fn vim_search_adapter(&self) -> &RefCell<VimSearchAdapter> {
1671 &self.vim_search_adapter
1672 }
1673
1674 pub fn set_sql_query(&mut self, table_name: &str, raw_table_name: &str) {
1676 use crate::ui::state::state_coordinator::StateCoordinator;
1677
1678 let auto_query = StateCoordinator::set_sql_query_with_refs(
1680 &mut self.state_container,
1681 &self.shadow_state,
1682 &mut self.hybrid_parser,
1683 table_name,
1684 raw_table_name,
1685 &self.config,
1686 );
1687
1688 self.set_input_text(auto_query);
1690 }
1691
1692 pub fn run(mut self) -> Result<()> {
1693 if let Err(e) = enable_raw_mode() {
1695 return Err(anyhow::anyhow!(
1696 "Failed to enable raw mode: {}. Try running with --classic flag.",
1697 e
1698 ));
1699 }
1700
1701 let mut stdout = io::stdout();
1702 if let Err(e) = execute!(stdout, EnterAlternateScreen, EnableMouseCapture) {
1703 let _ = disable_raw_mode();
1704 return Err(anyhow::anyhow!(
1705 "Failed to setup terminal: {}. Try running with --classic flag.",
1706 e
1707 ));
1708 }
1709
1710 let backend = CrosstermBackend::new(stdout);
1711 let mut terminal = match Terminal::new(backend) {
1712 Ok(t) => t,
1713 Err(e) => {
1714 let _ = disable_raw_mode();
1715 return Err(anyhow::anyhow!(
1716 "Failed to create terminal: {}. Try running with --classic flag.",
1717 e
1718 ));
1719 }
1720 };
1721
1722 let res = self.run_app(&mut terminal);
1723
1724 let _ = disable_raw_mode();
1726 let _ = execute!(
1727 terminal.backend_mut(),
1728 LeaveAlternateScreen,
1729 DisableMouseCapture
1730 );
1731 let _ = terminal.show_cursor();
1732
1733 match res {
1734 Ok(()) => Ok(()),
1735 Err(e) => Err(anyhow::anyhow!("TUI error: {}", e)),
1736 }
1737 }
1738
1739 fn initialize_viewport<B: Backend>(&mut self, terminal: &mut Terminal<B>) -> Result<()> {
1741 self.update_viewport_size();
1742 info!(target: "navigation", "Initial viewport size update completed");
1743 terminal.draw(|f| self.ui(f))?;
1744 Ok(())
1745 }
1746
1747 fn try_handle_debounced_actions<B: Backend>(
1749 &mut self,
1750 terminal: &mut Terminal<B>,
1751 ) -> Result<bool> {
1752 if !self.search_modes_widget.is_active() {
1753 return Ok(false);
1754 }
1755
1756 if let Some(action) = self.search_modes_widget.check_debounce() {
1757 let mut needs_redraw = false;
1758 if let SearchModesAction::ExecuteDebounced(mode, pattern) = action {
1759 info!(target: "search", "=== DEBOUNCED SEARCH EXECUTING ===");
1760 info!(target: "search", "Mode: {:?}, Pattern: '{}', AppMode: {:?}",
1761 mode, pattern, self.shadow_state.borrow().get_mode());
1762
1763 {
1765 let nav = self.state_container.navigation();
1766 info!(target: "search", "BEFORE: nav.selected_row={}, nav.selected_column={}",
1767 nav.selected_row, nav.selected_column);
1768 info!(target: "search", "BEFORE: buffer.selected_row={:?}, buffer.current_column={}",
1769 self.state_container.get_buffer_selected_row(), self.state_container.get_current_column());
1770 }
1771
1772 self.execute_search_action(mode, pattern);
1773
1774 {
1776 let nav = self.state_container.navigation();
1777 info!(target: "search", "AFTER: nav.selected_row={}, nav.selected_column={}",
1778 nav.selected_row, nav.selected_column);
1779 info!(target: "search", "AFTER: buffer.selected_row={:?}, buffer.current_column={}",
1780 self.state_container.get_buffer_selected_row(), self.state_container.get_current_column());
1781
1782 let viewport_manager = self.viewport_manager.borrow();
1784 if let Some(ref vm) = *viewport_manager {
1785 info!(target: "search", "AFTER: ViewportManager crosshair=({}, {})",
1786 vm.get_crosshair_row(), vm.get_crosshair_col());
1787 }
1788 }
1789
1790 info!(target: "search", "=== FORCING REDRAW ===");
1791 needs_redraw = true;
1793 }
1794
1795 if needs_redraw || self.table_widget_manager.borrow().needs_render() {
1797 info!(target: "search", "Triggering redraw: needs_redraw={}, table_needs_render={}",
1798 needs_redraw, self.table_widget_manager.borrow().needs_render());
1799 terminal.draw(|f| self.ui(f))?;
1800 self.table_widget_manager.borrow_mut().rendered();
1801 }
1802 }
1803 Ok(false)
1804 }
1805
1806 fn try_handle_chord_processing(&mut self, key: crossterm::event::KeyEvent) -> Result<bool> {
1808 if self
1811 .vim_search_adapter
1812 .borrow()
1813 .should_handle_key(&self.state_container)
1814 {
1815 let handled = self
1816 .vim_search_adapter
1817 .borrow_mut()
1818 .handle_key(key.code, &mut self.state_container);
1819 if handled {
1820 debug!("VimSearchAdapter handled key: {:?}", key.code);
1821 return Ok(false); }
1823 }
1824
1825 if let Some(result) = self.try_handle_buffer_operations(&key)? {
1827 return Ok(result);
1828 }
1829
1830 let chord_result = self.key_chord_handler.process_key(key);
1831 debug!("Chord handler returned: {:?}", chord_result);
1832
1833 match chord_result {
1834 ChordResult::CompleteChord(action) => {
1835 debug!("Chord completed: {:?}", action);
1837 self.key_sequence_renderer.clear_chord_mode();
1839
1840 let current_mode = self.shadow_state.borrow().get_mode();
1842
1843 self.try_handle_action(
1845 action,
1846 &ActionContext {
1847 mode: current_mode,
1848 selection_mode: self.state_container.get_selection_mode(),
1849 has_results: self.state_container.get_buffer_dataview().is_some(),
1850 has_filter: false,
1851 has_search: false,
1852 row_count: self.get_row_count(),
1853 column_count: self.get_column_count(),
1854 current_row: self.state_container.get_table_selected_row().unwrap_or(0),
1855 current_column: self.state_container.get_current_column(),
1856 },
1857 )?;
1858 Ok(false)
1859 }
1860 ChordResult::PartialChord(description) => {
1861 self.state_container.set_status_message(description.clone());
1863 if description.contains("y=row") {
1866 self.key_sequence_renderer
1867 .set_chord_mode(Some("y(a,c,q,r,v)".to_string()));
1868 } else {
1869 self.key_sequence_renderer
1870 .set_chord_mode(Some(description.clone()));
1871 }
1872 Ok(false) }
1874 ChordResult::Cancelled => {
1875 self.state_container
1876 .set_status_message("Chord cancelled".to_string());
1877 self.key_sequence_renderer.clear_chord_mode();
1879 Ok(false)
1880 }
1881 ChordResult::SingleKey(single_key) => {
1882 self.handle_results_input(single_key)
1884 }
1885 }
1886 }
1887
1888 fn try_handle_mode_dispatch(&mut self, key: crossterm::event::KeyEvent) -> Result<bool> {
1890 let mode = self.shadow_state.borrow().get_mode();
1891 debug!(
1892 "try_handle_mode_dispatch: mode={:?}, key={:?}",
1893 mode, key.code
1894 );
1895 match mode {
1896 AppMode::Command => self.handle_command_input(key),
1897 AppMode::Results => {
1898 self.try_handle_chord_processing(key)
1900 }
1901 AppMode::Search | AppMode::Filter | AppMode::FuzzyFilter | AppMode::ColumnSearch => {
1902 self.handle_search_modes_input(key)
1903 }
1904 AppMode::Help => self.handle_help_input(key),
1905 AppMode::History => self.handle_history_input(key),
1906 AppMode::Debug => self.handle_debug_input(key),
1907 AppMode::PrettyQuery => self.handle_pretty_query_input(key),
1908 AppMode::JumpToRow => self.handle_jump_to_row_input(key),
1909 AppMode::ColumnStats => self.handle_column_stats_input(key),
1910 }
1911 }
1912
1913 fn try_handle_key_event<B: Backend>(
1915 &mut self,
1916 terminal: &mut Terminal<B>,
1917 key: crossterm::event::KeyEvent,
1918 ) -> Result<bool> {
1919 if key.kind != crossterm::event::KeyEventKind::Press {
1922 return Ok(false);
1923 }
1924
1925 if key.code == KeyCode::Char('c') && key.modifiers.contains(KeyModifiers::CONTROL) {
1928 info!(target: "app", "Ctrl-C detected, forcing exit");
1929 return Ok(true);
1930 }
1931
1932 let key_display = format_key_for_display(&key);
1934 self.key_indicator.record_key(key_display.clone());
1935 self.key_sequence_renderer.record_key(key_display);
1936
1937 let should_exit = self.try_handle_mode_dispatch(key)?;
1939
1940 if should_exit {
1941 return Ok(true);
1942 }
1943
1944 if self.table_widget_manager.borrow().needs_render() {
1946 info!("TableWidgetManager needs render after key event");
1947 }
1948 terminal.draw(|f| self.ui(f))?;
1949 self.table_widget_manager.borrow_mut().rendered();
1950
1951 Ok(false)
1952 }
1953
1954 fn try_handle_events<B: Backend>(&mut self, terminal: &mut Terminal<B>) -> Result<bool> {
1956 if event::poll(std::time::Duration::from_millis(50))? {
1958 if let Event::Key(key) = event::read()? {
1959 if self.try_handle_key_event(terminal, key)? {
1960 return Ok(true);
1961 }
1962 } else {
1963 }
1965 } else {
1966 if self.search_modes_widget.is_active()
1968 || self.table_widget_manager.borrow().needs_render()
1969 {
1970 if self.table_widget_manager.borrow().needs_render() {
1971 info!("TableWidgetManager needs periodic render");
1972 }
1973 terminal.draw(|f| self.ui(f))?;
1974 self.table_widget_manager.borrow_mut().rendered();
1975 }
1976 }
1977 Ok(false)
1978 }
1979
1980 fn run_app<B: Backend>(&mut self, terminal: &mut Terminal<B>) -> Result<()> {
1981 self.initialize_viewport(terminal)?;
1982
1983 loop {
1984 if self.try_handle_debounced_actions(terminal)? {
1986 break;
1987 }
1988
1989 if self.try_handle_events(terminal)? {
1991 break;
1992 }
1993 }
1994 Ok(())
1995 }
1996
1997 fn handle_command_input(&mut self, key: crossterm::event::KeyEvent) -> Result<bool> {
1998 let normalized_key = self.normalize_and_log_key(key);
2000
2001 let is_special_combo = if let KeyCode::Char(c) = normalized_key.code {
2008 (normalized_key.modifiers.contains(KeyModifiers::CONTROL) && matches!(c,
2010 'x' | 'X' | 'p' | 'P' | 'n' | 'N' | 'r' | 'R' | 'j' | 'J' | 'o' | 'O' | 'b' | 'B' | 'l' | 'L' )) ||
2019 (normalized_key.modifiers.contains(KeyModifiers::ALT) && matches!(c,
2021 'x' | 'X' ))
2023 } else {
2024 false
2025 };
2026
2027 let should_try_command_editor = !is_special_combo
2028 && matches!(
2029 normalized_key.code,
2030 KeyCode::Char(_)
2031 | KeyCode::Backspace
2032 | KeyCode::Delete
2033 | KeyCode::Left
2034 | KeyCode::Right
2035 | KeyCode::Home
2036 | KeyCode::End
2037 );
2038
2039 if should_try_command_editor {
2040 let before_text = self.input.value().to_string();
2043 let before_cursor = self.input.cursor();
2044
2045 if self.command_editor.get_text() != before_text {
2046 self.command_editor.set_text(before_text.clone());
2047 }
2048 if self.command_editor.get_cursor() != before_cursor {
2049 self.command_editor.set_cursor(before_cursor);
2050 }
2051
2052 let result = self.command_editor.handle_input(
2054 normalized_key,
2055 &mut self.state_container,
2056 &self.shadow_state,
2057 )?;
2058
2059 let new_text = self.command_editor.get_text();
2062 let new_cursor = self.command_editor.get_cursor();
2063
2064 if new_text != before_text || new_cursor != before_cursor {
2066 debug!(
2067 "CommandEditor changed input: '{}' -> '{}', cursor: {} -> {}",
2068 before_text, new_text, before_cursor, new_cursor
2069 );
2070 }
2071
2072 self.input = tui_input::Input::from(new_text.clone()).with_cursor(new_cursor);
2074
2075 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
2078 buffer.set_input_text(new_text.clone());
2079 buffer.set_input_cursor_position(new_cursor);
2080 }
2081
2082 self.state_container.set_input_text(new_text);
2084 self.state_container.set_input_cursor_position(new_cursor);
2085
2086 if result {
2087 return Ok(true);
2088 }
2089
2090 return Ok(false);
2092 }
2093 if let Some(result) = self.try_action_system(normalized_key)? {
2097 return Ok(result);
2098 }
2099
2100 if let Some(result) = self.try_editor_widget(normalized_key)? {
2102 return Ok(result);
2103 }
2104
2105 if let Some(result) = self.try_handle_history_navigation(&normalized_key)? {
2109 return Ok(result);
2110 }
2111
2112 let old_cursor = self.get_input_cursor();
2114
2115 trace!(target: "input", "Key: {:?} Modifiers: {:?}", key.code, key.modifiers);
2117
2118 if let Some(result) = self.try_handle_buffer_operations(&key)? {
2123 return Ok(result);
2124 }
2125
2126 if let Some(result) = self.try_handle_function_keys(&key)? {
2128 return Ok(result);
2129 }
2130
2131 if let Some(result) = self.try_handle_text_editing(&key)? {
2133 return Ok(result);
2134 }
2135
2136 if let Some(result) = self.try_handle_mode_transitions(&key, old_cursor)? {
2138 return Ok(result);
2139 }
2140
2141 Ok(false)
2145 }
2146
2147 fn normalize_and_log_key(
2152 &mut self,
2153 key: crossterm::event::KeyEvent,
2154 ) -> crossterm::event::KeyEvent {
2155 let normalized = self.state_container.normalize_key(key);
2156
2157 let action = self
2159 .key_dispatcher
2160 .get_command_action(&normalized)
2161 .map(std::string::ToString::to_string);
2162
2163 if normalized != key {
2165 self.state_container
2166 .log_key_press(key, Some(format!("normalized to {normalized:?}")));
2167 }
2168 self.state_container.log_key_press(normalized, action);
2169
2170 normalized
2171 }
2172
2173 fn try_action_system(
2175 &mut self,
2176 normalized_key: crossterm::event::KeyEvent,
2177 ) -> Result<Option<bool>> {
2178 let action_context = self.build_action_context();
2179 if let Some(action) = self.key_mapper.map_key(normalized_key, &action_context) {
2180 info!(
2181 "✓ Action system (Command): key {:?} -> action {:?}",
2182 normalized_key.code, action
2183 );
2184 if let Ok(result) = self.try_handle_action(action, &action_context) {
2185 match result {
2186 ActionResult::Handled => {
2187 debug!("Action handled by new system in Command mode");
2188 return Ok(Some(false));
2189 }
2190 ActionResult::Exit => {
2191 return Ok(Some(true));
2192 }
2193 ActionResult::NotHandled => {
2194 }
2196 _ => {}
2197 }
2198 }
2199 }
2200 Ok(None)
2201 }
2202
2203 fn try_editor_widget(
2205 &mut self,
2206 normalized_key: crossterm::event::KeyEvent,
2207 ) -> Result<Option<bool>> {
2208 let key_dispatcher = self.key_dispatcher.clone();
2209 let editor_result = if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
2210 self.editor_widget
2211 .handle_key(normalized_key, &key_dispatcher, buffer)?
2212 } else {
2213 EditorAction::PassToMainApp(normalized_key)
2214 };
2215
2216 match editor_result {
2217 EditorAction::Quit => return Ok(Some(true)),
2218 EditorAction::ExecuteQuery => {
2219 return self.handle_execute_query().map(Some);
2220 }
2221 EditorAction::BufferAction(buffer_action) => {
2222 return self.handle_buffer_action(buffer_action).map(Some);
2223 }
2224 EditorAction::ExpandAsterisk => {
2225 return self.handle_expand_asterisk().map(Some);
2226 }
2227 EditorAction::ShowHelp => {
2228 self.state_container.set_help_visible(true);
2229 self.set_mode_via_shadow_state(AppMode::Help, "help_requested");
2231 return Ok(Some(false));
2232 }
2233 EditorAction::ShowDebug => {
2234 return Ok(Some(false));
2236 }
2237 EditorAction::ShowPrettyQuery => {
2238 self.show_pretty_query();
2239 return Ok(Some(false));
2240 }
2241 EditorAction::SwitchMode(mode) => {
2242 self.handle_editor_mode_switch(mode);
2243 return Ok(Some(false));
2244 }
2245 EditorAction::PassToMainApp(_) => {
2246 }
2248 EditorAction::Continue => return Ok(Some(false)),
2249 }
2250
2251 Ok(None)
2252 }
2253
2254 fn handle_editor_mode_switch(&mut self, mode: AppMode) {
2256 debug!(target: "shadow_state", "EditorAction::SwitchMode to {:?}", mode);
2257 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
2258 let trigger = match mode {
2260 AppMode::Results => "enter_results_mode",
2261 AppMode::Command => "enter_command_mode",
2262 AppMode::History => "enter_history_mode",
2263 _ => "switch_mode",
2264 };
2265 debug!(target: "shadow_state", "Setting mode via shadow state to {:?} with trigger {}", mode, trigger);
2266 self.shadow_state
2267 .borrow_mut()
2268 .set_mode(mode.clone(), buffer, trigger);
2269 } else {
2270 debug!(target: "shadow_state", "No buffer available for mode switch!");
2271 }
2272
2273 if mode == AppMode::History {
2275 eprintln!("[DEBUG] Using AppStateContainer for history search");
2276 let current_input = self.get_input_text();
2277
2278 self.state_container.start_history_search(current_input);
2280
2281 self.update_history_matches_in_container();
2283
2284 let match_count = self.state_container.history_search().matches.len();
2286
2287 self.state_container
2288 .set_status_message(format!("History search: {match_count} matches"));
2289 }
2290 }
2291
2292 fn try_handle_function_keys(
2294 &mut self,
2295 key: &crossterm::event::KeyEvent,
2296 ) -> Result<Option<bool>> {
2297 match key.code {
2298 KeyCode::F(1) | KeyCode::Char('?') => {
2299 if self.shadow_state.borrow().is_in_help_mode() {
2301 let mode = if self.state_container.has_dataview() {
2303 AppMode::Results
2304 } else {
2305 AppMode::Command
2306 };
2307 self.set_mode_via_shadow_state(mode, "exit_help");
2309 self.state_container.set_help_visible(false);
2310 self.help_widget.on_exit();
2311 } else {
2312 self.state_container.set_help_visible(true);
2314 self.set_mode_via_shadow_state(AppMode::Help, "help_requested");
2316 self.help_widget.on_enter();
2317 }
2318 Ok(Some(false))
2319 }
2320 KeyCode::F(3) => {
2321 self.show_pretty_query();
2323 Ok(Some(false))
2324 }
2325 KeyCode::F(5) => {
2326 self.toggle_debug_mode();
2328 Ok(Some(false))
2329 }
2330 KeyCode::F(6) => {
2331 let current = self.state_container.is_show_row_numbers();
2333 self.state_container.set_show_row_numbers(!current);
2334 self.state_container.set_status_message(format!(
2335 "Row numbers: {}",
2336 if current { "OFF" } else { "ON" }
2337 ));
2338 Ok(Some(false))
2339 }
2340 KeyCode::F(7) => {
2341 let current_mode = self.state_container.is_compact_mode();
2343 self.state_container.set_compact_mode(!current_mode);
2344 let message = if current_mode {
2345 "Compact mode disabled"
2346 } else {
2347 "Compact mode enabled"
2348 };
2349 self.state_container.set_status_message(message.to_string());
2350 Ok(Some(false))
2351 }
2352 KeyCode::F(8) => {
2353 let current = self.state_container.is_case_insensitive();
2355 self.state_container.set_case_insensitive(!current);
2356 self.state_container.set_status_message(format!(
2357 "Case-insensitive string comparisons: {}",
2358 if current { "OFF" } else { "ON" }
2359 ));
2360 Ok(Some(false))
2361 }
2362 KeyCode::F(9) => {
2363 use crate::ui::traits::input_ops::InputBehavior;
2365 InputBehavior::kill_line(self);
2366 let message = if self.state_container.is_kill_ring_empty() {
2367 "Killed to end of line".to_string()
2368 } else {
2369 format!(
2370 "Killed to end of line ('{}' saved to kill ring)",
2371 self.state_container.get_kill_ring()
2372 )
2373 };
2374 self.state_container.set_status_message(message);
2375 Ok(Some(false))
2376 }
2377 KeyCode::F(10) => {
2378 use crate::ui::traits::input_ops::InputBehavior;
2380 InputBehavior::kill_line_backward(self);
2381 let message = if self.state_container.is_kill_ring_empty() {
2382 "Killed to beginning of line".to_string()
2383 } else {
2384 format!(
2385 "Killed to beginning of line ('{}' saved to kill ring)",
2386 self.state_container.get_kill_ring()
2387 )
2388 };
2389 self.state_container.set_status_message(message);
2390 Ok(Some(false))
2391 }
2392 KeyCode::F(12) => {
2393 let enabled = !self.key_indicator.enabled;
2395 self.key_indicator.set_enabled(enabled);
2396 self.key_sequence_renderer.set_enabled(enabled);
2397 self.state_container.set_status_message(format!(
2398 "Key press indicator {}",
2399 if enabled { "enabled" } else { "disabled" }
2400 ));
2401 Ok(Some(false))
2402 }
2403 _ => Ok(None), }
2405 }
2406
2407 fn try_handle_buffer_operations(
2409 &mut self,
2410 key: &crossterm::event::KeyEvent,
2411 ) -> Result<Option<bool>> {
2412 if let Some(action) = self.key_dispatcher.get_command_action(key) {
2413 match action {
2414 "quit" => return Ok(Some(true)),
2415 "next_buffer" => {
2416 self.save_viewport_to_current_buffer();
2418
2419 let message = self
2420 .buffer_handler
2421 .next_buffer(self.state_container.buffers_mut());
2422 debug!("{}", message);
2423
2424 self.sync_after_buffer_switch();
2426 return Ok(Some(false));
2427 }
2428 "previous_buffer" => {
2429 self.save_viewport_to_current_buffer();
2431
2432 let message = self
2433 .buffer_handler
2434 .previous_buffer(self.state_container.buffers_mut());
2435 debug!("{}", message);
2436
2437 self.sync_after_buffer_switch();
2439 return Ok(Some(false));
2440 }
2441 "quick_switch_buffer" => {
2442 self.save_viewport_to_current_buffer();
2444
2445 let message = self
2446 .buffer_handler
2447 .quick_switch(self.state_container.buffers_mut());
2448 debug!("{}", message);
2449
2450 self.sync_after_buffer_switch();
2452
2453 return Ok(Some(false));
2454 }
2455 "new_buffer" => {
2456 let message = self
2457 .buffer_handler
2458 .new_buffer(self.state_container.buffers_mut(), &self.config);
2459 debug!("{}", message);
2460 return Ok(Some(false));
2461 }
2462 "close_buffer" => {
2463 let (success, message) = self
2464 .buffer_handler
2465 .close_buffer(self.state_container.buffers_mut());
2466 debug!("{}", message);
2467 return Ok(Some(!success)); }
2469 "list_buffers" => {
2470 let buffer_list = self
2471 .buffer_handler
2472 .list_buffers(self.state_container.buffers());
2473 for line in &buffer_list {
2474 debug!("{}", line);
2475 }
2476 return Ok(Some(false));
2477 }
2478 action if action.starts_with("switch_to_buffer_") => {
2479 if let Some(buffer_num_str) = action.strip_prefix("switch_to_buffer_") {
2480 if let Ok(buffer_num) = buffer_num_str.parse::<usize>() {
2481 self.save_viewport_to_current_buffer();
2483
2484 let message = self.buffer_handler.switch_to_buffer(
2485 self.state_container.buffers_mut(),
2486 buffer_num - 1,
2487 );
2488 debug!("{}", message);
2489
2490 self.sync_after_buffer_switch();
2492 }
2493 }
2494 return Ok(Some(false));
2495 }
2496 _ => {} }
2498 }
2499 Ok(None)
2500 }
2501
2502 fn try_handle_history_navigation(
2504 &mut self,
2505 key: &crossterm::event::KeyEvent,
2506 ) -> Result<Option<bool>> {
2507 if let KeyCode::Char('r') = key.code {
2509 if key.modifiers.contains(KeyModifiers::CONTROL) {
2510 let current_input = self.get_input_text();
2512
2513 self.state_container.start_history_search(current_input);
2515
2516 self.update_history_matches_in_container();
2518
2519 let match_count = self.state_container.history_search().matches.len();
2521
2522 self.state_container.set_mode(AppMode::History);
2523 self.shadow_state
2524 .borrow_mut()
2525 .observe_mode_change(AppMode::History, "history_search_started");
2526 self.state_container.set_status_message(format!(
2527 "History search started (Ctrl+R) - {match_count} matches"
2528 ));
2529 return Ok(Some(false));
2530 }
2531 }
2532
2533 if let KeyCode::Char('p') = key.code {
2535 if key.modifiers.contains(KeyModifiers::CONTROL) {
2536 let history_entries = self
2537 .state_container
2538 .command_history()
2539 .get_navigation_entries();
2540 let history_commands: Vec<String> =
2541 history_entries.iter().map(|e| e.command.clone()).collect();
2542
2543 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
2544 if buffer.navigate_history_up(&history_commands) {
2545 self.sync_all_input_states();
2546 self.state_container
2547 .set_status_message("Previous command from history".to_string());
2548 }
2549 }
2550 return Ok(Some(false));
2551 }
2552 }
2553
2554 if let KeyCode::Char('n') = key.code {
2556 if key.modifiers.contains(KeyModifiers::CONTROL) {
2557 let history_entries = self
2558 .state_container
2559 .command_history()
2560 .get_navigation_entries();
2561 let history_commands: Vec<String> =
2562 history_entries.iter().map(|e| e.command.clone()).collect();
2563
2564 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
2565 if buffer.navigate_history_down(&history_commands) {
2566 self.sync_all_input_states();
2567 self.state_container
2568 .set_status_message("Next command from history".to_string());
2569 }
2570 }
2571 return Ok(Some(false));
2572 }
2573 }
2574
2575 match key.code {
2577 KeyCode::Up if key.modifiers.contains(KeyModifiers::ALT) => {
2578 let history_entries = self
2579 .state_container
2580 .command_history()
2581 .get_navigation_entries();
2582 let history_commands: Vec<String> =
2583 history_entries.iter().map(|e| e.command.clone()).collect();
2584
2585 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
2586 if buffer.navigate_history_up(&history_commands) {
2587 self.sync_all_input_states();
2588 self.state_container
2589 .set_status_message("Previous command (Alt+Up)".to_string());
2590 }
2591 }
2592 Ok(Some(false))
2593 }
2594 KeyCode::Down if key.modifiers.contains(KeyModifiers::ALT) => {
2595 let history_entries = self
2596 .state_container
2597 .command_history()
2598 .get_navigation_entries();
2599 let history_commands: Vec<String> =
2600 history_entries.iter().map(|e| e.command.clone()).collect();
2601
2602 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
2603 if buffer.navigate_history_down(&history_commands) {
2604 self.sync_all_input_states();
2605 self.state_container
2606 .set_status_message("Next command (Alt+Down)".to_string());
2607 }
2608 }
2609 Ok(Some(false))
2610 }
2611 _ => Ok(None),
2612 }
2613 }
2614
2615 fn try_handle_text_editing(
2617 &mut self,
2618 key: &crossterm::event::KeyEvent,
2619 ) -> Result<Option<bool>> {
2620 if let Some(action) = self.key_dispatcher.get_command_action(key) {
2622 match action {
2623 "expand_asterisk" => {
2624 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
2625 if buffer.expand_asterisk(&self.hybrid_parser) {
2626 if buffer.get_edit_mode() == EditMode::SingleLine {
2628 let text = buffer.get_input_text();
2629 let cursor = buffer.get_input_cursor_position();
2630 self.set_input_text_with_cursor(text, cursor);
2631 }
2632 }
2633 }
2634 return Ok(Some(false));
2635 }
2636 "expand_asterisk_visible" => {
2637 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
2638 if buffer.expand_asterisk_visible() {
2639 if buffer.get_edit_mode() == EditMode::SingleLine {
2641 let text = buffer.get_input_text();
2642 let cursor = buffer.get_input_cursor_position();
2643 self.set_input_text_with_cursor(text, cursor);
2644 }
2645 }
2646 }
2647 return Ok(Some(false));
2648 }
2649 "delete_word_backward" => {
2651 use crate::ui::traits::input_ops::InputBehavior;
2652 InputBehavior::delete_word_backward(self);
2653 return Ok(Some(false));
2654 }
2655 "delete_word_forward" => {
2657 use crate::ui::traits::input_ops::InputBehavior;
2658 InputBehavior::delete_word_forward(self);
2659 return Ok(Some(false));
2660 }
2661 "kill_line" => {
2663 use crate::ui::traits::input_ops::InputBehavior;
2664 InputBehavior::kill_line(self);
2665 return Ok(Some(false));
2666 }
2667 "kill_line_backward" => {
2669 use crate::ui::traits::input_ops::InputBehavior;
2670 InputBehavior::kill_line_backward(self);
2671 return Ok(Some(false));
2672 }
2673 "move_word_backward" => {
2675 self.move_cursor_word_backward();
2676 return Ok(Some(false));
2677 }
2678 "move_word_forward" => {
2680 self.move_cursor_word_forward();
2681 return Ok(Some(false));
2682 }
2683 _ => {} }
2685 }
2686
2687 match key.code {
2689 KeyCode::Char('k') if key.modifiers.contains(KeyModifiers::CONTROL) => {
2691 self.state_container
2693 .set_status_message("Ctrl+K pressed - killing to end of line".to_string());
2694 use crate::ui::traits::input_ops::InputBehavior;
2695 InputBehavior::kill_line(self);
2696 Ok(Some(false))
2697 }
2698 KeyCode::Char('k') if key.modifiers.contains(KeyModifiers::ALT) => {
2700 self.state_container
2702 .set_status_message("Alt+K - killing to end of line".to_string());
2703 use crate::ui::traits::input_ops::InputBehavior;
2704 InputBehavior::kill_line(self);
2705 Ok(Some(false))
2706 }
2707 KeyCode::Char('u') if key.modifiers.contains(KeyModifiers::CONTROL) => {
2709 use crate::ui::traits::input_ops::InputBehavior;
2711 InputBehavior::kill_line_backward(self);
2712 Ok(Some(false))
2713 }
2714 KeyCode::Char('v') if key.modifiers.contains(KeyModifiers::CONTROL) => {
2715 self.paste_from_clipboard();
2717 Ok(Some(false))
2718 }
2719 KeyCode::Char('y') if key.modifiers.contains(KeyModifiers::CONTROL) => {
2720 self.yank();
2722 Ok(Some(false))
2723 }
2724 KeyCode::Left if key.modifiers.contains(KeyModifiers::CONTROL) => {
2726 self.move_cursor_word_backward();
2728 Ok(Some(false))
2729 }
2730 KeyCode::Right if key.modifiers.contains(KeyModifiers::CONTROL) => {
2732 self.move_cursor_word_forward();
2734 Ok(Some(false))
2735 }
2736 KeyCode::Char('b') if key.modifiers.contains(KeyModifiers::ALT) => {
2738 self.move_cursor_word_backward();
2740 Ok(Some(false))
2741 }
2742 KeyCode::Char('f') if key.modifiers.contains(KeyModifiers::ALT) => {
2744 self.move_cursor_word_forward();
2746 Ok(Some(false))
2747 }
2748 _ => Ok(None), }
2750 }
2751
2752 fn try_handle_mode_transitions(
2754 &mut self,
2755 key: &crossterm::event::KeyEvent,
2756 old_cursor: usize,
2757 ) -> Result<Option<bool>> {
2758 match key.code {
2759 KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => {
2760 Ok(Some(true))
2762 }
2763 KeyCode::Char('d') if key.modifiers.contains(KeyModifiers::CONTROL) => {
2764 Ok(Some(true))
2766 }
2767 KeyCode::Enter => {
2768 let query = self.get_input_text().trim().to_string();
2770 debug!(target: "action", "Executing query: {}", query);
2771
2772 if query.is_empty() {
2773 self.state_container
2774 .set_status_message("Empty query - please enter a SQL command".to_string());
2775 } else {
2776 if query == ":help" {
2778 self.state_container.set_help_visible(true);
2779 self.set_mode_via_shadow_state(AppMode::Help, "help_requested");
2781 self.state_container
2782 .set_status_message("Help Mode - Press ESC to return".to_string());
2783 } else if query == ":exit" || query == ":quit" || query == ":q" {
2784 return Ok(Some(true));
2785 } else if query == ":tui" {
2786 self.state_container
2788 .set_status_message("Already in TUI mode".to_string());
2789 } else {
2790 self.state_container
2791 .set_status_message(format!("Processing query: '{query}'"));
2792 self.execute_query_v2(&query)?;
2793 }
2794 }
2795 Ok(Some(false))
2796 }
2797 KeyCode::Tab => {
2798 self.apply_completion();
2800 Ok(Some(false))
2801 }
2802 KeyCode::Down => {
2803 debug!(target: "shadow_state", "Down arrow pressed in Command mode. has_dataview={}, edit_mode={:?}",
2804 self.state_container.has_dataview(),
2805 self.state_container.get_edit_mode());
2806
2807 if self.state_container.has_dataview()
2808 && self.state_container.get_edit_mode() == Some(EditMode::SingleLine)
2809 {
2810 debug!(target: "shadow_state", "Down arrow conditions met, switching to Results via set_mode");
2811 self.state_container.set_mode(AppMode::Results);
2813 self.shadow_state
2814 .borrow_mut()
2815 .observe_mode_change(AppMode::Results, "down_arrow_to_results");
2816 let row = self.state_container.get_last_results_row().unwrap_or(0);
2818 self.state_container.set_table_selected_row(Some(row));
2819
2820 let last_offset = self.state_container.get_last_scroll_offset();
2822 self.state_container.set_scroll_offset(last_offset);
2823 Ok(Some(false))
2824 } else {
2825 debug!(target: "shadow_state", "Down arrow conditions not met, falling through");
2826 Ok(None)
2828 }
2829 }
2830 _ => {
2831 self.handle_input_key(*key);
2833
2834 self.state_container.clear_completion();
2836
2837 self.handle_completion();
2839
2840 if self.get_input_cursor() != old_cursor {
2842 self.update_horizontal_scroll(120); }
2844
2845 Ok(Some(false))
2846 }
2847 }
2848 }
2849
2850 fn try_handle_results_navigation(
2852 &mut self,
2853 key: &crossterm::event::KeyEvent,
2854 ) -> Result<Option<bool>> {
2855 match key.code {
2856 KeyCode::PageDown | KeyCode::Char('f')
2857 if key.modifiers.contains(KeyModifiers::CONTROL) =>
2858 {
2859 NavigationBehavior::page_down(self);
2860 Ok(Some(false))
2861 }
2862 KeyCode::PageUp | KeyCode::Char('b')
2863 if key.modifiers.contains(KeyModifiers::CONTROL) =>
2864 {
2865 NavigationBehavior::page_up(self);
2866 Ok(Some(false))
2867 }
2868 _ => Ok(None), }
2870 }
2871
2872 fn try_handle_results_clipboard(
2874 &mut self,
2875 key: &crossterm::event::KeyEvent,
2876 ) -> Result<Option<bool>> {
2877 match key.code {
2878 KeyCode::Char('y') => {
2879 let selection_mode = self.get_selection_mode();
2880 debug!("'y' key pressed - selection_mode={:?}", selection_mode);
2881 match selection_mode {
2882 SelectionMode::Cell => {
2883 debug!("Yanking cell in cell selection mode");
2885 self.state_container
2886 .set_status_message("Yanking cell...".to_string());
2887 YankBehavior::yank_cell(self);
2888 }
2890 SelectionMode::Row => {
2891 debug!("'y' pressed in row mode - waiting for chord completion");
2894 self.state_container.set_status_message(
2895 "Press second key for chord: yy=row, yc=column, ya=all, yv=cell"
2896 .to_string(),
2897 );
2898 }
2899 SelectionMode::Column => {
2900 debug!("Yanking column in column selection mode");
2902 self.state_container
2903 .set_status_message("Yanking column...".to_string());
2904 YankBehavior::yank_column(self);
2905 }
2906 }
2907 Ok(Some(false))
2908 }
2909 _ => Ok(None),
2910 }
2911 }
2912
2913 fn try_handle_results_export(
2915 &mut self,
2916 key: &crossterm::event::KeyEvent,
2917 ) -> Result<Option<bool>> {
2918 match key.code {
2919 KeyCode::Char('e') if key.modifiers.contains(KeyModifiers::CONTROL) => {
2920 self.export_to_csv();
2921 Ok(Some(false))
2922 }
2923 KeyCode::Char('j') if key.modifiers.contains(KeyModifiers::CONTROL) => {
2924 self.export_to_json();
2925 Ok(Some(false))
2926 }
2927 _ => Ok(None),
2928 }
2929 }
2930
2931 fn try_handle_results_help(
2933 &mut self,
2934 key: &crossterm::event::KeyEvent,
2935 ) -> Result<Option<bool>> {
2936 match key.code {
2937 KeyCode::F(1) | KeyCode::Char('?') => {
2938 self.state_container.set_help_visible(true);
2939 self.set_mode_via_shadow_state(AppMode::Help, "help_requested");
2941 self.help_widget.on_enter();
2942 Ok(Some(false))
2943 }
2944 _ => Ok(None),
2945 }
2946 }
2947
2948 fn handle_results_input(&mut self, key: crossterm::event::KeyEvent) -> Result<bool> {
2949 debug!(
2951 "handle_results_input: Processing key {:?} in Results mode",
2952 key
2953 );
2954
2955 let is_vim_navigating = self.vim_search_adapter.borrow().is_navigating();
2957 let vim_is_active = self.vim_search_adapter.borrow().is_active();
2958 let has_search_pattern = !self.state_container.get_search_pattern().is_empty();
2959
2960 debug!(
2961 "Search state check: vim_navigating={}, vim_active={}, has_pattern={}, pattern='{}'",
2962 is_vim_navigating,
2963 vim_is_active,
2964 has_search_pattern,
2965 self.state_container.get_search_pattern()
2966 );
2967
2968 if key.code == KeyCode::Esc {
2970 info!("ESCAPE KEY DETECTED in Results mode!");
2971
2972 if is_vim_navigating || vim_is_active || has_search_pattern {
2973 info!("Escape pressed with active search - clearing via StateCoordinator");
2974 debug!(
2975 "Pre-clear state: vim_navigating={}, vim_active={}, pattern='{}'",
2976 is_vim_navigating,
2977 vim_is_active,
2978 self.state_container.get_search_pattern()
2979 );
2980
2981 use crate::ui::state::state_coordinator::StateCoordinator;
2983 StateCoordinator::cancel_search_with_refs(
2984 &mut self.state_container,
2985 &self.shadow_state,
2986 Some(&self.vim_search_adapter),
2987 );
2988
2989 let post_pattern = self.state_container.get_search_pattern();
2991 let post_vim_active = self.vim_search_adapter.borrow().is_active();
2992 info!(
2993 "Post-clear state: pattern='{}', vim_active={}",
2994 post_pattern, post_vim_active
2995 );
2996
2997 self.state_container
2998 .set_status_message("Search cleared".to_string());
2999 return Ok(false);
3000 }
3001 info!("Escape pressed but no active search to clear");
3002 }
3003
3004 let selection_mode = self.state_container.get_selection_mode();
3005
3006 debug!(
3007 "handle_results_input: key={:?}, selection_mode={:?}",
3008 key, selection_mode
3009 );
3010
3011 let normalized = self.state_container.normalize_key(key);
3013
3014 let action_context = self.build_action_context();
3016 let mapped_action = self.key_mapper.map_key(normalized, &action_context);
3017 let action = mapped_action.as_ref().map(|a| format!("{a:?}"));
3018
3019 if normalized != key {
3021 self.state_container
3022 .log_key_press(key, Some(format!("normalized to {normalized:?}")));
3023 }
3024 self.state_container
3025 .log_key_press(normalized, action.clone());
3026
3027 let normalized_key = normalized;
3028
3029 let action_context = self.build_action_context();
3033 debug!(
3034 "Action context for key {:?}: mode={:?}",
3035 normalized_key.code, action_context.mode
3036 );
3037 if let Some(action) = self.key_mapper.map_key(normalized_key, &action_context) {
3038 info!(
3039 "✓ Action system: key {:?} -> action {:?}",
3040 normalized_key.code, action
3041 );
3042 if let Ok(result) = self.try_handle_action(action, &action_context) {
3043 match result {
3044 ActionResult::Handled => {
3045 debug!("Action handled by new system");
3046 return Ok(false);
3047 }
3048 ActionResult::Exit => {
3049 debug!("Action requested exit");
3050 return Ok(true);
3051 }
3052 ActionResult::SwitchMode(mode) => {
3053 debug!("Action requested mode switch to {:?}", mode);
3054 self.state_container.set_mode(mode);
3055 return Ok(false);
3056 }
3057 ActionResult::Error(err) => {
3058 warn!("Action error: {}", err);
3059 self.state_container
3060 .set_status_message(format!("Error: {err}"));
3061 return Ok(false);
3062 }
3063 ActionResult::NotHandled => {
3064 debug!("Action not handled, falling back to legacy system");
3066 }
3067 }
3068 }
3069 }
3070
3071 if matches!(key.code, KeyCode::Char('G')) {
3073 debug!("Detected uppercase G key press!");
3074 }
3075
3076 if mapped_action.is_none() {
3090 debug!(
3091 "No action mapping for key {:?} in Results mode",
3092 normalized_key
3093 );
3094 }
3095
3096 if let Some(result) = self.try_handle_results_navigation(&normalized_key)? {
3098 return Ok(result);
3099 }
3100
3101 if let Some(result) = self.try_handle_results_clipboard(&normalized_key)? {
3103 return Ok(result);
3104 }
3105
3106 if let Some(result) = self.try_handle_results_export(&normalized_key)? {
3108 return Ok(result);
3109 }
3110
3111 if let Some(result) = self.try_handle_results_help(&normalized_key)? {
3113 return Ok(result);
3114 }
3115
3116 Ok(false)
3121 }
3122 fn execute_search_action(&mut self, mode: SearchMode, pattern: String) {
3125 debug!(target: "search", "execute_search_action called: mode={:?}, pattern='{}', current_app_mode={:?}, thread={:?}",
3126 mode, pattern, self.shadow_state.borrow().get_mode(), std::thread::current().id());
3127 match mode {
3128 SearchMode::Search => {
3129 debug!(target: "search", "Executing search with pattern: '{}', app_mode={:?}", pattern, self.shadow_state.borrow().get_mode());
3130 debug!(target: "search", "Search: current results count={}",
3131 self.state_container.get_buffer_dataview().map_or(0, |v| v.source().row_count()));
3132
3133 self.state_container.start_search(pattern.clone());
3135
3136 self.state_container.set_search_pattern(pattern.clone());
3137 self.perform_search();
3138 let matches_count = self.state_container.search().matches.len();
3139 debug!(target: "search", "After perform_search, app_mode={:?}, matches_found={}",
3140 self.shadow_state.borrow().get_mode(),
3141 matches_count);
3142
3143 if matches_count > 0 {
3145 let matches_for_vim: Vec<(usize, usize)> = {
3147 let search_manager = self.search_manager.borrow();
3148 search_manager
3149 .all_matches()
3150 .iter()
3151 .map(|m| (m.row, m.column))
3152 .collect()
3153 };
3154
3155 if let Some(dataview) = self.state_container.get_buffer_dataview() {
3157 info!(target: "search", "Syncing {} matches to VimSearchManager for pattern '{}'",
3158 matches_for_vim.len(), pattern);
3159 self.vim_search_adapter
3160 .borrow_mut()
3161 .set_search_state_from_external(
3162 pattern.clone(),
3163 matches_for_vim,
3164 dataview,
3165 );
3166 }
3167 }
3168
3169 if matches_count > 0 {
3171 let (row, col) = {
3173 let search_manager = self.search_manager.borrow();
3174 if let Some(first_match) = search_manager.first_match() {
3175 (first_match.row, first_match.column)
3176 } else {
3177 let search_state = self.state_container.search();
3179 if let Some((row, col, _, _)) = search_state.matches.first() {
3180 (*row, *col)
3181 } else {
3182 (0, 0)
3183 }
3184 }
3185 };
3186
3187 info!(target: "search", "NAVIGATION START: Moving to first match at data row={}, col={}", row, col);
3188
3189 self.state_container.set_table_selected_row(Some(row));
3192 self.state_container.set_selected_row(Some(row));
3193 info!(target: "search", " Set row position to {}", row);
3194
3195 {
3197 let mut nav = self.state_container.navigation_mut();
3198 nav.selected_column = col;
3199 }
3200 self.state_container.set_current_column_buffer(col);
3201 info!(target: "search", " Set column position to {}", col);
3202
3203 info!(target: "search", "Updating TableWidgetManager for debounced search to ({}, {})", row, col);
3205 self.table_widget_manager
3206 .borrow_mut()
3207 .on_debounced_search(row, col);
3208
3209 {
3211 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
3212 if let Some(ref mut viewport_manager) = *viewport_manager_borrow {
3213 let viewport_height = self.state_container.navigation().viewport_rows;
3215 let viewport_width = self.state_container.navigation().viewport_columns;
3216 let current_scroll = self.state_container.navigation().scroll_offset.0;
3217
3218 info!(target: "search", " Viewport dimensions: {}x{}, current_scroll: {}",
3219 viewport_height, viewport_width, current_scroll);
3220
3221 let new_row_offset = if row < current_scroll {
3223 info!(target: "search", " Match is above viewport, scrolling up to row {}", row);
3224 row } else if row >= current_scroll + viewport_height.saturating_sub(1) {
3226 let centered = row.saturating_sub(viewport_height / 2);
3227 info!(target: "search", " Match is below viewport, centering at row {}", centered);
3228 centered } else {
3230 info!(target: "search", " Match is already visible, keeping scroll at {}", current_scroll);
3231 current_scroll };
3233
3234 let current_col_scroll =
3236 self.state_container.navigation().scroll_offset.1;
3237 let new_col_offset = if col < current_col_scroll {
3238 info!(target: "search", " Match column {} is left of viewport (scroll={}), scrolling left", col, current_col_scroll);
3239 col } else if col >= current_col_scroll + viewport_width.saturating_sub(1) {
3241 let centered = col.saturating_sub(viewport_width / 4);
3242 info!(target: "search", " Match column {} is right of viewport (scroll={}, width={}), scrolling to {}",
3243 col, current_col_scroll, viewport_width, centered);
3244 centered } else {
3246 info!(target: "search", " Match column {} is visible, keeping scroll at {}", col, current_col_scroll);
3247 current_col_scroll };
3249
3250 viewport_manager.set_viewport(
3252 new_row_offset,
3253 new_col_offset,
3254 viewport_width as u16,
3255 viewport_height as u16,
3256 );
3257 info!(target: "search", " Set viewport to row_offset={}, col_offset={}", new_row_offset, new_col_offset);
3258
3259 let crosshair_row = row - new_row_offset;
3261 let crosshair_col = col - new_col_offset;
3262 viewport_manager.set_crosshair(crosshair_row, crosshair_col);
3263 info!(target: "search", " Set crosshair to viewport-relative ({}, {}), absolute was ({}, {})",
3264 crosshair_row, crosshair_col, row, col);
3265
3266 let mut nav = self.state_container.navigation_mut();
3268 nav.scroll_offset.0 = new_row_offset;
3269 nav.scroll_offset.1 = new_col_offset;
3270 }
3271 }
3272
3273 self.state_container.set_current_match(Some((row, col)));
3275
3276 {
3279 let mut nav = self.state_container.navigation_mut();
3280 nav.selected_row = row;
3281 nav.selected_column = col;
3282 }
3283 info!(target: "search", " Forced navigation state to row={}, col={}", row, col);
3284
3285 self.state_container.set_status_message(format!(
3287 "Match 1/{} at row {}, col {}",
3288 matches_count,
3289 row + 1,
3290 col + 1
3291 ));
3292 }
3293 }
3294 SearchMode::Filter => {
3295 use crate::ui::state::state_coordinator::StateCoordinator;
3296
3297 StateCoordinator::apply_filter_search_with_refs(
3299 &mut self.state_container,
3300 &self.shadow_state,
3301 &pattern,
3302 );
3303
3304 self.apply_filter(&pattern);
3306
3307 debug!(target: "search", "After apply_filter, filtered_count={}",
3308 self.state_container.get_buffer_dataview().map_or(0, super::super::data::data_view::DataView::row_count));
3309 }
3310 SearchMode::FuzzyFilter => {
3311 use crate::ui::state::state_coordinator::StateCoordinator;
3312
3313 StateCoordinator::apply_fuzzy_filter_search_with_refs(
3315 &mut self.state_container,
3316 &self.shadow_state,
3317 &pattern,
3318 );
3319
3320 self.apply_fuzzy_filter();
3322
3323 let indices_count = self.state_container.get_fuzzy_filter_indices().len();
3324 debug!(target: "search", "After apply_fuzzy_filter, matched_indices={}", indices_count);
3325 }
3326 SearchMode::ColumnSearch => {
3327 use crate::ui::state::state_coordinator::StateCoordinator;
3328
3329 debug!(target: "search", "Executing column search with pattern: '{}'", pattern);
3330
3331 StateCoordinator::apply_column_search_with_refs(
3333 &mut self.state_container,
3334 &self.shadow_state,
3335 &pattern,
3336 );
3337
3338 self.search_columns();
3340
3341 debug!(target: "search", "After search_columns, app_mode={:?}", self.shadow_state.borrow().get_mode());
3342 }
3343 }
3344 }
3345
3346 fn enter_search_mode(&mut self, mode: SearchMode) {
3347 debug!(target: "search", "enter_search_mode called for {:?}, current_mode={:?}, input_text='{}'",
3348 mode, self.shadow_state.borrow().get_mode(), self.state_container.get_input_text());
3349
3350 let current_sql = if self.shadow_state.borrow().is_in_results_mode() {
3352 let last_query = self.state_container.get_last_query();
3354 let input_text = self.state_container.get_input_text();
3355 debug!(target: "search", "COLUMN_SEARCH_SAVE_DEBUG: last_query='{}', input_text='{}'", last_query, input_text);
3356
3357 if !last_query.is_empty() {
3358 debug!(target: "search", "Using last_query for search mode: '{}'", last_query);
3359 last_query
3360 } else if !input_text.is_empty() {
3361 debug!(target: "search", "No last_query, using input_text as fallback: '{}'", input_text);
3364 input_text
3365 } else {
3366 warn!(target: "search", "No last_query or input_text found when entering search mode from Results!");
3368 String::new()
3369 }
3370 } else {
3371 self.get_input_text()
3373 };
3374
3375 let cursor_pos = current_sql.len();
3376
3377 debug!(
3378 "Entering {} mode, saving SQL: '{}', cursor: {}",
3379 mode.title(),
3380 current_sql,
3381 cursor_pos
3382 );
3383
3384 self.search_modes_widget
3386 .enter_mode(mode.clone(), current_sql, cursor_pos);
3387
3388 debug!(target: "mode", "Setting app mode from {:?} to {:?}", self.shadow_state.borrow().get_mode(), mode.to_app_mode());
3390 let trigger = match mode {
3391 SearchMode::ColumnSearch => "backslash_column_search",
3392 SearchMode::Search => "data_search_started",
3393 SearchMode::FuzzyFilter => "fuzzy_filter_started",
3394 SearchMode::Filter => "filter_started",
3395 };
3396 self.sync_mode(mode.to_app_mode(), trigger);
3397
3398 let search_type = match mode {
3400 SearchMode::ColumnSearch => crate::ui::state::shadow_state::SearchType::Column,
3401 SearchMode::Search => crate::ui::state::shadow_state::SearchType::Data,
3402 SearchMode::FuzzyFilter | SearchMode::Filter => {
3403 crate::ui::state::shadow_state::SearchType::Fuzzy
3404 }
3405 };
3406 self.shadow_state
3407 .borrow_mut()
3408 .observe_search_start(search_type, trigger);
3409
3410 match mode {
3412 SearchMode::Search => {
3413 self.state_container.clear_search();
3415 self.state_container.set_search_pattern(String::new());
3416 }
3417 SearchMode::Filter => {
3418 self.state_container.set_filter_pattern(String::new());
3419 self.state_container.filter_mut().clear();
3420 }
3421 SearchMode::FuzzyFilter => {
3422 self.state_container.set_fuzzy_filter_pattern(String::new());
3423 self.state_container.set_fuzzy_filter_indices(Vec::new());
3424 self.state_container.set_fuzzy_filter_active(false);
3425 }
3426 SearchMode::ColumnSearch => {
3427 self.state_container.clear_column_search();
3429 if let Some(dataview) = self.state_container.get_buffer_dataview_mut() {
3430 dataview.clear_column_search();
3431 }
3432
3433 }
3435 }
3436
3437 self.input = tui_input::Input::default();
3439 }
3441
3442 fn handle_search_modes_input(&mut self, key: crossterm::event::KeyEvent) -> Result<bool> {
3443 if key.code == KeyCode::Char('c') && key.modifiers.contains(KeyModifiers::CONTROL) {
3445 return Ok(true); }
3447
3448 let action = self.search_modes_widget.handle_key(key);
3451
3452 match action {
3453 SearchModesAction::Continue => {
3454 }
3456 SearchModesAction::InputChanged(mode, pattern) => {
3457 self.set_input_text_with_cursor(pattern.clone(), pattern.len());
3459
3460 match mode {
3462 SearchMode::Search => {
3463 self.state_container.set_search_pattern(pattern);
3464 }
3465 SearchMode::Filter => {
3466 self.state_container.set_filter_pattern(pattern.clone());
3467 let mut filter = self.state_container.filter_mut();
3468 filter.pattern = pattern.clone();
3469 filter.is_active = true;
3470 }
3471 SearchMode::FuzzyFilter => {
3472 self.state_container.set_fuzzy_filter_pattern(pattern);
3473 }
3474 SearchMode::ColumnSearch => {
3475 }
3477 }
3478 }
3479 SearchModesAction::ExecuteDebounced(mode, pattern) => {
3480 self.execute_search_action(mode, pattern);
3483 }
3485 SearchModesAction::Apply(mode, pattern) => {
3486 debug!(target: "search", "Apply action triggered for {:?} with pattern '{}'", mode, pattern);
3487 match mode {
3489 SearchMode::Search => {
3490 debug!(target: "search", "Search Apply: Applying search with pattern '{}'", pattern);
3491 self.execute_search_action(SearchMode::Search, pattern);
3493 debug!(target: "search", "Search Apply: last_query='{}', will restore saved SQL from widget", self.state_container.get_last_query());
3494 }
3496 SearchMode::Filter => {
3497 debug!(target: "search", "Filter Apply: Applying filter with pattern '{}'", pattern);
3498 self.state_container.set_filter_pattern(pattern.clone());
3499 {
3500 let mut filter = self.state_container.filter_mut();
3501 filter.pattern = pattern.clone();
3502 filter.is_active = true;
3503 } self.apply_filter(&pattern); debug!(target: "search", "Filter Apply: last_query='{}', will restore saved SQL from widget", self.state_container.get_last_query());
3506 }
3507 SearchMode::FuzzyFilter => {
3508 debug!(target: "search", "FuzzyFilter Apply: Applying filter with pattern '{}'", pattern);
3509 self.state_container.set_fuzzy_filter_pattern(pattern);
3510 self.apply_fuzzy_filter();
3511 debug!(target: "search", "FuzzyFilter Apply: last_query='{}', will restore saved SQL from widget", self.state_container.get_last_query());
3512 }
3513 SearchMode::ColumnSearch => {
3514 let column_info = {
3517 let column_search = self.state_container.column_search();
3518 if column_search.matching_columns.is_empty() {
3519 None
3520 } else {
3521 let current_match = column_search.current_match;
3522 Some(column_search.matching_columns[current_match].clone())
3523 }
3524 };
3525
3526 if let Some((col_idx, col_name)) = column_info {
3527 self.state_container.set_current_column(col_idx);
3528 self.state_container.set_current_column_buffer(col_idx);
3529
3530 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
3532 if let Some(viewport_manager) = viewport_manager_borrow.as_mut() {
3533 viewport_manager.set_current_column(col_idx);
3534 }
3535 drop(viewport_manager_borrow);
3536
3537 debug!(target: "column_search_sync", "ColumnSearch Apply: About to call sync_navigation_with_viewport() for column: {}", col_name);
3540 debug!(target: "column_search_sync", "ColumnSearch Apply: Pre-sync - viewport current_column: {}",
3541 if let Ok(vm) = self.viewport_manager.try_borrow() {
3542 vm.as_ref().map_or(0, super::viewport_manager::ViewportManager::get_crosshair_col)
3543 } else { 0 });
3544 self.sync_navigation_with_viewport();
3545 debug!(target: "column_search_sync", "ColumnSearch Apply: Post-sync - navigation current_column: {}",
3546 self.state_container.navigation().selected_column);
3547 debug!(target: "column_search_sync", "ColumnSearch Apply: sync_navigation_with_viewport() completed for column: {}", col_name);
3548
3549 self.state_container
3550 .set_status_message(format!("Jumped to column: {col_name}"));
3551 }
3552
3553 debug!(target: "search", "ColumnSearch Apply: Exiting without modifying input_text");
3556 debug!(target: "search", "ColumnSearch Apply: last_query='{}', will restore saved SQL from widget", self.state_container.get_last_query());
3557 }
3559 }
3560
3561 let saved_state = self.search_modes_widget.exit_mode();
3564
3565 if let Some((sql, cursor)) = saved_state {
3566 debug!(target: "search", "Exiting search mode. Original SQL was: '{}', cursor: {}", sql, cursor);
3567 debug!(target: "buffer", "Returning to Results mode, preserving last_query: '{}'",
3568 self.state_container.get_last_query());
3569
3570 debug!(target: "search", "Restoring saved SQL to input_text: '{}'", sql);
3573 self.set_input_text_with_cursor(sql, cursor);
3575 } else {
3576 if mode == SearchMode::ColumnSearch {
3578 let last_query = self.state_container.get_last_query();
3580 if last_query.is_empty() {
3581 debug!(target: "search", "Column search: No saved state or last_query, clearing input");
3582 self.set_input_text(String::new());
3583 } else {
3584 debug!(target: "search", "Column search: No saved state, restoring last_query: '{}'", last_query);
3585 self.set_input_text(last_query);
3586 }
3587 } else {
3588 debug!(target: "search", "No saved state from widget, keeping current SQL");
3589 }
3590 }
3591
3592 use crate::ui::state::state_coordinator::StateCoordinator;
3594
3595 if mode == SearchMode::ColumnSearch {
3598 debug!(target: "column_search_sync", "ColumnSearch Apply: Canceling column search completely with cancel_search_with_refs()");
3599
3600 if let Some(dataview) = self.state_container.get_buffer_dataview_mut() {
3602 dataview.clear_column_search();
3603 debug!(target: "column_search_sync", "ColumnSearch Apply: Cleared column search in DataView");
3604 }
3605
3606 StateCoordinator::cancel_search_with_refs(
3607 &mut self.state_container,
3608 &self.shadow_state,
3609 Some(&self.vim_search_adapter),
3610 );
3611 debug!(target: "column_search_sync", "ColumnSearch Apply: Column search canceled and mode switched to Results");
3613 } else {
3614 debug!(target: "column_search_sync", "Search Apply: About to call StateCoordinator::complete_search_with_refs() for mode: {:?}", mode);
3616 StateCoordinator::complete_search_with_refs(
3617 &mut self.state_container,
3618 &self.shadow_state,
3619 Some(&self.vim_search_adapter),
3620 AppMode::Results,
3621 "search_applied",
3622 );
3623 debug!(target: "column_search_sync", "Search Apply: StateCoordinator::complete_search_with_refs() completed - should now be in Results mode");
3624 }
3625
3626 let filter_msg = match mode {
3628 SearchMode::FuzzyFilter => {
3629 let query = self.state_container.get_last_query();
3630 format!(
3631 "Fuzzy filter applied. Query: '{}'. Press 'f' again to modify.",
3632 if query.len() > 30 {
3633 format!("{}...", &query[..30])
3634 } else {
3635 query
3636 }
3637 )
3638 }
3639 SearchMode::Filter => "Filter applied. Press 'F' again to modify.".to_string(),
3640 SearchMode::Search => {
3641 let matches = self.state_container.search().matches.len();
3642 if matches > 0 {
3643 format!("Found {matches} matches. Use n/N to navigate.")
3644 } else {
3645 "No matches found.".to_string()
3646 }
3647 }
3648 SearchMode::ColumnSearch => "Column search complete.".to_string(),
3649 };
3650 self.state_container.set_status_message(filter_msg);
3651 }
3652 SearchModesAction::Cancel => {
3653 let mode = self.shadow_state.borrow().get_mode();
3655 match mode {
3656 AppMode::FuzzyFilter => {
3657 debug!(target: "search", "FuzzyFilter Cancel: Clearing fuzzy filter");
3659 self.state_container.set_fuzzy_filter_pattern(String::new());
3660 self.apply_fuzzy_filter(); self.state_container.set_fuzzy_filter_indices(Vec::new());
3662 self.state_container.set_fuzzy_filter_active(false);
3663 }
3664 AppMode::Filter => {
3665 debug!(target: "search", "Filter Cancel: Clearing filter pattern and state");
3667 self.state_container.filter_mut().clear();
3668 self.state_container.set_filter_pattern(String::new());
3669 self.state_container.set_filter_active(false);
3670 self.apply_filter("");
3672 }
3673 AppMode::ColumnSearch => {
3674 self.state_container.clear_column_search();
3676 debug!(target: "search", "ColumnSearch Cancel: Exiting without modifying input_text");
3678 debug!(target: "search", "ColumnSearch Cancel: last_query='{}', will restore saved SQL from widget", self.state_container.get_last_query());
3679 }
3680 _ => {}
3681 }
3682
3683 if let Some((sql, cursor)) = self.search_modes_widget.exit_mode() {
3685 debug!(target: "search", "Cancel: Restoring saved SQL: '{}', cursor: {}", sql, cursor);
3686 if !sql.is_empty() {
3687 self.set_input_text_with_cursor(sql, cursor);
3689 }
3690 } else {
3691 debug!(target: "search", "Cancel: No saved SQL from widget");
3692 }
3693
3694 use crate::ui::state::state_coordinator::StateCoordinator;
3697 StateCoordinator::cancel_search_with_refs(
3698 &mut self.state_container,
3699 &self.shadow_state,
3700 Some(&self.vim_search_adapter),
3701 );
3702 }
3703 SearchModesAction::NextMatch => {
3704 debug!(target: "search", "NextMatch action, current_mode={:?}, widget_mode={:?}",
3705 self.shadow_state.borrow().get_mode(), self.search_modes_widget.current_mode());
3706
3707 if self.shadow_state.borrow().is_in_column_search()
3709 || self.search_modes_widget.current_mode() == Some(SearchMode::ColumnSearch)
3710 {
3711 debug!(target: "search", "Calling next_column_match");
3712 if !self.shadow_state.borrow().is_in_column_search() {
3714 debug!(target: "search", "WARNING: Mode mismatch - fixing");
3715 self.state_container.set_mode(AppMode::ColumnSearch);
3716 self.shadow_state.borrow_mut().observe_search_start(
3717 crate::ui::state::shadow_state::SearchType::Column,
3718 "column_search_mode_fix_next",
3719 );
3720 }
3721 self.next_column_match();
3722 } else {
3723 debug!(target: "search", "Not in ColumnSearch mode, skipping next_column_match");
3724 }
3725 }
3726 SearchModesAction::PreviousMatch => {
3727 debug!(target: "search", "PreviousMatch action, current_mode={:?}, widget_mode={:?}",
3728 self.shadow_state.borrow().get_mode(), self.search_modes_widget.current_mode());
3729
3730 if self.shadow_state.borrow().get_mode() == AppMode::ColumnSearch
3732 || self.search_modes_widget.current_mode() == Some(SearchMode::ColumnSearch)
3733 {
3734 debug!(target: "search", "Calling previous_column_match");
3735 if self.shadow_state.borrow().get_mode() != AppMode::ColumnSearch {
3737 debug!(target: "search", "WARNING: Mode mismatch - fixing");
3738 self.state_container.set_mode(AppMode::ColumnSearch);
3739 self.shadow_state.borrow_mut().observe_search_start(
3740 crate::ui::state::shadow_state::SearchType::Column,
3741 "column_search_mode_fix_prev",
3742 );
3743 }
3744 self.previous_column_match();
3745 } else {
3746 debug!(target: "search", "Not in ColumnSearch mode, skipping previous_column_match");
3747 }
3748 }
3749 SearchModesAction::PassThrough => {}
3750 }
3751
3752 Ok(false)
3755 }
3756
3757 fn handle_help_input(&mut self, key: crossterm::event::KeyEvent) -> Result<bool> {
3758 match key.code {
3761 crossterm::event::KeyCode::Esc | crossterm::event::KeyCode::Char('q') => {
3762 self.help_widget.on_exit();
3763 self.state_container.set_help_visible(false);
3764
3765 let target_mode = if self.state_container.has_dataview() {
3767 AppMode::Results
3768 } else {
3769 AppMode::Command
3770 };
3771
3772 self.set_mode_via_shadow_state(target_mode, "escape_from_help");
3774
3775 Ok(false)
3777 }
3778 _ => {
3779 self.help_widget.handle_key(key);
3781 Ok(false)
3782 }
3783 }
3784 }
3785
3786 fn handle_history_input(&mut self, key: crossterm::event::KeyEvent) -> Result<bool> {
3789 use crossterm::event::{KeyCode, KeyModifiers};
3791
3792 let result = match key.code {
3793 KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => {
3794 crate::ui::input::history_input_handler::HistoryInputResult::Exit
3795 }
3796 KeyCode::Esc => {
3797 let original_input = self.state_container.cancel_history_search();
3799 if let Some(buffer) = self.state_container.current_buffer_mut() {
3800 self.shadow_state.borrow_mut().set_mode(
3801 crate::buffer::AppMode::Command,
3802 buffer,
3803 "history_cancelled",
3804 );
3805 buffer.set_status_message("History search cancelled".to_string());
3806 }
3807 crate::ui::input::history_input_handler::HistoryInputResult::SwitchToCommand(Some(
3808 (original_input, 0),
3809 ))
3810 }
3811 KeyCode::Enter => {
3812 if let Some(command) = self.state_container.accept_history_search() {
3814 if let Some(buffer) = self.state_container.current_buffer_mut() {
3815 self.shadow_state.borrow_mut().set_mode(
3816 crate::buffer::AppMode::Command,
3817 buffer,
3818 "history_accepted",
3819 );
3820 buffer.set_status_message(
3821 "Command loaded from history (cursor at start)".to_string(),
3822 );
3823 }
3824 crate::ui::input::history_input_handler::HistoryInputResult::SwitchToCommand(
3826 Some((command, 0)),
3827 )
3828 } else {
3829 crate::ui::input::history_input_handler::HistoryInputResult::Continue
3830 }
3831 }
3832 KeyCode::Up => {
3833 self.state_container.history_search_previous();
3834 crate::ui::input::history_input_handler::HistoryInputResult::Continue
3835 }
3836 KeyCode::Down => {
3837 self.state_container.history_search_next();
3838 crate::ui::input::history_input_handler::HistoryInputResult::Continue
3839 }
3840 KeyCode::Char('r') if key.modifiers.contains(KeyModifiers::CONTROL) => {
3841 self.state_container.history_search_next();
3843 crate::ui::input::history_input_handler::HistoryInputResult::Continue
3844 }
3845 KeyCode::Backspace => {
3846 self.state_container.history_search_backspace();
3847 crate::ui::input::history_input_handler::HistoryInputResult::Continue
3848 }
3849 KeyCode::Char(c) => {
3850 self.state_container.history_search_add_char(c);
3851 crate::ui::input::history_input_handler::HistoryInputResult::Continue
3852 }
3853 _ => crate::ui::input::history_input_handler::HistoryInputResult::Continue,
3854 };
3855
3856 match result {
3858 crate::ui::input::history_input_handler::HistoryInputResult::Exit => return Ok(true),
3859 crate::ui::input::history_input_handler::HistoryInputResult::SwitchToCommand(
3860 input_data,
3861 ) => {
3862 if let Some((text, cursor_pos)) = input_data {
3863 self.set_input_text_with_cursor(text, cursor_pos);
3864 self.sync_all_input_states();
3866 }
3867 }
3868 crate::ui::input::history_input_handler::HistoryInputResult::Continue => {
3869 if crate::ui::input::history_input_handler::key_updates_search(key) {
3871 self.update_history_matches_in_container();
3872 }
3873 }
3874 }
3875
3876 Ok(false)
3877 }
3878
3879 fn update_history_matches_in_container(&mut self) {
3881 let (current_columns, current_source_str) =
3883 if let Some(dataview) = self.state_container.get_buffer_dataview() {
3884 (
3885 dataview.column_names(), Some(dataview.source().name.clone()), )
3888 } else {
3889 (vec![], None)
3890 };
3891
3892 let current_source = current_source_str.as_deref();
3893 let query = self.state_container.history_search().query.clone();
3894
3895 self.state_container.update_history_search_with_schema(
3896 query,
3897 ¤t_columns,
3898 current_source,
3899 );
3900 }
3901
3902 fn handle_debug_input(&mut self, key: crossterm::event::KeyEvent) -> Result<bool> {
3903 let mut ctx = crate::ui::input::input_handlers::DebugInputContext {
3905 buffer_manager: self.state_container.buffers_mut(),
3906 debug_widget: &mut self.debug_widget,
3907 shadow_state: &self.shadow_state,
3908 };
3909
3910 let should_quit = crate::ui::input::input_handlers::handle_debug_input(&mut ctx, key)?;
3911
3912 if !should_quit {
3915 match key.code {
3916 KeyCode::Char('t') if key.modifiers.contains(KeyModifiers::CONTROL) => {
3917 self.yank_as_test_case();
3919 }
3920 KeyCode::Char('y') if key.modifiers.contains(KeyModifiers::SHIFT) => {
3921 self.yank_debug_with_context();
3923 }
3924 _ => {}
3925 }
3926 }
3927
3928 Ok(should_quit)
3929 }
3931
3932 fn handle_pretty_query_input(&mut self, key: crossterm::event::KeyEvent) -> Result<bool> {
3933 let mut ctx = crate::ui::input::input_handlers::DebugInputContext {
3935 buffer_manager: self.state_container.buffers_mut(),
3936 debug_widget: &mut self.debug_widget,
3937 shadow_state: &self.shadow_state,
3938 };
3939
3940 crate::ui::input::input_handlers::handle_pretty_query_input(&mut ctx, key)
3941 }
3942
3943 pub fn execute_query_v2(&mut self, query: &str) -> Result<()> {
3944 let context = self.query_orchestrator.execute_query(
3946 query,
3947 &mut self.state_container,
3948 &self.vim_search_adapter,
3949 );
3950
3951 match context {
3952 Ok(ctx) => {
3953 self.state_container
3955 .set_dataview(Some(ctx.result.dataview.clone()));
3956
3957 self.update_viewport_manager(Some(ctx.result.dataview.clone()));
3959
3960 self.state_container
3962 .update_data_size(ctx.result.stats.row_count, ctx.result.stats.column_count);
3963
3964 self.calculate_optimal_column_widths();
3966
3967 self.state_container
3969 .set_status_message(ctx.result.status_message());
3970
3971 self.state_container
3973 .command_history_mut()
3974 .add_entry_with_schema(
3975 ctx.query.clone(),
3976 true,
3977 Some(ctx.result.stats.execution_time.as_millis() as u64),
3978 ctx.result.column_names(),
3979 Some(ctx.result.table_name()),
3980 )?;
3981
3982 self.sync_mode(AppMode::Results, "execute_query_success");
3984
3985 self.reset_table_state();
3987
3988 Ok(())
3989 }
3990 Err(e) => {
3991 let error_msg = format!("Query error: {e}");
3992 self.state_container.set_status_message(error_msg.clone());
3993
3994 self.state_container
3996 .command_history_mut()
3997 .add_entry_with_schema(query.to_string(), false, None, vec![], None)?;
3998
3999 Err(e)
4000 }
4001 }
4002 }
4003
4004 fn handle_completion(&mut self) {
4005 let cursor_pos = self.get_input_cursor();
4006 let query_str = self.get_input_text();
4007 let query = query_str.as_str();
4008
4009 let hybrid_result = self.hybrid_parser.get_completions(query, cursor_pos);
4010 if !hybrid_result.suggestions.is_empty() {
4011 self.state_container.set_status_message(format!(
4012 "Suggestions: {}",
4013 hybrid_result.suggestions.join(", ")
4014 ));
4015 }
4016 }
4017
4018 fn apply_completion(&mut self) {
4019 let cursor_pos = self.get_input_cursor();
4020 let query = self.get_input_text();
4021
4022 let suggestion = match self.get_or_refresh_completion(&query, cursor_pos) {
4024 Some(s) => s,
4025 None => return,
4026 };
4027
4028 self.apply_completion_to_input(&query, cursor_pos, &suggestion);
4030 }
4031
4032 fn get_or_refresh_completion(&mut self, query: &str, cursor_pos: usize) -> Option<String> {
4035 let is_same_context = self
4036 .state_container
4037 .is_same_completion_context(query, cursor_pos);
4038
4039 if !is_same_context {
4040 let hybrid_result = self.hybrid_parser.get_completions(query, cursor_pos);
4042 if hybrid_result.suggestions.is_empty() {
4043 self.state_container
4044 .set_status_message("No completions available".to_string());
4045 return None;
4046 }
4047
4048 self.state_container
4049 .set_completion_suggestions(hybrid_result.suggestions);
4050 } else if self.state_container.is_completion_active() {
4051 self.state_container.next_completion();
4053 } else {
4054 self.state_container
4055 .set_status_message("No completions available".to_string());
4056 return None;
4057 }
4058
4059 if let Some(sugg) = self.state_container.get_current_completion() {
4061 Some(sugg)
4062 } else {
4063 self.state_container
4064 .set_status_message("No completion selected".to_string());
4065 None
4066 }
4067 }
4068
4069 fn apply_completion_to_input(&mut self, query: &str, cursor_pos: usize, suggestion: &str) {
4071 let partial_word =
4072 crate::ui::utils::text_operations::extract_partial_word_at_cursor(query, cursor_pos);
4073
4074 if let Some(partial) = partial_word {
4075 self.apply_partial_completion(query, cursor_pos, &partial, suggestion);
4076 } else {
4077 self.apply_full_insertion(query, cursor_pos, suggestion);
4078 }
4079 }
4080
4081 fn apply_partial_completion(
4083 &mut self,
4084 query: &str,
4085 cursor_pos: usize,
4086 partial: &str,
4087 suggestion: &str,
4088 ) {
4089 let result = crate::ui::utils::text_operations::apply_completion_to_text(
4091 query, cursor_pos, partial, suggestion,
4092 );
4093
4094 self.set_input_text_with_cursor(result.new_text.clone(), result.new_cursor_position);
4096
4097 self.state_container
4099 .update_completion_context(result.new_text.clone(), result.new_cursor_position);
4100
4101 let completion = self.state_container.completion();
4103 let suggestion_info = if completion.suggestions.len() > 1 {
4104 format!(
4105 "Completed: {} ({}/{} - Tab for next)",
4106 suggestion,
4107 completion.current_index + 1,
4108 completion.suggestions.len()
4109 )
4110 } else {
4111 format!("Completed: {suggestion}")
4112 };
4113 drop(completion);
4114 self.state_container.set_status_message(suggestion_info);
4115 }
4116
4117 fn apply_full_insertion(&mut self, query: &str, cursor_pos: usize, suggestion: &str) {
4119 let before_cursor = &query[..cursor_pos];
4121 let after_cursor = &query[cursor_pos..];
4122 let new_query = format!("{before_cursor}{suggestion}{after_cursor}");
4123
4124 let cursor_pos_new = if suggestion.ends_with("('')") {
4126 cursor_pos + suggestion.len() - 2
4128 } else {
4129 cursor_pos + suggestion.len()
4130 };
4131
4132 self.set_input_text(new_query.clone());
4134
4135 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
4137 buffer.set_input_cursor_position(cursor_pos_new);
4138 self.sync_all_input_states();
4140 }
4141
4142 self.state_container
4144 .update_completion_context(new_query, cursor_pos_new);
4145
4146 self.state_container
4147 .set_status_message(format!("Inserted: {suggestion}"));
4148 }
4149
4150 fn get_column_count(&self) -> usize {
4157 if let Some(provider) = self.get_data_provider() {
4159 provider.get_column_count()
4160 } else {
4161 0
4162 }
4163 }
4164
4165 fn get_column_names_via_provider(&self) -> Vec<String> {
4170 if let Some(provider) = self.get_data_provider() {
4171 provider.get_column_names()
4172 } else {
4173 Vec::new()
4174 }
4175 }
4176
4177 fn toggle_column_pin_impl(&mut self) {
4182 let visual_col_idx = if let Some(ref viewport_manager) = *self.viewport_manager.borrow() {
4184 viewport_manager.get_crosshair_col()
4185 } else {
4186 0
4187 };
4188
4189 let column_name = if let Some(dataview) = self.state_container.get_buffer_dataview() {
4191 let all_columns = dataview.column_names();
4192 all_columns.get(visual_col_idx).cloned()
4193 } else {
4194 None
4195 };
4196
4197 if let Some(col_name) = column_name {
4198 if let Some(dataview) = self.state_container.get_buffer_dataview_mut() {
4199 let pinned_names = dataview.get_pinned_column_names();
4201 if pinned_names.contains(&col_name) {
4202 dataview.unpin_column_by_name(&col_name);
4204 self.state_container
4205 .set_status_message(format!("Column '{col_name}' unpinned"));
4206 } else {
4207 match dataview.pin_column_by_name(&col_name) {
4209 Ok(()) => {
4210 self.state_container
4211 .set_status_message(format!("Column '{col_name}' pinned [P]"));
4212 }
4213 Err(e) => {
4214 self.state_container.set_status_message(e.to_string());
4215 }
4216 }
4217 }
4218
4219 if let Some(updated_dataview) = self.state_container.get_buffer_dataview() {
4221 self.update_viewport_manager(Some(updated_dataview.clone()));
4222 }
4223 }
4224 } else {
4225 self.state_container
4226 .set_status_message("No column to pin at current position".to_string());
4227 }
4228 }
4229
4230 fn clear_all_pinned_columns_impl(&mut self) {
4231 if let Some(dataview) = self.state_container.get_buffer_dataview_mut() {
4232 dataview.clear_pinned_columns();
4233 }
4234 self.state_container
4235 .set_status_message("All columns unpinned".to_string());
4236
4237 if let Some(updated_dataview) = self.state_container.get_buffer_dataview() {
4239 self.update_viewport_manager(Some(updated_dataview.clone()));
4240 }
4241 }
4242
4243 fn calculate_column_statistics(&mut self) {
4244 use std::time::Instant;
4245
4246 let start_total = Instant::now();
4247
4248 let (column_name, data_to_analyze) = {
4250 let headers = self.get_column_names_via_provider();
4252 if headers.is_empty() {
4253 return;
4254 }
4255
4256 let current_column = self.state_container.get_current_column();
4257 if current_column >= headers.len() {
4258 return;
4259 }
4260
4261 let column_name = headers[current_column].clone();
4262
4263 let data_to_analyze: Vec<String> = if let Some(provider) = self.get_data_provider() {
4265 let row_count = provider.get_row_count();
4266 let mut column_data = Vec::with_capacity(row_count);
4267
4268 for row_idx in 0..row_count {
4269 if let Some(row) = provider.get_row(row_idx) {
4270 if current_column < row.len() {
4271 column_data.push(row[current_column].clone());
4272 } else {
4273 column_data.push(String::new());
4275 }
4276 }
4277 }
4278
4279 column_data
4280 } else {
4281 return;
4283 };
4284
4285 (column_name, data_to_analyze)
4286 };
4287
4288 let data_refs: Vec<&str> = data_to_analyze
4290 .iter()
4291 .map(std::string::String::as_str)
4292 .collect();
4293
4294 let analyzer_stats = self
4296 .data_analyzer
4297 .calculate_column_statistics(&column_name, &data_refs);
4298
4299 let stats = ColumnStatistics {
4301 column_name: analyzer_stats.column_name,
4302 column_type: match analyzer_stats.data_type {
4303 data_analyzer::ColumnType::Integer | data_analyzer::ColumnType::Float => {
4304 ColumnType::Numeric
4305 }
4306 data_analyzer::ColumnType::String
4307 | data_analyzer::ColumnType::Boolean
4308 | data_analyzer::ColumnType::Date => ColumnType::String,
4309 data_analyzer::ColumnType::Mixed => ColumnType::Mixed,
4310 data_analyzer::ColumnType::Unknown => ColumnType::Mixed,
4311 },
4312 total_count: analyzer_stats.total_values,
4313 null_count: analyzer_stats.null_values,
4314 unique_count: analyzer_stats.unique_values,
4315 frequency_map: analyzer_stats.frequency_map.clone(),
4316 min: analyzer_stats
4318 .min_value
4319 .as_ref()
4320 .and_then(|s| s.parse::<f64>().ok()),
4321 max: analyzer_stats
4322 .max_value
4323 .as_ref()
4324 .and_then(|s| s.parse::<f64>().ok()),
4325 sum: analyzer_stats.sum_value,
4326 mean: analyzer_stats.avg_value,
4327 median: analyzer_stats.median_value,
4328 };
4329
4330 let elapsed = start_total.elapsed();
4332
4333 self.state_container.set_column_stats(Some(stats));
4334
4335 self.state_container.set_status_message(format!(
4337 "Column stats: {:.1}ms for {} values ({} unique)",
4338 elapsed.as_secs_f64() * 1000.0,
4339 data_to_analyze.len(),
4340 analyzer_stats.unique_values
4341 ));
4342
4343 self.state_container.set_mode(AppMode::ColumnStats);
4344 self.shadow_state
4345 .borrow_mut()
4346 .observe_mode_change(AppMode::ColumnStats, "column_stats_requested");
4347 }
4348
4349 fn check_parser_error(&self, query: &str) -> Option<String> {
4350 crate::ui::operations::simple_operations::check_parser_error(query)
4351 }
4352
4353 fn update_viewport_size(&mut self) {
4354 if let Ok((width, height)) = crossterm::terminal::size() {
4356 let data_rows_available = Self::calculate_available_data_rows(height);
4358
4359 let visible_rows = {
4361 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
4362 let viewport_manager = viewport_manager_borrow
4363 .as_mut()
4364 .expect("ViewportManager must exist for viewport size update");
4365 viewport_manager.update_terminal_size(width, data_rows_available)
4366 };
4367
4368 self.state_container.set_last_visible_rows(visible_rows);
4370
4371 self.state_container
4373 .navigation_mut()
4374 .set_viewport_size(visible_rows, width as usize);
4375
4376 info!(target: "navigation", "update_viewport_size - viewport set to: {}x{} rows", visible_rows, width);
4377 }
4378 }
4379
4380 fn perform_search(&mut self) {
4384 if let Some(dataview) = self.get_current_data() {
4385 let data: Vec<Vec<String>> = (0..dataview.row_count())
4387 .filter_map(|i| dataview.get_row(i))
4388 .map(|row| {
4389 row.values
4390 .iter()
4391 .map(std::string::ToString::to_string)
4392 .collect()
4393 })
4394 .collect();
4395
4396 let pattern = self.state_container.get_search_pattern().to_string();
4398
4399 info!(target: "search", "=== SEARCH START ===");
4400 info!(target: "search", "Pattern: '{}', case_insensitive: {}",
4401 pattern, self.state_container.is_case_insensitive());
4402 info!(target: "search", "Data dimensions: {} rows x {} columns",
4403 data.len(), data.first().map_or(0, std::vec::Vec::len));
4404
4405 let column_names = dataview.column_names();
4407 info!(target: "search", "Column names (first 5): {:?}",
4408 column_names.iter().take(5).collect::<Vec<_>>());
4409
4410 for (i, row) in data.iter().take(10).enumerate() {
4412 info!(target: "search", " Data row {}: [{}]", i,
4413 row.iter().take(5).map(|s| format!("'{s}'")).collect::<Vec<_>>().join(", "));
4414 }
4415
4416 let visible_columns = None;
4418
4419 let match_count = {
4421 let mut search_manager = self.search_manager.borrow_mut();
4422
4423 search_manager.clear();
4425 info!(target: "search", "Cleared previous search results");
4426
4427 search_manager.set_case_sensitive(!self.state_container.is_case_insensitive());
4429 info!(target: "search", "Set case_sensitive to {}", !self.state_container.is_case_insensitive());
4430
4431 let count = search_manager.search(&pattern, &data, visible_columns);
4433 info!(target: "search", "SearchManager.search() returned {} matches", count);
4434 count
4435 };
4436
4437 info!(target: "search", "SearchManager found {} matches", match_count);
4438
4439 if match_count > 0 {
4441 let (first_row, first_col) = {
4443 let search_manager = self.search_manager.borrow();
4444 if let Some(first_match) = search_manager.first_match() {
4445 info!(target: "search", "FIRST MATCH DETAILS:");
4446 info!(target: "search", " Data coordinates: row={}, col={}",
4447 first_match.row, first_match.column);
4448 info!(target: "search", " Matched value: '{}'", first_match.value);
4449 info!(target: "search", " Highlight range: {:?}", first_match.highlight_range);
4450
4451 for (i, m) in search_manager.all_matches().iter().take(5).enumerate() {
4453 info!(target: "search", " Match #{}: row={}, col={}, value='{}'",
4454 i + 1, m.row, m.column, m.value);
4455 }
4456
4457 (first_match.row, first_match.column)
4458 } else {
4459 warn!(target: "search", "SearchManager reported matches but first_match() is None!");
4460 (0, 0)
4461 }
4462 };
4463
4464 self.state_container.set_table_selected_row(Some(first_row));
4466
4467 info!(target: "search", "Updating TableWidgetManager to navigate to ({}, {})", first_row, first_col);
4469 self.table_widget_manager
4470 .borrow_mut()
4471 .navigate_to_search_match(first_row, first_col);
4472
4473 if let Some(dataview) = self.get_current_data() {
4475 if let Some(row_data) = dataview.get_row(first_row) {
4476 if first_col < row_data.values.len() {
4477 info!(target: "search", "VALUE AT NAVIGATION TARGET ({}, {}): '{}'",
4478 first_row, first_col, row_data.values[first_col]);
4479 }
4480 }
4481 }
4482
4483 let buffer_matches: Vec<(usize, usize)> = {
4485 let search_manager = self.search_manager.borrow();
4486 search_manager
4487 .all_matches()
4488 .iter()
4489 .map(|m| (m.row, m.column))
4490 .collect()
4491 };
4492
4493 let state_matches: Vec<(usize, usize, usize, usize)> = {
4496 let search_manager = self.search_manager.borrow();
4497 search_manager
4498 .all_matches()
4499 .iter()
4500 .map(|m| {
4501 (m.row, m.column, m.row, m.column)
4503 })
4504 .collect()
4505 };
4506 self.state_container.search_mut().matches = state_matches;
4507
4508 self.state_container
4509 .set_search_matches_with_index(buffer_matches.clone(), 0);
4510 self.state_container
4511 .set_current_match(Some((first_row, first_col)));
4512 self.state_container
4513 .set_status_message(format!("Found {match_count} matches"));
4514
4515 info!(target: "search", "Search found {} matches for pattern '{}'", match_count, pattern);
4516 } else {
4517 self.state_container.search_mut().matches.clear();
4519 self.state_container.clear_search_state();
4520 self.state_container.set_current_match(None);
4521
4522 info!(target: "search", "No matches found for pattern '{}'", pattern);
4523 }
4524 }
4525 }
4526
4527 fn start_vim_search(&mut self) {
4531 info!(target: "vim_search", "Starting vim search mode");
4532
4533 self.vim_search_adapter.borrow_mut().start_search();
4535
4536 self.shadow_state.borrow_mut().observe_search_start(
4538 crate::ui::state::shadow_state::SearchType::Vim,
4539 "slash_key_pressed",
4540 );
4541
4542 self.enter_search_mode(SearchMode::Search);
4544 }
4545
4546 fn vim_search_next(&mut self) {
4548 if !self.vim_search_adapter.borrow().is_navigating() {
4549 let resumed = {
4551 let mut viewport_borrow = self.viewport_manager.borrow_mut();
4552 if let Some(ref mut viewport) = *viewport_borrow {
4553 if let Some(dataview) = self.state_container.get_buffer_dataview() {
4554 self.vim_search_adapter
4555 .borrow_mut()
4556 .resume_last_search(dataview, viewport)
4557 } else {
4558 false
4559 }
4560 } else {
4561 false
4562 }
4563 };
4564
4565 if !resumed {
4566 self.state_container
4567 .set_status_message("No previous search pattern".to_string());
4568 return;
4569 }
4570 }
4571
4572 let result = {
4574 let mut viewport_borrow = self.viewport_manager.borrow_mut();
4575 if let Some(ref mut viewport) = *viewport_borrow {
4576 let search_match = self.vim_search_adapter.borrow_mut().next_match(viewport);
4577 if search_match.is_some() {
4578 let match_info = self.vim_search_adapter.borrow().get_match_info();
4579 search_match.map(|m| (m, match_info))
4580 } else {
4581 None
4582 }
4583 } else {
4584 None
4585 }
4586 }; if let Some((ref search_match, _)) = result {
4590 info!(target: "search",
4592 "=== UPDATING TUI STATE FOR VIM SEARCH ===");
4593 info!(target: "search",
4594 "Setting selected row to: {}", search_match.row);
4595 info!(target: "search",
4596 "Setting selected column to: {} (visual col)", search_match.col);
4597
4598 if let Some(dataview) = self.state_container.get_buffer_dataview() {
4600 info!(target: "search",
4602 "DEBUG: Fetching row {} from dataview with {} total rows",
4603 search_match.row, dataview.row_count());
4604
4605 if let Some(row_data) = dataview.get_row(search_match.row) {
4606 info!(target: "search",
4608 "Row {} has {} values, first 5: {:?}",
4609 search_match.row, row_data.values.len(),
4610 row_data.values.iter().take(5).map(std::string::ToString::to_string).collect::<Vec<_>>());
4611
4612 if search_match.col < row_data.values.len() {
4613 let actual_value = &row_data.values[search_match.col];
4614 info!(target: "search",
4615 "Actual value at row {} col {}: '{}'",
4616 search_match.row, search_match.col, actual_value);
4617
4618 let pattern = self
4620 .vim_search_adapter
4621 .borrow()
4622 .get_pattern()
4623 .unwrap_or_default();
4624 let contains_pattern = actual_value
4625 .to_string()
4626 .to_lowercase()
4627 .contains(&pattern.to_lowercase());
4628 if contains_pattern {
4629 info!(target: "search",
4630 "✓ Confirmed: Cell contains pattern '{}'", pattern);
4631 } else {
4632 warn!(target: "search",
4633 "WARNING: Cell at ({}, {}) = '{}' does NOT contain pattern '{}'!",
4634 search_match.row, search_match.col, actual_value, pattern);
4635 }
4636 } else {
4637 warn!(target: "search",
4638 "Column {} is out of bounds for row {} (row has {} values)",
4639 search_match.col, search_match.row, row_data.values.len());
4640 }
4641 } else {
4642 warn!(target: "search",
4643 "Could not get row data for row {}", search_match.row);
4644 }
4645
4646 let display_columns = dataview.get_display_columns();
4648 info!(target: "search",
4649 "Display columns mapping (first 10): {:?}",
4650 display_columns.iter().take(10).collect::<Vec<_>>());
4651 }
4652
4653 self.state_container
4654 .set_table_selected_row(Some(search_match.row));
4655 self.state_container
4656 .set_selected_row(Some(search_match.row));
4657
4658 info!(target: "search",
4661 "Setting column to visual index {}",
4662 search_match.col);
4663
4664 self.state_container
4666 .set_current_column_buffer(search_match.col);
4667 self.state_container.navigation_mut().selected_column = search_match.col;
4668
4669 self.state_container.select_column(search_match.col);
4671 info!(target: "search",
4672 "Updated SelectionState column to: {}", search_match.col);
4673
4674 info!(target: "search",
4676 "Column state after update: nav.selected_column={}, buffer.current_column={}, selection.selected_column={}",
4677 self.state_container.navigation().selected_column,
4678 self.state_container.get_current_column(),
4679 self.state_container.selection().selected_column);
4680
4681 self.sync_navigation_with_viewport();
4684
4685 let scroll_offset = self.state_container.navigation().scroll_offset;
4687 self.state_container.set_scroll_offset(scroll_offset);
4688
4689 info!(target: "search", "Updating TableWidgetManager for vim search navigation to ({}, {})",
4691 search_match.row, search_match.col);
4692 self.table_widget_manager
4693 .borrow_mut()
4694 .navigate_to(search_match.row, search_match.col);
4695
4696 info!(target: "search",
4698 "After TableWidgetManager update: nav.selected_column={}, buffer.current_column={}, selection.selected_column={}",
4699 self.state_container.navigation().selected_column,
4700 self.state_container.get_current_column(),
4701 self.state_container.selection().selected_column);
4702
4703 }
4707
4708 if let Some((search_match, match_info)) = result {
4710 if let Some((current, total)) = match_info {
4711 self.state_container.set_status_message(format!(
4712 "Match {}/{} at ({}, {})",
4713 current,
4714 total,
4715 search_match.row + 1,
4716 search_match.col + 1
4717 ));
4718 }
4719
4720 info!(target: "search",
4722 "FINAL vim_search_next state: nav.selected_column={}, buffer.current_column={}, selection.selected_column={}",
4723 self.state_container.navigation().selected_column,
4724 self.state_container.get_current_column(),
4725 self.state_container.selection().selected_column);
4726
4727 if let Some(dataview) = self.state_container.get_buffer_dataview() {
4729 let final_row = self.state_container.navigation().selected_row;
4730 let final_col = self.state_container.navigation().selected_column;
4731
4732 if let Some(row_data) = dataview.get_row(final_row) {
4733 if final_col < row_data.values.len() {
4734 let actual_value = &row_data.values[final_col];
4735 info!(target: "search",
4736 "VERIFICATION: Cell at final position ({}, {}) contains: '{}'",
4737 final_row, final_col, actual_value);
4738
4739 let pattern = self
4740 .vim_search_adapter
4741 .borrow()
4742 .get_pattern()
4743 .unwrap_or_default();
4744 if !actual_value
4745 .to_string()
4746 .to_lowercase()
4747 .contains(&pattern.to_lowercase())
4748 {
4749 error!(target: "search",
4750 "ERROR: Final cell '{}' does NOT contain search pattern '{}'!",
4751 actual_value, pattern);
4752 }
4753 }
4754 }
4755 }
4756 }
4757 }
4758
4759 fn vim_search_previous(&mut self) {
4761 if !self.vim_search_adapter.borrow().is_navigating() {
4762 let resumed = {
4764 let mut viewport_borrow = self.viewport_manager.borrow_mut();
4765 if let Some(ref mut viewport) = *viewport_borrow {
4766 if let Some(dataview) = self.state_container.get_buffer_dataview() {
4767 self.vim_search_adapter
4768 .borrow_mut()
4769 .resume_last_search(dataview, viewport)
4770 } else {
4771 false
4772 }
4773 } else {
4774 false
4775 }
4776 };
4777
4778 if !resumed {
4779 self.state_container
4780 .set_status_message("No previous search pattern".to_string());
4781 return;
4782 }
4783 }
4784
4785 let result = {
4787 let mut viewport_borrow = self.viewport_manager.borrow_mut();
4788 if let Some(ref mut viewport) = *viewport_borrow {
4789 let search_match = self
4790 .vim_search_adapter
4791 .borrow_mut()
4792 .previous_match(viewport);
4793 if search_match.is_some() {
4794 let match_info = self.vim_search_adapter.borrow().get_match_info();
4795 search_match.map(|m| (m, match_info))
4796 } else {
4797 None
4798 }
4799 } else {
4800 None
4801 }
4802 }; if let Some((ref search_match, _)) = result {
4806 self.state_container
4807 .set_table_selected_row(Some(search_match.row));
4808 self.state_container
4809 .set_selected_row(Some(search_match.row));
4810
4811 info!(target: "search",
4814 "Setting column to visual index {}",
4815 search_match.col);
4816
4817 self.state_container
4819 .set_current_column_buffer(search_match.col);
4820 self.state_container.navigation_mut().selected_column = search_match.col;
4821
4822 self.state_container.select_column(search_match.col);
4824 info!(target: "search",
4825 "Updated SelectionState column to: {}", search_match.col);
4826
4827 info!(target: "search",
4829 "Column state after update: nav.selected_column={}, buffer.current_column={}, selection.selected_column={}",
4830 self.state_container.navigation().selected_column,
4831 self.state_container.get_current_column(),
4832 self.state_container.selection().selected_column);
4833
4834 self.sync_navigation_with_viewport();
4837
4838 let scroll_offset = self.state_container.navigation().scroll_offset;
4840 self.state_container.set_scroll_offset(scroll_offset);
4841
4842 info!(target: "search", "Updating TableWidgetManager for vim search navigation to ({}, {})",
4844 search_match.row, search_match.col);
4845 self.table_widget_manager
4846 .borrow_mut()
4847 .navigate_to(search_match.row, search_match.col);
4848
4849 info!(target: "search",
4851 "After TableWidgetManager update: nav.selected_column={}, buffer.current_column={}, selection.selected_column={}",
4852 self.state_container.navigation().selected_column,
4853 self.state_container.get_current_column(),
4854 self.state_container.selection().selected_column);
4855
4856 }
4860
4861 if let Some((search_match, match_info)) = result {
4863 if let Some((current, total)) = match_info {
4864 self.state_container.set_status_message(format!(
4865 "Match {}/{} at ({}, {})",
4866 current,
4867 total,
4868 search_match.row + 1,
4869 search_match.col + 1
4870 ));
4871 }
4872 }
4873 }
4875
4876 fn apply_filter(&mut self, pattern: &str) {
4877 use std::sync::atomic::{AtomicUsize, Ordering};
4878
4879 static FILTER_DEPTH: AtomicUsize = AtomicUsize::new(0);
4881 let depth = FILTER_DEPTH.fetch_add(1, Ordering::SeqCst);
4882 if depth > 0 {
4883 eprintln!(
4884 "WARNING: apply_filter re-entrancy detected! depth={}, pattern='{}', thread={:?}",
4885 depth,
4886 pattern,
4887 std::thread::current().id()
4888 );
4889 }
4890
4891 info!(
4892 "Applying filter: '{}' on thread {:?}",
4893 pattern,
4894 std::thread::current().id()
4895 );
4896
4897 use crate::ui::state::state_coordinator::StateCoordinator;
4899 let _rows_after =
4900 StateCoordinator::apply_text_filter_with_refs(&mut self.state_container, pattern);
4901
4902 self.sync_dataview_to_managers();
4905
4906 FILTER_DEPTH.fetch_sub(1, Ordering::SeqCst);
4908 }
4909 fn search_columns(&mut self) {
4910 static SEARCH_DEPTH: std::sync::atomic::AtomicUsize =
4912 std::sync::atomic::AtomicUsize::new(0);
4913 let depth = SEARCH_DEPTH.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
4914
4915 if depth > 10 {
4917 error!(target: "search", "Column search depth exceeded limit, aborting to prevent infinite loop");
4918 SEARCH_DEPTH.store(0, std::sync::atomic::Ordering::SeqCst);
4919 return;
4920 }
4921
4922 struct DepthGuard;
4924 impl Drop for DepthGuard {
4925 fn drop(&mut self) {
4926 SEARCH_DEPTH.fetch_sub(1, std::sync::atomic::Ordering::SeqCst);
4927 }
4928 }
4929 let _guard = DepthGuard;
4930
4931 let pattern = self.state_container.column_search().pattern.clone();
4932 debug!(target: "search", "search_columns called with pattern: '{}', depth: {}", pattern, depth);
4933
4934 if pattern.is_empty() {
4935 debug!(target: "search", "Pattern is empty, skipping column search");
4936 return;
4937 }
4938
4939 let matching_columns = if let Some(dataview) =
4941 self.state_container.get_buffer_dataview_mut()
4942 {
4943 dataview.search_columns(&pattern);
4944
4945 let matches = dataview.get_matching_columns().to_vec();
4947 debug!(target: "search", "DataView found {} matching columns", matches.len());
4948 if !matches.is_empty() {
4949 for (idx, (col_idx, col_name)) in matches.iter().enumerate() {
4950 debug!(target: "search", " Match {}: '{}' at visual index {}", idx + 1, col_name, col_idx);
4951 }
4952 }
4953
4954 let columns: Vec<(String, usize)> = matches
4956 .iter()
4957 .map(|(idx, name)| (name.clone(), *idx))
4958 .collect();
4959 self.state_container
4960 .update_column_search_matches(&columns, &pattern);
4961
4962 matches
4963 } else {
4964 debug!(target: "search", "No DataView available for column search");
4965 Vec::new()
4966 };
4967
4968 if matching_columns.is_empty() {
4969 let status_msg = format!("No columns matching '{pattern}'");
4970 debug!(target: "search", "Setting status: {}", status_msg);
4971 self.state_container.set_status_message(status_msg);
4972 } else {
4973 let first_match_visual_idx = matching_columns[0].0;
4975 let first_match_name = &matching_columns[0].1;
4976
4977 let datatable_idx = if let Some(dataview) = self.state_container.get_buffer_dataview() {
4979 let display_columns = dataview.get_display_columns();
4980 if first_match_visual_idx < display_columns.len() {
4981 display_columns[first_match_visual_idx]
4982 } else {
4983 first_match_visual_idx }
4985 } else {
4986 first_match_visual_idx
4987 };
4988
4989 self.state_container.set_current_column(datatable_idx);
4990 self.state_container
4991 .set_current_column_buffer(datatable_idx);
4992
4993 {
4996 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
4997 if let Some(viewport_manager) = viewport_manager_borrow.as_mut() {
4998 let viewport_changed =
4999 viewport_manager.set_current_column(first_match_visual_idx);
5000
5001 if viewport_changed {
5003 let new_viewport = viewport_manager.viewport_cols().clone();
5004 let pinned_count =
5005 if let Some(dv) = self.state_container.get_buffer_dataview() {
5006 dv.get_pinned_columns().len()
5007 } else {
5008 0
5009 };
5010 let scrollable_offset = new_viewport.start.saturating_sub(pinned_count);
5011 self.state_container.navigation_mut().scroll_offset.1 = scrollable_offset;
5012
5013 debug!(target: "navigation",
5014 "Column search initial: Jumped to column {} '{}', viewport adjusted to {:?}",
5015 first_match_visual_idx, first_match_name, new_viewport);
5016 }
5017 }
5018 }
5019
5020 debug!(target: "search", "Setting current column to visual index {} ('{}')",
5021 first_match_visual_idx, first_match_name);
5022 let status_msg = format!(
5023 "Found {} columns matching '{}'. Tab/Shift-Tab to navigate.",
5024 matching_columns.len(),
5025 pattern
5026 );
5027 debug!(target: "search", "Setting status: {}", status_msg);
5028 self.state_container.set_status_message(status_msg);
5029
5030 }
5032
5033 }
5035
5036 fn next_column_match(&mut self) {
5037 let column_match_data =
5040 if let Some(dataview) = self.state_container.get_buffer_dataview_mut() {
5041 if let Some(visual_idx) = dataview.next_column_match() {
5042 let matching_columns = dataview.get_matching_columns();
5044 let current_match_index = dataview.current_column_match_index();
5045 let current_match = current_match_index + 1;
5046 let total_matches = matching_columns.len();
5047 let col_name = matching_columns
5048 .get(current_match_index)
5049 .map(|(_, name)| name.clone())
5050 .unwrap_or_default();
5051
5052 let display_columns = dataview.get_display_columns();
5055 let datatable_idx = if visual_idx < display_columns.len() {
5056 display_columns[visual_idx]
5057 } else {
5058 visual_idx };
5060
5061 Some((
5062 visual_idx,
5063 datatable_idx,
5064 col_name,
5065 current_match,
5066 total_matches,
5067 current_match_index,
5068 ))
5069 } else {
5070 None
5071 }
5072 } else {
5073 None
5074 };
5075
5076 if let Some((
5078 visual_idx,
5079 datatable_idx,
5080 col_name,
5081 current_match,
5082 total_matches,
5083 current_match_index,
5084 )) = column_match_data
5085 {
5086 self.state_container.set_current_column(datatable_idx);
5088 self.state_container
5089 .set_current_column_buffer(datatable_idx);
5090
5091 {
5094 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
5095 if let Some(viewport_manager) = viewport_manager_borrow.as_mut() {
5096 viewport_manager.set_current_column(visual_idx);
5097 }
5098 }
5099
5100 debug!(target: "column_search_sync", "next_column_match: About to call sync_navigation_with_viewport() - visual_idx: {}, datatable_idx: {}", visual_idx, datatable_idx);
5102 debug!(target: "column_search_sync", "next_column_match: Pre-sync - viewport current_column: {}",
5103 if let Ok(vm) = self.viewport_manager.try_borrow() {
5104 vm.as_ref().map_or(0, super::viewport_manager::ViewportManager::get_crosshair_col)
5105 } else { 0 });
5106 self.sync_navigation_with_viewport();
5107 debug!(target: "column_search_sync", "next_column_match: Post-sync - navigation current_column: {}",
5108 self.state_container.navigation().selected_column);
5109 debug!(target: "column_search_sync", "next_column_match: sync_navigation_with_viewport() completed");
5110
5111 debug!(target: "navigation",
5112 "Column search: Jumped to visual column {} (datatable: {}) '{}', synced with viewport",
5113 visual_idx, datatable_idx, col_name);
5114
5115 {
5118 let mut column_search = self.state_container.column_search_mut();
5119 column_search.current_match = current_match_index;
5120 debug!(target: "column_search_sync", "next_column_match: Updated AppStateContainer column_search.current_match to {}", column_search.current_match);
5121 }
5122
5123 self.state_container.set_status_message(format!(
5124 "Column {current_match}/{total_matches}: {col_name} - Tab/Shift-Tab to navigate"
5125 ));
5126 }
5127 }
5128
5129 fn previous_column_match(&mut self) {
5130 let column_match_data =
5133 if let Some(dataview) = self.state_container.get_buffer_dataview_mut() {
5134 if let Some(visual_idx) = dataview.prev_column_match() {
5135 let matching_columns = dataview.get_matching_columns();
5137 let current_match_index = dataview.current_column_match_index();
5138 let current_match = current_match_index + 1;
5139 let total_matches = matching_columns.len();
5140 let col_name = matching_columns
5141 .get(current_match_index)
5142 .map(|(_, name)| name.clone())
5143 .unwrap_or_default();
5144
5145 let display_columns = dataview.get_display_columns();
5148 let datatable_idx = if visual_idx < display_columns.len() {
5149 display_columns[visual_idx]
5150 } else {
5151 visual_idx };
5153
5154 Some((
5155 visual_idx,
5156 datatable_idx,
5157 col_name,
5158 current_match,
5159 total_matches,
5160 current_match_index,
5161 ))
5162 } else {
5163 None
5164 }
5165 } else {
5166 None
5167 };
5168
5169 if let Some((
5171 visual_idx,
5172 datatable_idx,
5173 col_name,
5174 current_match,
5175 total_matches,
5176 current_match_index,
5177 )) = column_match_data
5178 {
5179 self.state_container.set_current_column(datatable_idx);
5181 self.state_container
5182 .set_current_column_buffer(datatable_idx);
5183
5184 {
5187 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
5188 if let Some(viewport_manager) = viewport_manager_borrow.as_mut() {
5189 viewport_manager.set_current_column(visual_idx);
5190 }
5191 }
5192
5193 debug!(target: "column_search_sync", "previous_column_match: About to call sync_navigation_with_viewport() - visual_idx: {}, datatable_idx: {}", visual_idx, datatable_idx);
5195 debug!(target: "column_search_sync", "previous_column_match: Pre-sync - viewport current_column: {}",
5196 if let Ok(vm) = self.viewport_manager.try_borrow() {
5197 vm.as_ref().map_or(0, super::viewport_manager::ViewportManager::get_crosshair_col)
5198 } else { 0 });
5199 self.sync_navigation_with_viewport();
5200 debug!(target: "column_search_sync", "previous_column_match: Post-sync - navigation current_column: {}",
5201 self.state_container.navigation().selected_column);
5202 debug!(target: "column_search_sync", "previous_column_match: sync_navigation_with_viewport() completed");
5203
5204 debug!(target: "navigation",
5205 "Column search (prev): Jumped to visual column {} (datatable: {}) '{}', synced with viewport",
5206 visual_idx, datatable_idx, col_name);
5207
5208 {
5211 let mut column_search = self.state_container.column_search_mut();
5212 column_search.current_match = current_match_index;
5213 debug!(target: "column_search_sync", "previous_column_match: Updated AppStateContainer column_search.current_match to {}", column_search.current_match);
5214 }
5215
5216 self.state_container.set_status_message(format!(
5217 "Column {current_match}/{total_matches}: {col_name} - Tab/Shift-Tab to navigate"
5218 ));
5219 }
5220 }
5221
5222 fn apply_fuzzy_filter(&mut self) {
5223 info!(
5224 "apply_fuzzy_filter called on thread {:?}",
5225 std::thread::current().id()
5226 );
5227
5228 use crate::ui::state::state_coordinator::StateCoordinator;
5230 let (_match_count, indices) = StateCoordinator::apply_fuzzy_filter_with_refs(
5231 &mut self.state_container,
5232 &self.viewport_manager,
5233 );
5234
5235 self.state_container.set_fuzzy_filter_indices(indices);
5237
5238 self.sync_dataview_to_managers();
5241 }
5242
5243 fn toggle_sort_current_column(&mut self) {
5244 let visual_col_idx = if let Some(ref viewport_manager) = *self.viewport_manager.borrow() {
5246 viewport_manager.get_crosshair_col()
5247 } else {
5248 0
5249 };
5250
5251 if let Some(dataview) = self.state_container.get_buffer_dataview_mut() {
5252 let column_names = dataview.column_names();
5255 let col_name = column_names
5256 .get(visual_col_idx)
5257 .cloned()
5258 .unwrap_or_else(|| format!("Column {visual_col_idx}"));
5259
5260 debug!(
5261 "toggle_sort_current_column: visual_idx={}, column_name={}",
5262 visual_col_idx, col_name
5263 );
5264
5265 if let Err(e) = dataview.toggle_sort(visual_col_idx) {
5266 self.state_container
5267 .set_status_message(format!("Sort error: {e}"));
5268 } else {
5269 let sort_state = dataview.get_sort_state();
5271 let message = match sort_state.order {
5272 crate::data::data_view::SortOrder::Ascending => {
5273 format!("Sorted '{col_name}' ascending ↑")
5274 }
5275 crate::data::data_view::SortOrder::Descending => {
5276 format!("Sorted '{col_name}' descending ↓")
5277 }
5278 crate::data::data_view::SortOrder::None => {
5279 format!("Cleared sort on '{col_name}'")
5280 }
5281 };
5282 self.state_container.set_status_message(message);
5283
5284 if let Some(updated_dataview) = self.state_container.get_buffer_dataview() {
5286 self.table_widget_manager
5288 .borrow_mut()
5289 .set_dataview(Arc::new(updated_dataview.clone()));
5290
5291 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
5292 if let Some(ref mut viewport_manager) = *viewport_manager_borrow {
5293 viewport_manager.set_dataview(Arc::new(updated_dataview.clone()));
5294 debug!("Updated ViewportManager with sorted DataView");
5295 }
5296 }
5297 }
5298 } else {
5299 self.state_container
5301 .set_status_message("Error: Invalid column position".to_string());
5302 }
5303 }
5304
5305 fn get_current_data(&self) -> Option<&DataView> {
5306 self.state_container.get_buffer_dataview()
5307 }
5308
5309 fn get_row_count(&self) -> usize {
5310 if self.state_container.is_fuzzy_filter_active() {
5312 self.state_container.get_fuzzy_filter_indices().len()
5314 } else if let Some(dataview) = self.state_container.get_buffer_dataview() {
5315 dataview.row_count()
5317 } else if let Some(provider) = self.get_data_provider() {
5318 provider.get_row_count()
5320 } else {
5321 0
5322 }
5323 }
5324
5325 fn sync_dataview_to_managers(&self) {
5327 if let Some(dataview) = self.state_container.get_buffer_dataview() {
5328 let arc_dataview = Arc::new(dataview.clone());
5329
5330 if let Some(ref mut viewport_manager) = *self.viewport_manager.borrow_mut() {
5332 viewport_manager.set_dataview(arc_dataview.clone());
5333 debug!(
5334 "Updated ViewportManager with DataView (row_count={})",
5335 arc_dataview.row_count()
5336 );
5337 }
5338
5339 self.table_widget_manager
5341 .borrow_mut()
5342 .set_dataview(arc_dataview);
5343 debug!("Updated TableWidgetManager with DataView");
5344 }
5345 }
5346
5347 pub fn reset_table_state(&mut self) {
5348 use crate::ui::state::state_coordinator::StateCoordinator;
5350 StateCoordinator::reset_table_state_with_refs(
5351 &mut self.state_container,
5352 &self.viewport_manager,
5353 );
5354 }
5355
5356 fn update_parser_for_current_buffer(&mut self) {
5357 self.sync_all_input_states();
5359
5360 use crate::ui::state::state_coordinator::StateCoordinator;
5362 StateCoordinator::update_parser_with_refs(&self.state_container, &mut self.hybrid_parser);
5363 }
5364
5365 fn sync_after_buffer_switch(&mut self) {
5370 self.restore_viewport_from_current_buffer();
5372
5373 self.update_parser_for_current_buffer();
5375 }
5376
5377 fn update_viewport_manager(&mut self, dataview: Option<DataView>) {
5379 if let Some(dv) = dataview {
5380 let current_column = self.state_container.get_current_column();
5382
5383 let mut new_viewport_manager = ViewportManager::new(Arc::new(dv));
5385
5386 if let Ok((width, height)) = crossterm::terminal::size() {
5388 let data_rows_available = Self::calculate_available_data_rows(height);
5390 new_viewport_manager.update_terminal_size(width, data_rows_available);
5391 debug!(
5392 "Updated new ViewportManager terminal size: {}x{} (data rows)",
5393 width, data_rows_available
5394 );
5395 }
5396
5397 if current_column < new_viewport_manager.dataview().column_count() {
5400 new_viewport_manager.set_current_column(current_column);
5401 } else {
5402 new_viewport_manager.set_current_column(0);
5404 self.state_container.set_current_column_buffer(0);
5405 }
5406
5407 *self.viewport_manager.borrow_mut() = Some(new_viewport_manager);
5408 debug!(
5409 "ViewportManager updated with new DataView, current_column={}",
5410 current_column
5411 );
5412 } else {
5413 *self.viewport_manager.borrow_mut() = None;
5415 debug!("ViewportManager cleared (no DataView)");
5416 }
5417 }
5418
5419 pub fn calculate_optimal_column_widths(&mut self) {
5420 let widths_from_viewport = {
5422 let mut viewport_opt = self.viewport_manager.borrow_mut();
5423 (*viewport_opt)
5424 .as_mut()
5425 .map(super::viewport_manager::ViewportManager::calculate_optimal_column_widths)
5426 };
5427
5428 if let Some(widths) = widths_from_viewport {
5429 self.state_container.set_column_widths(widths);
5430 }
5431 }
5432
5433 pub fn set_status_message(&mut self, message: impl Into<String>) {
5436 let msg = message.into();
5437 debug!("Status: {}", msg);
5438 self.state_container.set_status_message(msg.clone());
5439 }
5442
5443 fn set_error_status(&mut self, context: &str, error: impl std::fmt::Display) {
5445 let msg = format!("{context}: {error}");
5446 debug!("Error status: {}", msg);
5447 self.set_status_message(msg);
5448 }
5449
5450 fn export_to_csv(&mut self) {
5451 let result = {
5452 let ctx = crate::ui::operations::data_export_operations::DataExportContext {
5453 data_provider: self.get_data_provider(),
5454 };
5455 crate::ui::operations::data_export_operations::export_to_csv(&ctx)
5456 };
5457
5458 match result {
5459 crate::ui::operations::data_export_operations::ExportResult::Success(message) => {
5460 self.set_status_message(message);
5461 }
5462 crate::ui::operations::data_export_operations::ExportResult::Error(error) => {
5463 self.set_error_status("Export failed", error);
5464 }
5465 }
5466 }
5467
5468 fn paste_from_clipboard(&mut self) {
5475 match self.state_container.read_from_clipboard() {
5477 Ok(text) => {
5478 let mode = self.shadow_state.borrow().get_mode();
5479 match mode {
5480 AppMode::Command => {
5481 let cursor_pos = self.get_input_cursor();
5484 let current_value = self.get_input_text();
5485
5486 let mut new_value = String::new();
5488 new_value.push_str(¤t_value[..cursor_pos]);
5489 new_value.push_str(&text);
5490 new_value.push_str(¤t_value[cursor_pos..]);
5491
5492 self.set_input_text_with_cursor(new_value, cursor_pos + text.len());
5493
5494 self.state_container
5495 .set_status_message(format!("Pasted {} characters", text.len()));
5496 }
5497 AppMode::Filter
5498 | AppMode::FuzzyFilter
5499 | AppMode::Search
5500 | AppMode::ColumnSearch => {
5501 let cursor_pos = self.get_input_cursor();
5503 let current_value = self.get_input_text();
5504
5505 let mut new_value = String::new();
5506 new_value.push_str(¤t_value[..cursor_pos]);
5507 new_value.push_str(&text);
5508 new_value.push_str(¤t_value[cursor_pos..]);
5509
5510 self.set_input_text_with_cursor(new_value, cursor_pos + text.len());
5511
5512 match mode {
5514 AppMode::Filter => {
5515 let pattern = self.get_input_text();
5516 self.state_container.filter_mut().pattern = pattern.clone();
5517 self.apply_filter(&pattern);
5518 }
5519 AppMode::FuzzyFilter => {
5520 let input_text = self.get_input_text();
5521 self.state_container.set_fuzzy_filter_pattern(input_text);
5522 self.apply_fuzzy_filter();
5523 }
5524 AppMode::Search => {
5525 let search_text = self.get_input_text();
5526 self.state_container.set_search_pattern(search_text);
5527 }
5529 AppMode::ColumnSearch => {
5530 let input_text = self.get_input_text();
5531 self.state_container.start_column_search(input_text);
5532 }
5534 _ => {}
5535 }
5536 }
5537 _ => {
5538 self.state_container
5539 .set_status_message("Paste not available in this mode".to_string());
5540 }
5541 }
5542 }
5543 Err(e) => {
5544 self.state_container
5545 .set_status_message(format!("Failed to paste: {e}"));
5546 }
5547 }
5548 }
5549
5550 fn export_to_json(&mut self) {
5551 let result = {
5553 let ctx = crate::ui::operations::data_export_operations::DataExportContext {
5554 data_provider: self.get_data_provider(),
5555 };
5556 crate::ui::operations::data_export_operations::export_to_json(&ctx)
5557 };
5558
5559 match result {
5560 crate::ui::operations::data_export_operations::ExportResult::Success(message) => {
5561 self.set_status_message(message);
5562 }
5563 crate::ui::operations::data_export_operations::ExportResult::Error(error) => {
5564 self.set_error_status("Export failed", error);
5565 }
5566 }
5567 }
5568
5569 fn get_horizontal_scroll_offset(&self) -> u16 {
5570 let (horizontal, _vertical) = self.cursor_manager.scroll_offsets();
5572 horizontal
5573 }
5574
5575 fn update_horizontal_scroll(&mut self, terminal_width: u16) {
5576 let inner_width = terminal_width.saturating_sub(3) as usize; let cursor_pos = self.get_input_cursor();
5578
5579 self.cursor_manager
5581 .update_horizontal_scroll(cursor_pos, terminal_width.saturating_sub(3));
5582
5583 let mut scroll = self.state_container.scroll_mut();
5585 if cursor_pos < scroll.input_scroll_offset as usize {
5586 scroll.input_scroll_offset = cursor_pos as u16;
5587 }
5588 else if cursor_pos >= scroll.input_scroll_offset as usize + inner_width {
5590 scroll.input_scroll_offset = (cursor_pos + 1).saturating_sub(inner_width) as u16;
5591 }
5592 }
5593
5594 fn get_cursor_token_position(&self) -> (usize, usize) {
5595 let ctx = crate::ui::operations::simple_operations::TextNavigationContext {
5596 query: &self.get_input_text(),
5597 cursor_pos: self.get_input_cursor(),
5598 };
5599 crate::ui::operations::simple_operations::get_cursor_token_position(&ctx)
5600 }
5601
5602 fn get_token_at_cursor(&self) -> Option<String> {
5603 let ctx = crate::ui::operations::simple_operations::TextNavigationContext {
5604 query: &self.get_input_text(),
5605 cursor_pos: self.get_input_cursor(),
5606 };
5607 crate::ui::operations::simple_operations::get_token_at_cursor(&ctx)
5608 }
5609
5610 #[allow(dead_code)]
5612 fn ui(&mut self, f: &mut Frame) {
5613 let input_height = INPUT_AREA_HEIGHT;
5615
5616 let buffer_count = self.state_container.buffers().all_buffers().len();
5618 let tab_bar_height = 2; let chunks = Layout::default()
5621 .direction(Direction::Vertical)
5622 .constraints(
5623 [
5624 Constraint::Length(tab_bar_height), Constraint::Length(input_height), Constraint::Min(0), Constraint::Length(STATUS_BAR_HEIGHT), ]
5629 .as_ref(),
5630 )
5631 .split(f.area());
5632
5633 if buffer_count > 0 {
5635 let buffer_names: Vec<String> = self
5636 .state_container
5637 .buffers()
5638 .all_buffers()
5639 .iter()
5640 .map(super::super::buffer::BufferAPI::get_name)
5641 .collect();
5642 let current_index = self.state_container.buffers().current_index();
5643
5644 let tab_widget = TabBarWidget::new(current_index, buffer_names);
5645 tab_widget.render(f, chunks[0]);
5646 }
5647
5648 let input_chunk_idx = 1;
5650 let results_chunk_idx = 2;
5651 let status_chunk_idx = 3;
5652
5653 self.update_horizontal_scroll(chunks[input_chunk_idx].width);
5655
5656 let input_text_for_count = self.get_input_text();
5659 let char_count = input_text_for_count.len();
5660 let cursor_pos = self.get_input_cursor();
5661 let char_count_display = if char_count > 0 {
5662 format!(" [{cursor_pos}/{char_count} chars]")
5663 } else {
5664 String::new()
5665 };
5666
5667 let scroll_offset = self.get_horizontal_scroll_offset();
5668 let scroll_indicator = if scroll_offset > 0 {
5669 " ◀ " } else {
5671 ""
5672 };
5673
5674 let input_title = match self.shadow_state.borrow().get_mode() {
5675 AppMode::Command => format!("SQL Query{char_count_display}{scroll_indicator}"),
5676 AppMode::Results => format!(
5677 "SQL Query (Results Mode - Press ↑ to edit){char_count_display}{scroll_indicator}"
5678 ),
5679 AppMode::Search => format!("Search Pattern{char_count_display}{scroll_indicator}"),
5680 AppMode::Filter => format!("Filter Pattern{char_count_display}{scroll_indicator}"),
5681 AppMode::FuzzyFilter => {
5682 format!("Fuzzy Filter{char_count_display}{scroll_indicator}")
5683 }
5684 AppMode::ColumnSearch => {
5685 format!("Column Search{char_count_display}{scroll_indicator}")
5686 }
5687 AppMode::Help => "Help".to_string(),
5688 AppMode::History => {
5689 let query = self.state_container.history_search().query.clone();
5690 format!("History Search: '{query}' (Esc to cancel)")
5691 }
5692 AppMode::Debug => "Parser Debug (F5)".to_string(),
5693 AppMode::PrettyQuery => "Pretty Query View (F6)".to_string(),
5694 AppMode::JumpToRow => format!("Jump to row: {}", self.get_jump_to_row_input()),
5695 AppMode::ColumnStats => "Column Statistics (S to close)".to_string(),
5696 };
5697
5698 let input_block = Block::default().borders(Borders::ALL).title(input_title);
5699
5700 let use_search_widget = matches!(
5702 self.shadow_state.borrow().get_mode(),
5703 AppMode::Search | AppMode::Filter | AppMode::FuzzyFilter | AppMode::ColumnSearch
5704 ) && self.search_modes_widget.is_active();
5705
5706 if use_search_widget {
5707 self.search_modes_widget.render(f, chunks[input_chunk_idx]);
5709 } else {
5710 let input_text_string = self.get_input_text();
5712
5713 trace!(target: "render", "Rendering input: text='{}', mode={:?}, cursor={}",
5715 if input_text_string.len() > 50 {
5716 format!("{}...", &input_text_string[..50])
5717 } else {
5718 input_text_string.clone()
5719 },
5720 self.shadow_state.borrow().get_mode(),
5721 self.get_input_cursor());
5722
5723 let history_query_string = if self.shadow_state.borrow().is_in_history_mode() {
5725 self.state_container.history_search().query.clone()
5726 } else {
5727 String::new()
5728 };
5729
5730 let input_text = match self.shadow_state.borrow().get_mode() {
5731 AppMode::History => &history_query_string,
5732 _ => &input_text_string,
5733 };
5734
5735 let input_paragraph = match self.shadow_state.borrow().get_mode() {
5736 AppMode::Command => {
5737 match self.state_container.get_edit_mode() {
5738 Some(EditMode::SingleLine) => {
5739 let highlighted_line =
5741 self.sql_highlighter.simple_sql_highlight(input_text);
5742 Paragraph::new(Text::from(vec![highlighted_line]))
5743 .block(input_block)
5744 .scroll((0, self.get_horizontal_scroll_offset()))
5745 }
5746 Some(EditMode::MultiLine) => {
5747 let highlighted_line =
5749 self.sql_highlighter.simple_sql_highlight(input_text);
5750 Paragraph::new(Text::from(vec![highlighted_line]))
5751 .block(input_block)
5752 .scroll((0, self.get_horizontal_scroll_offset()))
5753 }
5754 None => {
5755 let highlighted_line =
5757 self.sql_highlighter.simple_sql_highlight(input_text);
5758 Paragraph::new(Text::from(vec![highlighted_line]))
5759 .block(input_block)
5760 .scroll((0, self.get_horizontal_scroll_offset()))
5761 }
5762 }
5763 }
5764 _ => {
5765 Paragraph::new(input_text.as_str())
5767 .block(input_block)
5768 .style(match self.shadow_state.borrow().get_mode() {
5769 AppMode::Results => Style::default().fg(Color::DarkGray),
5770 AppMode::Search => Style::default().fg(Color::Yellow),
5771 AppMode::Filter => Style::default().fg(Color::Cyan),
5772 AppMode::FuzzyFilter => Style::default().fg(Color::Magenta),
5773 AppMode::ColumnSearch => Style::default().fg(Color::Green),
5774 AppMode::Help => Style::default().fg(Color::DarkGray),
5775 AppMode::History => Style::default().fg(Color::Magenta),
5776 AppMode::Debug => Style::default().fg(Color::Yellow),
5777 AppMode::PrettyQuery => Style::default().fg(Color::Green),
5778 AppMode::JumpToRow => Style::default().fg(Color::Magenta),
5779 AppMode::ColumnStats => Style::default().fg(Color::Cyan),
5780 _ => Style::default(),
5781 })
5782 .scroll((0, self.get_horizontal_scroll_offset()))
5783 }
5784 };
5785
5786 f.render_widget(input_paragraph, chunks[input_chunk_idx]);
5788 }
5789 let results_area = chunks[results_chunk_idx];
5790
5791 if !use_search_widget {
5793 match self.shadow_state.borrow().get_mode() {
5794 AppMode::Command => {
5795 let inner_width = chunks[input_chunk_idx].width.saturating_sub(2) as usize;
5798 let cursor_pos = self.get_visual_cursor().1; let scroll_offset = self.get_horizontal_scroll_offset() as usize;
5800
5801 if cursor_pos >= scroll_offset && cursor_pos < scroll_offset + inner_width {
5803 let visible_pos = cursor_pos - scroll_offset;
5804 f.set_cursor_position((
5805 chunks[input_chunk_idx].x + visible_pos as u16 + 1,
5806 chunks[input_chunk_idx].y + 1,
5807 ));
5808 }
5809 }
5810 AppMode::Search => {
5811 f.set_cursor_position((
5812 chunks[input_chunk_idx].x + self.get_input_cursor() as u16 + 1,
5813 chunks[input_chunk_idx].y + 1,
5814 ));
5815 }
5816 AppMode::Filter => {
5817 f.set_cursor_position((
5818 chunks[input_chunk_idx].x + self.get_input_cursor() as u16 + 1,
5819 chunks[input_chunk_idx].y + 1,
5820 ));
5821 }
5822 AppMode::FuzzyFilter => {
5823 f.set_cursor_position((
5824 chunks[input_chunk_idx].x + self.get_input_cursor() as u16 + 1,
5825 chunks[input_chunk_idx].y + 1,
5826 ));
5827 }
5828 AppMode::ColumnSearch => {
5829 f.set_cursor_position((
5830 chunks[input_chunk_idx].x + self.get_input_cursor() as u16 + 1,
5831 chunks[input_chunk_idx].y + 1,
5832 ));
5833 }
5834 AppMode::JumpToRow => {
5835 f.set_cursor_position((
5836 chunks[input_chunk_idx].x + self.get_jump_to_row_input().len() as u16 + 1,
5837 chunks[input_chunk_idx].y + 1,
5838 ));
5839 }
5840 AppMode::History => {
5841 let query_len = self.state_container.history_search().query.len();
5842 f.set_cursor_position((
5843 chunks[input_chunk_idx].x + query_len as u16 + 1,
5844 chunks[input_chunk_idx].y + 1,
5845 ));
5846 }
5847 _ => {}
5848 }
5849 }
5850
5851 let mode = self.shadow_state.borrow().get_mode();
5853 match mode {
5854 AppMode::Help => self.render_help(f, results_area),
5855 AppMode::History => self.render_history(f, results_area),
5856 AppMode::Debug => self.render_debug(f, results_area),
5857 AppMode::PrettyQuery => self.render_pretty_query(f, results_area),
5858 AppMode::ColumnStats => self.render_column_stats(f, results_area),
5859 _ if self.state_container.has_dataview() => {
5860 if let Some(provider) = self.get_data_provider() {
5863 self.render_table_with_provider(f, results_area, provider.as_ref());
5864 }
5865 }
5866 _ => {
5867 let placeholder = Paragraph::new("Enter SQL query and press Enter\n\nTip: Use Tab for completion, Ctrl+R for history")
5869 .block(Block::default().borders(Borders::ALL).title("Results"))
5870 .style(Style::default().fg(Color::DarkGray));
5871 f.render_widget(placeholder, results_area);
5872 }
5873 }
5874
5875 self.render_status_line(f, chunks[status_chunk_idx]);
5877 }
5879
5880 fn add_mode_styling(&self, spans: &mut Vec<Span>) -> (Style, Color) {
5882 let (status_style, mode_color) = match self.shadow_state.borrow().get_mode() {
5884 AppMode::Command => (Style::default().fg(Color::Green), Color::Green),
5885 AppMode::Results => (Style::default().fg(Color::Blue), Color::Blue),
5886 AppMode::Search => (Style::default().fg(Color::Yellow), Color::Yellow),
5887 AppMode::Filter => (Style::default().fg(Color::Cyan), Color::Cyan),
5888 AppMode::FuzzyFilter => (Style::default().fg(Color::Magenta), Color::Magenta),
5889 AppMode::ColumnSearch => (Style::default().fg(Color::Green), Color::Green),
5890 AppMode::Help => (Style::default().fg(Color::Magenta), Color::Magenta),
5891 AppMode::History => (Style::default().fg(Color::Magenta), Color::Magenta),
5892 AppMode::Debug => (Style::default().fg(Color::Yellow), Color::Yellow),
5893 AppMode::PrettyQuery => (Style::default().fg(Color::Green), Color::Green),
5894 AppMode::JumpToRow => (Style::default().fg(Color::Magenta), Color::Magenta),
5895 AppMode::ColumnStats => (Style::default().fg(Color::Cyan), Color::Cyan),
5896 };
5897
5898 let mode_indicator = match self.shadow_state.borrow().get_mode() {
5899 AppMode::Command => "CMD",
5900 AppMode::Results => "NAV",
5901 AppMode::Search => "SEARCH",
5902 AppMode::Filter => "FILTER",
5903 AppMode::FuzzyFilter => "FUZZY",
5904 AppMode::ColumnSearch => "COL",
5905 AppMode::Help => "HELP",
5906 AppMode::History => "HISTORY",
5907 AppMode::Debug => "DEBUG",
5908 AppMode::PrettyQuery => "PRETTY",
5909 AppMode::JumpToRow => "JUMP",
5910 AppMode::ColumnStats => "STATS",
5911 };
5912
5913 spans.push(Span::styled(
5915 format!("[{mode_indicator}]"),
5916 Style::default().fg(mode_color).add_modifier(Modifier::BOLD),
5917 ));
5918
5919 (status_style, mode_color)
5920 }
5921
5922 fn add_data_source_display(&self, spans: &mut Vec<Span>) {
5924 }
5927
5928 fn add_buffer_information(&self, spans: &mut Vec<Span>) {
5930 let index = self.state_container.buffers().current_index();
5931 let total = self.state_container.buffers().all_buffers().len();
5932
5933 if total > 1 {
5935 spans.push(Span::raw(" "));
5936 spans.push(Span::styled(
5937 format!("[{}/{}]", index + 1, total),
5938 Style::default().fg(Color::Yellow),
5939 ));
5940 }
5941
5942 if let Some(buffer) = self.state_container.buffers().current() {
5945 let query = buffer.get_input_text();
5946 if let Some(from_pos) = query.to_uppercase().find(" FROM ") {
5948 let after_from = &query[from_pos + 6..];
5949 if let Some(table_name) = after_from.split_whitespace().next() {
5951 let clean_name = table_name
5953 .trim_matches('"')
5954 .trim_matches('\'')
5955 .trim_matches('`');
5956
5957 spans.push(Span::raw(" "));
5958 spans.push(Span::styled(
5959 clean_name.to_string(),
5960 Style::default().fg(Color::Cyan),
5961 ));
5962 }
5963 }
5964 }
5965 }
5966
5967 fn add_mode_specific_info(&self, spans: &mut Vec<Span>, mode_color: Color, area: Rect) {
5969 match self.shadow_state.borrow().get_mode() {
5970 AppMode::Command => {
5971 if !self.get_input_text().trim().is_empty() {
5973 let (token_pos, total_tokens) = self.get_cursor_token_position();
5974 spans.push(Span::raw(" | "));
5975 spans.push(Span::styled(
5976 format!("Token {token_pos}/{total_tokens}"),
5977 Style::default().fg(Color::DarkGray),
5978 ));
5979
5980 if let Some(token) = self.get_token_at_cursor() {
5982 spans.push(Span::raw(" "));
5983 spans.push(Span::styled(
5984 format!("[{token}]"),
5985 Style::default().fg(Color::Cyan),
5986 ));
5987 }
5988
5989 if let Some(error_msg) = self.check_parser_error(&self.get_input_text()) {
5991 spans.push(Span::raw(" | "));
5992 spans.push(Span::styled(
5993 format!("{} {}", self.config.display.icons.warning, error_msg),
5994 Style::default().fg(Color::Red).add_modifier(Modifier::BOLD),
5995 ));
5996 }
5997 }
5998 }
5999 AppMode::Results => {
6000 self.add_results_mode_info(spans, area);
6002 }
6003 AppMode::Search | AppMode::Filter | AppMode::FuzzyFilter | AppMode::ColumnSearch => {
6004 let pattern = self.get_input_text();
6006 if !pattern.is_empty() {
6007 spans.push(Span::raw(" | Pattern: "));
6008 spans.push(Span::styled(pattern, Style::default().fg(mode_color)));
6009 }
6010 }
6011 _ => {}
6012 }
6013 }
6014
6015 fn add_results_mode_info(&self, spans: &mut Vec<Span>, area: Rect) {
6017 let total_rows = self.get_row_count();
6018 if total_rows > 0 {
6019 let selected = self.state_container.navigation().selected_row + 1;
6021 spans.push(Span::raw(" | "));
6022
6023 let selection_mode = self.get_selection_mode();
6025 let mode_text = match selection_mode {
6026 SelectionMode::Cell => "CELL",
6027 SelectionMode::Row => "ROW",
6028 SelectionMode::Column => "COL",
6029 };
6030 spans.push(Span::styled(
6031 format!("[{mode_text}]"),
6032 Style::default()
6033 .fg(Color::Cyan)
6034 .add_modifier(Modifier::BOLD),
6035 ));
6036
6037 spans.push(Span::raw(" "));
6038 spans.push(Span::styled(
6039 format!("Row {selected}/{total_rows}"),
6040 Style::default().fg(Color::White),
6041 ));
6042
6043 let visual_col_display =
6046 if let Some(ref viewport_manager) = *self.viewport_manager.borrow() {
6047 viewport_manager.get_crosshair_col() + 1
6048 } else {
6049 1
6050 };
6051 spans.push(Span::raw(" "));
6052 spans.push(Span::styled(
6053 format!("({visual_col_display},{selected})"),
6054 Style::default().fg(Color::DarkGray),
6055 ));
6056
6057 if let Some(ref mut viewport_manager) = *self.viewport_manager.borrow_mut() {
6059 let available_width = area.width.saturating_sub(TABLE_BORDER_WIDTH);
6060 let visual_col = viewport_manager.get_crosshair_col();
6062 if let Some(x_pos) =
6063 viewport_manager.get_column_x_position(visual_col, available_width)
6064 {
6065 let terminal_x = x_pos + 2;
6067 let terminal_y = (selected as u16)
6068 .saturating_sub(self.state_container.get_scroll_offset().0 as u16)
6069 + 3;
6070 spans.push(Span::raw(" "));
6071 spans.push(Span::styled(
6072 format!("[{terminal_x}x{terminal_y}]"),
6073 Style::default().fg(Color::DarkGray),
6074 ));
6075 }
6076 }
6077
6078 if let Some(dataview) = self.state_container.get_buffer_dataview() {
6080 let headers = dataview.column_names();
6081
6082 let (visual_row, visual_col) =
6085 if let Some(ref viewport_manager) = *self.viewport_manager.borrow() {
6086 (
6087 viewport_manager.get_crosshair_row(),
6088 viewport_manager.get_crosshair_col(),
6089 )
6090 } else {
6091 (0, 0)
6092 };
6093
6094 if visual_col < headers.len() {
6096 spans.push(Span::raw(" | Col: "));
6097 spans.push(Span::styled(
6098 headers[visual_col].clone(),
6099 Style::default().fg(Color::Cyan),
6100 ));
6101
6102 let viewport_info =
6104 if let Some(ref viewport_manager) = *self.viewport_manager.borrow() {
6105 let viewport_rows = viewport_manager.get_viewport_rows();
6106 let viewport_height = viewport_rows.end - viewport_rows.start;
6107 format!("[V:{visual_row},{visual_col} @ {viewport_height}r]")
6108 } else {
6109 format!("[V:{visual_row},{visual_col}]")
6110 };
6111 spans.push(Span::raw(" "));
6112 spans.push(Span::styled(
6113 viewport_info,
6114 Style::default().fg(Color::Magenta),
6115 ));
6116 }
6117 }
6118 }
6119 }
6120
6121 fn render_status_line(&self, f: &mut Frame, area: Rect) {
6122 let mut spans = Vec::new();
6123
6124 let (status_style, mode_color) = self.add_mode_styling(&mut spans);
6126
6127 self.add_data_source_display(&mut spans);
6129
6130 self.add_buffer_information(&mut spans);
6132
6133 self.add_mode_specific_info(&mut spans, mode_color, area);
6135
6136 self.add_query_source_indicator(&mut spans);
6138
6139 self.add_case_sensitivity_indicator(&mut spans);
6141
6142 self.add_column_packing_indicator(&mut spans);
6144
6145 self.add_status_message(&mut spans);
6147
6148 let help_text = self.get_help_text_for_mode();
6150
6151 self.add_global_indicators(&mut spans);
6152
6153 self.add_shadow_state_display(&mut spans);
6155
6156 self.add_help_text_display(&mut spans, help_text, area);
6157
6158 let status_line = Line::from(spans);
6159 let status = Paragraph::new(status_line)
6160 .block(Block::default().borders(Borders::ALL))
6161 .style(status_style);
6162 f.render_widget(status, area);
6163 }
6164
6165 fn build_table_context(
6168 &self,
6169 area: Rect,
6170 provider: &dyn DataProvider,
6171 ) -> crate::ui::rendering::table_render_context::TableRenderContext {
6172 use crate::ui::rendering::table_render_context::TableRenderContextBuilder;
6173
6174 let row_count = provider.get_row_count();
6175 let available_width = area.width.saturating_sub(TABLE_BORDER_WIDTH);
6176 let available_height = area.height;
6179
6180 let headers = {
6182 let viewport_manager = self.viewport_manager.borrow();
6183 let viewport_manager = viewport_manager
6184 .as_ref()
6185 .expect("ViewportManager must exist");
6186 viewport_manager.get_column_names_ordered()
6187 };
6188
6189 {
6191 let mut viewport_opt = self.viewport_manager.borrow_mut();
6192 if let Some(ref mut viewport_manager) = *viewport_opt {
6193 let data_rows = Self::calculate_table_data_rows(available_height);
6195 viewport_manager.update_terminal_size(available_width, data_rows);
6196 let _ = viewport_manager.get_column_widths(); }
6198 }
6199
6200 let (pinned_visual_positions, crosshair_column_position, _) = {
6202 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
6203 let viewport_manager = viewport_manager_borrow
6204 .as_mut()
6205 .expect("ViewportManager must exist for rendering");
6206 let info = viewport_manager.get_visible_columns_info(available_width);
6207
6208 let visible_indices = &info.0;
6212 let pinned_source_indices = &info.1;
6213
6214 let mut pinned_visual_positions = Vec::new();
6217 for &source_idx in pinned_source_indices {
6218 if let Some(visual_pos) = visible_indices.iter().position(|&x| x == source_idx) {
6219 pinned_visual_positions.push(visual_pos);
6220 }
6221 }
6222
6223 let crosshair_column_position =
6227 if let Some((_, col_pos)) = viewport_manager.get_crosshair_viewport_position() {
6228 col_pos
6229 } else {
6230 0
6232 };
6233
6234 let crosshair_visual = viewport_manager.get_crosshair_col();
6235
6236 (
6237 pinned_visual_positions,
6238 crosshair_column_position,
6239 crosshair_visual,
6240 )
6241 };
6242
6243 let row_viewport_start = self
6245 .state_container
6246 .navigation()
6247 .scroll_offset
6248 .0
6249 .min(row_count.saturating_sub(1));
6250 let row_viewport_end = (row_viewport_start + available_height as usize).min(row_count);
6251 let visible_row_indices: Vec<usize> = (row_viewport_start..row_viewport_end).collect();
6252
6253 let (column_headers, data_to_display, column_widths_visual) = {
6255 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
6256 if let Some(ref mut viewport_manager) = *viewport_manager_borrow {
6257 viewport_manager.get_visual_display(available_width, &visible_row_indices)
6258 } else {
6259 let visible_rows = provider
6261 .get_visible_rows(row_viewport_start, row_viewport_end - row_viewport_start);
6262 let widths = vec![15u16; headers.len()];
6263 (headers.clone(), visible_rows, widths)
6264 }
6265 };
6266
6267 let sort_state = self
6269 .buffer()
6270 .get_dataview()
6271 .map(|dv| dv.get_sort_state().clone());
6272
6273 let fuzzy_filter_pattern = if self.state_container.is_fuzzy_filter_active() {
6275 let pattern = self.state_container.get_fuzzy_filter_pattern();
6276 if pattern.is_empty() {
6277 None
6278 } else {
6279 Some(pattern)
6280 }
6281 } else {
6282 None
6283 };
6284
6285 let selected_row = self.state_container.navigation().selected_row;
6287 let selected_col = crosshair_column_position;
6288
6289 trace!(target: "search", "Building TableRenderContext: selected_row={}, selected_col={}, mode={:?}",
6291 selected_row, selected_col, self.state_container.get_selection_mode());
6292
6293 TableRenderContextBuilder::new()
6294 .row_count(row_count)
6295 .visible_rows(visible_row_indices.clone(), data_to_display)
6296 .columns(column_headers, column_widths_visual)
6297 .pinned_columns(pinned_visual_positions)
6298 .selection(
6299 selected_row,
6300 selected_col,
6301 self.state_container.get_selection_mode(),
6302 )
6303 .row_viewport(row_viewport_start..row_viewport_end)
6304 .sort_state(sort_state)
6305 .display_options(
6306 self.state_container.is_show_row_numbers(),
6307 self.shadow_state.borrow().get_mode(),
6308 )
6309 .filter(
6310 fuzzy_filter_pattern,
6311 self.state_container.is_case_insensitive(),
6312 )
6313 .dimensions(available_width, available_height)
6314 .build()
6315 }
6316
6317 fn render_table_with_provider(&self, f: &mut Frame, area: Rect, provider: &dyn DataProvider) {
6320 let context = self.build_table_context(area, provider);
6322
6323 crate::ui::rendering::table_renderer::render_table(f, area, &context);
6325 }
6326
6327 fn render_help(&mut self, f: &mut Frame, area: Rect) {
6328 self.render_help_two_column(f, area);
6330 }
6331
6332 fn render_help_two_column(&self, f: &mut Frame, area: Rect) {
6333 let chunks = Layout::default()
6335 .direction(Direction::Horizontal)
6336 .constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
6337 .split(area);
6338
6339 let left_content = HelpText::left_column();
6341 let right_content = HelpText::right_column();
6342
6343 let visible_height = area.height.saturating_sub(2) as usize; let left_total_lines = left_content.len();
6346 let right_total_lines = right_content.len();
6347 let max_lines = left_total_lines.max(right_total_lines);
6348
6349 let scroll_offset = { self.state_container.help_scroll_offset() as usize };
6351
6352 let left_visible: Vec<Line> = left_content
6354 .into_iter()
6355 .skip(scroll_offset)
6356 .take(visible_height)
6357 .collect();
6358
6359 let right_visible: Vec<Line> = right_content
6360 .into_iter()
6361 .skip(scroll_offset)
6362 .take(visible_height)
6363 .collect();
6364
6365 let scroll_indicator = if max_lines > visible_height {
6367 format!(
6368 " (↓/↑ to scroll, {}/{})",
6369 scroll_offset + 1,
6370 max_lines.saturating_sub(visible_height) + 1
6371 )
6372 } else {
6373 String::new()
6374 };
6375
6376 let left_paragraph = Paragraph::new(Text::from(left_visible))
6378 .block(
6379 Block::default()
6380 .borders(Borders::ALL)
6381 .title(format!("Help - Commands{scroll_indicator}")),
6382 )
6383 .style(Style::default());
6384
6385 let right_paragraph = Paragraph::new(Text::from(right_visible))
6387 .block(
6388 Block::default()
6389 .borders(Borders::ALL)
6390 .title("Help - Navigation & Features"),
6391 )
6392 .style(Style::default());
6393
6394 f.render_widget(left_paragraph, chunks[0]);
6395 f.render_widget(right_paragraph, chunks[1]);
6396 }
6397
6398 fn render_debug(&self, f: &mut Frame, area: Rect) {
6399 <Self as DebugContext>::render_debug(self, f, area);
6400 }
6401
6402 fn render_pretty_query(&self, f: &mut Frame, area: Rect) {
6403 <Self as DebugContext>::render_pretty_query(self, f, area);
6404 }
6405
6406 fn render_history(&self, f: &mut Frame, area: Rect) {
6407 let history_search = self.state_container.history_search();
6409 let matches_empty = history_search.matches.is_empty();
6410 let search_query_empty = history_search.query.is_empty();
6411
6412 if matches_empty {
6413 let no_history = if search_query_empty {
6414 "No command history found.\nExecute some queries to build history."
6415 } else {
6416 "No matches found for your search.\nTry a different search term."
6417 };
6418
6419 let placeholder = Paragraph::new(no_history)
6420 .block(
6421 Block::default()
6422 .borders(Borders::ALL)
6423 .title("Command History"),
6424 )
6425 .style(Style::default().fg(Color::DarkGray));
6426 f.render_widget(placeholder, area);
6427 return;
6428 }
6429
6430 let chunks = Layout::default()
6432 .direction(Direction::Vertical)
6433 .constraints([
6434 Constraint::Percentage(50), Constraint::Percentage(50), ])
6437 .split(area);
6438
6439 self.render_history_list(f, chunks[0]);
6440 self.render_selected_command_preview(f, chunks[1]);
6441 }
6442
6443 fn render_history_list(&self, f: &mut Frame, area: Rect) {
6444 let history_search = self.state_container.history_search();
6446 let matches = history_search.matches.clone();
6447 let selected_index = history_search.selected_index;
6448 let match_count = matches.len();
6449
6450 let history_items: Vec<Line> = matches
6452 .iter()
6453 .enumerate()
6454 .map(|(i, history_match)| {
6455 let entry = &history_match.entry;
6456 let is_selected = i == selected_index;
6457
6458 let success_indicator = if entry.success { "✓" } else { "✗" };
6459 let time_ago = {
6460 let elapsed = chrono::Utc::now() - entry.timestamp;
6461 if elapsed.num_days() > 0 {
6462 format!("{}d", elapsed.num_days())
6463 } else if elapsed.num_hours() > 0 {
6464 format!("{}h", elapsed.num_hours())
6465 } else if elapsed.num_minutes() > 0 {
6466 format!("{}m", elapsed.num_minutes())
6467 } else {
6468 "now".to_string()
6469 }
6470 };
6471
6472 let terminal_width = area.width as usize;
6474 let metadata_space = 15; let available_for_command = terminal_width.saturating_sub(metadata_space).max(50);
6476
6477 let command_text = if entry.command.len() > available_for_command {
6478 format!(
6479 "{}…",
6480 &entry.command[..available_for_command.saturating_sub(1)]
6481 )
6482 } else {
6483 entry.command.clone()
6484 };
6485
6486 let line_text = format!(
6487 "{} {} {} {}x {}",
6488 if is_selected { "►" } else { " " },
6489 command_text,
6490 success_indicator,
6491 entry.execution_count,
6492 time_ago
6493 );
6494
6495 let mut style = Style::default();
6496 if is_selected {
6497 style = style.bg(Color::DarkGray).add_modifier(Modifier::BOLD);
6498 }
6499 if !entry.success {
6500 style = style.fg(Color::Red);
6501 }
6502
6503 if !history_match.indices.is_empty() && is_selected {
6505 style = style.fg(Color::Yellow);
6506 }
6507
6508 Line::from(line_text).style(style)
6509 })
6510 .collect();
6511
6512 let history_paragraph = Paragraph::new(history_items)
6513 .block(Block::default().borders(Borders::ALL).title(format!(
6514 "History ({match_count} matches) - j/k to navigate, Enter to select"
6515 )))
6516 .wrap(ratatui::widgets::Wrap { trim: false });
6517
6518 f.render_widget(history_paragraph, area);
6519 }
6520
6521 fn render_selected_command_preview(&self, f: &mut Frame, area: Rect) {
6522 let history_search = self.state_container.history_search();
6524 let selected_match = history_search
6525 .matches
6526 .get(history_search.selected_index)
6527 .cloned();
6528
6529 if let Some(selected_match) = selected_match {
6530 let entry = &selected_match.entry;
6531
6532 use crate::recursive_parser::format_sql_pretty_compact;
6534
6535 let available_width = area.width.saturating_sub(6) as usize; let avg_col_width = 15; let cols_per_line = (available_width / avg_col_width).max(3).min(12); let mut pretty_lines = format_sql_pretty_compact(&entry.command, cols_per_line);
6541
6542 let max_lines = area.height.saturating_sub(2) as usize; if pretty_lines.len() > max_lines && cols_per_line < 12 {
6545 pretty_lines = format_sql_pretty_compact(&entry.command, 15);
6547 }
6548
6549 let mut highlighted_lines = Vec::new();
6551 for line in pretty_lines {
6552 highlighted_lines.push(self.sql_highlighter.simple_sql_highlight(&line));
6553 }
6554
6555 let preview_text = Text::from(highlighted_lines);
6556
6557 let duration_text = entry
6558 .duration_ms
6559 .map_or_else(|| "?ms".to_string(), |d| format!("{d}ms"));
6560
6561 let success_text = if entry.success {
6562 "✓ Success"
6563 } else {
6564 "✗ Failed"
6565 };
6566
6567 let preview = Paragraph::new(preview_text)
6568 .block(Block::default().borders(Borders::ALL).title(format!(
6569 "Pretty SQL Preview: {} | {} | Used {}x",
6570 success_text, duration_text, entry.execution_count
6571 )))
6572 .scroll((0, 0)); f.render_widget(preview, area);
6575 } else {
6576 let empty_preview = Paragraph::new("No command selected")
6577 .block(Block::default().borders(Borders::ALL).title("Preview"))
6578 .style(Style::default().fg(Color::DarkGray));
6579 f.render_widget(empty_preview, area);
6580 }
6581 }
6582
6583 fn handle_column_stats_input(&mut self, key: crossterm::event::KeyEvent) -> Result<bool> {
6584 let mut ctx = crate::ui::input::input_handlers::StatsInputContext {
6586 buffer_manager: self.state_container.buffers_mut(),
6587 stats_widget: &mut self.stats_widget,
6588 shadow_state: &self.shadow_state,
6589 };
6590
6591 let result = crate::ui::input::input_handlers::handle_column_stats_input(&mut ctx, key)?;
6592
6593 if self.shadow_state.borrow().get_mode() == AppMode::Results {
6595 self.shadow_state
6596 .borrow_mut()
6597 .observe_mode_change(AppMode::Results, "column_stats_closed");
6598 }
6599
6600 Ok(result)
6601 }
6602
6603 fn handle_jump_to_row_input(&mut self, key: crossterm::event::KeyEvent) -> Result<bool> {
6604 match key.code {
6605 KeyCode::Enter => {
6606 let input = self.get_jump_to_row_input();
6608 self.complete_jump_to_row(&input);
6609 }
6610 _ => {
6611 self.process_jump_to_row_key(key);
6613 }
6614 }
6615 Ok(false)
6616 }
6617
6618 fn render_column_stats(&self, f: &mut Frame, area: Rect) {
6619 self.stats_widget.render(
6621 f,
6622 area,
6623 self.state_container
6624 .current_buffer()
6625 .expect("Buffer should exist"),
6626 );
6627 }
6628
6629 fn handle_execute_query(&mut self) -> Result<bool> {
6635 use crate::ui::state::state_coordinator::StateCoordinator;
6636
6637 let query = self.get_input_text().trim().to_string();
6638 debug!(target: "action", "Executing query: {}", query);
6639
6640 let should_exit = StateCoordinator::handle_execute_query_with_refs(
6642 &mut self.state_container,
6643 &self.shadow_state,
6644 &query,
6645 )?;
6646
6647 if should_exit {
6648 return Ok(true);
6649 }
6650
6651 if !query.is_empty() && !query.starts_with(':') {
6653 if let Err(e) = self.execute_query_v2(&query) {
6654 self.state_container
6655 .set_status_message(format!("Error executing query: {e}"));
6656 }
6657 }
6659
6660 Ok(false) }
6662
6663 fn handle_buffer_action(&mut self, action: BufferAction) -> Result<bool> {
6664 match action {
6665 BufferAction::NextBuffer => {
6666 let message = self
6667 .buffer_handler
6668 .next_buffer(self.state_container.buffers_mut());
6669 debug!("{}", message);
6670 self.sync_after_buffer_switch();
6672 Ok(false)
6673 }
6674 BufferAction::PreviousBuffer => {
6675 let message = self
6676 .buffer_handler
6677 .previous_buffer(self.state_container.buffers_mut());
6678 debug!("{}", message);
6679 self.sync_after_buffer_switch();
6681 Ok(false)
6682 }
6683 BufferAction::QuickSwitch => {
6684 let message = self
6685 .buffer_handler
6686 .quick_switch(self.state_container.buffers_mut());
6687 debug!("{}", message);
6688 self.sync_after_buffer_switch();
6690 Ok(false)
6691 }
6692 BufferAction::NewBuffer => {
6693 let message = self
6694 .buffer_handler
6695 .new_buffer(self.state_container.buffers_mut(), &self.config);
6696 debug!("{}", message);
6697 Ok(false)
6698 }
6699 BufferAction::CloseBuffer => {
6700 let (success, message) = self
6701 .buffer_handler
6702 .close_buffer(self.state_container.buffers_mut());
6703 debug!("{}", message);
6704 Ok(!success) }
6706 BufferAction::ListBuffers => {
6707 let buffer_list = self
6708 .buffer_handler
6709 .list_buffers(self.state_container.buffers());
6710 for line in &buffer_list {
6712 debug!("{}", line);
6713 }
6714 Ok(false)
6715 }
6716 BufferAction::SwitchToBuffer(buffer_index) => {
6717 let message = self
6718 .buffer_handler
6719 .switch_to_buffer(self.state_container.buffers_mut(), buffer_index);
6720 debug!("{}", message);
6721
6722 self.sync_after_buffer_switch();
6724
6725 Ok(false)
6726 }
6727 }
6728 }
6729
6730 fn handle_expand_asterisk(&mut self) -> Result<bool> {
6731 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
6732 if buffer.expand_asterisk(&self.hybrid_parser) {
6733 if buffer.get_edit_mode() == EditMode::SingleLine {
6735 let text = buffer.get_input_text();
6736 let cursor = buffer.get_input_cursor_position();
6737 self.set_input_text_with_cursor(text, cursor);
6738 }
6739 }
6740 }
6741 Ok(false)
6742 }
6743
6744 pub(crate) fn toggle_debug_mode(&mut self) {
6745 DebugContext::toggle_debug_mode(self);
6747 }
6748
6749 pub(crate) fn debug_generate_parser_info(&self, query: &str) -> String {
6754 self.hybrid_parser
6755 .get_detailed_debug_info(query, query.len())
6756 }
6757
6758 fn debug_generate_navigation_state(&self) -> String {
6759 let mut debug_info = String::new();
6760 debug_info.push_str("\n========== NAVIGATION DEBUG ==========\n");
6761 let current_column = self.state_container.get_current_column();
6762 let scroll_offset = self.state_container.get_scroll_offset();
6763 let nav_state = self.state_container.navigation();
6764
6765 debug_info.push_str(&format!("Buffer Column Position: {current_column}\n"));
6766 debug_info.push_str(&format!(
6767 "Buffer Scroll Offset: row={}, col={}\n",
6768 scroll_offset.0, scroll_offset.1
6769 ));
6770 debug_info.push_str(&format!(
6771 "NavigationState Column: {}\n",
6772 nav_state.selected_column
6773 ));
6774 debug_info.push_str(&format!(
6775 "NavigationState Row: {:?}\n",
6776 nav_state.selected_row
6777 ));
6778 debug_info.push_str(&format!(
6779 "NavigationState Scroll Offset: row={}, col={}\n",
6780 nav_state.scroll_offset.0, nav_state.scroll_offset.1
6781 ));
6782
6783 if current_column != nav_state.selected_column {
6785 debug_info.push_str(&format!(
6786 "⚠️ WARNING: Column mismatch! Buffer={}, Nav={}\n",
6787 current_column, nav_state.selected_column
6788 ));
6789 }
6790 if scroll_offset.1 != nav_state.scroll_offset.1 {
6791 debug_info.push_str(&format!(
6792 "⚠️ WARNING: Scroll column mismatch! Buffer={}, Nav={}\n",
6793 scroll_offset.1, nav_state.scroll_offset.1
6794 ));
6795 }
6796
6797 debug_info.push_str("\n--- Navigation Flow ---\n");
6798 debug_info.push_str(
6799 "(Enable RUST_LOG=sql_cli::ui::viewport_manager=debug,navigation=debug to see flow)\n",
6800 );
6801
6802 if let Some(dataview) = self.state_container.get_buffer_dataview() {
6804 let pinned_count = dataview.get_pinned_columns().len();
6805 let pinned_names = dataview.get_pinned_column_names();
6806 debug_info.push_str(&format!("Pinned Column Count: {pinned_count}\n"));
6807 if !pinned_names.is_empty() {
6808 debug_info.push_str(&format!("Pinned Column Names: {pinned_names:?}\n"));
6809 }
6810 debug_info.push_str(&format!("First Scrollable Column: {pinned_count}\n"));
6811
6812 if current_column < pinned_count {
6814 debug_info.push_str(&format!(
6815 "Current Position: PINNED area (column {current_column})\n"
6816 ));
6817 } else {
6818 debug_info.push_str(&format!(
6819 "Current Position: SCROLLABLE area (column {}, scrollable index {})\n",
6820 current_column,
6821 current_column - pinned_count
6822 ));
6823 }
6824
6825 let display_columns = dataview.get_display_columns();
6827 debug_info.push_str("\n--- COLUMN ORDERING ---\n");
6828 debug_info.push_str(&format!(
6829 "Display column order (first 10): {:?}\n",
6830 &display_columns[..display_columns.len().min(10)]
6831 ));
6832 if display_columns.len() > 10 {
6833 debug_info.push_str(&format!(
6834 "... and {} more columns\n",
6835 display_columns.len() - 10
6836 ));
6837 }
6838
6839 if let Some(display_idx) = display_columns
6841 .iter()
6842 .position(|&idx| idx == current_column)
6843 {
6844 debug_info.push_str(&format!(
6845 "Current column {} is at display index {}/{}\n",
6846 current_column,
6847 display_idx,
6848 display_columns.len()
6849 ));
6850
6851 if display_idx + 1 < display_columns.len() {
6853 let next_col = display_columns[display_idx + 1];
6854 debug_info.push_str(&format!(
6855 "Next 'l' press should move to column {} (display index {})\n",
6856 next_col,
6857 display_idx + 1
6858 ));
6859 } else {
6860 debug_info.push_str("Next 'l' press should wrap to first column\n");
6861 }
6862 } else {
6863 debug_info.push_str(&format!(
6864 "WARNING: Current column {current_column} not found in display order!\n"
6865 ));
6866 }
6867 }
6868 debug_info.push_str("==========================================\n");
6869 debug_info
6870 }
6871
6872 fn debug_generate_column_search_state(&self) -> String {
6873 let mut debug_info = String::new();
6874 let show_column_search = self.shadow_state.borrow().get_mode() == AppMode::ColumnSearch
6875 || !self.state_container.column_search().pattern.is_empty();
6876 if show_column_search {
6877 let column_search = self.state_container.column_search();
6878 debug_info.push_str("\n========== COLUMN SEARCH STATE ==========\n");
6879 debug_info.push_str(&format!("Pattern: '{}'\n", column_search.pattern));
6880 debug_info.push_str(&format!(
6881 "Matching Columns: {} found\n",
6882 column_search.matching_columns.len()
6883 ));
6884 if !column_search.matching_columns.is_empty() {
6885 debug_info.push_str("Matches:\n");
6886 for (idx, (col_idx, col_name)) in column_search.matching_columns.iter().enumerate()
6887 {
6888 let marker = if idx == column_search.current_match {
6889 " <--"
6890 } else {
6891 ""
6892 };
6893 debug_info
6894 .push_str(&format!(" [{idx}] {col_name} (index {col_idx}){marker}\n"));
6895 }
6896 }
6897 debug_info.push_str(&format!(
6898 "Current Match Index: {}\n",
6899 column_search.current_match
6900 ));
6901 debug_info.push_str(&format!(
6902 "Current Column: {}\n",
6903 self.state_container.get_current_column()
6904 ));
6905 debug_info.push_str("==========================================\n");
6906 }
6907 debug_info
6908 }
6909
6910 pub(crate) fn debug_generate_trace_logs(&self) -> String {
6911 let mut debug_info = String::from("\n========== TRACE LOGS ==========\n");
6912 debug_info.push_str("(Most recent at bottom, last 100 entries)\n");
6913
6914 if let Some(ref log_buffer) = self.log_buffer {
6915 let recent_logs = log_buffer.get_recent(100);
6916 for entry in recent_logs {
6917 debug_info.push_str(&entry.format_for_display());
6918 debug_info.push('\n');
6919 }
6920 debug_info.push_str(&format!("Total log entries: {}\n", log_buffer.len()));
6921 } else {
6922 debug_info.push_str("Log buffer not initialized\n");
6923 }
6924 debug_info.push_str("================================\n");
6925
6926 debug_info
6927 }
6928
6929 pub(crate) fn debug_generate_state_logs(&self) -> String {
6931 let mut debug_info = String::new();
6932
6933 if let Some(ref debug_service) = self.debug_service {
6934 debug_info.push_str("\n========== STATE CHANGE LOGS ==========\n");
6935 debug_info.push_str("(Most recent at bottom, from DebugService)\n");
6936 let debug_entries = debug_service.get_entries();
6937 let recent = debug_entries.iter().rev().take(50).rev();
6938 for entry in recent {
6939 debug_info.push_str(&format!(
6940 "[{}] {:?} [{}]: {}\n",
6941 entry.timestamp, entry.level, entry.component, entry.message
6942 ));
6943 }
6944 debug_info.push_str(&format!(
6945 "Total state change entries: {}\n",
6946 debug_entries.len()
6947 ));
6948 debug_info.push_str("================================\n");
6949 } else {
6950 debug_info.push_str("\n========== STATE CHANGE LOGS ==========\n");
6951 debug_info.push_str("DebugService not available (service_container is None)\n");
6952 debug_info.push_str("================================\n");
6953 }
6954
6955 debug_info
6956 }
6957
6958 pub(crate) fn debug_extract_timing(&self, s: &str) -> Option<f64> {
6960 crate::ui::rendering::ui_layout_utils::extract_timing_from_debug_string(s)
6961 }
6962
6963 fn show_pretty_query(&mut self) {
6964 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
6965 self.shadow_state.borrow_mut().set_mode(
6966 AppMode::PrettyQuery,
6967 buffer,
6968 "pretty_query_show",
6969 );
6970 let query = buffer.get_input_text();
6971 self.debug_widget.generate_pretty_sql(&query);
6972 }
6973 }
6974
6975 fn add_global_indicators(&self, spans: &mut Vec<Span>) {
6977 if self.key_sequence_renderer.has_content() {
6978 let key_display = self.key_sequence_renderer.get_display();
6979 if !key_display.is_empty() {
6980 spans.push(Span::raw(" | Keys: "));
6981 spans.push(Span::styled(
6982 key_display,
6983 Style::default()
6984 .fg(Color::Cyan)
6985 .add_modifier(Modifier::ITALIC),
6986 ));
6987 }
6988 }
6989 }
6990
6991 fn add_query_source_indicator(&self, spans: &mut Vec<Span>) {
6992 if let Some(source) = self.state_container.get_last_query_source() {
6993 spans.push(Span::raw(" | "));
6994 let (icon, label, color) = match source.as_str() {
6995 "cache" => (
6996 &self.config.display.icons.cache,
6997 "CACHE".to_string(),
6998 Color::Cyan,
6999 ),
7000 "file" | "FileDataSource" => (
7001 &self.config.display.icons.file,
7002 "FILE".to_string(),
7003 Color::Green,
7004 ),
7005 "SqlServerDataSource" => (
7006 &self.config.display.icons.database,
7007 "SQL".to_string(),
7008 Color::Blue,
7009 ),
7010 "PublicApiDataSource" => (
7011 &self.config.display.icons.api,
7012 "API".to_string(),
7013 Color::Yellow,
7014 ),
7015 _ => (
7016 &self.config.display.icons.api,
7017 source.clone(),
7018 Color::Magenta,
7019 ),
7020 };
7021 spans.push(Span::raw(format!("{icon} ")));
7022 spans.push(Span::styled(label, Style::default().fg(color)));
7023 }
7024 }
7025
7026 fn add_case_sensitivity_indicator(&self, spans: &mut Vec<Span>) {
7027 let case_insensitive = self.state_container.is_case_insensitive();
7028 if case_insensitive {
7029 spans.push(Span::raw(" | "));
7030 let icon = self.config.display.icons.case_insensitive.clone();
7031 spans.push(Span::styled(
7032 format!("{icon} CASE"),
7033 Style::default().fg(Color::Cyan),
7034 ));
7035 }
7036 }
7037
7038 fn add_column_packing_indicator(&self, spans: &mut Vec<Span>) {
7039 if let Some(ref viewport_manager) = *self.viewport_manager.borrow() {
7040 let packing_mode = viewport_manager.get_packing_mode();
7041 spans.push(Span::raw(" | "));
7042 let (text, color) = match packing_mode {
7043 ColumnPackingMode::DataFocus => ("DATA", Color::Cyan),
7044 ColumnPackingMode::HeaderFocus => ("HEADER", Color::Yellow),
7045 ColumnPackingMode::Balanced => ("BALANCED", Color::Green),
7046 };
7047 spans.push(Span::styled(text, Style::default().fg(color)));
7048 }
7049 }
7050
7051 fn add_status_message(&self, spans: &mut Vec<Span>) {
7052 let status_msg = self.state_container.get_buffer_status_message();
7053 if !status_msg.is_empty() {
7054 spans.push(Span::raw(" | "));
7055 spans.push(Span::styled(
7056 status_msg,
7057 Style::default()
7058 .fg(Color::Yellow)
7059 .add_modifier(Modifier::BOLD),
7060 ));
7061 }
7062 }
7063
7064 fn get_help_text_for_mode(&self) -> &str {
7065 match self.shadow_state.borrow().get_mode() {
7066 AppMode::Command => "Enter:Run | Tab:Complete | ↓:Results | F1:Help",
7067 AppMode::Results => match self.get_selection_mode() {
7068 SelectionMode::Cell => "v:Row mode | y:Yank cell | ↑:Edit | F1:Help",
7069 SelectionMode::Row => "v:Cell mode | y:Yank | f:Filter | ↑:Edit | F1:Help",
7070 SelectionMode::Column => "v:Cell mode | y:Yank col | ↑:Edit | F1:Help",
7071 },
7072 AppMode::Search | AppMode::Filter | AppMode::FuzzyFilter | AppMode::ColumnSearch => {
7073 "Enter:Apply | Esc:Cancel"
7074 }
7075 AppMode::Help | AppMode::Debug | AppMode::PrettyQuery | AppMode::ColumnStats => {
7076 "Esc:Close"
7077 }
7078 AppMode::History => "Enter:Select | Esc:Cancel",
7079 AppMode::JumpToRow => "Enter:Jump | Esc:Cancel",
7080 }
7081 }
7082
7083 fn add_shadow_state_display(&self, spans: &mut Vec<Span>) {
7084 let shadow_display = self.shadow_state.borrow().status_display();
7085 spans.push(Span::raw(" "));
7086 spans.push(Span::styled(
7087 shadow_display,
7088 Style::default().fg(Color::Cyan),
7089 ));
7090 }
7091
7092 fn add_help_text_display<'a>(&self, spans: &mut Vec<Span<'a>>, help_text: &'a str, area: Rect) {
7094 let current_length: usize = spans.iter().map(|s| s.content.len()).sum();
7095 let available_width = area.width.saturating_sub(TABLE_BORDER_WIDTH) as usize;
7096 let help_length = help_text.len();
7097
7098 if current_length + help_length + 3 < available_width {
7099 let padding = available_width - current_length - help_length - 3;
7100 spans.push(Span::raw(" ".repeat(padding)));
7101 spans.push(Span::raw(" | "));
7102 spans.push(Span::styled(
7103 help_text,
7104 Style::default().fg(Color::DarkGray),
7105 ));
7106 }
7107 }
7108}
7109
7110impl ActionHandlerContext for EnhancedTuiApp {
7112 fn previous_row(&mut self) {
7114 <Self as NavigationBehavior>::previous_row(self);
7115
7116 let current_row = self.state_container.navigation().selected_row;
7118 let current_col = self.state_container.navigation().selected_column;
7119 self.table_widget_manager
7120 .borrow_mut()
7121 .navigate_to(current_row, current_col);
7122 info!(target: "navigation", "previous_row: Updated TableWidgetManager to ({}, {})", current_row, current_col);
7123 }
7124
7125 fn next_row(&mut self) {
7126 info!(target: "navigation", "next_row called - calling NavigationBehavior::next_row");
7127 <Self as NavigationBehavior>::next_row(self);
7128
7129 let current_row = self.state_container.navigation().selected_row;
7131 let current_col = self.state_container.navigation().selected_column;
7132 self.table_widget_manager
7133 .borrow_mut()
7134 .navigate_to(current_row, current_col);
7135 info!(target: "navigation", "next_row: Updated TableWidgetManager to ({}, {})", current_row, current_col);
7136 }
7137
7138 fn move_column_left(&mut self) {
7139 <Self as ColumnBehavior>::move_column_left(self);
7140
7141 let current_row = self.state_container.navigation().selected_row;
7143 let current_col = self.state_container.navigation().selected_column;
7144 self.table_widget_manager
7145 .borrow_mut()
7146 .navigate_to(current_row, current_col);
7147 info!(target: "navigation", "move_column_left: Updated TableWidgetManager to ({}, {})", current_row, current_col);
7148 }
7149
7150 fn move_column_right(&mut self) {
7151 <Self as ColumnBehavior>::move_column_right(self);
7152
7153 let current_row = self.state_container.navigation().selected_row;
7155 let current_col = self.state_container.navigation().selected_column;
7156 self.table_widget_manager
7157 .borrow_mut()
7158 .navigate_to(current_row, current_col);
7159 info!(target: "navigation", "move_column_right: Updated TableWidgetManager to ({}, {})", current_row, current_col);
7160 }
7161
7162 fn page_up(&mut self) {
7163 <Self as NavigationBehavior>::page_up(self);
7164 }
7165
7166 fn page_down(&mut self) {
7167 <Self as NavigationBehavior>::page_down(self);
7168 }
7169
7170 fn goto_first_row(&mut self) {
7171 use crate::ui::state::state_coordinator::StateCoordinator;
7172
7173 <Self as NavigationBehavior>::goto_first_row(self);
7175
7176 StateCoordinator::goto_first_row_with_refs(
7178 &mut self.state_container,
7179 Some(&self.vim_search_adapter),
7180 Some(&self.viewport_manager),
7181 );
7182 }
7183
7184 fn goto_last_row(&mut self) {
7185 use crate::ui::state::state_coordinator::StateCoordinator;
7186
7187 <Self as NavigationBehavior>::goto_last_row(self);
7189
7190 StateCoordinator::goto_last_row_with_refs(&mut self.state_container);
7192 }
7193
7194 fn goto_first_column(&mut self) {
7195 <Self as ColumnBehavior>::goto_first_column(self);
7196 }
7197
7198 fn goto_last_column(&mut self) {
7199 <Self as ColumnBehavior>::goto_last_column(self);
7200 }
7201
7202 fn goto_row(&mut self, row: usize) {
7203 use crate::ui::state::state_coordinator::StateCoordinator;
7204
7205 <Self as NavigationBehavior>::goto_line(self, row + 1); StateCoordinator::goto_row_with_refs(&mut self.state_container, row);
7210 }
7211
7212 fn goto_column(&mut self, col: usize) {
7213 let current_col = self.state_container.get_current_column();
7216 if col < current_col {
7217 for _ in 0..(current_col - col) {
7218 <Self as ColumnBehavior>::move_column_left(self);
7219 }
7220 } else if col > current_col {
7221 for _ in 0..(col - current_col) {
7222 <Self as ColumnBehavior>::move_column_right(self);
7223 }
7224 }
7225 }
7226
7227 fn set_mode(&mut self, mode: AppMode) {
7229 self.set_mode_via_shadow_state(mode, "action_handler");
7231 }
7232
7233 fn get_mode(&self) -> AppMode {
7234 self.shadow_state.borrow().get_mode()
7235 }
7236
7237 fn set_status_message(&mut self, message: String) {
7238 self.state_container.set_status_message(message);
7239 }
7240
7241 fn toggle_column_pin(&mut self) {
7243 self.toggle_column_pin_impl();
7245 }
7246
7247 fn hide_current_column(&mut self) {
7248 <Self as ColumnBehavior>::hide_current_column(self);
7249 }
7250
7251 fn unhide_all_columns(&mut self) {
7252 <Self as ColumnBehavior>::unhide_all_columns(self);
7253 }
7254
7255 fn clear_all_pinned_columns(&mut self) {
7256 self.clear_all_pinned_columns_impl();
7258 }
7259
7260 fn export_to_csv(&mut self) {
7262 self.state_container
7264 .set_status_message("CSV export not yet implemented".to_string());
7265 }
7266
7267 fn export_to_json(&mut self) {
7268 self.state_container
7270 .set_status_message("JSON export not yet implemented".to_string());
7271 }
7272
7273 fn yank_cell(&mut self) {
7275 YankBehavior::yank_cell(self);
7276 }
7277
7278 fn yank_row(&mut self) {
7279 YankBehavior::yank_row(self);
7280 }
7281
7282 fn yank_column(&mut self) {
7283 YankBehavior::yank_column(self);
7284 }
7285
7286 fn yank_all(&mut self) {
7287 YankBehavior::yank_all(self);
7288 }
7289
7290 fn yank_query(&mut self) {
7291 YankBehavior::yank_query(self);
7292 }
7293
7294 fn toggle_selection_mode(&mut self) {
7296 self.state_container.toggle_selection_mode();
7297 let new_mode = self.state_container.get_selection_mode();
7298 let msg = match new_mode {
7299 SelectionMode::Cell => "Cell mode - Navigate to select individual cells",
7300 SelectionMode::Row => "Row mode - Navigate to select rows",
7301 SelectionMode::Column => "Column mode - Navigate to select columns",
7302 };
7303 self.state_container.set_status_message(msg.to_string());
7304 }
7305
7306 fn toggle_row_numbers(&mut self) {
7307 let current = self.state_container.is_show_row_numbers();
7308 self.state_container.set_show_row_numbers(!current);
7309 let message = if current {
7310 "Row numbers: OFF".to_string()
7311 } else {
7312 "Row numbers: ON (showing line numbers)".to_string()
7313 };
7314 self.state_container.set_status_message(message);
7315 self.calculate_optimal_column_widths();
7317 }
7318
7319 fn toggle_compact_mode(&mut self) {
7320 let current_mode = self.state_container.is_compact_mode();
7321 self.state_container.set_compact_mode(!current_mode);
7322 let message = if current_mode {
7323 "Compact mode disabled"
7324 } else {
7325 "Compact mode enabled"
7326 };
7327 self.state_container.set_status_message(message.to_string());
7328 }
7329
7330 fn toggle_case_insensitive(&mut self) {
7331 let current = self.state_container.is_case_insensitive();
7332 self.state_container.set_case_insensitive(!current);
7333 self.state_container.set_status_message(format!(
7334 "Case-insensitive string comparisons: {}",
7335 if current { "OFF" } else { "ON" }
7336 ));
7337 }
7338
7339 fn toggle_key_indicator(&mut self) {
7340 let enabled = !self.key_indicator.enabled;
7341 self.key_indicator.set_enabled(enabled);
7342 self.key_sequence_renderer.set_enabled(enabled);
7343 self.state_container.set_status_message(format!(
7344 "Key press indicator {}",
7345 if enabled { "enabled" } else { "disabled" }
7346 ));
7347 }
7348
7349 fn clear_filter(&mut self) {
7351 if let Some(dataview) = self.state_container.get_buffer_dataview() {
7353 if dataview.has_filter() {
7354 if let Some(dataview_mut) = self.state_container.get_buffer_dataview_mut() {
7356 dataview_mut.clear_filter();
7357 self.state_container
7358 .set_status_message("Filter cleared".to_string());
7359 }
7360
7361 self.sync_dataview_to_managers();
7364 } else {
7365 self.state_container
7366 .set_status_message("No active filter to clear".to_string());
7367 }
7368 } else {
7369 self.state_container
7370 .set_status_message("No data loaded".to_string());
7371 }
7372 }
7373
7374 fn clear_line(&mut self) {
7375 self.state_container.clear_line();
7376 }
7377
7378 fn start_search(&mut self) {
7380 self.start_vim_search();
7381 }
7382
7383 fn start_column_search(&mut self) {
7384 self.enter_search_mode(SearchMode::ColumnSearch);
7385 }
7386
7387 fn start_filter(&mut self) {
7388 self.enter_search_mode(SearchMode::Filter);
7389 }
7390
7391 fn start_fuzzy_filter(&mut self) {
7392 self.enter_search_mode(SearchMode::FuzzyFilter);
7393 }
7394
7395 fn exit_current_mode(&mut self) {
7396 let mode = self.shadow_state.borrow().get_mode();
7398 match mode {
7399 AppMode::Results => {
7400 self.state_container.set_mode(AppMode::Command);
7403 }
7404 AppMode::Command => {
7405 self.state_container.set_mode(AppMode::Results);
7406 }
7407 AppMode::Help => {
7408 self.state_container.set_mode(AppMode::Results);
7409 }
7410 AppMode::JumpToRow => {
7411 self.state_container.set_mode(AppMode::Results);
7412 <Self as InputBehavior>::clear_jump_to_row_input(self);
7413 self.state_container.jump_to_row_mut().is_active = false;
7415 self.state_container
7416 .set_status_message("Jump to row cancelled".to_string());
7417 }
7418 _ => {
7419 self.state_container.set_mode(AppMode::Results);
7421 }
7422 }
7423 }
7424
7425 fn toggle_debug_mode(&mut self) {
7426 <Self as DebugContext>::toggle_debug_mode(self);
7428 }
7429
7430 fn move_current_column_left(&mut self) {
7432 <Self as ColumnBehavior>::move_current_column_left(self);
7433 }
7434
7435 fn move_current_column_right(&mut self) {
7436 <Self as ColumnBehavior>::move_current_column_right(self);
7437 }
7438
7439 fn next_search_match(&mut self) {
7441 self.vim_search_next();
7442 }
7443
7444 fn previous_search_match(&mut self) {
7445 if self.vim_search_adapter.borrow().is_active()
7446 || self.vim_search_adapter.borrow().get_pattern().is_some()
7447 {
7448 self.vim_search_previous();
7449 }
7450 }
7451
7452 fn show_column_statistics(&mut self) {
7454 self.calculate_column_statistics();
7455 }
7456
7457 fn cycle_column_packing(&mut self) {
7458 let message = {
7459 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
7460 let viewport_manager = viewport_manager_borrow
7461 .as_mut()
7462 .expect("ViewportManager must exist");
7463 let new_mode = viewport_manager.cycle_packing_mode();
7464 format!("Column packing: {}", new_mode.display_name())
7465 };
7466 self.state_container.set_status_message(message);
7467 }
7468
7469 fn navigate_to_viewport_top(&mut self) {
7471 let result = {
7472 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
7473 (*viewport_manager_borrow)
7474 .as_mut()
7475 .map(super::viewport_manager::ViewportManager::navigate_to_viewport_top)
7476 };
7477
7478 if let Some(result) = result {
7479 self.sync_navigation_with_viewport();
7481
7482 self.state_container
7484 .set_selected_row(Some(result.row_position));
7485
7486 if result.viewport_changed {
7488 let scroll_offset = self.state_container.navigation().scroll_offset;
7489 self.state_container.set_scroll_offset(scroll_offset);
7490 }
7491 }
7492 }
7493
7494 fn navigate_to_viewport_middle(&mut self) {
7495 let result = {
7496 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
7497 (*viewport_manager_borrow)
7498 .as_mut()
7499 .map(super::viewport_manager::ViewportManager::navigate_to_viewport_middle)
7500 };
7501
7502 if let Some(result) = result {
7503 self.sync_navigation_with_viewport();
7505
7506 self.state_container
7508 .set_selected_row(Some(result.row_position));
7509
7510 if result.viewport_changed {
7512 let scroll_offset = self.state_container.navigation().scroll_offset;
7513 self.state_container.set_scroll_offset(scroll_offset);
7514 }
7515 }
7516 }
7517
7518 fn navigate_to_viewport_bottom(&mut self) {
7519 let result = {
7520 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
7521 (*viewport_manager_borrow)
7522 .as_mut()
7523 .map(super::viewport_manager::ViewportManager::navigate_to_viewport_bottom)
7524 };
7525
7526 if let Some(result) = result {
7527 self.sync_navigation_with_viewport();
7529
7530 self.state_container
7532 .set_selected_row(Some(result.row_position));
7533
7534 if result.viewport_changed {
7536 let scroll_offset = self.state_container.navigation().scroll_offset;
7537 self.state_container.set_scroll_offset(scroll_offset);
7538 }
7539 }
7540 }
7541
7542 fn move_input_cursor_left(&mut self) {
7544 self.state_container.move_input_cursor_left();
7545 }
7546
7547 fn move_input_cursor_right(&mut self) {
7548 self.state_container.move_input_cursor_right();
7549 }
7550
7551 fn move_input_cursor_home(&mut self) {
7552 self.state_container.set_input_cursor_position(0);
7553 }
7554
7555 fn move_input_cursor_end(&mut self) {
7556 let text_len = self.state_container.get_input_text().chars().count();
7557 self.state_container.set_input_cursor_position(text_len);
7558 }
7559
7560 fn backspace(&mut self) {
7561 self.state_container.backspace();
7562 }
7563
7564 fn delete(&mut self) {
7565 self.state_container.delete();
7566 }
7567
7568 fn undo(&mut self) {
7569 self.state_container.perform_undo();
7570 }
7571
7572 fn redo(&mut self) {
7573 self.state_container.perform_redo();
7574 }
7575
7576 fn start_jump_to_row(&mut self) {
7577 self.state_container.set_mode(AppMode::JumpToRow);
7578 self.shadow_state
7579 .borrow_mut()
7580 .observe_mode_change(AppMode::JumpToRow, "jump_to_row_requested");
7581 <Self as InputBehavior>::clear_jump_to_row_input(self);
7582
7583 self.state_container.jump_to_row_mut().is_active = true;
7585
7586 self.state_container
7587 .set_status_message("Enter row number (1-based):".to_string());
7588 }
7589
7590 fn clear_jump_to_row_input(&mut self) {
7591 <Self as InputBehavior>::clear_jump_to_row_input(self);
7592 }
7593
7594 fn toggle_cursor_lock(&mut self) {
7595 let is_locked = {
7597 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
7598 if let Some(ref mut viewport_manager) = *viewport_manager_borrow {
7599 viewport_manager.toggle_cursor_lock();
7600 Some(viewport_manager.is_cursor_locked())
7601 } else {
7602 None
7603 }
7604 };
7605
7606 if let Some(is_locked) = is_locked {
7607 let msg = if is_locked {
7608 "Cursor lock ON - cursor stays in viewport position while scrolling"
7609 } else {
7610 "Cursor lock OFF"
7611 };
7612 self.state_container.set_status_message(msg.to_string());
7613
7614 info!(target: "shadow_state",
7616 "Cursor lock toggled: {} (in {:?} mode)",
7617 if is_locked { "ON" } else { "OFF" },
7618 self.shadow_state.borrow().get_mode()
7619 );
7620 }
7621 }
7622
7623 fn toggle_viewport_lock(&mut self) {
7624 let is_locked = {
7626 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
7627 if let Some(ref mut viewport_manager) = *viewport_manager_borrow {
7628 viewport_manager.toggle_viewport_lock();
7629 Some(viewport_manager.is_viewport_locked())
7630 } else {
7631 None
7632 }
7633 };
7634
7635 if let Some(is_locked) = is_locked {
7636 let msg = if is_locked {
7637 "Viewport lock ON - navigation constrained to current viewport"
7638 } else {
7639 "Viewport lock OFF"
7640 };
7641 self.state_container.set_status_message(msg.to_string());
7642
7643 info!(target: "shadow_state",
7645 "Viewport lock toggled: {} (in {:?} mode)",
7646 if is_locked { "ON" } else { "OFF" },
7647 self.shadow_state.borrow().get_mode()
7648 );
7649 }
7650 }
7651
7652 fn show_debug_info(&mut self) {
7654 <Self as DebugContext>::toggle_debug_mode(self);
7655 }
7656
7657 fn show_pretty_query(&mut self) {
7658 self.show_pretty_query();
7659 }
7660
7661 fn show_help(&mut self) {
7662 self.state_container.set_help_visible(true);
7663 self.set_mode_via_shadow_state(AppMode::Help, "help_requested");
7664 self.help_widget.on_enter();
7665 }
7666
7667 fn kill_line(&mut self) {
7669 use crate::ui::traits::input_ops::InputBehavior;
7670 InputBehavior::kill_line(self);
7671 let message = if self.state_container.is_kill_ring_empty() {
7672 "Kill line - nothing to kill".to_string()
7673 } else {
7674 let kill_ring = self.state_container.get_kill_ring();
7675 format!(
7676 "Killed to end of line - {} chars in kill ring",
7677 kill_ring.len()
7678 )
7679 };
7680 self.state_container.set_status_message(message);
7681 }
7682
7683 fn kill_line_backward(&mut self) {
7684 use crate::ui::traits::input_ops::InputBehavior;
7685 InputBehavior::kill_line_backward(self);
7686 let message = if self.state_container.is_kill_ring_empty() {
7687 "Kill line backward - nothing to kill".to_string()
7688 } else {
7689 let kill_ring = self.state_container.get_kill_ring();
7690 format!(
7691 "Killed to beginning of line - {} chars in kill ring",
7692 kill_ring.len()
7693 )
7694 };
7695 self.state_container.set_status_message(message);
7696 }
7697
7698 fn delete_word_backward(&mut self) {
7699 use crate::ui::traits::input_ops::InputBehavior;
7700 InputBehavior::delete_word_backward(self);
7701 }
7702
7703 fn delete_word_forward(&mut self) {
7704 use crate::ui::traits::input_ops::InputBehavior;
7705 InputBehavior::delete_word_forward(self);
7706 }
7707
7708 fn expand_asterisk(&mut self) {
7709 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
7710 if buffer.expand_asterisk(&self.hybrid_parser) {
7711 if buffer.get_edit_mode() == EditMode::SingleLine {
7713 let text = buffer.get_input_text();
7714 let cursor = buffer.get_input_cursor_position();
7715 self.set_input_text_with_cursor(text, cursor);
7716 }
7717 }
7718 }
7719 }
7720
7721 fn expand_asterisk_visible(&mut self) {
7722 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
7723 if buffer.expand_asterisk_visible() {
7724 if buffer.get_edit_mode() == EditMode::SingleLine {
7726 let text = buffer.get_input_text();
7727 let cursor = buffer.get_input_cursor_position();
7728 self.set_input_text_with_cursor(text, cursor);
7729 }
7730 }
7731 }
7732 }
7733
7734 fn previous_history_command(&mut self) {
7735 let history_entries = self
7736 .state_container
7737 .command_history()
7738 .get_navigation_entries();
7739 let history_commands: Vec<String> =
7740 history_entries.iter().map(|e| e.command.clone()).collect();
7741
7742 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
7743 if buffer.navigate_history_up(&history_commands) {
7744 self.sync_all_input_states();
7745 self.state_container
7746 .set_status_message("Previous command from history".to_string());
7747 }
7748 }
7749 }
7750
7751 fn next_history_command(&mut self) {
7752 let history_entries = self
7753 .state_container
7754 .command_history()
7755 .get_navigation_entries();
7756 let history_commands: Vec<String> =
7757 history_entries.iter().map(|e| e.command.clone()).collect();
7758
7759 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
7760 if buffer.navigate_history_down(&history_commands) {
7761 self.sync_all_input_states();
7762 self.state_container
7763 .set_status_message("Next command from history".to_string());
7764 }
7765 }
7766 }
7767}
7768
7769impl NavigationBehavior for EnhancedTuiApp {
7771 fn viewport_manager(&self) -> &RefCell<Option<ViewportManager>> {
7772 &self.viewport_manager
7773 }
7774
7775 fn buffer_mut(&mut self) -> &mut dyn BufferAPI {
7776 self.state_container
7777 .current_buffer_mut()
7778 .expect("Buffer should exist")
7779 }
7780
7781 fn buffer(&self) -> &dyn BufferAPI {
7782 self.state_container
7783 .current_buffer()
7784 .expect("Buffer should exist")
7785 }
7786
7787 fn state_container(&self) -> &AppStateContainer {
7788 &self.state_container
7789 }
7790
7791 fn state_container_mut(&mut self) -> &mut AppStateContainer {
7792 &mut self.state_container
7793 }
7794
7795 fn get_row_count(&self) -> usize {
7796 self.get_row_count()
7797 }
7798
7799 fn set_mode_with_sync(&mut self, mode: AppMode, trigger: &str) {
7800 self.set_mode_via_shadow_state(mode, trigger);
7802 }
7803}
7804
7805impl ColumnBehavior for EnhancedTuiApp {
7807 fn viewport_manager(&self) -> &RefCell<Option<ViewportManager>> {
7808 &self.viewport_manager
7809 }
7810
7811 fn buffer_mut(&mut self) -> &mut dyn BufferAPI {
7812 self.state_container
7813 .current_buffer_mut()
7814 .expect("Buffer should exist")
7815 }
7816
7817 fn buffer(&self) -> &dyn BufferAPI {
7818 self.state_container
7819 .current_buffer()
7820 .expect("Buffer should exist")
7821 }
7822
7823 fn state_container(&self) -> &AppStateContainer {
7824 &self.state_container
7825 }
7826
7827 fn is_in_results_mode(&self) -> bool {
7828 self.shadow_state.borrow().is_in_results_mode()
7829 }
7830}
7831
7832impl InputBehavior for EnhancedTuiApp {
7833 fn buffer_manager(&mut self) -> &mut BufferManager {
7834 self.state_container.buffers_mut()
7835 }
7836
7837 fn cursor_manager(&mut self) -> &mut CursorManager {
7838 &mut self.cursor_manager
7839 }
7840
7841 fn set_input_text_with_cursor(&mut self, text: String, cursor: usize) {
7842 self.set_input_text_with_cursor(text, cursor);
7843 }
7844
7845 fn state_container(&self) -> &AppStateContainer {
7846 &self.state_container
7847 }
7848
7849 fn state_container_mut(&mut self) -> &mut AppStateContainer {
7850 &mut self.state_container
7851 }
7852
7853 fn buffer_mut(&mut self) -> &mut dyn BufferAPI {
7854 self.state_container
7855 .current_buffer_mut()
7856 .expect("Buffer should exist")
7857 }
7858
7859 fn set_mode_with_sync(&mut self, mode: AppMode, trigger: &str) {
7860 self.set_mode_via_shadow_state(mode, trigger);
7862 }
7863}
7864
7865impl YankBehavior for EnhancedTuiApp {
7866 fn buffer(&self) -> &dyn BufferAPI {
7867 self.state_container
7868 .current_buffer()
7869 .expect("Buffer should exist")
7870 }
7871
7872 fn buffer_mut(&mut self) -> &mut dyn BufferAPI {
7873 self.state_container
7874 .current_buffer_mut()
7875 .expect("Buffer should exist")
7876 }
7877
7878 fn state_container(&self) -> &AppStateContainer {
7879 &self.state_container
7880 }
7881
7882 fn set_status_message(&mut self, message: String) {
7883 self.state_container.set_status_message(message);
7884 }
7885
7886 fn set_error_status(&mut self, prefix: &str, error: anyhow::Error) {
7887 self.set_error_status(prefix, error);
7888 }
7889}
7890
7891impl BufferManagementBehavior for EnhancedTuiApp {
7892 fn buffer_manager(&mut self) -> &mut BufferManager {
7893 self.state_container.buffers_mut()
7894 }
7895
7896 fn buffer_handler(&mut self) -> &mut BufferHandler {
7897 &mut self.buffer_handler
7898 }
7899
7900 fn buffer(&self) -> &dyn BufferAPI {
7901 self.state_container
7902 .current_buffer()
7903 .expect("Buffer should exist")
7904 }
7905
7906 fn buffer_mut(&mut self) -> &mut dyn BufferAPI {
7907 self.state_container
7908 .current_buffer_mut()
7909 .expect("Buffer should exist")
7910 }
7911
7912 fn config(&self) -> &Config {
7913 &self.config
7914 }
7915
7916 fn cursor_manager(&mut self) -> &mut CursorManager {
7917 &mut self.cursor_manager
7918 }
7919
7920 fn set_input_text_with_cursor(&mut self, text: String, cursor: usize) {
7921 self.set_input_text_with_cursor(text, cursor);
7922 }
7923
7924 fn next_buffer(&mut self) -> String {
7925 self.save_viewport_to_current_buffer();
7927
7928 let result = self
7929 .buffer_handler
7930 .next_buffer(self.state_container.buffers_mut());
7931
7932 self.sync_after_buffer_switch();
7934
7935 result
7936 }
7937
7938 fn previous_buffer(&mut self) -> String {
7939 self.save_viewport_to_current_buffer();
7941
7942 let result = self
7943 .buffer_handler
7944 .previous_buffer(self.state_container.buffers_mut());
7945
7946 self.sync_after_buffer_switch();
7948
7949 result
7950 }
7951
7952 fn quick_switch_buffer(&mut self) -> String {
7953 self.save_viewport_to_current_buffer();
7955
7956 let result = self
7957 .buffer_handler
7958 .quick_switch(self.state_container.buffers_mut());
7959
7960 self.sync_after_buffer_switch();
7962
7963 result
7964 }
7965
7966 fn close_buffer(&mut self) -> (bool, String) {
7967 self.buffer_handler
7968 .close_buffer(self.state_container.buffers_mut())
7969 }
7970
7971 fn switch_to_buffer(&mut self, index: usize) -> String {
7972 self.save_viewport_to_current_buffer();
7974
7975 let result = self
7977 .buffer_handler
7978 .switch_to_buffer(self.state_container.buffers_mut(), index);
7979
7980 self.sync_after_buffer_switch();
7982
7983 result
7984 }
7985
7986 fn buffer_count(&self) -> usize {
7987 self.state_container.buffers().all_buffers().len()
7988 }
7989
7990 fn current_buffer_index(&self) -> usize {
7991 self.state_container.buffers().current_index()
7992 }
7993}
7994
7995pub fn run_enhanced_tui_multi(api_url: &str, data_files: Vec<&str>) -> Result<()> {
7996 let original_hook = std::panic::take_hook();
7998 std::panic::set_hook(Box::new(move |panic_info| {
7999 let _ = disable_raw_mode();
8001 let _ = execute!(
8002 io::stdout(),
8003 LeaveAlternateScreen,
8004 DisableMouseCapture,
8005 crossterm::cursor::Show
8006 );
8007
8008 original_hook(panic_info);
8010 }));
8011
8012 let app = if data_files.is_empty() {
8013 EnhancedTuiApp::new(api_url)
8014 } else {
8015 use crate::services::ApplicationOrchestrator;
8017
8018 let config = Config::default();
8020 let orchestrator = ApplicationOrchestrator::new(
8021 config.behavior.case_insensitive_default,
8022 config.behavior.hide_empty_columns,
8023 );
8024
8025 let mut app = orchestrator.create_tui_with_file(data_files[0])?;
8027
8028 for file_path in data_files.iter().skip(1) {
8030 if let Err(e) = orchestrator.load_additional_file(&mut app, file_path) {
8031 app.state_container
8032 .set_status_message(format!("Error loading {file_path}: {e}"));
8033 continue;
8034 }
8035 }
8036
8037 if data_files.len() > 1 {
8039 app.state_container.buffers_mut().switch_to(0);
8040 app.sync_after_buffer_switch();
8042 app.state_container.set_status_message(format!(
8043 "Loaded {} files into separate buffers. Use Alt+Tab to switch.",
8044 data_files.len()
8045 ));
8046 } else if data_files.len() == 1 {
8047 app.update_parser_for_current_buffer();
8049 }
8050
8051 app
8052 };
8053
8054 let result = app.run();
8055
8056 let _ = std::panic::take_hook(); result
8061}
8062
8063pub fn run_enhanced_tui(api_url: &str, data_file: Option<&str>) -> Result<()> {
8064 let files = if let Some(file) = data_file {
8066 vec![file]
8067 } else {
8068 vec![]
8069 };
8070 run_enhanced_tui_multi(api_url, files)
8071}