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' | '[' | ']' | ',' | '.' ))
2025 } else {
2026 false
2027 };
2028
2029 let should_try_command_editor = !is_special_combo
2030 && matches!(
2031 normalized_key.code,
2032 KeyCode::Char(_)
2033 | KeyCode::Backspace
2034 | KeyCode::Delete
2035 | KeyCode::Left
2036 | KeyCode::Right
2037 | KeyCode::Home
2038 | KeyCode::End
2039 );
2040
2041 if should_try_command_editor {
2042 let before_text = self.input.value().to_string();
2045 let before_cursor = self.input.cursor();
2046
2047 if self.command_editor.get_text() != before_text {
2048 self.command_editor.set_text(before_text.clone());
2049 }
2050 if self.command_editor.get_cursor() != before_cursor {
2051 self.command_editor.set_cursor(before_cursor);
2052 }
2053
2054 let result = self.command_editor.handle_input(
2056 normalized_key,
2057 &mut self.state_container,
2058 &self.shadow_state,
2059 )?;
2060
2061 let new_text = self.command_editor.get_text();
2064 let new_cursor = self.command_editor.get_cursor();
2065
2066 if new_text != before_text || new_cursor != before_cursor {
2068 debug!(
2069 "CommandEditor changed input: '{}' -> '{}', cursor: {} -> {}",
2070 before_text, new_text, before_cursor, new_cursor
2071 );
2072 }
2073
2074 self.input = tui_input::Input::from(new_text.clone()).with_cursor(new_cursor);
2076
2077 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
2080 buffer.set_input_text(new_text.clone());
2081 buffer.set_input_cursor_position(new_cursor);
2082 }
2083
2084 self.state_container.set_input_text(new_text);
2086 self.state_container.set_input_cursor_position(new_cursor);
2087
2088 if result {
2089 return Ok(true);
2090 }
2091
2092 return Ok(false);
2094 }
2095 if let Some(result) = self.try_action_system(normalized_key)? {
2099 return Ok(result);
2100 }
2101
2102 if let Some(result) = self.try_editor_widget(normalized_key)? {
2104 return Ok(result);
2105 }
2106
2107 if let Some(result) = self.try_handle_history_navigation(&normalized_key)? {
2111 return Ok(result);
2112 }
2113
2114 let old_cursor = self.get_input_cursor();
2116
2117 trace!(target: "input", "Key: {:?} Modifiers: {:?}", key.code, key.modifiers);
2119
2120 if let Some(result) = self.try_handle_buffer_operations(&key)? {
2125 return Ok(result);
2126 }
2127
2128 if let Some(result) = self.try_handle_function_keys(&key)? {
2130 return Ok(result);
2131 }
2132
2133 if let Some(result) = self.try_handle_text_editing(&key)? {
2135 return Ok(result);
2136 }
2137
2138 if let Some(result) = self.try_handle_mode_transitions(&key, old_cursor)? {
2140 return Ok(result);
2141 }
2142
2143 Ok(false)
2147 }
2148
2149 fn normalize_and_log_key(
2154 &mut self,
2155 key: crossterm::event::KeyEvent,
2156 ) -> crossterm::event::KeyEvent {
2157 let normalized = self.state_container.normalize_key(key);
2158
2159 let action = self
2161 .key_dispatcher
2162 .get_command_action(&normalized)
2163 .map(std::string::ToString::to_string);
2164
2165 if normalized != key {
2167 self.state_container
2168 .log_key_press(key, Some(format!("normalized to {normalized:?}")));
2169 }
2170 self.state_container.log_key_press(normalized, action);
2171
2172 normalized
2173 }
2174
2175 fn try_action_system(
2177 &mut self,
2178 normalized_key: crossterm::event::KeyEvent,
2179 ) -> Result<Option<bool>> {
2180 let action_context = self.build_action_context();
2181 if let Some(action) = self.key_mapper.map_key(normalized_key, &action_context) {
2182 info!(
2183 "✓ Action system (Command): key {:?} -> action {:?}",
2184 normalized_key.code, action
2185 );
2186 if let Ok(result) = self.try_handle_action(action, &action_context) {
2187 match result {
2188 ActionResult::Handled => {
2189 debug!("Action handled by new system in Command mode");
2190 return Ok(Some(false));
2191 }
2192 ActionResult::Exit => {
2193 return Ok(Some(true));
2194 }
2195 ActionResult::NotHandled => {
2196 }
2198 _ => {}
2199 }
2200 }
2201 }
2202 Ok(None)
2203 }
2204
2205 fn try_editor_widget(
2207 &mut self,
2208 normalized_key: crossterm::event::KeyEvent,
2209 ) -> Result<Option<bool>> {
2210 let key_dispatcher = self.key_dispatcher.clone();
2211 let editor_result = if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
2212 self.editor_widget
2213 .handle_key(normalized_key, &key_dispatcher, buffer)?
2214 } else {
2215 EditorAction::PassToMainApp(normalized_key)
2216 };
2217
2218 match editor_result {
2219 EditorAction::Quit => return Ok(Some(true)),
2220 EditorAction::ExecuteQuery => {
2221 return self.handle_execute_query().map(Some);
2222 }
2223 EditorAction::BufferAction(buffer_action) => {
2224 return self.handle_buffer_action(buffer_action).map(Some);
2225 }
2226 EditorAction::ExpandAsterisk => {
2227 return self.handle_expand_asterisk().map(Some);
2228 }
2229 EditorAction::ShowHelp => {
2230 self.state_container.set_help_visible(true);
2231 self.set_mode_via_shadow_state(AppMode::Help, "help_requested");
2233 return Ok(Some(false));
2234 }
2235 EditorAction::ShowDebug => {
2236 return Ok(Some(false));
2238 }
2239 EditorAction::ShowPrettyQuery => {
2240 self.show_pretty_query();
2241 return Ok(Some(false));
2242 }
2243 EditorAction::SwitchMode(mode) => {
2244 self.handle_editor_mode_switch(mode);
2245 return Ok(Some(false));
2246 }
2247 EditorAction::PassToMainApp(_) => {
2248 }
2250 EditorAction::Continue => return Ok(Some(false)),
2251 }
2252
2253 Ok(None)
2254 }
2255
2256 fn handle_editor_mode_switch(&mut self, mode: AppMode) {
2258 debug!(target: "shadow_state", "EditorAction::SwitchMode to {:?}", mode);
2259 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
2260 let trigger = match mode {
2262 AppMode::Results => "enter_results_mode",
2263 AppMode::Command => "enter_command_mode",
2264 AppMode::History => "enter_history_mode",
2265 _ => "switch_mode",
2266 };
2267 debug!(target: "shadow_state", "Setting mode via shadow state to {:?} with trigger {}", mode, trigger);
2268 self.shadow_state
2269 .borrow_mut()
2270 .set_mode(mode.clone(), buffer, trigger);
2271 } else {
2272 debug!(target: "shadow_state", "No buffer available for mode switch!");
2273 }
2274
2275 if mode == AppMode::History {
2277 eprintln!("[DEBUG] Using AppStateContainer for history search");
2278 let current_input = self.get_input_text();
2279
2280 self.state_container.start_history_search(current_input);
2282
2283 self.update_history_matches_in_container();
2285
2286 let match_count = self.state_container.history_search().matches.len();
2288
2289 self.state_container
2290 .set_status_message(format!("History search: {match_count} matches"));
2291 }
2292 }
2293
2294 fn try_handle_function_keys(
2296 &mut self,
2297 key: &crossterm::event::KeyEvent,
2298 ) -> Result<Option<bool>> {
2299 match key.code {
2300 KeyCode::F(1) | KeyCode::Char('?') => {
2301 if self.shadow_state.borrow().is_in_help_mode() {
2303 let mode = if self.state_container.has_dataview() {
2305 AppMode::Results
2306 } else {
2307 AppMode::Command
2308 };
2309 self.set_mode_via_shadow_state(mode, "exit_help");
2311 self.state_container.set_help_visible(false);
2312 self.help_widget.on_exit();
2313 } else {
2314 self.state_container.set_help_visible(true);
2316 self.set_mode_via_shadow_state(AppMode::Help, "help_requested");
2318 self.help_widget.on_enter();
2319 }
2320 Ok(Some(false))
2321 }
2322 KeyCode::F(3) => {
2323 self.show_pretty_query();
2325 Ok(Some(false))
2326 }
2327 KeyCode::F(5) => {
2328 self.toggle_debug_mode();
2330 Ok(Some(false))
2331 }
2332 KeyCode::F(6) => {
2333 let current = self.state_container.is_show_row_numbers();
2335 self.state_container.set_show_row_numbers(!current);
2336 self.state_container.set_status_message(format!(
2337 "Row numbers: {}",
2338 if current { "OFF" } else { "ON" }
2339 ));
2340 Ok(Some(false))
2341 }
2342 KeyCode::F(7) => {
2343 let current_mode = self.state_container.is_compact_mode();
2345 self.state_container.set_compact_mode(!current_mode);
2346 let message = if current_mode {
2347 "Compact mode disabled"
2348 } else {
2349 "Compact mode enabled"
2350 };
2351 self.state_container.set_status_message(message.to_string());
2352 Ok(Some(false))
2353 }
2354 KeyCode::F(8) => {
2355 let current = self.state_container.is_case_insensitive();
2357 self.state_container.set_case_insensitive(!current);
2358 self.state_container.set_status_message(format!(
2359 "Case-insensitive string comparisons: {}",
2360 if current { "OFF" } else { "ON" }
2361 ));
2362 Ok(Some(false))
2363 }
2364 KeyCode::F(9) => {
2365 use crate::ui::traits::input_ops::InputBehavior;
2367 InputBehavior::kill_line(self);
2368 let message = if self.state_container.is_kill_ring_empty() {
2369 "Killed to end of line".to_string()
2370 } else {
2371 format!(
2372 "Killed to end of line ('{}' saved to kill ring)",
2373 self.state_container.get_kill_ring()
2374 )
2375 };
2376 self.state_container.set_status_message(message);
2377 Ok(Some(false))
2378 }
2379 KeyCode::F(10) => {
2380 use crate::ui::traits::input_ops::InputBehavior;
2382 InputBehavior::kill_line_backward(self);
2383 let message = if self.state_container.is_kill_ring_empty() {
2384 "Killed to beginning of line".to_string()
2385 } else {
2386 format!(
2387 "Killed to beginning of line ('{}' saved to kill ring)",
2388 self.state_container.get_kill_ring()
2389 )
2390 };
2391 self.state_container.set_status_message(message);
2392 Ok(Some(false))
2393 }
2394 KeyCode::F(12) => {
2395 let enabled = !self.key_indicator.enabled;
2397 self.key_indicator.set_enabled(enabled);
2398 self.key_sequence_renderer.set_enabled(enabled);
2399 self.state_container.set_status_message(format!(
2400 "Key press indicator {}",
2401 if enabled { "enabled" } else { "disabled" }
2402 ));
2403 Ok(Some(false))
2404 }
2405 _ => Ok(None), }
2407 }
2408
2409 fn try_handle_buffer_operations(
2411 &mut self,
2412 key: &crossterm::event::KeyEvent,
2413 ) -> Result<Option<bool>> {
2414 if let Some(action) = self.key_dispatcher.get_command_action(key) {
2415 match action {
2416 "quit" => return Ok(Some(true)),
2417 "next_buffer" => {
2418 self.save_viewport_to_current_buffer();
2420
2421 let message = self
2422 .buffer_handler
2423 .next_buffer(self.state_container.buffers_mut());
2424 debug!("{}", message);
2425
2426 self.sync_after_buffer_switch();
2428 return Ok(Some(false));
2429 }
2430 "previous_buffer" => {
2431 self.save_viewport_to_current_buffer();
2433
2434 let message = self
2435 .buffer_handler
2436 .previous_buffer(self.state_container.buffers_mut());
2437 debug!("{}", message);
2438
2439 self.sync_after_buffer_switch();
2441 return Ok(Some(false));
2442 }
2443 "quick_switch_buffer" => {
2444 self.save_viewport_to_current_buffer();
2446
2447 let message = self
2448 .buffer_handler
2449 .quick_switch(self.state_container.buffers_mut());
2450 debug!("{}", message);
2451
2452 self.sync_after_buffer_switch();
2454
2455 return Ok(Some(false));
2456 }
2457 "new_buffer" => {
2458 let message = self
2459 .buffer_handler
2460 .new_buffer(self.state_container.buffers_mut(), &self.config);
2461 debug!("{}", message);
2462 return Ok(Some(false));
2463 }
2464 "close_buffer" => {
2465 let (success, message) = self
2466 .buffer_handler
2467 .close_buffer(self.state_container.buffers_mut());
2468 debug!("{}", message);
2469 return Ok(Some(!success)); }
2471 "list_buffers" => {
2472 let buffer_list = self
2473 .buffer_handler
2474 .list_buffers(self.state_container.buffers());
2475 for line in &buffer_list {
2476 debug!("{}", line);
2477 }
2478 return Ok(Some(false));
2479 }
2480 action if action.starts_with("switch_to_buffer_") => {
2481 if let Some(buffer_num_str) = action.strip_prefix("switch_to_buffer_") {
2482 if let Ok(buffer_num) = buffer_num_str.parse::<usize>() {
2483 self.save_viewport_to_current_buffer();
2485
2486 let message = self.buffer_handler.switch_to_buffer(
2487 self.state_container.buffers_mut(),
2488 buffer_num - 1,
2489 );
2490 debug!("{}", message);
2491
2492 self.sync_after_buffer_switch();
2494 }
2495 }
2496 return Ok(Some(false));
2497 }
2498 _ => {} }
2500 }
2501 Ok(None)
2502 }
2503
2504 fn try_handle_history_navigation(
2506 &mut self,
2507 key: &crossterm::event::KeyEvent,
2508 ) -> Result<Option<bool>> {
2509 if let KeyCode::Char('r') = key.code {
2511 if key.modifiers.contains(KeyModifiers::CONTROL) {
2512 let current_input = self.get_input_text();
2514
2515 self.state_container.start_history_search(current_input);
2517
2518 self.update_history_matches_in_container();
2520
2521 let match_count = self.state_container.history_search().matches.len();
2523
2524 self.state_container.set_mode(AppMode::History);
2525 self.shadow_state
2526 .borrow_mut()
2527 .observe_mode_change(AppMode::History, "history_search_started");
2528 self.state_container.set_status_message(format!(
2529 "History search started (Ctrl+R) - {match_count} matches"
2530 ));
2531 return Ok(Some(false));
2532 }
2533 }
2534
2535 if let KeyCode::Char('p') = key.code {
2537 if key.modifiers.contains(KeyModifiers::CONTROL) {
2538 let history_entries = self
2539 .state_container
2540 .command_history()
2541 .get_navigation_entries();
2542 let history_commands: Vec<String> =
2543 history_entries.iter().map(|e| e.command.clone()).collect();
2544
2545 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
2546 if buffer.navigate_history_up(&history_commands) {
2547 self.sync_all_input_states();
2548 self.state_container
2549 .set_status_message("Previous command from history".to_string());
2550 }
2551 }
2552 return Ok(Some(false));
2553 }
2554 }
2555
2556 if let KeyCode::Char('n') = key.code {
2558 if key.modifiers.contains(KeyModifiers::CONTROL) {
2559 let history_entries = self
2560 .state_container
2561 .command_history()
2562 .get_navigation_entries();
2563 let history_commands: Vec<String> =
2564 history_entries.iter().map(|e| e.command.clone()).collect();
2565
2566 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
2567 if buffer.navigate_history_down(&history_commands) {
2568 self.sync_all_input_states();
2569 self.state_container
2570 .set_status_message("Next command from history".to_string());
2571 }
2572 }
2573 return Ok(Some(false));
2574 }
2575 }
2576
2577 match key.code {
2579 KeyCode::Up if key.modifiers.contains(KeyModifiers::ALT) => {
2580 let history_entries = self
2581 .state_container
2582 .command_history()
2583 .get_navigation_entries();
2584 let history_commands: Vec<String> =
2585 history_entries.iter().map(|e| e.command.clone()).collect();
2586
2587 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
2588 if buffer.navigate_history_up(&history_commands) {
2589 self.sync_all_input_states();
2590 self.state_container
2591 .set_status_message("Previous command (Alt+Up)".to_string());
2592 }
2593 }
2594 Ok(Some(false))
2595 }
2596 KeyCode::Down if key.modifiers.contains(KeyModifiers::ALT) => {
2597 let history_entries = self
2598 .state_container
2599 .command_history()
2600 .get_navigation_entries();
2601 let history_commands: Vec<String> =
2602 history_entries.iter().map(|e| e.command.clone()).collect();
2603
2604 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
2605 if buffer.navigate_history_down(&history_commands) {
2606 self.sync_all_input_states();
2607 self.state_container
2608 .set_status_message("Next command (Alt+Down)".to_string());
2609 }
2610 }
2611 Ok(Some(false))
2612 }
2613 _ => Ok(None),
2614 }
2615 }
2616
2617 fn try_handle_text_editing(
2619 &mut self,
2620 key: &crossterm::event::KeyEvent,
2621 ) -> Result<Option<bool>> {
2622 if let Some(action) = self.key_dispatcher.get_command_action(key) {
2624 match action {
2625 "expand_asterisk" => {
2626 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
2627 if buffer.expand_asterisk(&self.hybrid_parser) {
2628 if buffer.get_edit_mode() == EditMode::SingleLine {
2630 let text = buffer.get_input_text();
2631 let cursor = buffer.get_input_cursor_position();
2632 self.set_input_text_with_cursor(text, cursor);
2633 }
2634 }
2635 }
2636 return Ok(Some(false));
2637 }
2638 "expand_asterisk_visible" => {
2639 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
2640 if buffer.expand_asterisk_visible() {
2641 if buffer.get_edit_mode() == EditMode::SingleLine {
2643 let text = buffer.get_input_text();
2644 let cursor = buffer.get_input_cursor_position();
2645 self.set_input_text_with_cursor(text, cursor);
2646 }
2647 }
2648 }
2649 return Ok(Some(false));
2650 }
2651 "delete_word_backward" => {
2653 use crate::ui::traits::input_ops::InputBehavior;
2654 InputBehavior::delete_word_backward(self);
2655 return Ok(Some(false));
2656 }
2657 "delete_word_forward" => {
2659 use crate::ui::traits::input_ops::InputBehavior;
2660 InputBehavior::delete_word_forward(self);
2661 return Ok(Some(false));
2662 }
2663 "kill_line" => {
2665 use crate::ui::traits::input_ops::InputBehavior;
2666 InputBehavior::kill_line(self);
2667 return Ok(Some(false));
2668 }
2669 "kill_line_backward" => {
2671 use crate::ui::traits::input_ops::InputBehavior;
2672 InputBehavior::kill_line_backward(self);
2673 return Ok(Some(false));
2674 }
2675 "move_word_backward" => {
2677 self.move_cursor_word_backward();
2678 return Ok(Some(false));
2679 }
2680 "move_word_forward" => {
2682 self.move_cursor_word_forward();
2683 return Ok(Some(false));
2684 }
2685 _ => {} }
2687 }
2688
2689 match key.code {
2691 KeyCode::Char('k') if key.modifiers.contains(KeyModifiers::CONTROL) => {
2693 self.state_container
2695 .set_status_message("Ctrl+K pressed - killing to end of line".to_string());
2696 use crate::ui::traits::input_ops::InputBehavior;
2697 InputBehavior::kill_line(self);
2698 Ok(Some(false))
2699 }
2700 KeyCode::Char('k') if key.modifiers.contains(KeyModifiers::ALT) => {
2702 self.state_container
2704 .set_status_message("Alt+K - killing to end of line".to_string());
2705 use crate::ui::traits::input_ops::InputBehavior;
2706 InputBehavior::kill_line(self);
2707 Ok(Some(false))
2708 }
2709 KeyCode::Char('u') if key.modifiers.contains(KeyModifiers::CONTROL) => {
2711 use crate::ui::traits::input_ops::InputBehavior;
2713 InputBehavior::kill_line_backward(self);
2714 Ok(Some(false))
2715 }
2716 KeyCode::Char('v') if key.modifiers.contains(KeyModifiers::CONTROL) => {
2717 self.paste_from_clipboard();
2719 Ok(Some(false))
2720 }
2721 KeyCode::Char('y') if key.modifiers.contains(KeyModifiers::CONTROL) => {
2722 self.yank();
2724 Ok(Some(false))
2725 }
2726 KeyCode::Left if key.modifiers.contains(KeyModifiers::CONTROL) => {
2728 self.move_cursor_word_backward();
2730 Ok(Some(false))
2731 }
2732 KeyCode::Right if key.modifiers.contains(KeyModifiers::CONTROL) => {
2734 self.move_cursor_word_forward();
2736 Ok(Some(false))
2737 }
2738 KeyCode::Char('b') if key.modifiers.contains(KeyModifiers::ALT) => {
2740 self.move_cursor_word_backward();
2742 Ok(Some(false))
2743 }
2744 KeyCode::Char('f') if key.modifiers.contains(KeyModifiers::ALT) => {
2746 self.move_cursor_word_forward();
2748 Ok(Some(false))
2749 }
2750 _ => Ok(None), }
2752 }
2753
2754 fn try_handle_mode_transitions(
2756 &mut self,
2757 key: &crossterm::event::KeyEvent,
2758 old_cursor: usize,
2759 ) -> Result<Option<bool>> {
2760 match key.code {
2761 KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => {
2762 Ok(Some(true))
2764 }
2765 KeyCode::Char('d') if key.modifiers.contains(KeyModifiers::CONTROL) => {
2766 Ok(Some(true))
2768 }
2769 KeyCode::Enter => {
2770 let query = self.get_input_text().trim().to_string();
2772 debug!(target: "action", "Executing query: {}", query);
2773
2774 if query.is_empty() {
2775 self.state_container
2776 .set_status_message("Empty query - please enter a SQL command".to_string());
2777 } else {
2778 if query == ":help" {
2780 self.state_container.set_help_visible(true);
2781 self.set_mode_via_shadow_state(AppMode::Help, "help_requested");
2783 self.state_container
2784 .set_status_message("Help Mode - Press ESC to return".to_string());
2785 } else if query == ":exit" || query == ":quit" || query == ":q" {
2786 return Ok(Some(true));
2787 } else if query == ":tui" {
2788 self.state_container
2790 .set_status_message("Already in TUI mode".to_string());
2791 } else {
2792 self.state_container
2793 .set_status_message(format!("Processing query: '{query}'"));
2794 self.execute_query_v2(&query)?;
2795 }
2796 }
2797 Ok(Some(false))
2798 }
2799 KeyCode::Tab => {
2800 self.apply_completion();
2802 Ok(Some(false))
2803 }
2804 KeyCode::Down => {
2805 debug!(target: "shadow_state", "Down arrow pressed in Command mode. has_dataview={}, edit_mode={:?}",
2806 self.state_container.has_dataview(),
2807 self.state_container.get_edit_mode());
2808
2809 if self.state_container.has_dataview()
2810 && self.state_container.get_edit_mode() == Some(EditMode::SingleLine)
2811 {
2812 debug!(target: "shadow_state", "Down arrow conditions met, switching to Results via set_mode");
2813 self.state_container.set_mode(AppMode::Results);
2815 self.shadow_state
2816 .borrow_mut()
2817 .observe_mode_change(AppMode::Results, "down_arrow_to_results");
2818 let row = self.state_container.get_last_results_row().unwrap_or(0);
2820 self.state_container.set_table_selected_row(Some(row));
2821
2822 let last_offset = self.state_container.get_last_scroll_offset();
2824 self.state_container.set_scroll_offset(last_offset);
2825 Ok(Some(false))
2826 } else {
2827 debug!(target: "shadow_state", "Down arrow conditions not met, falling through");
2828 Ok(None)
2830 }
2831 }
2832 _ => {
2833 self.handle_input_key(*key);
2835
2836 self.state_container.clear_completion();
2838
2839 self.handle_completion();
2841
2842 if self.get_input_cursor() != old_cursor {
2844 self.update_horizontal_scroll(120); }
2846
2847 Ok(Some(false))
2848 }
2849 }
2850 }
2851
2852 fn try_handle_results_navigation(
2854 &mut self,
2855 key: &crossterm::event::KeyEvent,
2856 ) -> Result<Option<bool>> {
2857 match key.code {
2858 KeyCode::PageDown | KeyCode::Char('f')
2859 if key.modifiers.contains(KeyModifiers::CONTROL) =>
2860 {
2861 NavigationBehavior::page_down(self);
2862 Ok(Some(false))
2863 }
2864 KeyCode::PageUp | KeyCode::Char('b')
2865 if key.modifiers.contains(KeyModifiers::CONTROL) =>
2866 {
2867 NavigationBehavior::page_up(self);
2868 Ok(Some(false))
2869 }
2870 _ => Ok(None), }
2872 }
2873
2874 fn try_handle_results_clipboard(
2876 &mut self,
2877 key: &crossterm::event::KeyEvent,
2878 ) -> Result<Option<bool>> {
2879 match key.code {
2880 KeyCode::Char('y') => {
2881 let selection_mode = self.get_selection_mode();
2882 debug!("'y' key pressed - selection_mode={:?}", selection_mode);
2883 match selection_mode {
2884 SelectionMode::Cell => {
2885 debug!("Yanking cell in cell selection mode");
2887 self.state_container
2888 .set_status_message("Yanking cell...".to_string());
2889 YankBehavior::yank_cell(self);
2890 }
2892 SelectionMode::Row => {
2893 debug!("'y' pressed in row mode - waiting for chord completion");
2896 self.state_container.set_status_message(
2897 "Press second key for chord: yy=row, yc=column, ya=all, yv=cell"
2898 .to_string(),
2899 );
2900 }
2901 SelectionMode::Column => {
2902 debug!("Yanking column in column selection mode");
2904 self.state_container
2905 .set_status_message("Yanking column...".to_string());
2906 YankBehavior::yank_column(self);
2907 }
2908 }
2909 Ok(Some(false))
2910 }
2911 _ => Ok(None),
2912 }
2913 }
2914
2915 fn try_handle_results_export(
2917 &mut self,
2918 key: &crossterm::event::KeyEvent,
2919 ) -> Result<Option<bool>> {
2920 match key.code {
2921 KeyCode::Char('e') if key.modifiers.contains(KeyModifiers::CONTROL) => {
2922 self.export_to_csv();
2923 Ok(Some(false))
2924 }
2925 KeyCode::Char('j') if key.modifiers.contains(KeyModifiers::CONTROL) => {
2926 self.export_to_json();
2927 Ok(Some(false))
2928 }
2929 _ => Ok(None),
2930 }
2931 }
2932
2933 fn try_handle_results_help(
2935 &mut self,
2936 key: &crossterm::event::KeyEvent,
2937 ) -> Result<Option<bool>> {
2938 match key.code {
2939 KeyCode::F(1) | KeyCode::Char('?') => {
2940 self.state_container.set_help_visible(true);
2941 self.set_mode_via_shadow_state(AppMode::Help, "help_requested");
2943 self.help_widget.on_enter();
2944 Ok(Some(false))
2945 }
2946 _ => Ok(None),
2947 }
2948 }
2949
2950 fn handle_results_input(&mut self, key: crossterm::event::KeyEvent) -> Result<bool> {
2951 debug!(
2953 "handle_results_input: Processing key {:?} in Results mode",
2954 key
2955 );
2956
2957 let is_vim_navigating = self.vim_search_adapter.borrow().is_navigating();
2959 let vim_is_active = self.vim_search_adapter.borrow().is_active();
2960 let has_search_pattern = !self.state_container.get_search_pattern().is_empty();
2961
2962 debug!(
2963 "Search state check: vim_navigating={}, vim_active={}, has_pattern={}, pattern='{}'",
2964 is_vim_navigating,
2965 vim_is_active,
2966 has_search_pattern,
2967 self.state_container.get_search_pattern()
2968 );
2969
2970 if key.code == KeyCode::Esc {
2972 info!("ESCAPE KEY DETECTED in Results mode!");
2973
2974 if is_vim_navigating || vim_is_active || has_search_pattern {
2975 info!("Escape pressed with active search - clearing via StateCoordinator");
2976 debug!(
2977 "Pre-clear state: vim_navigating={}, vim_active={}, pattern='{}'",
2978 is_vim_navigating,
2979 vim_is_active,
2980 self.state_container.get_search_pattern()
2981 );
2982
2983 use crate::ui::state::state_coordinator::StateCoordinator;
2985 StateCoordinator::cancel_search_with_refs(
2986 &mut self.state_container,
2987 &self.shadow_state,
2988 Some(&self.vim_search_adapter),
2989 );
2990
2991 let post_pattern = self.state_container.get_search_pattern();
2993 let post_vim_active = self.vim_search_adapter.borrow().is_active();
2994 info!(
2995 "Post-clear state: pattern='{}', vim_active={}",
2996 post_pattern, post_vim_active
2997 );
2998
2999 self.state_container
3000 .set_status_message("Search cleared".to_string());
3001 return Ok(false);
3002 }
3003 info!("Escape pressed but no active search to clear");
3004 }
3005
3006 let selection_mode = self.state_container.get_selection_mode();
3007
3008 debug!(
3009 "handle_results_input: key={:?}, selection_mode={:?}",
3010 key, selection_mode
3011 );
3012
3013 let normalized = self.state_container.normalize_key(key);
3015
3016 let action_context = self.build_action_context();
3018 let mapped_action = self.key_mapper.map_key(normalized, &action_context);
3019 let action = mapped_action.as_ref().map(|a| format!("{a:?}"));
3020
3021 if normalized != key {
3023 self.state_container
3024 .log_key_press(key, Some(format!("normalized to {normalized:?}")));
3025 }
3026 self.state_container
3027 .log_key_press(normalized, action.clone());
3028
3029 let normalized_key = normalized;
3030
3031 let action_context = self.build_action_context();
3035 debug!(
3036 "Action context for key {:?}: mode={:?}",
3037 normalized_key.code, action_context.mode
3038 );
3039 if let Some(action) = self.key_mapper.map_key(normalized_key, &action_context) {
3040 info!(
3041 "✓ Action system: key {:?} -> action {:?}",
3042 normalized_key.code, action
3043 );
3044 if let Ok(result) = self.try_handle_action(action, &action_context) {
3045 match result {
3046 ActionResult::Handled => {
3047 debug!("Action handled by new system");
3048 return Ok(false);
3049 }
3050 ActionResult::Exit => {
3051 debug!("Action requested exit");
3052 return Ok(true);
3053 }
3054 ActionResult::SwitchMode(mode) => {
3055 debug!("Action requested mode switch to {:?}", mode);
3056 self.state_container.set_mode(mode);
3057 return Ok(false);
3058 }
3059 ActionResult::Error(err) => {
3060 warn!("Action error: {}", err);
3061 self.state_container
3062 .set_status_message(format!("Error: {err}"));
3063 return Ok(false);
3064 }
3065 ActionResult::NotHandled => {
3066 debug!("Action not handled, falling back to legacy system");
3068 }
3069 }
3070 }
3071 }
3072
3073 if matches!(key.code, KeyCode::Char('G')) {
3075 debug!("Detected uppercase G key press!");
3076 }
3077
3078 if mapped_action.is_none() {
3092 debug!(
3093 "No action mapping for key {:?} in Results mode",
3094 normalized_key
3095 );
3096 }
3097
3098 if let Some(result) = self.try_handle_results_navigation(&normalized_key)? {
3100 return Ok(result);
3101 }
3102
3103 if let Some(result) = self.try_handle_results_clipboard(&normalized_key)? {
3105 return Ok(result);
3106 }
3107
3108 if let Some(result) = self.try_handle_results_export(&normalized_key)? {
3110 return Ok(result);
3111 }
3112
3113 if let Some(result) = self.try_handle_results_help(&normalized_key)? {
3115 return Ok(result);
3116 }
3117
3118 Ok(false)
3123 }
3124 fn execute_search_action(&mut self, mode: SearchMode, pattern: String) {
3127 debug!(target: "search", "execute_search_action called: mode={:?}, pattern='{}', current_app_mode={:?}, thread={:?}",
3128 mode, pattern, self.shadow_state.borrow().get_mode(), std::thread::current().id());
3129 match mode {
3130 SearchMode::Search => {
3131 debug!(target: "search", "Executing search with pattern: '{}', app_mode={:?}", pattern, self.shadow_state.borrow().get_mode());
3132 debug!(target: "search", "Search: current results count={}",
3133 self.state_container.get_buffer_dataview().map_or(0, |v| v.source().row_count()));
3134
3135 self.state_container.start_search(pattern.clone());
3137
3138 self.state_container.set_search_pattern(pattern.clone());
3139 self.perform_search();
3140 let matches_count = self.state_container.search().matches.len();
3141 debug!(target: "search", "After perform_search, app_mode={:?}, matches_found={}",
3142 self.shadow_state.borrow().get_mode(),
3143 matches_count);
3144
3145 if matches_count > 0 {
3147 let matches_for_vim: Vec<(usize, usize)> = {
3149 let search_manager = self.search_manager.borrow();
3150 search_manager
3151 .all_matches()
3152 .iter()
3153 .map(|m| (m.row, m.column))
3154 .collect()
3155 };
3156
3157 if let Some(dataview) = self.state_container.get_buffer_dataview() {
3159 info!(target: "search", "Syncing {} matches to VimSearchManager for pattern '{}'",
3160 matches_for_vim.len(), pattern);
3161 self.vim_search_adapter
3162 .borrow_mut()
3163 .set_search_state_from_external(
3164 pattern.clone(),
3165 matches_for_vim,
3166 dataview,
3167 );
3168 }
3169 }
3170
3171 if matches_count > 0 {
3173 let (row, col) = {
3175 let search_manager = self.search_manager.borrow();
3176 if let Some(first_match) = search_manager.first_match() {
3177 (first_match.row, first_match.column)
3178 } else {
3179 let search_state = self.state_container.search();
3181 if let Some((row, col, _, _)) = search_state.matches.first() {
3182 (*row, *col)
3183 } else {
3184 (0, 0)
3185 }
3186 }
3187 };
3188
3189 info!(target: "search", "NAVIGATION START: Moving to first match at data row={}, col={}", row, col);
3190
3191 self.state_container.set_table_selected_row(Some(row));
3194 self.state_container.set_selected_row(Some(row));
3195 info!(target: "search", " Set row position to {}", row);
3196
3197 {
3199 let mut nav = self.state_container.navigation_mut();
3200 nav.selected_column = col;
3201 }
3202 self.state_container.set_current_column_buffer(col);
3203 info!(target: "search", " Set column position to {}", col);
3204
3205 info!(target: "search", "Updating TableWidgetManager for debounced search to ({}, {})", row, col);
3207 self.table_widget_manager
3208 .borrow_mut()
3209 .on_debounced_search(row, col);
3210
3211 {
3213 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
3214 if let Some(ref mut viewport_manager) = *viewport_manager_borrow {
3215 let viewport_height = self.state_container.navigation().viewport_rows;
3217 let viewport_width = self.state_container.navigation().viewport_columns;
3218 let current_scroll = self.state_container.navigation().scroll_offset.0;
3219
3220 info!(target: "search", " Viewport dimensions: {}x{}, current_scroll: {}",
3221 viewport_height, viewport_width, current_scroll);
3222
3223 let new_row_offset = if row < current_scroll {
3225 info!(target: "search", " Match is above viewport, scrolling up to row {}", row);
3226 row } else if row >= current_scroll + viewport_height.saturating_sub(1) {
3228 let centered = row.saturating_sub(viewport_height / 2);
3229 info!(target: "search", " Match is below viewport, centering at row {}", centered);
3230 centered } else {
3232 info!(target: "search", " Match is already visible, keeping scroll at {}", current_scroll);
3233 current_scroll };
3235
3236 let current_col_scroll =
3238 self.state_container.navigation().scroll_offset.1;
3239 let new_col_offset = if col < current_col_scroll {
3240 info!(target: "search", " Match column {} is left of viewport (scroll={}), scrolling left", col, current_col_scroll);
3241 col } else if col >= current_col_scroll + viewport_width.saturating_sub(1) {
3243 let centered = col.saturating_sub(viewport_width / 4);
3244 info!(target: "search", " Match column {} is right of viewport (scroll={}, width={}), scrolling to {}",
3245 col, current_col_scroll, viewport_width, centered);
3246 centered } else {
3248 info!(target: "search", " Match column {} is visible, keeping scroll at {}", col, current_col_scroll);
3249 current_col_scroll };
3251
3252 viewport_manager.set_viewport(
3254 new_row_offset,
3255 new_col_offset,
3256 viewport_width as u16,
3257 viewport_height as u16,
3258 );
3259 info!(target: "search", " Set viewport to row_offset={}, col_offset={}", new_row_offset, new_col_offset);
3260
3261 let crosshair_row = row - new_row_offset;
3263 let crosshair_col = col - new_col_offset;
3264 viewport_manager.set_crosshair(crosshair_row, crosshair_col);
3265 info!(target: "search", " Set crosshair to viewport-relative ({}, {}), absolute was ({}, {})",
3266 crosshair_row, crosshair_col, row, col);
3267
3268 let mut nav = self.state_container.navigation_mut();
3270 nav.scroll_offset.0 = new_row_offset;
3271 nav.scroll_offset.1 = new_col_offset;
3272 }
3273 }
3274
3275 self.state_container.set_current_match(Some((row, col)));
3277
3278 {
3281 let mut nav = self.state_container.navigation_mut();
3282 nav.selected_row = row;
3283 nav.selected_column = col;
3284 }
3285 info!(target: "search", " Forced navigation state to row={}, col={}", row, col);
3286
3287 self.state_container.set_status_message(format!(
3289 "Match 1/{} at row {}, col {}",
3290 matches_count,
3291 row + 1,
3292 col + 1
3293 ));
3294 }
3295 }
3296 SearchMode::Filter => {
3297 use crate::ui::state::state_coordinator::StateCoordinator;
3298
3299 StateCoordinator::apply_filter_search_with_refs(
3301 &mut self.state_container,
3302 &self.shadow_state,
3303 &pattern,
3304 );
3305
3306 self.apply_filter(&pattern);
3308
3309 debug!(target: "search", "After apply_filter, filtered_count={}",
3310 self.state_container.get_buffer_dataview().map_or(0, super::super::data::data_view::DataView::row_count));
3311 }
3312 SearchMode::FuzzyFilter => {
3313 use crate::ui::state::state_coordinator::StateCoordinator;
3314
3315 StateCoordinator::apply_fuzzy_filter_search_with_refs(
3317 &mut self.state_container,
3318 &self.shadow_state,
3319 &pattern,
3320 );
3321
3322 self.apply_fuzzy_filter();
3324
3325 let indices_count = self.state_container.get_fuzzy_filter_indices().len();
3326 debug!(target: "search", "After apply_fuzzy_filter, matched_indices={}", indices_count);
3327 }
3328 SearchMode::ColumnSearch => {
3329 use crate::ui::state::state_coordinator::StateCoordinator;
3330
3331 debug!(target: "search", "Executing column search with pattern: '{}'", pattern);
3332
3333 StateCoordinator::apply_column_search_with_refs(
3335 &mut self.state_container,
3336 &self.shadow_state,
3337 &pattern,
3338 );
3339
3340 self.search_columns();
3342
3343 debug!(target: "search", "After search_columns, app_mode={:?}", self.shadow_state.borrow().get_mode());
3344 }
3345 }
3346 }
3347
3348 fn enter_search_mode(&mut self, mode: SearchMode) {
3349 debug!(target: "search", "enter_search_mode called for {:?}, current_mode={:?}, input_text='{}'",
3350 mode, self.shadow_state.borrow().get_mode(), self.state_container.get_input_text());
3351
3352 let current_sql = if self.shadow_state.borrow().is_in_results_mode() {
3354 let last_query = self.state_container.get_last_query();
3356 let input_text = self.state_container.get_input_text();
3357 debug!(target: "search", "COLUMN_SEARCH_SAVE_DEBUG: last_query='{}', input_text='{}'", last_query, input_text);
3358
3359 if !last_query.is_empty() {
3360 debug!(target: "search", "Using last_query for search mode: '{}'", last_query);
3361 last_query
3362 } else if !input_text.is_empty() {
3363 debug!(target: "search", "No last_query, using input_text as fallback: '{}'", input_text);
3366 input_text
3367 } else {
3368 warn!(target: "search", "No last_query or input_text found when entering search mode from Results!");
3370 String::new()
3371 }
3372 } else {
3373 self.get_input_text()
3375 };
3376
3377 let cursor_pos = current_sql.len();
3378
3379 debug!(
3380 "Entering {} mode, saving SQL: '{}', cursor: {}",
3381 mode.title(),
3382 current_sql,
3383 cursor_pos
3384 );
3385
3386 self.search_modes_widget
3388 .enter_mode(mode.clone(), current_sql, cursor_pos);
3389
3390 debug!(target: "mode", "Setting app mode from {:?} to {:?}", self.shadow_state.borrow().get_mode(), mode.to_app_mode());
3392 let trigger = match mode {
3393 SearchMode::ColumnSearch => "backslash_column_search",
3394 SearchMode::Search => "data_search_started",
3395 SearchMode::FuzzyFilter => "fuzzy_filter_started",
3396 SearchMode::Filter => "filter_started",
3397 };
3398 self.sync_mode(mode.to_app_mode(), trigger);
3399
3400 let search_type = match mode {
3402 SearchMode::ColumnSearch => crate::ui::state::shadow_state::SearchType::Column,
3403 SearchMode::Search => crate::ui::state::shadow_state::SearchType::Data,
3404 SearchMode::FuzzyFilter | SearchMode::Filter => {
3405 crate::ui::state::shadow_state::SearchType::Fuzzy
3406 }
3407 };
3408 self.shadow_state
3409 .borrow_mut()
3410 .observe_search_start(search_type, trigger);
3411
3412 match mode {
3414 SearchMode::Search => {
3415 self.state_container.clear_search();
3417 self.state_container.set_search_pattern(String::new());
3418 }
3419 SearchMode::Filter => {
3420 self.state_container.set_filter_pattern(String::new());
3421 self.state_container.filter_mut().clear();
3422 }
3423 SearchMode::FuzzyFilter => {
3424 self.state_container.set_fuzzy_filter_pattern(String::new());
3425 self.state_container.set_fuzzy_filter_indices(Vec::new());
3426 self.state_container.set_fuzzy_filter_active(false);
3427 }
3428 SearchMode::ColumnSearch => {
3429 self.state_container.clear_column_search();
3431 if let Some(dataview) = self.state_container.get_buffer_dataview_mut() {
3432 dataview.clear_column_search();
3433 }
3434
3435 }
3437 }
3438
3439 self.input = tui_input::Input::default();
3441 }
3443
3444 fn handle_search_modes_input(&mut self, key: crossterm::event::KeyEvent) -> Result<bool> {
3445 if key.code == KeyCode::Char('c') && key.modifiers.contains(KeyModifiers::CONTROL) {
3447 return Ok(true); }
3449
3450 let action = self.search_modes_widget.handle_key(key);
3453
3454 match action {
3455 SearchModesAction::Continue => {
3456 }
3458 SearchModesAction::InputChanged(mode, pattern) => {
3459 self.set_input_text_with_cursor(pattern.clone(), pattern.len());
3461
3462 match mode {
3464 SearchMode::Search => {
3465 self.state_container.set_search_pattern(pattern);
3466 }
3467 SearchMode::Filter => {
3468 self.state_container.set_filter_pattern(pattern.clone());
3469 let mut filter = self.state_container.filter_mut();
3470 filter.pattern = pattern.clone();
3471 filter.is_active = true;
3472 }
3473 SearchMode::FuzzyFilter => {
3474 self.state_container.set_fuzzy_filter_pattern(pattern);
3475 }
3476 SearchMode::ColumnSearch => {
3477 }
3479 }
3480 }
3481 SearchModesAction::ExecuteDebounced(mode, pattern) => {
3482 self.execute_search_action(mode, pattern);
3485 }
3487 SearchModesAction::Apply(mode, pattern) => {
3488 debug!(target: "search", "Apply action triggered for {:?} with pattern '{}'", mode, pattern);
3489 match mode {
3491 SearchMode::Search => {
3492 debug!(target: "search", "Search Apply: Applying search with pattern '{}'", pattern);
3493 self.execute_search_action(SearchMode::Search, pattern);
3495 debug!(target: "search", "Search Apply: last_query='{}', will restore saved SQL from widget", self.state_container.get_last_query());
3496 }
3498 SearchMode::Filter => {
3499 debug!(target: "search", "Filter Apply: Applying filter with pattern '{}'", pattern);
3500 self.state_container.set_filter_pattern(pattern.clone());
3501 {
3502 let mut filter = self.state_container.filter_mut();
3503 filter.pattern = pattern.clone();
3504 filter.is_active = true;
3505 } self.apply_filter(&pattern); debug!(target: "search", "Filter Apply: last_query='{}', will restore saved SQL from widget", self.state_container.get_last_query());
3508 }
3509 SearchMode::FuzzyFilter => {
3510 debug!(target: "search", "FuzzyFilter Apply: Applying filter with pattern '{}'", pattern);
3511 self.state_container.set_fuzzy_filter_pattern(pattern);
3512 self.apply_fuzzy_filter();
3513 debug!(target: "search", "FuzzyFilter Apply: last_query='{}', will restore saved SQL from widget", self.state_container.get_last_query());
3514 }
3515 SearchMode::ColumnSearch => {
3516 let column_info = {
3519 let column_search = self.state_container.column_search();
3520 if column_search.matching_columns.is_empty() {
3521 None
3522 } else {
3523 let current_match = column_search.current_match;
3524 Some(column_search.matching_columns[current_match].clone())
3525 }
3526 };
3527
3528 if let Some((col_idx, col_name)) = column_info {
3529 self.state_container.set_current_column(col_idx);
3530 self.state_container.set_current_column_buffer(col_idx);
3531
3532 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
3534 if let Some(viewport_manager) = viewport_manager_borrow.as_mut() {
3535 viewport_manager.set_current_column(col_idx);
3536 }
3537 drop(viewport_manager_borrow);
3538
3539 debug!(target: "column_search_sync", "ColumnSearch Apply: About to call sync_navigation_with_viewport() for column: {}", col_name);
3542 debug!(target: "column_search_sync", "ColumnSearch Apply: Pre-sync - viewport current_column: {}",
3543 if let Ok(vm) = self.viewport_manager.try_borrow() {
3544 vm.as_ref().map_or(0, super::viewport_manager::ViewportManager::get_crosshair_col)
3545 } else { 0 });
3546 self.sync_navigation_with_viewport();
3547 debug!(target: "column_search_sync", "ColumnSearch Apply: Post-sync - navigation current_column: {}",
3548 self.state_container.navigation().selected_column);
3549 debug!(target: "column_search_sync", "ColumnSearch Apply: sync_navigation_with_viewport() completed for column: {}", col_name);
3550
3551 self.state_container
3552 .set_status_message(format!("Jumped to column: {col_name}"));
3553 }
3554
3555 debug!(target: "search", "ColumnSearch Apply: Exiting without modifying input_text");
3558 debug!(target: "search", "ColumnSearch Apply: last_query='{}', will restore saved SQL from widget", self.state_container.get_last_query());
3559 }
3561 }
3562
3563 let saved_state = self.search_modes_widget.exit_mode();
3566
3567 if let Some((sql, cursor)) = saved_state {
3568 debug!(target: "search", "Exiting search mode. Original SQL was: '{}', cursor: {}", sql, cursor);
3569 debug!(target: "buffer", "Returning to Results mode, preserving last_query: '{}'",
3570 self.state_container.get_last_query());
3571
3572 debug!(target: "search", "Restoring saved SQL to input_text: '{}'", sql);
3575 self.set_input_text_with_cursor(sql, cursor);
3577 } else {
3578 if mode == SearchMode::ColumnSearch {
3580 let last_query = self.state_container.get_last_query();
3582 if last_query.is_empty() {
3583 debug!(target: "search", "Column search: No saved state or last_query, clearing input");
3584 self.set_input_text(String::new());
3585 } else {
3586 debug!(target: "search", "Column search: No saved state, restoring last_query: '{}'", last_query);
3587 self.set_input_text(last_query);
3588 }
3589 } else {
3590 debug!(target: "search", "No saved state from widget, keeping current SQL");
3591 }
3592 }
3593
3594 use crate::ui::state::state_coordinator::StateCoordinator;
3596
3597 if mode == SearchMode::ColumnSearch {
3600 debug!(target: "column_search_sync", "ColumnSearch Apply: Canceling column search completely with cancel_search_with_refs()");
3601
3602 if let Some(dataview) = self.state_container.get_buffer_dataview_mut() {
3604 dataview.clear_column_search();
3605 debug!(target: "column_search_sync", "ColumnSearch Apply: Cleared column search in DataView");
3606 }
3607
3608 StateCoordinator::cancel_search_with_refs(
3609 &mut self.state_container,
3610 &self.shadow_state,
3611 Some(&self.vim_search_adapter),
3612 );
3613 debug!(target: "column_search_sync", "ColumnSearch Apply: Column search canceled and mode switched to Results");
3615 } else {
3616 debug!(target: "column_search_sync", "Search Apply: About to call StateCoordinator::complete_search_with_refs() for mode: {:?}", mode);
3618 StateCoordinator::complete_search_with_refs(
3619 &mut self.state_container,
3620 &self.shadow_state,
3621 Some(&self.vim_search_adapter),
3622 AppMode::Results,
3623 "search_applied",
3624 );
3625 debug!(target: "column_search_sync", "Search Apply: StateCoordinator::complete_search_with_refs() completed - should now be in Results mode");
3626 }
3627
3628 let filter_msg = match mode {
3630 SearchMode::FuzzyFilter => {
3631 let query = self.state_container.get_last_query();
3632 format!(
3633 "Fuzzy filter applied. Query: '{}'. Press 'f' again to modify.",
3634 if query.len() > 30 {
3635 format!("{}...", &query[..30])
3636 } else {
3637 query
3638 }
3639 )
3640 }
3641 SearchMode::Filter => "Filter applied. Press 'F' again to modify.".to_string(),
3642 SearchMode::Search => {
3643 let matches = self.state_container.search().matches.len();
3644 if matches > 0 {
3645 format!("Found {matches} matches. Use n/N to navigate.")
3646 } else {
3647 "No matches found.".to_string()
3648 }
3649 }
3650 SearchMode::ColumnSearch => "Column search complete.".to_string(),
3651 };
3652 self.state_container.set_status_message(filter_msg);
3653 }
3654 SearchModesAction::Cancel => {
3655 let mode = self.shadow_state.borrow().get_mode();
3657 match mode {
3658 AppMode::FuzzyFilter => {
3659 debug!(target: "search", "FuzzyFilter Cancel: Clearing fuzzy filter");
3661 self.state_container.set_fuzzy_filter_pattern(String::new());
3662 self.apply_fuzzy_filter(); self.state_container.set_fuzzy_filter_indices(Vec::new());
3664 self.state_container.set_fuzzy_filter_active(false);
3665 }
3666 AppMode::Filter => {
3667 debug!(target: "search", "Filter Cancel: Clearing filter pattern and state");
3669 self.state_container.filter_mut().clear();
3670 self.state_container.set_filter_pattern(String::new());
3671 self.state_container.set_filter_active(false);
3672 self.apply_filter("");
3674 }
3675 AppMode::ColumnSearch => {
3676 self.state_container.clear_column_search();
3678 debug!(target: "search", "ColumnSearch Cancel: Exiting without modifying input_text");
3680 debug!(target: "search", "ColumnSearch Cancel: last_query='{}', will restore saved SQL from widget", self.state_container.get_last_query());
3681 }
3682 _ => {}
3683 }
3684
3685 if let Some((sql, cursor)) = self.search_modes_widget.exit_mode() {
3687 debug!(target: "search", "Cancel: Restoring saved SQL: '{}', cursor: {}", sql, cursor);
3688 if !sql.is_empty() {
3689 self.set_input_text_with_cursor(sql, cursor);
3691 }
3692 } else {
3693 debug!(target: "search", "Cancel: No saved SQL from widget");
3694 }
3695
3696 use crate::ui::state::state_coordinator::StateCoordinator;
3699 StateCoordinator::cancel_search_with_refs(
3700 &mut self.state_container,
3701 &self.shadow_state,
3702 Some(&self.vim_search_adapter),
3703 );
3704 }
3705 SearchModesAction::NextMatch => {
3706 debug!(target: "search", "NextMatch action, current_mode={:?}, widget_mode={:?}",
3707 self.shadow_state.borrow().get_mode(), self.search_modes_widget.current_mode());
3708
3709 if self.shadow_state.borrow().is_in_column_search()
3711 || self.search_modes_widget.current_mode() == Some(SearchMode::ColumnSearch)
3712 {
3713 debug!(target: "search", "Calling next_column_match");
3714 if !self.shadow_state.borrow().is_in_column_search() {
3716 debug!(target: "search", "WARNING: Mode mismatch - fixing");
3717 self.state_container.set_mode(AppMode::ColumnSearch);
3718 self.shadow_state.borrow_mut().observe_search_start(
3719 crate::ui::state::shadow_state::SearchType::Column,
3720 "column_search_mode_fix_next",
3721 );
3722 }
3723 self.next_column_match();
3724 } else {
3725 debug!(target: "search", "Not in ColumnSearch mode, skipping next_column_match");
3726 }
3727 }
3728 SearchModesAction::PreviousMatch => {
3729 debug!(target: "search", "PreviousMatch action, current_mode={:?}, widget_mode={:?}",
3730 self.shadow_state.borrow().get_mode(), self.search_modes_widget.current_mode());
3731
3732 if self.shadow_state.borrow().get_mode() == AppMode::ColumnSearch
3734 || self.search_modes_widget.current_mode() == Some(SearchMode::ColumnSearch)
3735 {
3736 debug!(target: "search", "Calling previous_column_match");
3737 if self.shadow_state.borrow().get_mode() != AppMode::ColumnSearch {
3739 debug!(target: "search", "WARNING: Mode mismatch - fixing");
3740 self.state_container.set_mode(AppMode::ColumnSearch);
3741 self.shadow_state.borrow_mut().observe_search_start(
3742 crate::ui::state::shadow_state::SearchType::Column,
3743 "column_search_mode_fix_prev",
3744 );
3745 }
3746 self.previous_column_match();
3747 } else {
3748 debug!(target: "search", "Not in ColumnSearch mode, skipping previous_column_match");
3749 }
3750 }
3751 SearchModesAction::PassThrough => {}
3752 }
3753
3754 Ok(false)
3757 }
3758
3759 fn handle_help_input(&mut self, key: crossterm::event::KeyEvent) -> Result<bool> {
3760 match key.code {
3763 crossterm::event::KeyCode::Esc | crossterm::event::KeyCode::Char('q') => {
3764 self.help_widget.on_exit();
3765 self.state_container.set_help_visible(false);
3766
3767 let target_mode = if self.state_container.has_dataview() {
3769 AppMode::Results
3770 } else {
3771 AppMode::Command
3772 };
3773
3774 self.set_mode_via_shadow_state(target_mode, "escape_from_help");
3776
3777 Ok(false)
3779 }
3780 _ => {
3781 self.help_widget.handle_key(key);
3783 Ok(false)
3784 }
3785 }
3786 }
3787
3788 fn handle_history_input(&mut self, key: crossterm::event::KeyEvent) -> Result<bool> {
3791 use crossterm::event::{KeyCode, KeyModifiers};
3793
3794 let result = match key.code {
3795 KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => {
3796 crate::ui::input::history_input_handler::HistoryInputResult::Exit
3797 }
3798 KeyCode::Esc => {
3799 let original_input = self.state_container.cancel_history_search();
3801 if let Some(buffer) = self.state_container.current_buffer_mut() {
3802 self.shadow_state.borrow_mut().set_mode(
3803 crate::buffer::AppMode::Command,
3804 buffer,
3805 "history_cancelled",
3806 );
3807 buffer.set_status_message("History search cancelled".to_string());
3808 }
3809 crate::ui::input::history_input_handler::HistoryInputResult::SwitchToCommand(Some(
3810 (original_input, 0),
3811 ))
3812 }
3813 KeyCode::Enter => {
3814 if let Some(command) = self.state_container.accept_history_search() {
3816 if let Some(buffer) = self.state_container.current_buffer_mut() {
3817 self.shadow_state.borrow_mut().set_mode(
3818 crate::buffer::AppMode::Command,
3819 buffer,
3820 "history_accepted",
3821 );
3822 buffer.set_status_message(
3823 "Command loaded from history (cursor at start)".to_string(),
3824 );
3825 }
3826 crate::ui::input::history_input_handler::HistoryInputResult::SwitchToCommand(
3828 Some((command, 0)),
3829 )
3830 } else {
3831 crate::ui::input::history_input_handler::HistoryInputResult::Continue
3832 }
3833 }
3834 KeyCode::Up => {
3835 self.state_container.history_search_previous();
3836 crate::ui::input::history_input_handler::HistoryInputResult::Continue
3837 }
3838 KeyCode::Down => {
3839 self.state_container.history_search_next();
3840 crate::ui::input::history_input_handler::HistoryInputResult::Continue
3841 }
3842 KeyCode::Char('r') if key.modifiers.contains(KeyModifiers::CONTROL) => {
3843 self.state_container.history_search_next();
3845 crate::ui::input::history_input_handler::HistoryInputResult::Continue
3846 }
3847 KeyCode::Backspace => {
3848 self.state_container.history_search_backspace();
3849 crate::ui::input::history_input_handler::HistoryInputResult::Continue
3850 }
3851 KeyCode::Char(c) => {
3852 self.state_container.history_search_add_char(c);
3853 crate::ui::input::history_input_handler::HistoryInputResult::Continue
3854 }
3855 _ => crate::ui::input::history_input_handler::HistoryInputResult::Continue,
3856 };
3857
3858 match result {
3860 crate::ui::input::history_input_handler::HistoryInputResult::Exit => return Ok(true),
3861 crate::ui::input::history_input_handler::HistoryInputResult::SwitchToCommand(
3862 input_data,
3863 ) => {
3864 if let Some((text, cursor_pos)) = input_data {
3865 self.set_input_text_with_cursor(text, cursor_pos);
3866 self.sync_all_input_states();
3868 }
3869 }
3870 crate::ui::input::history_input_handler::HistoryInputResult::Continue => {
3871 if crate::ui::input::history_input_handler::key_updates_search(key) {
3873 self.update_history_matches_in_container();
3874 }
3875 }
3876 }
3877
3878 Ok(false)
3879 }
3880
3881 fn update_history_matches_in_container(&mut self) {
3883 let (current_columns, current_source_str) =
3885 if let Some(dataview) = self.state_container.get_buffer_dataview() {
3886 (
3887 dataview.column_names(), Some(dataview.source().name.clone()), )
3890 } else {
3891 (vec![], None)
3892 };
3893
3894 let current_source = current_source_str.as_deref();
3895 let query = self.state_container.history_search().query.clone();
3896
3897 self.state_container.update_history_search_with_schema(
3898 query,
3899 ¤t_columns,
3900 current_source,
3901 );
3902 }
3903
3904 fn handle_debug_input(&mut self, key: crossterm::event::KeyEvent) -> Result<bool> {
3905 let mut ctx = crate::ui::input::input_handlers::DebugInputContext {
3907 buffer_manager: self.state_container.buffers_mut(),
3908 debug_widget: &mut self.debug_widget,
3909 shadow_state: &self.shadow_state,
3910 };
3911
3912 let should_quit = crate::ui::input::input_handlers::handle_debug_input(&mut ctx, key)?;
3913
3914 if !should_quit {
3917 match key.code {
3918 KeyCode::Char('t') if key.modifiers.contains(KeyModifiers::CONTROL) => {
3919 self.yank_as_test_case();
3921 }
3922 KeyCode::Char('y') if key.modifiers.contains(KeyModifiers::SHIFT) => {
3923 self.yank_debug_with_context();
3925 }
3926 _ => {}
3927 }
3928 }
3929
3930 Ok(should_quit)
3931 }
3933
3934 fn handle_pretty_query_input(&mut self, key: crossterm::event::KeyEvent) -> Result<bool> {
3935 let mut ctx = crate::ui::input::input_handlers::DebugInputContext {
3937 buffer_manager: self.state_container.buffers_mut(),
3938 debug_widget: &mut self.debug_widget,
3939 shadow_state: &self.shadow_state,
3940 };
3941
3942 crate::ui::input::input_handlers::handle_pretty_query_input(&mut ctx, key)
3943 }
3944
3945 pub fn execute_query_v2(&mut self, query: &str) -> Result<()> {
3946 let context = self.query_orchestrator.execute_query(
3948 query,
3949 &mut self.state_container,
3950 &self.vim_search_adapter,
3951 );
3952
3953 match context {
3954 Ok(ctx) => {
3955 self.state_container
3957 .set_dataview(Some(ctx.result.dataview.clone()));
3958
3959 self.update_viewport_manager(Some(ctx.result.dataview.clone()));
3961
3962 self.state_container
3964 .update_data_size(ctx.result.stats.row_count, ctx.result.stats.column_count);
3965
3966 self.calculate_optimal_column_widths();
3968
3969 self.state_container
3971 .set_status_message(ctx.result.status_message());
3972
3973 self.state_container
3975 .command_history_mut()
3976 .add_entry_with_schema(
3977 ctx.query.clone(),
3978 true,
3979 Some(ctx.result.stats.execution_time.as_millis() as u64),
3980 ctx.result.column_names(),
3981 Some(ctx.result.table_name()),
3982 )?;
3983
3984 self.sync_mode(AppMode::Results, "execute_query_success");
3986
3987 self.reset_table_state();
3989
3990 Ok(())
3991 }
3992 Err(e) => {
3993 let error_msg = format!("Query error: {e}");
3994 self.state_container.set_status_message(error_msg.clone());
3995
3996 self.state_container
3998 .command_history_mut()
3999 .add_entry_with_schema(query.to_string(), false, None, vec![], None)?;
4000
4001 Err(e)
4002 }
4003 }
4004 }
4005
4006 fn handle_completion(&mut self) {
4007 let cursor_pos = self.get_input_cursor();
4008 let query_str = self.get_input_text();
4009 let query = query_str.as_str();
4010
4011 let hybrid_result = self.hybrid_parser.get_completions(query, cursor_pos);
4012 if !hybrid_result.suggestions.is_empty() {
4013 self.state_container.set_status_message(format!(
4014 "Suggestions: {}",
4015 hybrid_result.suggestions.join(", ")
4016 ));
4017 }
4018 }
4019
4020 fn apply_completion(&mut self) {
4021 let cursor_pos = self.get_input_cursor();
4022 let query = self.get_input_text();
4023
4024 let suggestion = match self.get_or_refresh_completion(&query, cursor_pos) {
4026 Some(s) => s,
4027 None => return,
4028 };
4029
4030 self.apply_completion_to_input(&query, cursor_pos, &suggestion);
4032 }
4033
4034 fn get_or_refresh_completion(&mut self, query: &str, cursor_pos: usize) -> Option<String> {
4037 let is_same_context = self
4038 .state_container
4039 .is_same_completion_context(query, cursor_pos);
4040
4041 if !is_same_context {
4042 let hybrid_result = self.hybrid_parser.get_completions(query, cursor_pos);
4044 if hybrid_result.suggestions.is_empty() {
4045 self.state_container
4046 .set_status_message("No completions available".to_string());
4047 return None;
4048 }
4049
4050 self.state_container
4051 .set_completion_suggestions(hybrid_result.suggestions);
4052 } else if self.state_container.is_completion_active() {
4053 self.state_container.next_completion();
4055 } else {
4056 self.state_container
4057 .set_status_message("No completions available".to_string());
4058 return None;
4059 }
4060
4061 if let Some(sugg) = self.state_container.get_current_completion() {
4063 Some(sugg)
4064 } else {
4065 self.state_container
4066 .set_status_message("No completion selected".to_string());
4067 None
4068 }
4069 }
4070
4071 fn apply_completion_to_input(&mut self, query: &str, cursor_pos: usize, suggestion: &str) {
4073 let partial_word =
4074 crate::ui::utils::text_operations::extract_partial_word_at_cursor(query, cursor_pos);
4075
4076 if let Some(partial) = partial_word {
4077 self.apply_partial_completion(query, cursor_pos, &partial, suggestion);
4078 } else {
4079 self.apply_full_insertion(query, cursor_pos, suggestion);
4080 }
4081 }
4082
4083 fn apply_partial_completion(
4085 &mut self,
4086 query: &str,
4087 cursor_pos: usize,
4088 partial: &str,
4089 suggestion: &str,
4090 ) {
4091 let result = crate::ui::utils::text_operations::apply_completion_to_text(
4093 query, cursor_pos, partial, suggestion,
4094 );
4095
4096 self.set_input_text_with_cursor(result.new_text.clone(), result.new_cursor_position);
4098
4099 self.state_container
4101 .update_completion_context(result.new_text.clone(), result.new_cursor_position);
4102
4103 let completion = self.state_container.completion();
4105 let suggestion_info = if completion.suggestions.len() > 1 {
4106 format!(
4107 "Completed: {} ({}/{} - Tab for next)",
4108 suggestion,
4109 completion.current_index + 1,
4110 completion.suggestions.len()
4111 )
4112 } else {
4113 format!("Completed: {suggestion}")
4114 };
4115 drop(completion);
4116 self.state_container.set_status_message(suggestion_info);
4117 }
4118
4119 fn apply_full_insertion(&mut self, query: &str, cursor_pos: usize, suggestion: &str) {
4121 let before_cursor = &query[..cursor_pos];
4123 let after_cursor = &query[cursor_pos..];
4124 let new_query = format!("{before_cursor}{suggestion}{after_cursor}");
4125
4126 let cursor_pos_new = if suggestion.ends_with("('')") {
4128 cursor_pos + suggestion.len() - 2
4130 } else {
4131 cursor_pos + suggestion.len()
4132 };
4133
4134 self.set_input_text(new_query.clone());
4136
4137 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
4139 buffer.set_input_cursor_position(cursor_pos_new);
4140 self.sync_all_input_states();
4142 }
4143
4144 self.state_container
4146 .update_completion_context(new_query, cursor_pos_new);
4147
4148 self.state_container
4149 .set_status_message(format!("Inserted: {suggestion}"));
4150 }
4151
4152 fn get_column_count(&self) -> usize {
4159 if let Some(provider) = self.get_data_provider() {
4161 provider.get_column_count()
4162 } else {
4163 0
4164 }
4165 }
4166
4167 fn get_column_names_via_provider(&self) -> Vec<String> {
4172 if let Some(provider) = self.get_data_provider() {
4173 provider.get_column_names()
4174 } else {
4175 Vec::new()
4176 }
4177 }
4178
4179 fn toggle_column_pin_impl(&mut self) {
4184 let visual_col_idx = if let Some(ref viewport_manager) = *self.viewport_manager.borrow() {
4186 viewport_manager.get_crosshair_col()
4187 } else {
4188 0
4189 };
4190
4191 let column_name = if let Some(dataview) = self.state_container.get_buffer_dataview() {
4193 let all_columns = dataview.column_names();
4194 all_columns.get(visual_col_idx).cloned()
4195 } else {
4196 None
4197 };
4198
4199 if let Some(col_name) = column_name {
4200 if let Some(dataview) = self.state_container.get_buffer_dataview_mut() {
4201 let pinned_names = dataview.get_pinned_column_names();
4203 if pinned_names.contains(&col_name) {
4204 dataview.unpin_column_by_name(&col_name);
4206 self.state_container
4207 .set_status_message(format!("Column '{col_name}' unpinned"));
4208 } else {
4209 match dataview.pin_column_by_name(&col_name) {
4211 Ok(()) => {
4212 self.state_container
4213 .set_status_message(format!("Column '{col_name}' pinned [P]"));
4214 }
4215 Err(e) => {
4216 self.state_container.set_status_message(e.to_string());
4217 }
4218 }
4219 }
4220
4221 if let Some(updated_dataview) = self.state_container.get_buffer_dataview() {
4223 self.update_viewport_manager(Some(updated_dataview.clone()));
4224 }
4225 }
4226 } else {
4227 self.state_container
4228 .set_status_message("No column to pin at current position".to_string());
4229 }
4230 }
4231
4232 fn clear_all_pinned_columns_impl(&mut self) {
4233 if let Some(dataview) = self.state_container.get_buffer_dataview_mut() {
4234 dataview.clear_pinned_columns();
4235 }
4236 self.state_container
4237 .set_status_message("All columns unpinned".to_string());
4238
4239 if let Some(updated_dataview) = self.state_container.get_buffer_dataview() {
4241 self.update_viewport_manager(Some(updated_dataview.clone()));
4242 }
4243 }
4244
4245 fn calculate_column_statistics(&mut self) {
4246 use std::time::Instant;
4247
4248 let start_total = Instant::now();
4249
4250 let (column_name, data_to_analyze) = {
4252 let headers = self.get_column_names_via_provider();
4254 if headers.is_empty() {
4255 return;
4256 }
4257
4258 let current_column = self.state_container.get_current_column();
4259 if current_column >= headers.len() {
4260 return;
4261 }
4262
4263 let column_name = headers[current_column].clone();
4264
4265 let data_to_analyze: Vec<String> = if let Some(provider) = self.get_data_provider() {
4267 let row_count = provider.get_row_count();
4268 let mut column_data = Vec::with_capacity(row_count);
4269
4270 for row_idx in 0..row_count {
4271 if let Some(row) = provider.get_row(row_idx) {
4272 if current_column < row.len() {
4273 column_data.push(row[current_column].clone());
4274 } else {
4275 column_data.push(String::new());
4277 }
4278 }
4279 }
4280
4281 column_data
4282 } else {
4283 return;
4285 };
4286
4287 (column_name, data_to_analyze)
4288 };
4289
4290 let data_refs: Vec<&str> = data_to_analyze
4292 .iter()
4293 .map(std::string::String::as_str)
4294 .collect();
4295
4296 let analyzer_stats = self
4298 .data_analyzer
4299 .calculate_column_statistics(&column_name, &data_refs);
4300
4301 let stats = ColumnStatistics {
4303 column_name: analyzer_stats.column_name,
4304 column_type: match analyzer_stats.data_type {
4305 data_analyzer::ColumnType::Integer | data_analyzer::ColumnType::Float => {
4306 ColumnType::Numeric
4307 }
4308 data_analyzer::ColumnType::String
4309 | data_analyzer::ColumnType::Boolean
4310 | data_analyzer::ColumnType::Date => ColumnType::String,
4311 data_analyzer::ColumnType::Mixed => ColumnType::Mixed,
4312 data_analyzer::ColumnType::Unknown => ColumnType::Mixed,
4313 },
4314 total_count: analyzer_stats.total_values,
4315 null_count: analyzer_stats.null_values,
4316 unique_count: analyzer_stats.unique_values,
4317 frequency_map: analyzer_stats.frequency_map.clone(),
4318 min: analyzer_stats
4320 .min_value
4321 .as_ref()
4322 .and_then(|s| s.parse::<f64>().ok()),
4323 max: analyzer_stats
4324 .max_value
4325 .as_ref()
4326 .and_then(|s| s.parse::<f64>().ok()),
4327 sum: analyzer_stats.sum_value,
4328 mean: analyzer_stats.avg_value,
4329 median: analyzer_stats.median_value,
4330 };
4331
4332 let elapsed = start_total.elapsed();
4334
4335 self.state_container.set_column_stats(Some(stats));
4336
4337 self.state_container.set_status_message(format!(
4339 "Column stats: {:.1}ms for {} values ({} unique)",
4340 elapsed.as_secs_f64() * 1000.0,
4341 data_to_analyze.len(),
4342 analyzer_stats.unique_values
4343 ));
4344
4345 self.state_container.set_mode(AppMode::ColumnStats);
4346 self.shadow_state
4347 .borrow_mut()
4348 .observe_mode_change(AppMode::ColumnStats, "column_stats_requested");
4349 }
4350
4351 fn check_parser_error(&self, query: &str) -> Option<String> {
4352 crate::ui::operations::simple_operations::check_parser_error(query)
4353 }
4354
4355 fn update_viewport_size(&mut self) {
4356 if let Ok((width, height)) = crossterm::terminal::size() {
4358 let data_rows_available = Self::calculate_available_data_rows(height);
4360
4361 let visible_rows = {
4363 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
4364 let viewport_manager = viewport_manager_borrow
4365 .as_mut()
4366 .expect("ViewportManager must exist for viewport size update");
4367 viewport_manager.update_terminal_size(width, data_rows_available)
4368 };
4369
4370 self.state_container.set_last_visible_rows(visible_rows);
4372
4373 self.state_container
4375 .navigation_mut()
4376 .set_viewport_size(visible_rows, width as usize);
4377
4378 info!(target: "navigation", "update_viewport_size - viewport set to: {}x{} rows", visible_rows, width);
4379 }
4380 }
4381
4382 fn perform_search(&mut self) {
4386 if let Some(dataview) = self.get_current_data() {
4387 let data: Vec<Vec<String>> = (0..dataview.row_count())
4389 .filter_map(|i| dataview.get_row(i))
4390 .map(|row| {
4391 row.values
4392 .iter()
4393 .map(std::string::ToString::to_string)
4394 .collect()
4395 })
4396 .collect();
4397
4398 let pattern = self.state_container.get_search_pattern().to_string();
4400
4401 info!(target: "search", "=== SEARCH START ===");
4402 info!(target: "search", "Pattern: '{}', case_insensitive: {}",
4403 pattern, self.state_container.is_case_insensitive());
4404 info!(target: "search", "Data dimensions: {} rows x {} columns",
4405 data.len(), data.first().map_or(0, std::vec::Vec::len));
4406
4407 let column_names = dataview.column_names();
4409 info!(target: "search", "Column names (first 5): {:?}",
4410 column_names.iter().take(5).collect::<Vec<_>>());
4411
4412 for (i, row) in data.iter().take(10).enumerate() {
4414 info!(target: "search", " Data row {}: [{}]", i,
4415 row.iter().take(5).map(|s| format!("'{s}'")).collect::<Vec<_>>().join(", "));
4416 }
4417
4418 let visible_columns = None;
4420
4421 let match_count = {
4423 let mut search_manager = self.search_manager.borrow_mut();
4424
4425 search_manager.clear();
4427 info!(target: "search", "Cleared previous search results");
4428
4429 search_manager.set_case_sensitive(!self.state_container.is_case_insensitive());
4431 info!(target: "search", "Set case_sensitive to {}", !self.state_container.is_case_insensitive());
4432
4433 let count = search_manager.search(&pattern, &data, visible_columns);
4435 info!(target: "search", "SearchManager.search() returned {} matches", count);
4436 count
4437 };
4438
4439 info!(target: "search", "SearchManager found {} matches", match_count);
4440
4441 if match_count > 0 {
4443 let (first_row, first_col) = {
4445 let search_manager = self.search_manager.borrow();
4446 if let Some(first_match) = search_manager.first_match() {
4447 info!(target: "search", "FIRST MATCH DETAILS:");
4448 info!(target: "search", " Data coordinates: row={}, col={}",
4449 first_match.row, first_match.column);
4450 info!(target: "search", " Matched value: '{}'", first_match.value);
4451 info!(target: "search", " Highlight range: {:?}", first_match.highlight_range);
4452
4453 for (i, m) in search_manager.all_matches().iter().take(5).enumerate() {
4455 info!(target: "search", " Match #{}: row={}, col={}, value='{}'",
4456 i + 1, m.row, m.column, m.value);
4457 }
4458
4459 (first_match.row, first_match.column)
4460 } else {
4461 warn!(target: "search", "SearchManager reported matches but first_match() is None!");
4462 (0, 0)
4463 }
4464 };
4465
4466 self.state_container.set_table_selected_row(Some(first_row));
4468
4469 info!(target: "search", "Updating TableWidgetManager to navigate to ({}, {})", first_row, first_col);
4471 self.table_widget_manager
4472 .borrow_mut()
4473 .navigate_to_search_match(first_row, first_col);
4474
4475 if let Some(dataview) = self.get_current_data() {
4477 if let Some(row_data) = dataview.get_row(first_row) {
4478 if first_col < row_data.values.len() {
4479 info!(target: "search", "VALUE AT NAVIGATION TARGET ({}, {}): '{}'",
4480 first_row, first_col, row_data.values[first_col]);
4481 }
4482 }
4483 }
4484
4485 let buffer_matches: Vec<(usize, usize)> = {
4487 let search_manager = self.search_manager.borrow();
4488 search_manager
4489 .all_matches()
4490 .iter()
4491 .map(|m| (m.row, m.column))
4492 .collect()
4493 };
4494
4495 let state_matches: Vec<(usize, usize, usize, usize)> = {
4498 let search_manager = self.search_manager.borrow();
4499 search_manager
4500 .all_matches()
4501 .iter()
4502 .map(|m| {
4503 (m.row, m.column, m.row, m.column)
4505 })
4506 .collect()
4507 };
4508 self.state_container.search_mut().matches = state_matches;
4509
4510 self.state_container
4511 .set_search_matches_with_index(buffer_matches.clone(), 0);
4512 self.state_container
4513 .set_current_match(Some((first_row, first_col)));
4514 self.state_container
4515 .set_status_message(format!("Found {match_count} matches"));
4516
4517 info!(target: "search", "Search found {} matches for pattern '{}'", match_count, pattern);
4518 } else {
4519 self.state_container.search_mut().matches.clear();
4521 self.state_container.clear_search_state();
4522 self.state_container.set_current_match(None);
4523
4524 info!(target: "search", "No matches found for pattern '{}'", pattern);
4525 }
4526 }
4527 }
4528
4529 fn start_vim_search(&mut self) {
4533 info!(target: "vim_search", "Starting vim search mode");
4534
4535 self.vim_search_adapter.borrow_mut().start_search();
4537
4538 self.shadow_state.borrow_mut().observe_search_start(
4540 crate::ui::state::shadow_state::SearchType::Vim,
4541 "slash_key_pressed",
4542 );
4543
4544 self.enter_search_mode(SearchMode::Search);
4546 }
4547
4548 fn vim_search_next(&mut self) {
4550 if !self.vim_search_adapter.borrow().is_navigating() {
4551 let resumed = {
4553 let mut viewport_borrow = self.viewport_manager.borrow_mut();
4554 if let Some(ref mut viewport) = *viewport_borrow {
4555 if let Some(dataview) = self.state_container.get_buffer_dataview() {
4556 self.vim_search_adapter
4557 .borrow_mut()
4558 .resume_last_search(dataview, viewport)
4559 } else {
4560 false
4561 }
4562 } else {
4563 false
4564 }
4565 };
4566
4567 if !resumed {
4568 self.state_container
4569 .set_status_message("No previous search pattern".to_string());
4570 return;
4571 }
4572 }
4573
4574 let result = {
4576 let mut viewport_borrow = self.viewport_manager.borrow_mut();
4577 if let Some(ref mut viewport) = *viewport_borrow {
4578 let search_match = self.vim_search_adapter.borrow_mut().next_match(viewport);
4579 if search_match.is_some() {
4580 let match_info = self.vim_search_adapter.borrow().get_match_info();
4581 search_match.map(|m| (m, match_info))
4582 } else {
4583 None
4584 }
4585 } else {
4586 None
4587 }
4588 }; if let Some((ref search_match, _)) = result {
4592 info!(target: "search",
4594 "=== UPDATING TUI STATE FOR VIM SEARCH ===");
4595 info!(target: "search",
4596 "Setting selected row to: {}", search_match.row);
4597 info!(target: "search",
4598 "Setting selected column to: {} (visual col)", search_match.col);
4599
4600 if let Some(dataview) = self.state_container.get_buffer_dataview() {
4602 info!(target: "search",
4604 "DEBUG: Fetching row {} from dataview with {} total rows",
4605 search_match.row, dataview.row_count());
4606
4607 if let Some(row_data) = dataview.get_row(search_match.row) {
4608 info!(target: "search",
4610 "Row {} has {} values, first 5: {:?}",
4611 search_match.row, row_data.values.len(),
4612 row_data.values.iter().take(5).map(std::string::ToString::to_string).collect::<Vec<_>>());
4613
4614 if search_match.col < row_data.values.len() {
4615 let actual_value = &row_data.values[search_match.col];
4616 info!(target: "search",
4617 "Actual value at row {} col {}: '{}'",
4618 search_match.row, search_match.col, actual_value);
4619
4620 let pattern = self
4622 .vim_search_adapter
4623 .borrow()
4624 .get_pattern()
4625 .unwrap_or_default();
4626 let contains_pattern = actual_value
4627 .to_string()
4628 .to_lowercase()
4629 .contains(&pattern.to_lowercase());
4630 if contains_pattern {
4631 info!(target: "search",
4632 "✓ Confirmed: Cell contains pattern '{}'", pattern);
4633 } else {
4634 warn!(target: "search",
4635 "WARNING: Cell at ({}, {}) = '{}' does NOT contain pattern '{}'!",
4636 search_match.row, search_match.col, actual_value, pattern);
4637 }
4638 } else {
4639 warn!(target: "search",
4640 "Column {} is out of bounds for row {} (row has {} values)",
4641 search_match.col, search_match.row, row_data.values.len());
4642 }
4643 } else {
4644 warn!(target: "search",
4645 "Could not get row data for row {}", search_match.row);
4646 }
4647
4648 let display_columns = dataview.get_display_columns();
4650 info!(target: "search",
4651 "Display columns mapping (first 10): {:?}",
4652 display_columns.iter().take(10).collect::<Vec<_>>());
4653 }
4654
4655 self.state_container
4656 .set_table_selected_row(Some(search_match.row));
4657 self.state_container
4658 .set_selected_row(Some(search_match.row));
4659
4660 info!(target: "search",
4663 "Setting column to visual index {}",
4664 search_match.col);
4665
4666 self.state_container
4668 .set_current_column_buffer(search_match.col);
4669 self.state_container.navigation_mut().selected_column = search_match.col;
4670
4671 self.state_container.select_column(search_match.col);
4673 info!(target: "search",
4674 "Updated SelectionState column to: {}", search_match.col);
4675
4676 info!(target: "search",
4678 "Column state after update: nav.selected_column={}, buffer.current_column={}, selection.selected_column={}",
4679 self.state_container.navigation().selected_column,
4680 self.state_container.get_current_column(),
4681 self.state_container.selection().selected_column);
4682
4683 self.sync_navigation_with_viewport();
4686
4687 let scroll_offset = self.state_container.navigation().scroll_offset;
4689 self.state_container.set_scroll_offset(scroll_offset);
4690
4691 info!(target: "search", "Updating TableWidgetManager for vim search navigation to ({}, {})",
4693 search_match.row, search_match.col);
4694 self.table_widget_manager
4695 .borrow_mut()
4696 .navigate_to(search_match.row, search_match.col);
4697
4698 info!(target: "search",
4700 "After TableWidgetManager update: nav.selected_column={}, buffer.current_column={}, selection.selected_column={}",
4701 self.state_container.navigation().selected_column,
4702 self.state_container.get_current_column(),
4703 self.state_container.selection().selected_column);
4704
4705 }
4709
4710 if let Some((search_match, match_info)) = result {
4712 if let Some((current, total)) = match_info {
4713 self.state_container.set_status_message(format!(
4714 "Match {}/{} at ({}, {})",
4715 current,
4716 total,
4717 search_match.row + 1,
4718 search_match.col + 1
4719 ));
4720 }
4721
4722 info!(target: "search",
4724 "FINAL vim_search_next state: nav.selected_column={}, buffer.current_column={}, selection.selected_column={}",
4725 self.state_container.navigation().selected_column,
4726 self.state_container.get_current_column(),
4727 self.state_container.selection().selected_column);
4728
4729 if let Some(dataview) = self.state_container.get_buffer_dataview() {
4731 let final_row = self.state_container.navigation().selected_row;
4732 let final_col = self.state_container.navigation().selected_column;
4733
4734 if let Some(row_data) = dataview.get_row(final_row) {
4735 if final_col < row_data.values.len() {
4736 let actual_value = &row_data.values[final_col];
4737 info!(target: "search",
4738 "VERIFICATION: Cell at final position ({}, {}) contains: '{}'",
4739 final_row, final_col, actual_value);
4740
4741 let pattern = self
4742 .vim_search_adapter
4743 .borrow()
4744 .get_pattern()
4745 .unwrap_or_default();
4746 if !actual_value
4747 .to_string()
4748 .to_lowercase()
4749 .contains(&pattern.to_lowercase())
4750 {
4751 error!(target: "search",
4752 "ERROR: Final cell '{}' does NOT contain search pattern '{}'!",
4753 actual_value, pattern);
4754 }
4755 }
4756 }
4757 }
4758 }
4759 }
4760
4761 fn vim_search_previous(&mut self) {
4763 if !self.vim_search_adapter.borrow().is_navigating() {
4764 let resumed = {
4766 let mut viewport_borrow = self.viewport_manager.borrow_mut();
4767 if let Some(ref mut viewport) = *viewport_borrow {
4768 if let Some(dataview) = self.state_container.get_buffer_dataview() {
4769 self.vim_search_adapter
4770 .borrow_mut()
4771 .resume_last_search(dataview, viewport)
4772 } else {
4773 false
4774 }
4775 } else {
4776 false
4777 }
4778 };
4779
4780 if !resumed {
4781 self.state_container
4782 .set_status_message("No previous search pattern".to_string());
4783 return;
4784 }
4785 }
4786
4787 let result = {
4789 let mut viewport_borrow = self.viewport_manager.borrow_mut();
4790 if let Some(ref mut viewport) = *viewport_borrow {
4791 let search_match = self
4792 .vim_search_adapter
4793 .borrow_mut()
4794 .previous_match(viewport);
4795 if search_match.is_some() {
4796 let match_info = self.vim_search_adapter.borrow().get_match_info();
4797 search_match.map(|m| (m, match_info))
4798 } else {
4799 None
4800 }
4801 } else {
4802 None
4803 }
4804 }; if let Some((ref search_match, _)) = result {
4808 self.state_container
4809 .set_table_selected_row(Some(search_match.row));
4810 self.state_container
4811 .set_selected_row(Some(search_match.row));
4812
4813 info!(target: "search",
4816 "Setting column to visual index {}",
4817 search_match.col);
4818
4819 self.state_container
4821 .set_current_column_buffer(search_match.col);
4822 self.state_container.navigation_mut().selected_column = search_match.col;
4823
4824 self.state_container.select_column(search_match.col);
4826 info!(target: "search",
4827 "Updated SelectionState column to: {}", search_match.col);
4828
4829 info!(target: "search",
4831 "Column state after update: nav.selected_column={}, buffer.current_column={}, selection.selected_column={}",
4832 self.state_container.navigation().selected_column,
4833 self.state_container.get_current_column(),
4834 self.state_container.selection().selected_column);
4835
4836 self.sync_navigation_with_viewport();
4839
4840 let scroll_offset = self.state_container.navigation().scroll_offset;
4842 self.state_container.set_scroll_offset(scroll_offset);
4843
4844 info!(target: "search", "Updating TableWidgetManager for vim search navigation to ({}, {})",
4846 search_match.row, search_match.col);
4847 self.table_widget_manager
4848 .borrow_mut()
4849 .navigate_to(search_match.row, search_match.col);
4850
4851 info!(target: "search",
4853 "After TableWidgetManager update: nav.selected_column={}, buffer.current_column={}, selection.selected_column={}",
4854 self.state_container.navigation().selected_column,
4855 self.state_container.get_current_column(),
4856 self.state_container.selection().selected_column);
4857
4858 }
4862
4863 if let Some((search_match, match_info)) = result {
4865 if let Some((current, total)) = match_info {
4866 self.state_container.set_status_message(format!(
4867 "Match {}/{} at ({}, {})",
4868 current,
4869 total,
4870 search_match.row + 1,
4871 search_match.col + 1
4872 ));
4873 }
4874 }
4875 }
4877
4878 fn apply_filter(&mut self, pattern: &str) {
4879 use std::sync::atomic::{AtomicUsize, Ordering};
4880
4881 static FILTER_DEPTH: AtomicUsize = AtomicUsize::new(0);
4883 let depth = FILTER_DEPTH.fetch_add(1, Ordering::SeqCst);
4884 if depth > 0 {
4885 eprintln!(
4886 "WARNING: apply_filter re-entrancy detected! depth={}, pattern='{}', thread={:?}",
4887 depth,
4888 pattern,
4889 std::thread::current().id()
4890 );
4891 }
4892
4893 info!(
4894 "Applying filter: '{}' on thread {:?}",
4895 pattern,
4896 std::thread::current().id()
4897 );
4898
4899 use crate::ui::state::state_coordinator::StateCoordinator;
4901 let _rows_after =
4902 StateCoordinator::apply_text_filter_with_refs(&mut self.state_container, pattern);
4903
4904 self.sync_dataview_to_managers();
4907
4908 FILTER_DEPTH.fetch_sub(1, Ordering::SeqCst);
4910 }
4911 fn search_columns(&mut self) {
4912 static SEARCH_DEPTH: std::sync::atomic::AtomicUsize =
4914 std::sync::atomic::AtomicUsize::new(0);
4915 let depth = SEARCH_DEPTH.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
4916
4917 if depth > 10 {
4919 error!(target: "search", "Column search depth exceeded limit, aborting to prevent infinite loop");
4920 SEARCH_DEPTH.store(0, std::sync::atomic::Ordering::SeqCst);
4921 return;
4922 }
4923
4924 struct DepthGuard;
4926 impl Drop for DepthGuard {
4927 fn drop(&mut self) {
4928 SEARCH_DEPTH.fetch_sub(1, std::sync::atomic::Ordering::SeqCst);
4929 }
4930 }
4931 let _guard = DepthGuard;
4932
4933 let pattern = self.state_container.column_search().pattern.clone();
4934 debug!(target: "search", "search_columns called with pattern: '{}', depth: {}", pattern, depth);
4935
4936 if pattern.is_empty() {
4937 debug!(target: "search", "Pattern is empty, skipping column search");
4938 return;
4939 }
4940
4941 let matching_columns = if let Some(dataview) =
4943 self.state_container.get_buffer_dataview_mut()
4944 {
4945 dataview.search_columns(&pattern);
4946
4947 let matches = dataview.get_matching_columns().to_vec();
4949 debug!(target: "search", "DataView found {} matching columns", matches.len());
4950 if !matches.is_empty() {
4951 for (idx, (col_idx, col_name)) in matches.iter().enumerate() {
4952 debug!(target: "search", " Match {}: '{}' at visual index {}", idx + 1, col_name, col_idx);
4953 }
4954 }
4955
4956 let columns: Vec<(String, usize)> = matches
4958 .iter()
4959 .map(|(idx, name)| (name.clone(), *idx))
4960 .collect();
4961 self.state_container
4962 .update_column_search_matches(&columns, &pattern);
4963
4964 matches
4965 } else {
4966 debug!(target: "search", "No DataView available for column search");
4967 Vec::new()
4968 };
4969
4970 if matching_columns.is_empty() {
4971 let status_msg = format!("No columns matching '{pattern}'");
4972 debug!(target: "search", "Setting status: {}", status_msg);
4973 self.state_container.set_status_message(status_msg);
4974 } else {
4975 let first_match_visual_idx = matching_columns[0].0;
4977 let first_match_name = &matching_columns[0].1;
4978
4979 let datatable_idx = if let Some(dataview) = self.state_container.get_buffer_dataview() {
4981 let display_columns = dataview.get_display_columns();
4982 if first_match_visual_idx < display_columns.len() {
4983 display_columns[first_match_visual_idx]
4984 } else {
4985 first_match_visual_idx }
4987 } else {
4988 first_match_visual_idx
4989 };
4990
4991 self.state_container.set_current_column(datatable_idx);
4992 self.state_container
4993 .set_current_column_buffer(datatable_idx);
4994
4995 {
4998 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
4999 if let Some(viewport_manager) = viewport_manager_borrow.as_mut() {
5000 let viewport_changed =
5001 viewport_manager.set_current_column(first_match_visual_idx);
5002
5003 if viewport_changed {
5005 let new_viewport = viewport_manager.viewport_cols().clone();
5006 let pinned_count =
5007 if let Some(dv) = self.state_container.get_buffer_dataview() {
5008 dv.get_pinned_columns().len()
5009 } else {
5010 0
5011 };
5012 let scrollable_offset = new_viewport.start.saturating_sub(pinned_count);
5013 self.state_container.navigation_mut().scroll_offset.1 = scrollable_offset;
5014
5015 debug!(target: "navigation",
5016 "Column search initial: Jumped to column {} '{}', viewport adjusted to {:?}",
5017 first_match_visual_idx, first_match_name, new_viewport);
5018 }
5019 }
5020 }
5021
5022 debug!(target: "search", "Setting current column to visual index {} ('{}')",
5023 first_match_visual_idx, first_match_name);
5024 let status_msg = format!(
5025 "Found {} columns matching '{}'. Tab/Shift-Tab to navigate.",
5026 matching_columns.len(),
5027 pattern
5028 );
5029 debug!(target: "search", "Setting status: {}", status_msg);
5030 self.state_container.set_status_message(status_msg);
5031
5032 }
5034
5035 }
5037
5038 fn next_column_match(&mut self) {
5039 let column_match_data =
5042 if let Some(dataview) = self.state_container.get_buffer_dataview_mut() {
5043 if let Some(visual_idx) = dataview.next_column_match() {
5044 let matching_columns = dataview.get_matching_columns();
5046 let current_match_index = dataview.current_column_match_index();
5047 let current_match = current_match_index + 1;
5048 let total_matches = matching_columns.len();
5049 let col_name = matching_columns
5050 .get(current_match_index)
5051 .map(|(_, name)| name.clone())
5052 .unwrap_or_default();
5053
5054 let display_columns = dataview.get_display_columns();
5057 let datatable_idx = if visual_idx < display_columns.len() {
5058 display_columns[visual_idx]
5059 } else {
5060 visual_idx };
5062
5063 Some((
5064 visual_idx,
5065 datatable_idx,
5066 col_name,
5067 current_match,
5068 total_matches,
5069 current_match_index,
5070 ))
5071 } else {
5072 None
5073 }
5074 } else {
5075 None
5076 };
5077
5078 if let Some((
5080 visual_idx,
5081 datatable_idx,
5082 col_name,
5083 current_match,
5084 total_matches,
5085 current_match_index,
5086 )) = column_match_data
5087 {
5088 self.state_container.set_current_column(datatable_idx);
5090 self.state_container
5091 .set_current_column_buffer(datatable_idx);
5092
5093 {
5096 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
5097 if let Some(viewport_manager) = viewport_manager_borrow.as_mut() {
5098 viewport_manager.set_current_column(visual_idx);
5099 }
5100 }
5101
5102 debug!(target: "column_search_sync", "next_column_match: About to call sync_navigation_with_viewport() - visual_idx: {}, datatable_idx: {}", visual_idx, datatable_idx);
5104 debug!(target: "column_search_sync", "next_column_match: Pre-sync - viewport current_column: {}",
5105 if let Ok(vm) = self.viewport_manager.try_borrow() {
5106 vm.as_ref().map_or(0, super::viewport_manager::ViewportManager::get_crosshair_col)
5107 } else { 0 });
5108 self.sync_navigation_with_viewport();
5109 debug!(target: "column_search_sync", "next_column_match: Post-sync - navigation current_column: {}",
5110 self.state_container.navigation().selected_column);
5111 debug!(target: "column_search_sync", "next_column_match: sync_navigation_with_viewport() completed");
5112
5113 debug!(target: "navigation",
5114 "Column search: Jumped to visual column {} (datatable: {}) '{}', synced with viewport",
5115 visual_idx, datatable_idx, col_name);
5116
5117 {
5120 let mut column_search = self.state_container.column_search_mut();
5121 column_search.current_match = current_match_index;
5122 debug!(target: "column_search_sync", "next_column_match: Updated AppStateContainer column_search.current_match to {}", column_search.current_match);
5123 }
5124
5125 self.state_container.set_status_message(format!(
5126 "Column {current_match}/{total_matches}: {col_name} - Tab/Shift-Tab to navigate"
5127 ));
5128 }
5129 }
5130
5131 fn previous_column_match(&mut self) {
5132 let column_match_data =
5135 if let Some(dataview) = self.state_container.get_buffer_dataview_mut() {
5136 if let Some(visual_idx) = dataview.prev_column_match() {
5137 let matching_columns = dataview.get_matching_columns();
5139 let current_match_index = dataview.current_column_match_index();
5140 let current_match = current_match_index + 1;
5141 let total_matches = matching_columns.len();
5142 let col_name = matching_columns
5143 .get(current_match_index)
5144 .map(|(_, name)| name.clone())
5145 .unwrap_or_default();
5146
5147 let display_columns = dataview.get_display_columns();
5150 let datatable_idx = if visual_idx < display_columns.len() {
5151 display_columns[visual_idx]
5152 } else {
5153 visual_idx };
5155
5156 Some((
5157 visual_idx,
5158 datatable_idx,
5159 col_name,
5160 current_match,
5161 total_matches,
5162 current_match_index,
5163 ))
5164 } else {
5165 None
5166 }
5167 } else {
5168 None
5169 };
5170
5171 if let Some((
5173 visual_idx,
5174 datatable_idx,
5175 col_name,
5176 current_match,
5177 total_matches,
5178 current_match_index,
5179 )) = column_match_data
5180 {
5181 self.state_container.set_current_column(datatable_idx);
5183 self.state_container
5184 .set_current_column_buffer(datatable_idx);
5185
5186 {
5189 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
5190 if let Some(viewport_manager) = viewport_manager_borrow.as_mut() {
5191 viewport_manager.set_current_column(visual_idx);
5192 }
5193 }
5194
5195 debug!(target: "column_search_sync", "previous_column_match: About to call sync_navigation_with_viewport() - visual_idx: {}, datatable_idx: {}", visual_idx, datatable_idx);
5197 debug!(target: "column_search_sync", "previous_column_match: Pre-sync - viewport current_column: {}",
5198 if let Ok(vm) = self.viewport_manager.try_borrow() {
5199 vm.as_ref().map_or(0, super::viewport_manager::ViewportManager::get_crosshair_col)
5200 } else { 0 });
5201 self.sync_navigation_with_viewport();
5202 debug!(target: "column_search_sync", "previous_column_match: Post-sync - navigation current_column: {}",
5203 self.state_container.navigation().selected_column);
5204 debug!(target: "column_search_sync", "previous_column_match: sync_navigation_with_viewport() completed");
5205
5206 debug!(target: "navigation",
5207 "Column search (prev): Jumped to visual column {} (datatable: {}) '{}', synced with viewport",
5208 visual_idx, datatable_idx, col_name);
5209
5210 {
5213 let mut column_search = self.state_container.column_search_mut();
5214 column_search.current_match = current_match_index;
5215 debug!(target: "column_search_sync", "previous_column_match: Updated AppStateContainer column_search.current_match to {}", column_search.current_match);
5216 }
5217
5218 self.state_container.set_status_message(format!(
5219 "Column {current_match}/{total_matches}: {col_name} - Tab/Shift-Tab to navigate"
5220 ));
5221 }
5222 }
5223
5224 fn apply_fuzzy_filter(&mut self) {
5225 info!(
5226 "apply_fuzzy_filter called on thread {:?}",
5227 std::thread::current().id()
5228 );
5229
5230 use crate::ui::state::state_coordinator::StateCoordinator;
5232 let (_match_count, indices) = StateCoordinator::apply_fuzzy_filter_with_refs(
5233 &mut self.state_container,
5234 &self.viewport_manager,
5235 );
5236
5237 self.state_container.set_fuzzy_filter_indices(indices);
5239
5240 self.sync_dataview_to_managers();
5243 }
5244
5245 fn toggle_sort_current_column(&mut self) {
5246 let visual_col_idx = if let Some(ref viewport_manager) = *self.viewport_manager.borrow() {
5248 viewport_manager.get_crosshair_col()
5249 } else {
5250 0
5251 };
5252
5253 if let Some(dataview) = self.state_container.get_buffer_dataview_mut() {
5254 let column_names = dataview.column_names();
5257 let col_name = column_names
5258 .get(visual_col_idx)
5259 .cloned()
5260 .unwrap_or_else(|| format!("Column {visual_col_idx}"));
5261
5262 debug!(
5263 "toggle_sort_current_column: visual_idx={}, column_name={}",
5264 visual_col_idx, col_name
5265 );
5266
5267 if let Err(e) = dataview.toggle_sort(visual_col_idx) {
5268 self.state_container
5269 .set_status_message(format!("Sort error: {e}"));
5270 } else {
5271 let sort_state = dataview.get_sort_state();
5273 let message = match sort_state.order {
5274 crate::data::data_view::SortOrder::Ascending => {
5275 format!("Sorted '{col_name}' ascending ↑")
5276 }
5277 crate::data::data_view::SortOrder::Descending => {
5278 format!("Sorted '{col_name}' descending ↓")
5279 }
5280 crate::data::data_view::SortOrder::None => {
5281 format!("Cleared sort on '{col_name}'")
5282 }
5283 };
5284 self.state_container.set_status_message(message);
5285
5286 if let Some(updated_dataview) = self.state_container.get_buffer_dataview() {
5288 self.table_widget_manager
5290 .borrow_mut()
5291 .set_dataview(Arc::new(updated_dataview.clone()));
5292
5293 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
5294 if let Some(ref mut viewport_manager) = *viewport_manager_borrow {
5295 viewport_manager.set_dataview(Arc::new(updated_dataview.clone()));
5296 debug!("Updated ViewportManager with sorted DataView");
5297 }
5298 }
5299 }
5300 } else {
5301 self.state_container
5303 .set_status_message("Error: Invalid column position".to_string());
5304 }
5305 }
5306
5307 fn get_current_data(&self) -> Option<&DataView> {
5308 self.state_container.get_buffer_dataview()
5309 }
5310
5311 fn get_row_count(&self) -> usize {
5312 if self.state_container.is_fuzzy_filter_active() {
5314 self.state_container.get_fuzzy_filter_indices().len()
5316 } else if let Some(dataview) = self.state_container.get_buffer_dataview() {
5317 dataview.row_count()
5319 } else if let Some(provider) = self.get_data_provider() {
5320 provider.get_row_count()
5322 } else {
5323 0
5324 }
5325 }
5326
5327 fn sync_dataview_to_managers(&self) {
5329 if let Some(dataview) = self.state_container.get_buffer_dataview() {
5330 let arc_dataview = Arc::new(dataview.clone());
5331
5332 if let Some(ref mut viewport_manager) = *self.viewport_manager.borrow_mut() {
5334 viewport_manager.set_dataview(arc_dataview.clone());
5335 debug!(
5336 "Updated ViewportManager with DataView (row_count={})",
5337 arc_dataview.row_count()
5338 );
5339 }
5340
5341 self.table_widget_manager
5343 .borrow_mut()
5344 .set_dataview(arc_dataview);
5345 debug!("Updated TableWidgetManager with DataView");
5346 }
5347 }
5348
5349 pub fn reset_table_state(&mut self) {
5350 use crate::ui::state::state_coordinator::StateCoordinator;
5352 StateCoordinator::reset_table_state_with_refs(
5353 &mut self.state_container,
5354 &self.viewport_manager,
5355 );
5356 }
5357
5358 fn update_parser_for_current_buffer(&mut self) {
5359 self.sync_all_input_states();
5361
5362 use crate::ui::state::state_coordinator::StateCoordinator;
5364 StateCoordinator::update_parser_with_refs(&self.state_container, &mut self.hybrid_parser);
5365 }
5366
5367 fn sync_after_buffer_switch(&mut self) {
5372 self.restore_viewport_from_current_buffer();
5374
5375 self.update_parser_for_current_buffer();
5377 }
5378
5379 fn update_viewport_manager(&mut self, dataview: Option<DataView>) {
5381 if let Some(dv) = dataview {
5382 let current_column = self.state_container.get_current_column();
5384
5385 let mut new_viewport_manager = ViewportManager::new(Arc::new(dv));
5387
5388 if let Ok((width, height)) = crossterm::terminal::size() {
5390 let data_rows_available = Self::calculate_available_data_rows(height);
5392 new_viewport_manager.update_terminal_size(width, data_rows_available);
5393 debug!(
5394 "Updated new ViewportManager terminal size: {}x{} (data rows)",
5395 width, data_rows_available
5396 );
5397 }
5398
5399 if current_column < new_viewport_manager.dataview().column_count() {
5402 new_viewport_manager.set_current_column(current_column);
5403 } else {
5404 new_viewport_manager.set_current_column(0);
5406 self.state_container.set_current_column_buffer(0);
5407 }
5408
5409 *self.viewport_manager.borrow_mut() = Some(new_viewport_manager);
5410 debug!(
5411 "ViewportManager updated with new DataView, current_column={}",
5412 current_column
5413 );
5414 } else {
5415 *self.viewport_manager.borrow_mut() = None;
5417 debug!("ViewportManager cleared (no DataView)");
5418 }
5419 }
5420
5421 pub fn calculate_optimal_column_widths(&mut self) {
5422 let widths_from_viewport = {
5424 let mut viewport_opt = self.viewport_manager.borrow_mut();
5425 (*viewport_opt)
5426 .as_mut()
5427 .map(super::viewport_manager::ViewportManager::calculate_optimal_column_widths)
5428 };
5429
5430 if let Some(widths) = widths_from_viewport {
5431 self.state_container.set_column_widths(widths);
5432 }
5433 }
5434
5435 pub fn set_status_message(&mut self, message: impl Into<String>) {
5438 let msg = message.into();
5439 debug!("Status: {}", msg);
5440 self.state_container.set_status_message(msg.clone());
5441 }
5444
5445 fn set_error_status(&mut self, context: &str, error: impl std::fmt::Display) {
5447 let msg = format!("{context}: {error}");
5448 debug!("Error status: {}", msg);
5449 self.set_status_message(msg);
5450 }
5451
5452 fn export_to_csv(&mut self) {
5453 let result = {
5454 let ctx = crate::ui::operations::data_export_operations::DataExportContext {
5455 data_provider: self.get_data_provider(),
5456 };
5457 crate::ui::operations::data_export_operations::export_to_csv(&ctx)
5458 };
5459
5460 match result {
5461 crate::ui::operations::data_export_operations::ExportResult::Success(message) => {
5462 self.set_status_message(message);
5463 }
5464 crate::ui::operations::data_export_operations::ExportResult::Error(error) => {
5465 self.set_error_status("Export failed", error);
5466 }
5467 }
5468 }
5469
5470 fn paste_from_clipboard(&mut self) {
5477 match self.state_container.read_from_clipboard() {
5479 Ok(text) => {
5480 let mode = self.shadow_state.borrow().get_mode();
5481 match mode {
5482 AppMode::Command => {
5483 let cursor_pos = self.get_input_cursor();
5486 let current_value = self.get_input_text();
5487
5488 let mut new_value = String::new();
5490 new_value.push_str(¤t_value[..cursor_pos]);
5491 new_value.push_str(&text);
5492 new_value.push_str(¤t_value[cursor_pos..]);
5493
5494 self.set_input_text_with_cursor(new_value, cursor_pos + text.len());
5495
5496 self.state_container
5497 .set_status_message(format!("Pasted {} characters", text.len()));
5498 }
5499 AppMode::Filter
5500 | AppMode::FuzzyFilter
5501 | AppMode::Search
5502 | AppMode::ColumnSearch => {
5503 let cursor_pos = self.get_input_cursor();
5505 let current_value = self.get_input_text();
5506
5507 let mut new_value = String::new();
5508 new_value.push_str(¤t_value[..cursor_pos]);
5509 new_value.push_str(&text);
5510 new_value.push_str(¤t_value[cursor_pos..]);
5511
5512 self.set_input_text_with_cursor(new_value, cursor_pos + text.len());
5513
5514 match mode {
5516 AppMode::Filter => {
5517 let pattern = self.get_input_text();
5518 self.state_container.filter_mut().pattern = pattern.clone();
5519 self.apply_filter(&pattern);
5520 }
5521 AppMode::FuzzyFilter => {
5522 let input_text = self.get_input_text();
5523 self.state_container.set_fuzzy_filter_pattern(input_text);
5524 self.apply_fuzzy_filter();
5525 }
5526 AppMode::Search => {
5527 let search_text = self.get_input_text();
5528 self.state_container.set_search_pattern(search_text);
5529 }
5531 AppMode::ColumnSearch => {
5532 let input_text = self.get_input_text();
5533 self.state_container.start_column_search(input_text);
5534 }
5536 _ => {}
5537 }
5538 }
5539 _ => {
5540 self.state_container
5541 .set_status_message("Paste not available in this mode".to_string());
5542 }
5543 }
5544 }
5545 Err(e) => {
5546 self.state_container
5547 .set_status_message(format!("Failed to paste: {e}"));
5548 }
5549 }
5550 }
5551
5552 fn export_to_json(&mut self) {
5553 let result = {
5555 let ctx = crate::ui::operations::data_export_operations::DataExportContext {
5556 data_provider: self.get_data_provider(),
5557 };
5558 crate::ui::operations::data_export_operations::export_to_json(&ctx)
5559 };
5560
5561 match result {
5562 crate::ui::operations::data_export_operations::ExportResult::Success(message) => {
5563 self.set_status_message(message);
5564 }
5565 crate::ui::operations::data_export_operations::ExportResult::Error(error) => {
5566 self.set_error_status("Export failed", error);
5567 }
5568 }
5569 }
5570
5571 fn get_horizontal_scroll_offset(&self) -> u16 {
5572 let (horizontal, _vertical) = self.cursor_manager.scroll_offsets();
5574 horizontal
5575 }
5576
5577 fn update_horizontal_scroll(&mut self, terminal_width: u16) {
5578 let inner_width = terminal_width.saturating_sub(3) as usize; let cursor_pos = self.get_input_cursor();
5580
5581 self.cursor_manager
5583 .update_horizontal_scroll(cursor_pos, terminal_width.saturating_sub(3));
5584
5585 let mut scroll = self.state_container.scroll_mut();
5587 if cursor_pos < scroll.input_scroll_offset as usize {
5588 scroll.input_scroll_offset = cursor_pos as u16;
5589 }
5590 else if cursor_pos >= scroll.input_scroll_offset as usize + inner_width {
5592 scroll.input_scroll_offset = (cursor_pos + 1).saturating_sub(inner_width) as u16;
5593 }
5594 }
5595
5596 fn get_cursor_token_position(&self) -> (usize, usize) {
5597 let ctx = crate::ui::operations::simple_operations::TextNavigationContext {
5598 query: &self.get_input_text(),
5599 cursor_pos: self.get_input_cursor(),
5600 };
5601 crate::ui::operations::simple_operations::get_cursor_token_position(&ctx)
5602 }
5603
5604 fn get_token_at_cursor(&self) -> Option<String> {
5605 let ctx = crate::ui::operations::simple_operations::TextNavigationContext {
5606 query: &self.get_input_text(),
5607 cursor_pos: self.get_input_cursor(),
5608 };
5609 crate::ui::operations::simple_operations::get_token_at_cursor(&ctx)
5610 }
5611
5612 #[allow(dead_code)]
5614 fn ui(&mut self, f: &mut Frame) {
5615 let input_height = INPUT_AREA_HEIGHT;
5617
5618 let buffer_count = self.state_container.buffers().all_buffers().len();
5620 let tab_bar_height = 2; let chunks = Layout::default()
5623 .direction(Direction::Vertical)
5624 .constraints(
5625 [
5626 Constraint::Length(tab_bar_height), Constraint::Length(input_height), Constraint::Min(0), Constraint::Length(STATUS_BAR_HEIGHT), ]
5631 .as_ref(),
5632 )
5633 .split(f.area());
5634
5635 if buffer_count > 0 {
5637 let buffer_names: Vec<String> = self
5638 .state_container
5639 .buffers()
5640 .all_buffers()
5641 .iter()
5642 .map(super::super::buffer::BufferAPI::get_name)
5643 .collect();
5644 let current_index = self.state_container.buffers().current_index();
5645
5646 let tab_widget = TabBarWidget::new(current_index, buffer_names);
5647 tab_widget.render(f, chunks[0]);
5648 }
5649
5650 let input_chunk_idx = 1;
5652 let results_chunk_idx = 2;
5653 let status_chunk_idx = 3;
5654
5655 self.update_horizontal_scroll(chunks[input_chunk_idx].width);
5657
5658 let input_text_for_count = self.get_input_text();
5661 let char_count = input_text_for_count.len();
5662 let cursor_pos = self.get_input_cursor();
5663 let char_count_display = if char_count > 0 {
5664 format!(" [{cursor_pos}/{char_count} chars]")
5665 } else {
5666 String::new()
5667 };
5668
5669 let scroll_offset = self.get_horizontal_scroll_offset();
5670 let scroll_indicator = if scroll_offset > 0 {
5671 " ◀ " } else {
5673 ""
5674 };
5675
5676 let input_title = match self.shadow_state.borrow().get_mode() {
5677 AppMode::Command => format!("SQL Query{char_count_display}{scroll_indicator}"),
5678 AppMode::Results => format!(
5679 "SQL Query (Results Mode - Press ↑ to edit){char_count_display}{scroll_indicator}"
5680 ),
5681 AppMode::Search => format!("Search Pattern{char_count_display}{scroll_indicator}"),
5682 AppMode::Filter => format!("Filter Pattern{char_count_display}{scroll_indicator}"),
5683 AppMode::FuzzyFilter => {
5684 format!("Fuzzy Filter{char_count_display}{scroll_indicator}")
5685 }
5686 AppMode::ColumnSearch => {
5687 format!("Column Search{char_count_display}{scroll_indicator}")
5688 }
5689 AppMode::Help => "Help".to_string(),
5690 AppMode::History => {
5691 let query = self.state_container.history_search().query.clone();
5692 format!("History Search: '{query}' (Esc to cancel)")
5693 }
5694 AppMode::Debug => "Parser Debug (F5)".to_string(),
5695 AppMode::PrettyQuery => "Pretty Query View (F6)".to_string(),
5696 AppMode::JumpToRow => format!("Jump to row: {}", self.get_jump_to_row_input()),
5697 AppMode::ColumnStats => "Column Statistics (S to close)".to_string(),
5698 };
5699
5700 let input_block = Block::default().borders(Borders::ALL).title(input_title);
5701
5702 let use_search_widget = matches!(
5704 self.shadow_state.borrow().get_mode(),
5705 AppMode::Search | AppMode::Filter | AppMode::FuzzyFilter | AppMode::ColumnSearch
5706 ) && self.search_modes_widget.is_active();
5707
5708 if use_search_widget {
5709 self.search_modes_widget.render(f, chunks[input_chunk_idx]);
5711 } else {
5712 let input_text_string = self.get_input_text();
5714
5715 trace!(target: "render", "Rendering input: text='{}', mode={:?}, cursor={}",
5717 if input_text_string.len() > 50 {
5718 format!("{}...", &input_text_string[..50])
5719 } else {
5720 input_text_string.clone()
5721 },
5722 self.shadow_state.borrow().get_mode(),
5723 self.get_input_cursor());
5724
5725 let history_query_string = if self.shadow_state.borrow().is_in_history_mode() {
5727 self.state_container.history_search().query.clone()
5728 } else {
5729 String::new()
5730 };
5731
5732 let input_text = match self.shadow_state.borrow().get_mode() {
5733 AppMode::History => &history_query_string,
5734 _ => &input_text_string,
5735 };
5736
5737 let input_paragraph = match self.shadow_state.borrow().get_mode() {
5738 AppMode::Command => {
5739 match self.state_container.get_edit_mode() {
5740 Some(EditMode::SingleLine) => {
5741 let highlighted_line =
5743 self.sql_highlighter.simple_sql_highlight(input_text);
5744 Paragraph::new(Text::from(vec![highlighted_line]))
5745 .block(input_block)
5746 .scroll((0, self.get_horizontal_scroll_offset()))
5747 }
5748 Some(EditMode::MultiLine) => {
5749 let highlighted_line =
5751 self.sql_highlighter.simple_sql_highlight(input_text);
5752 Paragraph::new(Text::from(vec![highlighted_line]))
5753 .block(input_block)
5754 .scroll((0, self.get_horizontal_scroll_offset()))
5755 }
5756 None => {
5757 let highlighted_line =
5759 self.sql_highlighter.simple_sql_highlight(input_text);
5760 Paragraph::new(Text::from(vec![highlighted_line]))
5761 .block(input_block)
5762 .scroll((0, self.get_horizontal_scroll_offset()))
5763 }
5764 }
5765 }
5766 _ => {
5767 Paragraph::new(input_text.as_str())
5769 .block(input_block)
5770 .style(match self.shadow_state.borrow().get_mode() {
5771 AppMode::Results => Style::default().fg(Color::DarkGray),
5772 AppMode::Search => Style::default().fg(Color::Yellow),
5773 AppMode::Filter => Style::default().fg(Color::Cyan),
5774 AppMode::FuzzyFilter => Style::default().fg(Color::Magenta),
5775 AppMode::ColumnSearch => Style::default().fg(Color::Green),
5776 AppMode::Help => Style::default().fg(Color::DarkGray),
5777 AppMode::History => Style::default().fg(Color::Magenta),
5778 AppMode::Debug => Style::default().fg(Color::Yellow),
5779 AppMode::PrettyQuery => Style::default().fg(Color::Green),
5780 AppMode::JumpToRow => Style::default().fg(Color::Magenta),
5781 AppMode::ColumnStats => Style::default().fg(Color::Cyan),
5782 _ => Style::default(),
5783 })
5784 .scroll((0, self.get_horizontal_scroll_offset()))
5785 }
5786 };
5787
5788 f.render_widget(input_paragraph, chunks[input_chunk_idx]);
5790 }
5791 let results_area = chunks[results_chunk_idx];
5792
5793 if !use_search_widget {
5795 match self.shadow_state.borrow().get_mode() {
5796 AppMode::Command => {
5797 let inner_width = chunks[input_chunk_idx].width.saturating_sub(2) as usize;
5800 let cursor_pos = self.get_visual_cursor().1; let scroll_offset = self.get_horizontal_scroll_offset() as usize;
5802
5803 if cursor_pos >= scroll_offset && cursor_pos < scroll_offset + inner_width {
5805 let visible_pos = cursor_pos - scroll_offset;
5806 f.set_cursor_position((
5807 chunks[input_chunk_idx].x + visible_pos as u16 + 1,
5808 chunks[input_chunk_idx].y + 1,
5809 ));
5810 }
5811 }
5812 AppMode::Search => {
5813 f.set_cursor_position((
5814 chunks[input_chunk_idx].x + self.get_input_cursor() as u16 + 1,
5815 chunks[input_chunk_idx].y + 1,
5816 ));
5817 }
5818 AppMode::Filter => {
5819 f.set_cursor_position((
5820 chunks[input_chunk_idx].x + self.get_input_cursor() as u16 + 1,
5821 chunks[input_chunk_idx].y + 1,
5822 ));
5823 }
5824 AppMode::FuzzyFilter => {
5825 f.set_cursor_position((
5826 chunks[input_chunk_idx].x + self.get_input_cursor() as u16 + 1,
5827 chunks[input_chunk_idx].y + 1,
5828 ));
5829 }
5830 AppMode::ColumnSearch => {
5831 f.set_cursor_position((
5832 chunks[input_chunk_idx].x + self.get_input_cursor() as u16 + 1,
5833 chunks[input_chunk_idx].y + 1,
5834 ));
5835 }
5836 AppMode::JumpToRow => {
5837 f.set_cursor_position((
5838 chunks[input_chunk_idx].x + self.get_jump_to_row_input().len() as u16 + 1,
5839 chunks[input_chunk_idx].y + 1,
5840 ));
5841 }
5842 AppMode::History => {
5843 let query_len = self.state_container.history_search().query.len();
5844 f.set_cursor_position((
5845 chunks[input_chunk_idx].x + query_len as u16 + 1,
5846 chunks[input_chunk_idx].y + 1,
5847 ));
5848 }
5849 _ => {}
5850 }
5851 }
5852
5853 let mode = self.shadow_state.borrow().get_mode();
5855 match mode {
5856 AppMode::Help => self.render_help(f, results_area),
5857 AppMode::History => self.render_history(f, results_area),
5858 AppMode::Debug => self.render_debug(f, results_area),
5859 AppMode::PrettyQuery => self.render_pretty_query(f, results_area),
5860 AppMode::ColumnStats => self.render_column_stats(f, results_area),
5861 _ if self.state_container.has_dataview() => {
5862 if let Some(provider) = self.get_data_provider() {
5865 self.render_table_with_provider(f, results_area, provider.as_ref());
5866 }
5867 }
5868 _ => {
5869 let placeholder = Paragraph::new("Enter SQL query and press Enter\n\nTip: Use Tab for completion, Ctrl+R for history")
5871 .block(Block::default().borders(Borders::ALL).title("Results"))
5872 .style(Style::default().fg(Color::DarkGray));
5873 f.render_widget(placeholder, results_area);
5874 }
5875 }
5876
5877 self.render_status_line(f, chunks[status_chunk_idx]);
5879 }
5881
5882 fn add_mode_styling(&self, spans: &mut Vec<Span>) -> (Style, Color) {
5884 let (status_style, mode_color) = match self.shadow_state.borrow().get_mode() {
5886 AppMode::Command => (Style::default().fg(Color::Green), Color::Green),
5887 AppMode::Results => (Style::default().fg(Color::Blue), Color::Blue),
5888 AppMode::Search => (Style::default().fg(Color::Yellow), Color::Yellow),
5889 AppMode::Filter => (Style::default().fg(Color::Cyan), Color::Cyan),
5890 AppMode::FuzzyFilter => (Style::default().fg(Color::Magenta), Color::Magenta),
5891 AppMode::ColumnSearch => (Style::default().fg(Color::Green), Color::Green),
5892 AppMode::Help => (Style::default().fg(Color::Magenta), Color::Magenta),
5893 AppMode::History => (Style::default().fg(Color::Magenta), Color::Magenta),
5894 AppMode::Debug => (Style::default().fg(Color::Yellow), Color::Yellow),
5895 AppMode::PrettyQuery => (Style::default().fg(Color::Green), Color::Green),
5896 AppMode::JumpToRow => (Style::default().fg(Color::Magenta), Color::Magenta),
5897 AppMode::ColumnStats => (Style::default().fg(Color::Cyan), Color::Cyan),
5898 };
5899
5900 let mode_indicator = match self.shadow_state.borrow().get_mode() {
5901 AppMode::Command => "CMD",
5902 AppMode::Results => "NAV",
5903 AppMode::Search => "SEARCH",
5904 AppMode::Filter => "FILTER",
5905 AppMode::FuzzyFilter => "FUZZY",
5906 AppMode::ColumnSearch => "COL",
5907 AppMode::Help => "HELP",
5908 AppMode::History => "HISTORY",
5909 AppMode::Debug => "DEBUG",
5910 AppMode::PrettyQuery => "PRETTY",
5911 AppMode::JumpToRow => "JUMP",
5912 AppMode::ColumnStats => "STATS",
5913 };
5914
5915 spans.push(Span::styled(
5917 format!("[{mode_indicator}]"),
5918 Style::default().fg(mode_color).add_modifier(Modifier::BOLD),
5919 ));
5920
5921 (status_style, mode_color)
5922 }
5923
5924 fn add_data_source_display(&self, _spans: &mut Vec<Span>) {
5926 }
5929
5930 fn add_buffer_information(&self, spans: &mut Vec<Span>) {
5932 let index = self.state_container.buffers().current_index();
5933 let total = self.state_container.buffers().all_buffers().len();
5934
5935 if total > 1 {
5937 spans.push(Span::raw(" "));
5938 spans.push(Span::styled(
5939 format!("[{}/{}]", index + 1, total),
5940 Style::default().fg(Color::Yellow),
5941 ));
5942 }
5943
5944 if let Some(buffer) = self.state_container.buffers().current() {
5947 let query = buffer.get_input_text();
5948 if let Some(from_pos) = query.to_uppercase().find(" FROM ") {
5950 let after_from = &query[from_pos + 6..];
5951 if let Some(table_name) = after_from.split_whitespace().next() {
5953 let clean_name = table_name
5955 .trim_matches('"')
5956 .trim_matches('\'')
5957 .trim_matches('`');
5958
5959 spans.push(Span::raw(" "));
5960 spans.push(Span::styled(
5961 clean_name.to_string(),
5962 Style::default().fg(Color::Cyan),
5963 ));
5964 }
5965 }
5966 }
5967 }
5968
5969 fn add_mode_specific_info(&self, spans: &mut Vec<Span>, mode_color: Color, area: Rect) {
5971 match self.shadow_state.borrow().get_mode() {
5972 AppMode::Command => {
5973 if !self.get_input_text().trim().is_empty() {
5975 let (token_pos, total_tokens) = self.get_cursor_token_position();
5976 spans.push(Span::raw(" | "));
5977 spans.push(Span::styled(
5978 format!("Token {token_pos}/{total_tokens}"),
5979 Style::default().fg(Color::DarkGray),
5980 ));
5981
5982 if let Some(token) = self.get_token_at_cursor() {
5984 spans.push(Span::raw(" "));
5985 spans.push(Span::styled(
5986 format!("[{token}]"),
5987 Style::default().fg(Color::Cyan),
5988 ));
5989 }
5990
5991 if let Some(error_msg) = self.check_parser_error(&self.get_input_text()) {
5993 spans.push(Span::raw(" | "));
5994 spans.push(Span::styled(
5995 format!("{} {}", self.config.display.icons.warning, error_msg),
5996 Style::default().fg(Color::Red).add_modifier(Modifier::BOLD),
5997 ));
5998 }
5999 }
6000 }
6001 AppMode::Results => {
6002 self.add_results_mode_info(spans, area);
6004 }
6005 AppMode::Search | AppMode::Filter | AppMode::FuzzyFilter | AppMode::ColumnSearch => {
6006 let pattern = self.get_input_text();
6008 if !pattern.is_empty() {
6009 spans.push(Span::raw(" | Pattern: "));
6010 spans.push(Span::styled(pattern, Style::default().fg(mode_color)));
6011 }
6012 }
6013 _ => {}
6014 }
6015 }
6016
6017 fn add_results_mode_info(&self, spans: &mut Vec<Span>, area: Rect) {
6019 let total_rows = self.get_row_count();
6020 if total_rows > 0 {
6021 let selected = self.state_container.navigation().selected_row + 1;
6023 spans.push(Span::raw(" | "));
6024
6025 let selection_mode = self.get_selection_mode();
6027 let mode_text = match selection_mode {
6028 SelectionMode::Cell => "CELL",
6029 SelectionMode::Row => "ROW",
6030 SelectionMode::Column => "COL",
6031 };
6032 spans.push(Span::styled(
6033 format!("[{mode_text}]"),
6034 Style::default()
6035 .fg(Color::Cyan)
6036 .add_modifier(Modifier::BOLD),
6037 ));
6038
6039 spans.push(Span::raw(" "));
6040 spans.push(Span::styled(
6041 format!("Row {selected}/{total_rows}"),
6042 Style::default().fg(Color::White),
6043 ));
6044
6045 let visual_col_display =
6048 if let Some(ref viewport_manager) = *self.viewport_manager.borrow() {
6049 viewport_manager.get_crosshair_col() + 1
6050 } else {
6051 1
6052 };
6053 spans.push(Span::raw(" "));
6054 spans.push(Span::styled(
6055 format!("({visual_col_display},{selected})"),
6056 Style::default().fg(Color::DarkGray),
6057 ));
6058
6059 if let Some(ref mut viewport_manager) = *self.viewport_manager.borrow_mut() {
6061 let available_width = area.width.saturating_sub(TABLE_BORDER_WIDTH);
6062 let visual_col = viewport_manager.get_crosshair_col();
6064 if let Some(x_pos) =
6065 viewport_manager.get_column_x_position(visual_col, available_width)
6066 {
6067 let terminal_x = x_pos + 2;
6069 let terminal_y = (selected as u16)
6070 .saturating_sub(self.state_container.get_scroll_offset().0 as u16)
6071 + 3;
6072 spans.push(Span::raw(" "));
6073 spans.push(Span::styled(
6074 format!("[{terminal_x}x{terminal_y}]"),
6075 Style::default().fg(Color::DarkGray),
6076 ));
6077 }
6078 }
6079
6080 if let Some(dataview) = self.state_container.get_buffer_dataview() {
6082 let headers = dataview.column_names();
6083
6084 let (visual_row, visual_col) =
6087 if let Some(ref viewport_manager) = *self.viewport_manager.borrow() {
6088 (
6089 viewport_manager.get_crosshair_row(),
6090 viewport_manager.get_crosshair_col(),
6091 )
6092 } else {
6093 (0, 0)
6094 };
6095
6096 if visual_col < headers.len() {
6098 spans.push(Span::raw(" | Col: "));
6099 spans.push(Span::styled(
6100 headers[visual_col].clone(),
6101 Style::default().fg(Color::Cyan),
6102 ));
6103
6104 let viewport_info =
6106 if let Some(ref viewport_manager) = *self.viewport_manager.borrow() {
6107 let viewport_rows = viewport_manager.get_viewport_rows();
6108 let viewport_height = viewport_rows.end - viewport_rows.start;
6109 format!("[V:{visual_row},{visual_col} @ {viewport_height}r]")
6110 } else {
6111 format!("[V:{visual_row},{visual_col}]")
6112 };
6113 spans.push(Span::raw(" "));
6114 spans.push(Span::styled(
6115 viewport_info,
6116 Style::default().fg(Color::Magenta),
6117 ));
6118 }
6119 }
6120 }
6121 }
6122
6123 fn render_status_line(&self, f: &mut Frame, area: Rect) {
6124 let mut spans = Vec::new();
6125
6126 let (status_style, mode_color) = self.add_mode_styling(&mut spans);
6128
6129 self.add_data_source_display(&mut spans);
6131
6132 self.add_buffer_information(&mut spans);
6134
6135 self.add_mode_specific_info(&mut spans, mode_color, area);
6137
6138 self.add_query_source_indicator(&mut spans);
6140
6141 self.add_case_sensitivity_indicator(&mut spans);
6143
6144 self.add_column_packing_indicator(&mut spans);
6146
6147 self.add_status_message(&mut spans);
6149
6150 let help_text = self.get_help_text_for_mode();
6152
6153 self.add_global_indicators(&mut spans);
6154
6155 self.add_shadow_state_display(&mut spans);
6157
6158 self.add_help_text_display(&mut spans, help_text, area);
6159
6160 let status_line = Line::from(spans);
6161 let status = Paragraph::new(status_line)
6162 .block(Block::default().borders(Borders::ALL))
6163 .style(status_style);
6164 f.render_widget(status, area);
6165 }
6166
6167 fn build_table_context(
6170 &self,
6171 area: Rect,
6172 provider: &dyn DataProvider,
6173 ) -> crate::ui::rendering::table_render_context::TableRenderContext {
6174 use crate::ui::rendering::table_render_context::TableRenderContextBuilder;
6175
6176 let row_count = provider.get_row_count();
6177 let available_width = area.width.saturating_sub(TABLE_BORDER_WIDTH);
6178 let available_height = area.height;
6181
6182 let headers = {
6184 let viewport_manager = self.viewport_manager.borrow();
6185 let viewport_manager = viewport_manager
6186 .as_ref()
6187 .expect("ViewportManager must exist");
6188 viewport_manager.get_column_names_ordered()
6189 };
6190
6191 {
6193 let mut viewport_opt = self.viewport_manager.borrow_mut();
6194 if let Some(ref mut viewport_manager) = *viewport_opt {
6195 let data_rows = Self::calculate_table_data_rows(available_height);
6197 viewport_manager.update_terminal_size(available_width, data_rows);
6198 let _ = viewport_manager.get_column_widths(); }
6200 }
6201
6202 let (pinned_visual_positions, crosshair_column_position, _) = {
6204 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
6205 let viewport_manager = viewport_manager_borrow
6206 .as_mut()
6207 .expect("ViewportManager must exist for rendering");
6208 let info = viewport_manager.get_visible_columns_info(available_width);
6209
6210 let visible_indices = &info.0;
6214 let pinned_source_indices = &info.1;
6215
6216 let mut pinned_visual_positions = Vec::new();
6219 for &source_idx in pinned_source_indices {
6220 if let Some(visual_pos) = visible_indices.iter().position(|&x| x == source_idx) {
6221 pinned_visual_positions.push(visual_pos);
6222 }
6223 }
6224
6225 let crosshair_column_position =
6229 if let Some((_, col_pos)) = viewport_manager.get_crosshair_viewport_position() {
6230 col_pos
6231 } else {
6232 0
6234 };
6235
6236 let crosshair_visual = viewport_manager.get_crosshair_col();
6237
6238 (
6239 pinned_visual_positions,
6240 crosshair_column_position,
6241 crosshair_visual,
6242 )
6243 };
6244
6245 let row_viewport_start = self
6247 .state_container
6248 .navigation()
6249 .scroll_offset
6250 .0
6251 .min(row_count.saturating_sub(1));
6252 let row_viewport_end = (row_viewport_start + available_height as usize).min(row_count);
6253 let visible_row_indices: Vec<usize> = (row_viewport_start..row_viewport_end).collect();
6254
6255 let (column_headers, data_to_display, column_widths_visual) = {
6257 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
6258 if let Some(ref mut viewport_manager) = *viewport_manager_borrow {
6259 viewport_manager.get_visual_display(available_width, &visible_row_indices)
6260 } else {
6261 let visible_rows = provider
6263 .get_visible_rows(row_viewport_start, row_viewport_end - row_viewport_start);
6264 let widths = vec![15u16; headers.len()];
6265 (headers.clone(), visible_rows, widths)
6266 }
6267 };
6268
6269 let sort_state = self
6271 .buffer()
6272 .get_dataview()
6273 .map(|dv| dv.get_sort_state().clone());
6274
6275 let fuzzy_filter_pattern = if self.state_container.is_fuzzy_filter_active() {
6277 let pattern = self.state_container.get_fuzzy_filter_pattern();
6278 if pattern.is_empty() {
6279 None
6280 } else {
6281 Some(pattern)
6282 }
6283 } else {
6284 None
6285 };
6286
6287 let selected_row = self.state_container.navigation().selected_row;
6289 let selected_col = crosshair_column_position;
6290
6291 trace!(target: "search", "Building TableRenderContext: selected_row={}, selected_col={}, mode={:?}",
6293 selected_row, selected_col, self.state_container.get_selection_mode());
6294
6295 TableRenderContextBuilder::new()
6296 .row_count(row_count)
6297 .visible_rows(visible_row_indices.clone(), data_to_display)
6298 .columns(column_headers, column_widths_visual)
6299 .pinned_columns(pinned_visual_positions)
6300 .selection(
6301 selected_row,
6302 selected_col,
6303 self.state_container.get_selection_mode(),
6304 )
6305 .row_viewport(row_viewport_start..row_viewport_end)
6306 .sort_state(sort_state)
6307 .display_options(
6308 self.state_container.is_show_row_numbers(),
6309 self.shadow_state.borrow().get_mode(),
6310 )
6311 .filter(
6312 fuzzy_filter_pattern,
6313 self.state_container.is_case_insensitive(),
6314 )
6315 .dimensions(available_width, available_height)
6316 .build()
6317 }
6318
6319 fn render_table_with_provider(&self, f: &mut Frame, area: Rect, provider: &dyn DataProvider) {
6322 let context = self.build_table_context(area, provider);
6324
6325 crate::ui::rendering::table_renderer::render_table(f, area, &context);
6327 }
6328
6329 fn render_help(&mut self, f: &mut Frame, area: Rect) {
6330 self.help_widget.render(f, area);
6332 }
6333
6334 fn render_help_two_column(&self, f: &mut Frame, area: Rect) {
6335 let chunks = Layout::default()
6337 .direction(Direction::Horizontal)
6338 .constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
6339 .split(area);
6340
6341 let left_content = HelpText::left_column();
6343 let right_content = HelpText::right_column();
6344
6345 let visible_height = area.height.saturating_sub(2) as usize; let left_total_lines = left_content.len();
6348 let right_total_lines = right_content.len();
6349 let max_lines = left_total_lines.max(right_total_lines);
6350
6351 let scroll_offset = { self.state_container.help_scroll_offset() as usize };
6353
6354 let left_visible: Vec<Line> = left_content
6356 .into_iter()
6357 .skip(scroll_offset)
6358 .take(visible_height)
6359 .collect();
6360
6361 let right_visible: Vec<Line> = right_content
6362 .into_iter()
6363 .skip(scroll_offset)
6364 .take(visible_height)
6365 .collect();
6366
6367 let scroll_indicator = if max_lines > visible_height {
6369 format!(
6370 " (↓/↑ to scroll, {}/{})",
6371 scroll_offset + 1,
6372 max_lines.saturating_sub(visible_height) + 1
6373 )
6374 } else {
6375 String::new()
6376 };
6377
6378 let left_paragraph = Paragraph::new(Text::from(left_visible))
6380 .block(
6381 Block::default()
6382 .borders(Borders::ALL)
6383 .title(format!("Help - Commands{scroll_indicator}")),
6384 )
6385 .style(Style::default());
6386
6387 let right_paragraph = Paragraph::new(Text::from(right_visible))
6389 .block(
6390 Block::default()
6391 .borders(Borders::ALL)
6392 .title("Help - Navigation & Features"),
6393 )
6394 .style(Style::default());
6395
6396 f.render_widget(left_paragraph, chunks[0]);
6397 f.render_widget(right_paragraph, chunks[1]);
6398 }
6399
6400 fn render_debug(&self, f: &mut Frame, area: Rect) {
6401 <Self as DebugContext>::render_debug(self, f, area);
6402 }
6403
6404 fn render_pretty_query(&self, f: &mut Frame, area: Rect) {
6405 <Self as DebugContext>::render_pretty_query(self, f, area);
6406 }
6407
6408 fn render_history(&self, f: &mut Frame, area: Rect) {
6409 let history_search = self.state_container.history_search();
6411 let matches_empty = history_search.matches.is_empty();
6412 let search_query_empty = history_search.query.is_empty();
6413
6414 if matches_empty {
6415 let no_history = if search_query_empty {
6416 "No command history found.\nExecute some queries to build history."
6417 } else {
6418 "No matches found for your search.\nTry a different search term."
6419 };
6420
6421 let placeholder = Paragraph::new(no_history)
6422 .block(
6423 Block::default()
6424 .borders(Borders::ALL)
6425 .title("Command History"),
6426 )
6427 .style(Style::default().fg(Color::DarkGray));
6428 f.render_widget(placeholder, area);
6429 return;
6430 }
6431
6432 let chunks = Layout::default()
6434 .direction(Direction::Vertical)
6435 .constraints([
6436 Constraint::Percentage(50), Constraint::Percentage(50), ])
6439 .split(area);
6440
6441 self.render_history_list(f, chunks[0]);
6442 self.render_selected_command_preview(f, chunks[1]);
6443 }
6444
6445 fn render_history_list(&self, f: &mut Frame, area: Rect) {
6446 let history_search = self.state_container.history_search();
6448 let matches = history_search.matches.clone();
6449 let selected_index = history_search.selected_index;
6450 let match_count = matches.len();
6451
6452 let history_items: Vec<Line> = matches
6454 .iter()
6455 .enumerate()
6456 .map(|(i, history_match)| {
6457 let entry = &history_match.entry;
6458 let is_selected = i == selected_index;
6459
6460 let success_indicator = if entry.success { "✓" } else { "✗" };
6461 let time_ago = {
6462 let elapsed = chrono::Utc::now() - entry.timestamp;
6463 if elapsed.num_days() > 0 {
6464 format!("{}d", elapsed.num_days())
6465 } else if elapsed.num_hours() > 0 {
6466 format!("{}h", elapsed.num_hours())
6467 } else if elapsed.num_minutes() > 0 {
6468 format!("{}m", elapsed.num_minutes())
6469 } else {
6470 "now".to_string()
6471 }
6472 };
6473
6474 let terminal_width = area.width as usize;
6476 let metadata_space = 15; let available_for_command = terminal_width.saturating_sub(metadata_space).max(50);
6478
6479 let command_text = if entry.command.len() > available_for_command {
6480 format!(
6481 "{}…",
6482 &entry.command[..available_for_command.saturating_sub(1)]
6483 )
6484 } else {
6485 entry.command.clone()
6486 };
6487
6488 let line_text = format!(
6489 "{} {} {} {}x {}",
6490 if is_selected { "►" } else { " " },
6491 command_text,
6492 success_indicator,
6493 entry.execution_count,
6494 time_ago
6495 );
6496
6497 let mut style = Style::default();
6498 if is_selected {
6499 style = style.bg(Color::DarkGray).add_modifier(Modifier::BOLD);
6500 }
6501 if !entry.success {
6502 style = style.fg(Color::Red);
6503 }
6504
6505 if !history_match.indices.is_empty() && is_selected {
6507 style = style.fg(Color::Yellow);
6508 }
6509
6510 Line::from(line_text).style(style)
6511 })
6512 .collect();
6513
6514 let history_paragraph = Paragraph::new(history_items)
6515 .block(Block::default().borders(Borders::ALL).title(format!(
6516 "History ({match_count} matches) - j/k to navigate, Enter to select"
6517 )))
6518 .wrap(ratatui::widgets::Wrap { trim: false });
6519
6520 f.render_widget(history_paragraph, area);
6521 }
6522
6523 fn render_selected_command_preview(&self, f: &mut Frame, area: Rect) {
6524 let history_search = self.state_container.history_search();
6526 let selected_match = history_search
6527 .matches
6528 .get(history_search.selected_index)
6529 .cloned();
6530
6531 if let Some(selected_match) = selected_match {
6532 let entry = &selected_match.entry;
6533
6534 use crate::recursive_parser::format_sql_pretty_compact;
6536
6537 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);
6543
6544 let max_lines = area.height.saturating_sub(2) as usize; if pretty_lines.len() > max_lines && cols_per_line < 12 {
6547 pretty_lines = format_sql_pretty_compact(&entry.command, 15);
6549 }
6550
6551 let mut highlighted_lines = Vec::new();
6553 for line in pretty_lines {
6554 highlighted_lines.push(self.sql_highlighter.simple_sql_highlight(&line));
6555 }
6556
6557 let preview_text = Text::from(highlighted_lines);
6558
6559 let duration_text = entry
6560 .duration_ms
6561 .map_or_else(|| "?ms".to_string(), |d| format!("{d}ms"));
6562
6563 let success_text = if entry.success {
6564 "✓ Success"
6565 } else {
6566 "✗ Failed"
6567 };
6568
6569 let preview = Paragraph::new(preview_text)
6570 .block(Block::default().borders(Borders::ALL).title(format!(
6571 "Pretty SQL Preview: {} | {} | Used {}x",
6572 success_text, duration_text, entry.execution_count
6573 )))
6574 .scroll((0, 0)); f.render_widget(preview, area);
6577 } else {
6578 let empty_preview = Paragraph::new("No command selected")
6579 .block(Block::default().borders(Borders::ALL).title("Preview"))
6580 .style(Style::default().fg(Color::DarkGray));
6581 f.render_widget(empty_preview, area);
6582 }
6583 }
6584
6585 fn handle_column_stats_input(&mut self, key: crossterm::event::KeyEvent) -> Result<bool> {
6586 let mut ctx = crate::ui::input::input_handlers::StatsInputContext {
6588 buffer_manager: self.state_container.buffers_mut(),
6589 stats_widget: &mut self.stats_widget,
6590 shadow_state: &self.shadow_state,
6591 };
6592
6593 let result = crate::ui::input::input_handlers::handle_column_stats_input(&mut ctx, key)?;
6594
6595 if self.shadow_state.borrow().get_mode() == AppMode::Results {
6597 self.shadow_state
6598 .borrow_mut()
6599 .observe_mode_change(AppMode::Results, "column_stats_closed");
6600 }
6601
6602 Ok(result)
6603 }
6604
6605 fn handle_jump_to_row_input(&mut self, key: crossterm::event::KeyEvent) -> Result<bool> {
6606 match key.code {
6607 KeyCode::Enter => {
6608 let input = self.get_jump_to_row_input();
6610 self.complete_jump_to_row(&input);
6611 }
6612 _ => {
6613 self.process_jump_to_row_key(key);
6615 }
6616 }
6617 Ok(false)
6618 }
6619
6620 fn render_column_stats(&self, f: &mut Frame, area: Rect) {
6621 self.stats_widget.render(
6623 f,
6624 area,
6625 self.state_container
6626 .current_buffer()
6627 .expect("Buffer should exist"),
6628 );
6629 }
6630
6631 fn handle_execute_query(&mut self) -> Result<bool> {
6637 use crate::ui::state::state_coordinator::StateCoordinator;
6638
6639 let query = self.get_input_text().trim().to_string();
6640 debug!(target: "action", "Executing query: {}", query);
6641
6642 let should_exit = StateCoordinator::handle_execute_query_with_refs(
6644 &mut self.state_container,
6645 &self.shadow_state,
6646 &query,
6647 )?;
6648
6649 if should_exit {
6650 return Ok(true);
6651 }
6652
6653 if !query.is_empty() && !query.starts_with(':') {
6655 if let Err(e) = self.execute_query_v2(&query) {
6656 self.state_container
6657 .set_status_message(format!("Error executing query: {e}"));
6658 }
6659 }
6661
6662 Ok(false) }
6664
6665 fn handle_buffer_action(&mut self, action: BufferAction) -> Result<bool> {
6666 match action {
6667 BufferAction::NextBuffer => {
6668 let message = self
6669 .buffer_handler
6670 .next_buffer(self.state_container.buffers_mut());
6671 debug!("{}", message);
6672 self.sync_after_buffer_switch();
6674 Ok(false)
6675 }
6676 BufferAction::PreviousBuffer => {
6677 let message = self
6678 .buffer_handler
6679 .previous_buffer(self.state_container.buffers_mut());
6680 debug!("{}", message);
6681 self.sync_after_buffer_switch();
6683 Ok(false)
6684 }
6685 BufferAction::QuickSwitch => {
6686 let message = self
6687 .buffer_handler
6688 .quick_switch(self.state_container.buffers_mut());
6689 debug!("{}", message);
6690 self.sync_after_buffer_switch();
6692 Ok(false)
6693 }
6694 BufferAction::NewBuffer => {
6695 let message = self
6696 .buffer_handler
6697 .new_buffer(self.state_container.buffers_mut(), &self.config);
6698 debug!("{}", message);
6699 Ok(false)
6700 }
6701 BufferAction::CloseBuffer => {
6702 let (success, message) = self
6703 .buffer_handler
6704 .close_buffer(self.state_container.buffers_mut());
6705 debug!("{}", message);
6706 Ok(!success) }
6708 BufferAction::ListBuffers => {
6709 let buffer_list = self
6710 .buffer_handler
6711 .list_buffers(self.state_container.buffers());
6712 for line in &buffer_list {
6714 debug!("{}", line);
6715 }
6716 Ok(false)
6717 }
6718 BufferAction::SwitchToBuffer(buffer_index) => {
6719 let message = self
6720 .buffer_handler
6721 .switch_to_buffer(self.state_container.buffers_mut(), buffer_index);
6722 debug!("{}", message);
6723
6724 self.sync_after_buffer_switch();
6726
6727 Ok(false)
6728 }
6729 }
6730 }
6731
6732 fn handle_expand_asterisk(&mut self) -> Result<bool> {
6733 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
6734 if buffer.expand_asterisk(&self.hybrid_parser) {
6735 if buffer.get_edit_mode() == EditMode::SingleLine {
6737 let text = buffer.get_input_text();
6738 let cursor = buffer.get_input_cursor_position();
6739 self.set_input_text_with_cursor(text, cursor);
6740 }
6741 }
6742 }
6743 Ok(false)
6744 }
6745
6746 pub(crate) fn toggle_debug_mode(&mut self) {
6747 DebugContext::toggle_debug_mode(self);
6749 }
6750
6751 pub(crate) fn debug_generate_parser_info(&self, query: &str) -> String {
6756 self.hybrid_parser
6757 .get_detailed_debug_info(query, query.len())
6758 }
6759
6760 fn debug_generate_navigation_state(&self) -> String {
6761 let mut debug_info = String::new();
6762 debug_info.push_str("\n========== NAVIGATION DEBUG ==========\n");
6763 let current_column = self.state_container.get_current_column();
6764 let scroll_offset = self.state_container.get_scroll_offset();
6765 let nav_state = self.state_container.navigation();
6766
6767 debug_info.push_str(&format!("Buffer Column Position: {current_column}\n"));
6768 debug_info.push_str(&format!(
6769 "Buffer Scroll Offset: row={}, col={}\n",
6770 scroll_offset.0, scroll_offset.1
6771 ));
6772 debug_info.push_str(&format!(
6773 "NavigationState Column: {}\n",
6774 nav_state.selected_column
6775 ));
6776 debug_info.push_str(&format!(
6777 "NavigationState Row: {:?}\n",
6778 nav_state.selected_row
6779 ));
6780 debug_info.push_str(&format!(
6781 "NavigationState Scroll Offset: row={}, col={}\n",
6782 nav_state.scroll_offset.0, nav_state.scroll_offset.1
6783 ));
6784
6785 if current_column != nav_state.selected_column {
6787 debug_info.push_str(&format!(
6788 "⚠️ WARNING: Column mismatch! Buffer={}, Nav={}\n",
6789 current_column, nav_state.selected_column
6790 ));
6791 }
6792 if scroll_offset.1 != nav_state.scroll_offset.1 {
6793 debug_info.push_str(&format!(
6794 "⚠️ WARNING: Scroll column mismatch! Buffer={}, Nav={}\n",
6795 scroll_offset.1, nav_state.scroll_offset.1
6796 ));
6797 }
6798
6799 debug_info.push_str("\n--- Navigation Flow ---\n");
6800 debug_info.push_str(
6801 "(Enable RUST_LOG=sql_cli::ui::viewport_manager=debug,navigation=debug to see flow)\n",
6802 );
6803
6804 if let Some(dataview) = self.state_container.get_buffer_dataview() {
6806 let pinned_count = dataview.get_pinned_columns().len();
6807 let pinned_names = dataview.get_pinned_column_names();
6808 debug_info.push_str(&format!("Pinned Column Count: {pinned_count}\n"));
6809 if !pinned_names.is_empty() {
6810 debug_info.push_str(&format!("Pinned Column Names: {pinned_names:?}\n"));
6811 }
6812 debug_info.push_str(&format!("First Scrollable Column: {pinned_count}\n"));
6813
6814 if current_column < pinned_count {
6816 debug_info.push_str(&format!(
6817 "Current Position: PINNED area (column {current_column})\n"
6818 ));
6819 } else {
6820 debug_info.push_str(&format!(
6821 "Current Position: SCROLLABLE area (column {}, scrollable index {})\n",
6822 current_column,
6823 current_column - pinned_count
6824 ));
6825 }
6826
6827 let display_columns = dataview.get_display_columns();
6829 debug_info.push_str("\n--- COLUMN ORDERING ---\n");
6830 debug_info.push_str(&format!(
6831 "Display column order (first 10): {:?}\n",
6832 &display_columns[..display_columns.len().min(10)]
6833 ));
6834 if display_columns.len() > 10 {
6835 debug_info.push_str(&format!(
6836 "... and {} more columns\n",
6837 display_columns.len() - 10
6838 ));
6839 }
6840
6841 if let Some(display_idx) = display_columns
6843 .iter()
6844 .position(|&idx| idx == current_column)
6845 {
6846 debug_info.push_str(&format!(
6847 "Current column {} is at display index {}/{}\n",
6848 current_column,
6849 display_idx,
6850 display_columns.len()
6851 ));
6852
6853 if display_idx + 1 < display_columns.len() {
6855 let next_col = display_columns[display_idx + 1];
6856 debug_info.push_str(&format!(
6857 "Next 'l' press should move to column {} (display index {})\n",
6858 next_col,
6859 display_idx + 1
6860 ));
6861 } else {
6862 debug_info.push_str("Next 'l' press should wrap to first column\n");
6863 }
6864 } else {
6865 debug_info.push_str(&format!(
6866 "WARNING: Current column {current_column} not found in display order!\n"
6867 ));
6868 }
6869 }
6870 debug_info.push_str("==========================================\n");
6871 debug_info
6872 }
6873
6874 fn debug_generate_column_search_state(&self) -> String {
6875 let mut debug_info = String::new();
6876 let show_column_search = self.shadow_state.borrow().get_mode() == AppMode::ColumnSearch
6877 || !self.state_container.column_search().pattern.is_empty();
6878 if show_column_search {
6879 let column_search = self.state_container.column_search();
6880 debug_info.push_str("\n========== COLUMN SEARCH STATE ==========\n");
6881 debug_info.push_str(&format!("Pattern: '{}'\n", column_search.pattern));
6882 debug_info.push_str(&format!(
6883 "Matching Columns: {} found\n",
6884 column_search.matching_columns.len()
6885 ));
6886 if !column_search.matching_columns.is_empty() {
6887 debug_info.push_str("Matches:\n");
6888 for (idx, (col_idx, col_name)) in column_search.matching_columns.iter().enumerate()
6889 {
6890 let marker = if idx == column_search.current_match {
6891 " <--"
6892 } else {
6893 ""
6894 };
6895 debug_info
6896 .push_str(&format!(" [{idx}] {col_name} (index {col_idx}){marker}\n"));
6897 }
6898 }
6899 debug_info.push_str(&format!(
6900 "Current Match Index: {}\n",
6901 column_search.current_match
6902 ));
6903 debug_info.push_str(&format!(
6904 "Current Column: {}\n",
6905 self.state_container.get_current_column()
6906 ));
6907 debug_info.push_str("==========================================\n");
6908 }
6909 debug_info
6910 }
6911
6912 pub(crate) fn debug_generate_trace_logs(&self) -> String {
6913 let mut debug_info = String::from("\n========== TRACE LOGS ==========\n");
6914 debug_info.push_str("(Most recent at bottom, last 100 entries)\n");
6915
6916 if let Some(ref log_buffer) = self.log_buffer {
6917 let recent_logs = log_buffer.get_recent(100);
6918 for entry in recent_logs {
6919 debug_info.push_str(&entry.format_for_display());
6920 debug_info.push('\n');
6921 }
6922 debug_info.push_str(&format!("Total log entries: {}\n", log_buffer.len()));
6923 } else {
6924 debug_info.push_str("Log buffer not initialized\n");
6925 }
6926 debug_info.push_str("================================\n");
6927
6928 debug_info
6929 }
6930
6931 pub(crate) fn debug_generate_state_logs(&self) -> String {
6933 let mut debug_info = String::new();
6934
6935 if let Some(ref debug_service) = self.debug_service {
6936 debug_info.push_str("\n========== STATE CHANGE LOGS ==========\n");
6937 debug_info.push_str("(Most recent at bottom, from DebugService)\n");
6938 let debug_entries = debug_service.get_entries();
6939 let recent = debug_entries.iter().rev().take(50).rev();
6940 for entry in recent {
6941 debug_info.push_str(&format!(
6942 "[{}] {:?} [{}]: {}\n",
6943 entry.timestamp, entry.level, entry.component, entry.message
6944 ));
6945 }
6946 debug_info.push_str(&format!(
6947 "Total state change entries: {}\n",
6948 debug_entries.len()
6949 ));
6950 debug_info.push_str("================================\n");
6951 } else {
6952 debug_info.push_str("\n========== STATE CHANGE LOGS ==========\n");
6953 debug_info.push_str("DebugService not available (service_container is None)\n");
6954 debug_info.push_str("================================\n");
6955 }
6956
6957 debug_info
6958 }
6959
6960 pub(crate) fn debug_extract_timing(&self, s: &str) -> Option<f64> {
6962 crate::ui::rendering::ui_layout_utils::extract_timing_from_debug_string(s)
6963 }
6964
6965 fn show_pretty_query(&mut self) {
6966 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
6967 self.shadow_state.borrow_mut().set_mode(
6968 AppMode::PrettyQuery,
6969 buffer,
6970 "pretty_query_show",
6971 );
6972 let query = buffer.get_input_text();
6973 self.debug_widget.generate_pretty_sql(&query);
6974 }
6975 }
6976
6977 fn add_global_indicators(&self, spans: &mut Vec<Span>) {
6979 if self.key_sequence_renderer.has_content() {
6980 let key_display = self.key_sequence_renderer.get_display();
6981 if !key_display.is_empty() {
6982 spans.push(Span::raw(" | Keys: "));
6983 spans.push(Span::styled(
6984 key_display,
6985 Style::default()
6986 .fg(Color::Cyan)
6987 .add_modifier(Modifier::ITALIC),
6988 ));
6989 }
6990 }
6991 }
6992
6993 fn add_query_source_indicator(&self, spans: &mut Vec<Span>) {
6994 if let Some(source) = self.state_container.get_last_query_source() {
6995 spans.push(Span::raw(" | "));
6996 let (icon, label, color) = match source.as_str() {
6997 "cache" => (
6998 &self.config.display.icons.cache,
6999 "CACHE".to_string(),
7000 Color::Cyan,
7001 ),
7002 "file" | "FileDataSource" => (
7003 &self.config.display.icons.file,
7004 "FILE".to_string(),
7005 Color::Green,
7006 ),
7007 "SqlServerDataSource" => (
7008 &self.config.display.icons.database,
7009 "SQL".to_string(),
7010 Color::Blue,
7011 ),
7012 "PublicApiDataSource" => (
7013 &self.config.display.icons.api,
7014 "API".to_string(),
7015 Color::Yellow,
7016 ),
7017 _ => (
7018 &self.config.display.icons.api,
7019 source.clone(),
7020 Color::Magenta,
7021 ),
7022 };
7023 spans.push(Span::raw(format!("{icon} ")));
7024 spans.push(Span::styled(label, Style::default().fg(color)));
7025 }
7026 }
7027
7028 fn add_case_sensitivity_indicator(&self, spans: &mut Vec<Span>) {
7029 let case_insensitive = self.state_container.is_case_insensitive();
7030 if case_insensitive {
7031 spans.push(Span::raw(" | "));
7032 let icon = self.config.display.icons.case_insensitive.clone();
7033 spans.push(Span::styled(
7034 format!("{icon} CASE"),
7035 Style::default().fg(Color::Cyan),
7036 ));
7037 }
7038 }
7039
7040 fn add_column_packing_indicator(&self, spans: &mut Vec<Span>) {
7041 if let Some(ref viewport_manager) = *self.viewport_manager.borrow() {
7042 let packing_mode = viewport_manager.get_packing_mode();
7043 spans.push(Span::raw(" | "));
7044 let (text, color) = match packing_mode {
7045 ColumnPackingMode::DataFocus => ("DATA", Color::Cyan),
7046 ColumnPackingMode::HeaderFocus => ("HEADER", Color::Yellow),
7047 ColumnPackingMode::Balanced => ("BALANCED", Color::Green),
7048 };
7049 spans.push(Span::styled(text, Style::default().fg(color)));
7050 }
7051 }
7052
7053 fn add_status_message(&self, spans: &mut Vec<Span>) {
7054 let status_msg = self.state_container.get_buffer_status_message();
7055 if !status_msg.is_empty() {
7056 spans.push(Span::raw(" | "));
7057 spans.push(Span::styled(
7058 status_msg,
7059 Style::default()
7060 .fg(Color::Yellow)
7061 .add_modifier(Modifier::BOLD),
7062 ));
7063 }
7064 }
7065
7066 fn get_help_text_for_mode(&self) -> &str {
7067 match self.shadow_state.borrow().get_mode() {
7068 AppMode::Command => "Enter:Run | Tab:Complete | ↓:Results | F1:Help",
7069 AppMode::Results => match self.get_selection_mode() {
7070 SelectionMode::Cell => "v:Row mode | y:Yank cell | ↑:Edit | F1:Help",
7071 SelectionMode::Row => "v:Cell mode | y:Yank | f:Filter | ↑:Edit | F1:Help",
7072 SelectionMode::Column => "v:Cell mode | y:Yank col | ↑:Edit | F1:Help",
7073 },
7074 AppMode::Search | AppMode::Filter | AppMode::FuzzyFilter | AppMode::ColumnSearch => {
7075 "Enter:Apply | Esc:Cancel"
7076 }
7077 AppMode::Help | AppMode::Debug | AppMode::PrettyQuery | AppMode::ColumnStats => {
7078 "Esc:Close"
7079 }
7080 AppMode::History => "Enter:Select | Esc:Cancel",
7081 AppMode::JumpToRow => "Enter:Jump | Esc:Cancel",
7082 }
7083 }
7084
7085 fn add_shadow_state_display(&self, spans: &mut Vec<Span>) {
7086 let shadow_display = self.shadow_state.borrow().status_display();
7087 spans.push(Span::raw(" "));
7088 spans.push(Span::styled(
7089 shadow_display,
7090 Style::default().fg(Color::Cyan),
7091 ));
7092 }
7093
7094 fn add_help_text_display<'a>(&self, spans: &mut Vec<Span<'a>>, help_text: &'a str, area: Rect) {
7096 let current_length: usize = spans.iter().map(|s| s.content.len()).sum();
7097 let available_width = area.width.saturating_sub(TABLE_BORDER_WIDTH) as usize;
7098 let help_length = help_text.len();
7099
7100 if current_length + help_length + 3 < available_width {
7101 let padding = available_width - current_length - help_length - 3;
7102 spans.push(Span::raw(" ".repeat(padding)));
7103 spans.push(Span::raw(" | "));
7104 spans.push(Span::styled(
7105 help_text,
7106 Style::default().fg(Color::DarkGray),
7107 ));
7108 }
7109 }
7110}
7111
7112impl ActionHandlerContext for EnhancedTuiApp {
7114 fn previous_row(&mut self) {
7116 <Self as NavigationBehavior>::previous_row(self);
7117
7118 let current_row = self.state_container.navigation().selected_row;
7120 let current_col = self.state_container.navigation().selected_column;
7121 self.table_widget_manager
7122 .borrow_mut()
7123 .navigate_to(current_row, current_col);
7124 info!(target: "navigation", "previous_row: Updated TableWidgetManager to ({}, {})", current_row, current_col);
7125 }
7126
7127 fn next_row(&mut self) {
7128 info!(target: "navigation", "next_row called - calling NavigationBehavior::next_row");
7129 <Self as NavigationBehavior>::next_row(self);
7130
7131 let current_row = self.state_container.navigation().selected_row;
7133 let current_col = self.state_container.navigation().selected_column;
7134 self.table_widget_manager
7135 .borrow_mut()
7136 .navigate_to(current_row, current_col);
7137 info!(target: "navigation", "next_row: Updated TableWidgetManager to ({}, {})", current_row, current_col);
7138 }
7139
7140 fn move_column_left(&mut self) {
7141 <Self as ColumnBehavior>::move_column_left(self);
7142
7143 let current_row = self.state_container.navigation().selected_row;
7145 let current_col = self.state_container.navigation().selected_column;
7146 self.table_widget_manager
7147 .borrow_mut()
7148 .navigate_to(current_row, current_col);
7149 info!(target: "navigation", "move_column_left: Updated TableWidgetManager to ({}, {})", current_row, current_col);
7150 }
7151
7152 fn move_column_right(&mut self) {
7153 <Self as ColumnBehavior>::move_column_right(self);
7154
7155 let current_row = self.state_container.navigation().selected_row;
7157 let current_col = self.state_container.navigation().selected_column;
7158 self.table_widget_manager
7159 .borrow_mut()
7160 .navigate_to(current_row, current_col);
7161 info!(target: "navigation", "move_column_right: Updated TableWidgetManager to ({}, {})", current_row, current_col);
7162 }
7163
7164 fn page_up(&mut self) {
7165 <Self as NavigationBehavior>::page_up(self);
7166 }
7167
7168 fn page_down(&mut self) {
7169 <Self as NavigationBehavior>::page_down(self);
7170 }
7171
7172 fn goto_first_row(&mut self) {
7173 use crate::ui::state::state_coordinator::StateCoordinator;
7174
7175 <Self as NavigationBehavior>::goto_first_row(self);
7177
7178 StateCoordinator::goto_first_row_with_refs(
7180 &mut self.state_container,
7181 Some(&self.vim_search_adapter),
7182 Some(&self.viewport_manager),
7183 );
7184 }
7185
7186 fn goto_last_row(&mut self) {
7187 use crate::ui::state::state_coordinator::StateCoordinator;
7188
7189 <Self as NavigationBehavior>::goto_last_row(self);
7191
7192 StateCoordinator::goto_last_row_with_refs(&mut self.state_container);
7194 }
7195
7196 fn goto_first_column(&mut self) {
7197 <Self as ColumnBehavior>::goto_first_column(self);
7198 }
7199
7200 fn goto_last_column(&mut self) {
7201 <Self as ColumnBehavior>::goto_last_column(self);
7202 }
7203
7204 fn goto_row(&mut self, row: usize) {
7205 use crate::ui::state::state_coordinator::StateCoordinator;
7206
7207 <Self as NavigationBehavior>::goto_line(self, row + 1); StateCoordinator::goto_row_with_refs(&mut self.state_container, row);
7212 }
7213
7214 fn goto_column(&mut self, col: usize) {
7215 let current_col = self.state_container.get_current_column();
7218 if col < current_col {
7219 for _ in 0..(current_col - col) {
7220 <Self as ColumnBehavior>::move_column_left(self);
7221 }
7222 } else if col > current_col {
7223 for _ in 0..(col - current_col) {
7224 <Self as ColumnBehavior>::move_column_right(self);
7225 }
7226 }
7227 }
7228
7229 fn set_mode(&mut self, mode: AppMode) {
7231 self.set_mode_via_shadow_state(mode, "action_handler");
7233 }
7234
7235 fn get_mode(&self) -> AppMode {
7236 self.shadow_state.borrow().get_mode()
7237 }
7238
7239 fn set_status_message(&mut self, message: String) {
7240 self.state_container.set_status_message(message);
7241 }
7242
7243 fn toggle_column_pin(&mut self) {
7245 self.toggle_column_pin_impl();
7247 }
7248
7249 fn hide_current_column(&mut self) {
7250 <Self as ColumnBehavior>::hide_current_column(self);
7251 }
7252
7253 fn unhide_all_columns(&mut self) {
7254 <Self as ColumnBehavior>::unhide_all_columns(self);
7255 }
7256
7257 fn clear_all_pinned_columns(&mut self) {
7258 self.clear_all_pinned_columns_impl();
7260 }
7261
7262 fn export_to_csv(&mut self) {
7264 self.state_container
7266 .set_status_message("CSV export not yet implemented".to_string());
7267 }
7268
7269 fn export_to_json(&mut self) {
7270 self.state_container
7272 .set_status_message("JSON export not yet implemented".to_string());
7273 }
7274
7275 fn yank_cell(&mut self) {
7277 YankBehavior::yank_cell(self);
7278 }
7279
7280 fn yank_row(&mut self) {
7281 YankBehavior::yank_row(self);
7282 }
7283
7284 fn yank_column(&mut self) {
7285 YankBehavior::yank_column(self);
7286 }
7287
7288 fn yank_all(&mut self) {
7289 YankBehavior::yank_all(self);
7290 }
7291
7292 fn yank_query(&mut self) {
7293 YankBehavior::yank_query(self);
7294 }
7295
7296 fn toggle_selection_mode(&mut self) {
7298 self.state_container.toggle_selection_mode();
7299 let new_mode = self.state_container.get_selection_mode();
7300 let msg = match new_mode {
7301 SelectionMode::Cell => "Cell mode - Navigate to select individual cells",
7302 SelectionMode::Row => "Row mode - Navigate to select rows",
7303 SelectionMode::Column => "Column mode - Navigate to select columns",
7304 };
7305 self.state_container.set_status_message(msg.to_string());
7306 }
7307
7308 fn toggle_row_numbers(&mut self) {
7309 let current = self.state_container.is_show_row_numbers();
7310 self.state_container.set_show_row_numbers(!current);
7311 let message = if current {
7312 "Row numbers: OFF".to_string()
7313 } else {
7314 "Row numbers: ON (showing line numbers)".to_string()
7315 };
7316 self.state_container.set_status_message(message);
7317 self.calculate_optimal_column_widths();
7319 }
7320
7321 fn toggle_compact_mode(&mut self) {
7322 let current_mode = self.state_container.is_compact_mode();
7323 self.state_container.set_compact_mode(!current_mode);
7324 let message = if current_mode {
7325 "Compact mode disabled"
7326 } else {
7327 "Compact mode enabled"
7328 };
7329 self.state_container.set_status_message(message.to_string());
7330 }
7331
7332 fn toggle_case_insensitive(&mut self) {
7333 let current = self.state_container.is_case_insensitive();
7334 self.state_container.set_case_insensitive(!current);
7335 self.state_container.set_status_message(format!(
7336 "Case-insensitive string comparisons: {}",
7337 if current { "OFF" } else { "ON" }
7338 ));
7339 }
7340
7341 fn toggle_key_indicator(&mut self) {
7342 let enabled = !self.key_indicator.enabled;
7343 self.key_indicator.set_enabled(enabled);
7344 self.key_sequence_renderer.set_enabled(enabled);
7345 self.state_container.set_status_message(format!(
7346 "Key press indicator {}",
7347 if enabled { "enabled" } else { "disabled" }
7348 ));
7349 }
7350
7351 fn clear_filter(&mut self) {
7353 if let Some(dataview) = self.state_container.get_buffer_dataview() {
7355 if dataview.has_filter() {
7356 if let Some(dataview_mut) = self.state_container.get_buffer_dataview_mut() {
7358 dataview_mut.clear_filter();
7359 self.state_container
7360 .set_status_message("Filter cleared".to_string());
7361 }
7362
7363 self.sync_dataview_to_managers();
7366 } else {
7367 self.state_container
7368 .set_status_message("No active filter to clear".to_string());
7369 }
7370 } else {
7371 self.state_container
7372 .set_status_message("No data loaded".to_string());
7373 }
7374 }
7375
7376 fn clear_line(&mut self) {
7377 self.state_container.clear_line();
7378 }
7379
7380 fn start_search(&mut self) {
7382 self.start_vim_search();
7383 }
7384
7385 fn start_column_search(&mut self) {
7386 self.enter_search_mode(SearchMode::ColumnSearch);
7387 }
7388
7389 fn start_filter(&mut self) {
7390 self.enter_search_mode(SearchMode::Filter);
7391 }
7392
7393 fn start_fuzzy_filter(&mut self) {
7394 self.enter_search_mode(SearchMode::FuzzyFilter);
7395 }
7396
7397 fn exit_current_mode(&mut self) {
7398 let mode = self.shadow_state.borrow().get_mode();
7406 match mode {
7407 AppMode::Results => {
7408 self.set_mode_via_shadow_state(AppMode::Command, "escape_from_results");
7411 }
7412 AppMode::Command => {
7413 self.set_mode_via_shadow_state(AppMode::Results, "escape_from_command");
7414 }
7415 AppMode::Help => {
7416 self.set_mode_via_shadow_state(AppMode::Results, "escape_from_help");
7417 }
7418 AppMode::JumpToRow => {
7419 self.set_mode_via_shadow_state(AppMode::Results, "escape_from_jump_to_row");
7420 <Self as InputBehavior>::clear_jump_to_row_input(self);
7421 self.state_container.jump_to_row_mut().is_active = false;
7423 self.state_container
7424 .set_status_message("Jump to row cancelled".to_string());
7425 }
7426 _ => {
7427 self.set_mode_via_shadow_state(AppMode::Results, "escape_to_results");
7429 }
7430 }
7431 }
7432
7433 fn toggle_debug_mode(&mut self) {
7434 <Self as DebugContext>::toggle_debug_mode(self);
7436 }
7437
7438 fn move_current_column_left(&mut self) {
7440 <Self as ColumnBehavior>::move_current_column_left(self);
7441 }
7442
7443 fn move_current_column_right(&mut self) {
7444 <Self as ColumnBehavior>::move_current_column_right(self);
7445 }
7446
7447 fn next_search_match(&mut self) {
7449 self.vim_search_next();
7450 }
7451
7452 fn previous_search_match(&mut self) {
7453 if self.vim_search_adapter.borrow().is_active()
7454 || self.vim_search_adapter.borrow().get_pattern().is_some()
7455 {
7456 self.vim_search_previous();
7457 }
7458 }
7459
7460 fn show_column_statistics(&mut self) {
7462 self.calculate_column_statistics();
7463 }
7464
7465 fn cycle_column_packing(&mut self) {
7466 let message = {
7467 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
7468 let viewport_manager = viewport_manager_borrow
7469 .as_mut()
7470 .expect("ViewportManager must exist");
7471 let new_mode = viewport_manager.cycle_packing_mode();
7472 format!("Column packing: {}", new_mode.display_name())
7473 };
7474 self.state_container.set_status_message(message);
7475 }
7476
7477 fn navigate_to_viewport_top(&mut self) {
7479 let result = {
7480 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
7481 (*viewport_manager_borrow)
7482 .as_mut()
7483 .map(super::viewport_manager::ViewportManager::navigate_to_viewport_top)
7484 };
7485
7486 if let Some(result) = result {
7487 self.sync_navigation_with_viewport();
7489
7490 self.state_container
7492 .set_selected_row(Some(result.row_position));
7493
7494 if result.viewport_changed {
7496 let scroll_offset = self.state_container.navigation().scroll_offset;
7497 self.state_container.set_scroll_offset(scroll_offset);
7498 }
7499 }
7500 }
7501
7502 fn navigate_to_viewport_middle(&mut self) {
7503 let result = {
7504 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
7505 (*viewport_manager_borrow)
7506 .as_mut()
7507 .map(super::viewport_manager::ViewportManager::navigate_to_viewport_middle)
7508 };
7509
7510 if let Some(result) = result {
7511 self.sync_navigation_with_viewport();
7513
7514 self.state_container
7516 .set_selected_row(Some(result.row_position));
7517
7518 if result.viewport_changed {
7520 let scroll_offset = self.state_container.navigation().scroll_offset;
7521 self.state_container.set_scroll_offset(scroll_offset);
7522 }
7523 }
7524 }
7525
7526 fn navigate_to_viewport_bottom(&mut self) {
7527 let result = {
7528 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
7529 (*viewport_manager_borrow)
7530 .as_mut()
7531 .map(super::viewport_manager::ViewportManager::navigate_to_viewport_bottom)
7532 };
7533
7534 if let Some(result) = result {
7535 self.sync_navigation_with_viewport();
7537
7538 self.state_container
7540 .set_selected_row(Some(result.row_position));
7541
7542 if result.viewport_changed {
7544 let scroll_offset = self.state_container.navigation().scroll_offset;
7545 self.state_container.set_scroll_offset(scroll_offset);
7546 }
7547 }
7548 }
7549
7550 fn move_input_cursor_left(&mut self) {
7552 self.state_container.move_input_cursor_left();
7553 }
7554
7555 fn move_input_cursor_right(&mut self) {
7556 self.state_container.move_input_cursor_right();
7557 }
7558
7559 fn move_input_cursor_home(&mut self) {
7560 self.state_container.set_input_cursor_position(0);
7561 }
7562
7563 fn move_input_cursor_end(&mut self) {
7564 let text_len = self.state_container.get_input_text().chars().count();
7565 self.state_container.set_input_cursor_position(text_len);
7566 }
7567
7568 fn backspace(&mut self) {
7569 self.state_container.backspace();
7570 }
7571
7572 fn delete(&mut self) {
7573 self.state_container.delete();
7574 }
7575
7576 fn undo(&mut self) {
7577 self.state_container.perform_undo();
7578 }
7579
7580 fn redo(&mut self) {
7581 self.state_container.perform_redo();
7582 }
7583
7584 fn start_jump_to_row(&mut self) {
7585 self.state_container.set_mode(AppMode::JumpToRow);
7586 self.shadow_state
7587 .borrow_mut()
7588 .observe_mode_change(AppMode::JumpToRow, "jump_to_row_requested");
7589 <Self as InputBehavior>::clear_jump_to_row_input(self);
7590
7591 self.state_container.jump_to_row_mut().is_active = true;
7593
7594 self.state_container
7595 .set_status_message("Enter row number (1-based):".to_string());
7596 }
7597
7598 fn clear_jump_to_row_input(&mut self) {
7599 <Self as InputBehavior>::clear_jump_to_row_input(self);
7600 }
7601
7602 fn toggle_cursor_lock(&mut self) {
7603 let is_locked = {
7605 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
7606 if let Some(ref mut viewport_manager) = *viewport_manager_borrow {
7607 viewport_manager.toggle_cursor_lock();
7608 Some(viewport_manager.is_cursor_locked())
7609 } else {
7610 None
7611 }
7612 };
7613
7614 if let Some(is_locked) = is_locked {
7615 let msg = if is_locked {
7616 "Cursor lock ON - cursor stays in viewport position while scrolling"
7617 } else {
7618 "Cursor lock OFF"
7619 };
7620 self.state_container.set_status_message(msg.to_string());
7621
7622 info!(target: "shadow_state",
7624 "Cursor lock toggled: {} (in {:?} mode)",
7625 if is_locked { "ON" } else { "OFF" },
7626 self.shadow_state.borrow().get_mode()
7627 );
7628 }
7629 }
7630
7631 fn toggle_viewport_lock(&mut self) {
7632 let is_locked = {
7634 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
7635 if let Some(ref mut viewport_manager) = *viewport_manager_borrow {
7636 viewport_manager.toggle_viewport_lock();
7637 Some(viewport_manager.is_viewport_locked())
7638 } else {
7639 None
7640 }
7641 };
7642
7643 if let Some(is_locked) = is_locked {
7644 let msg = if is_locked {
7645 "Viewport lock ON - navigation constrained to current viewport"
7646 } else {
7647 "Viewport lock OFF"
7648 };
7649 self.state_container.set_status_message(msg.to_string());
7650
7651 info!(target: "shadow_state",
7653 "Viewport lock toggled: {} (in {:?} mode)",
7654 if is_locked { "ON" } else { "OFF" },
7655 self.shadow_state.borrow().get_mode()
7656 );
7657 }
7658 }
7659
7660 fn show_debug_info(&mut self) {
7662 <Self as DebugContext>::toggle_debug_mode(self);
7663 }
7664
7665 fn show_pretty_query(&mut self) {
7666 self.show_pretty_query();
7667 }
7668
7669 fn show_help(&mut self) {
7670 self.state_container.set_help_visible(true);
7671 self.set_mode_via_shadow_state(AppMode::Help, "help_requested");
7672 self.help_widget.on_enter();
7673 }
7674
7675 fn kill_line(&mut self) {
7677 use crate::ui::traits::input_ops::InputBehavior;
7678 InputBehavior::kill_line(self);
7679 let message = if self.state_container.is_kill_ring_empty() {
7680 "Kill line - nothing to kill".to_string()
7681 } else {
7682 let kill_ring = self.state_container.get_kill_ring();
7683 format!(
7684 "Killed to end of line - {} chars in kill ring",
7685 kill_ring.len()
7686 )
7687 };
7688 self.state_container.set_status_message(message);
7689 }
7690
7691 fn kill_line_backward(&mut self) {
7692 use crate::ui::traits::input_ops::InputBehavior;
7693 InputBehavior::kill_line_backward(self);
7694 let message = if self.state_container.is_kill_ring_empty() {
7695 "Kill line backward - nothing to kill".to_string()
7696 } else {
7697 let kill_ring = self.state_container.get_kill_ring();
7698 format!(
7699 "Killed to beginning of line - {} chars in kill ring",
7700 kill_ring.len()
7701 )
7702 };
7703 self.state_container.set_status_message(message);
7704 }
7705
7706 fn delete_word_backward(&mut self) {
7707 use crate::ui::traits::input_ops::InputBehavior;
7708 InputBehavior::delete_word_backward(self);
7709 }
7710
7711 fn delete_word_forward(&mut self) {
7712 use crate::ui::traits::input_ops::InputBehavior;
7713 InputBehavior::delete_word_forward(self);
7714 }
7715
7716 fn jump_to_prev_token(&mut self) {
7717 use crate::ui::traits::input_ops::InputBehavior;
7718 InputBehavior::jump_to_prev_token(self);
7719 }
7720
7721 fn jump_to_next_token(&mut self) {
7722 use crate::ui::traits::input_ops::InputBehavior;
7723 InputBehavior::jump_to_next_token(self);
7724 }
7725
7726 fn expand_asterisk(&mut self) {
7727 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
7728 if buffer.expand_asterisk(&self.hybrid_parser) {
7729 if buffer.get_edit_mode() == EditMode::SingleLine {
7731 let text = buffer.get_input_text();
7732 let cursor = buffer.get_input_cursor_position();
7733 self.set_input_text_with_cursor(text, cursor);
7734 }
7735 }
7736 }
7737 }
7738
7739 fn expand_asterisk_visible(&mut self) {
7740 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
7741 if buffer.expand_asterisk_visible() {
7742 if buffer.get_edit_mode() == EditMode::SingleLine {
7744 let text = buffer.get_input_text();
7745 let cursor = buffer.get_input_cursor_position();
7746 self.set_input_text_with_cursor(text, cursor);
7747 }
7748 }
7749 }
7750 }
7751
7752 fn previous_history_command(&mut self) {
7753 let history_entries = self
7754 .state_container
7755 .command_history()
7756 .get_navigation_entries();
7757 let history_commands: Vec<String> =
7758 history_entries.iter().map(|e| e.command.clone()).collect();
7759
7760 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
7761 if buffer.navigate_history_up(&history_commands) {
7762 self.sync_all_input_states();
7763 self.state_container
7764 .set_status_message("Previous command from history".to_string());
7765 }
7766 }
7767 }
7768
7769 fn next_history_command(&mut self) {
7770 let history_entries = self
7771 .state_container
7772 .command_history()
7773 .get_navigation_entries();
7774 let history_commands: Vec<String> =
7775 history_entries.iter().map(|e| e.command.clone()).collect();
7776
7777 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
7778 if buffer.navigate_history_down(&history_commands) {
7779 self.sync_all_input_states();
7780 self.state_container
7781 .set_status_message("Next command from history".to_string());
7782 }
7783 }
7784 }
7785}
7786
7787impl NavigationBehavior for EnhancedTuiApp {
7789 fn viewport_manager(&self) -> &RefCell<Option<ViewportManager>> {
7790 &self.viewport_manager
7791 }
7792
7793 fn buffer_mut(&mut self) -> &mut dyn BufferAPI {
7794 self.state_container
7795 .current_buffer_mut()
7796 .expect("Buffer should exist")
7797 }
7798
7799 fn buffer(&self) -> &dyn BufferAPI {
7800 self.state_container
7801 .current_buffer()
7802 .expect("Buffer should exist")
7803 }
7804
7805 fn state_container(&self) -> &AppStateContainer {
7806 &self.state_container
7807 }
7808
7809 fn state_container_mut(&mut self) -> &mut AppStateContainer {
7810 &mut self.state_container
7811 }
7812
7813 fn get_row_count(&self) -> usize {
7814 self.get_row_count()
7815 }
7816
7817 fn set_mode_with_sync(&mut self, mode: AppMode, trigger: &str) {
7818 self.set_mode_via_shadow_state(mode, trigger);
7820 }
7821}
7822
7823impl ColumnBehavior for EnhancedTuiApp {
7825 fn viewport_manager(&self) -> &RefCell<Option<ViewportManager>> {
7826 &self.viewport_manager
7827 }
7828
7829 fn buffer_mut(&mut self) -> &mut dyn BufferAPI {
7830 self.state_container
7831 .current_buffer_mut()
7832 .expect("Buffer should exist")
7833 }
7834
7835 fn buffer(&self) -> &dyn BufferAPI {
7836 self.state_container
7837 .current_buffer()
7838 .expect("Buffer should exist")
7839 }
7840
7841 fn state_container(&self) -> &AppStateContainer {
7842 &self.state_container
7843 }
7844
7845 fn is_in_results_mode(&self) -> bool {
7846 self.shadow_state.borrow().is_in_results_mode()
7847 }
7848}
7849
7850impl InputBehavior for EnhancedTuiApp {
7851 fn buffer_manager(&mut self) -> &mut BufferManager {
7852 self.state_container.buffers_mut()
7853 }
7854
7855 fn cursor_manager(&mut self) -> &mut CursorManager {
7856 &mut self.cursor_manager
7857 }
7858
7859 fn set_input_text_with_cursor(&mut self, text: String, cursor: usize) {
7860 self.set_input_text_with_cursor(text, cursor);
7861 }
7862
7863 fn state_container(&self) -> &AppStateContainer {
7864 &self.state_container
7865 }
7866
7867 fn state_container_mut(&mut self) -> &mut AppStateContainer {
7868 &mut self.state_container
7869 }
7870
7871 fn buffer_mut(&mut self) -> &mut dyn BufferAPI {
7872 self.state_container
7873 .current_buffer_mut()
7874 .expect("Buffer should exist")
7875 }
7876
7877 fn set_mode_with_sync(&mut self, mode: AppMode, trigger: &str) {
7878 self.set_mode_via_shadow_state(mode, trigger);
7880 }
7881}
7882
7883impl YankBehavior for EnhancedTuiApp {
7884 fn buffer(&self) -> &dyn BufferAPI {
7885 self.state_container
7886 .current_buffer()
7887 .expect("Buffer should exist")
7888 }
7889
7890 fn buffer_mut(&mut self) -> &mut dyn BufferAPI {
7891 self.state_container
7892 .current_buffer_mut()
7893 .expect("Buffer should exist")
7894 }
7895
7896 fn state_container(&self) -> &AppStateContainer {
7897 &self.state_container
7898 }
7899
7900 fn set_status_message(&mut self, message: String) {
7901 self.state_container.set_status_message(message);
7902 }
7903
7904 fn set_error_status(&mut self, prefix: &str, error: anyhow::Error) {
7905 self.set_error_status(prefix, error);
7906 }
7907}
7908
7909impl BufferManagementBehavior for EnhancedTuiApp {
7910 fn buffer_manager(&mut self) -> &mut BufferManager {
7911 self.state_container.buffers_mut()
7912 }
7913
7914 fn buffer_handler(&mut self) -> &mut BufferHandler {
7915 &mut self.buffer_handler
7916 }
7917
7918 fn buffer(&self) -> &dyn BufferAPI {
7919 self.state_container
7920 .current_buffer()
7921 .expect("Buffer should exist")
7922 }
7923
7924 fn buffer_mut(&mut self) -> &mut dyn BufferAPI {
7925 self.state_container
7926 .current_buffer_mut()
7927 .expect("Buffer should exist")
7928 }
7929
7930 fn config(&self) -> &Config {
7931 &self.config
7932 }
7933
7934 fn cursor_manager(&mut self) -> &mut CursorManager {
7935 &mut self.cursor_manager
7936 }
7937
7938 fn set_input_text_with_cursor(&mut self, text: String, cursor: usize) {
7939 self.set_input_text_with_cursor(text, cursor);
7940 }
7941
7942 fn next_buffer(&mut self) -> String {
7943 self.save_viewport_to_current_buffer();
7945
7946 let result = self
7947 .buffer_handler
7948 .next_buffer(self.state_container.buffers_mut());
7949
7950 self.sync_after_buffer_switch();
7952
7953 result
7954 }
7955
7956 fn previous_buffer(&mut self) -> String {
7957 self.save_viewport_to_current_buffer();
7959
7960 let result = self
7961 .buffer_handler
7962 .previous_buffer(self.state_container.buffers_mut());
7963
7964 self.sync_after_buffer_switch();
7966
7967 result
7968 }
7969
7970 fn quick_switch_buffer(&mut self) -> String {
7971 self.save_viewport_to_current_buffer();
7973
7974 let result = self
7975 .buffer_handler
7976 .quick_switch(self.state_container.buffers_mut());
7977
7978 self.sync_after_buffer_switch();
7980
7981 result
7982 }
7983
7984 fn close_buffer(&mut self) -> (bool, String) {
7985 self.buffer_handler
7986 .close_buffer(self.state_container.buffers_mut())
7987 }
7988
7989 fn switch_to_buffer(&mut self, index: usize) -> String {
7990 self.save_viewport_to_current_buffer();
7992
7993 let result = self
7995 .buffer_handler
7996 .switch_to_buffer(self.state_container.buffers_mut(), index);
7997
7998 self.sync_after_buffer_switch();
8000
8001 result
8002 }
8003
8004 fn buffer_count(&self) -> usize {
8005 self.state_container.buffers().all_buffers().len()
8006 }
8007
8008 fn current_buffer_index(&self) -> usize {
8009 self.state_container.buffers().current_index()
8010 }
8011}
8012
8013pub fn run_enhanced_tui_multi(api_url: &str, data_files: Vec<&str>) -> Result<()> {
8014 let original_hook = std::panic::take_hook();
8016 std::panic::set_hook(Box::new(move |panic_info| {
8017 let _ = disable_raw_mode();
8019 let _ = execute!(
8020 io::stdout(),
8021 LeaveAlternateScreen,
8022 DisableMouseCapture,
8023 crossterm::cursor::Show
8024 );
8025
8026 original_hook(panic_info);
8028 }));
8029
8030 let app = if data_files.is_empty() {
8031 EnhancedTuiApp::new(api_url)
8032 } else {
8033 use crate::services::ApplicationOrchestrator;
8035
8036 let config = Config::default();
8038 let orchestrator = ApplicationOrchestrator::new(
8039 config.behavior.case_insensitive_default,
8040 config.behavior.hide_empty_columns,
8041 );
8042
8043 let mut app = orchestrator.create_tui_with_file(data_files[0])?;
8045
8046 for file_path in data_files.iter().skip(1) {
8048 if let Err(e) = orchestrator.load_additional_file(&mut app, file_path) {
8049 app.state_container
8050 .set_status_message(format!("Error loading {file_path}: {e}"));
8051 continue;
8052 }
8053 }
8054
8055 if data_files.len() > 1 {
8057 app.state_container.buffers_mut().switch_to(0);
8058 app.sync_after_buffer_switch();
8060 app.state_container.set_status_message(format!(
8061 "Loaded {} files into separate buffers. Use Alt+Tab to switch.",
8062 data_files.len()
8063 ));
8064 } else if data_files.len() == 1 {
8065 app.update_parser_for_current_buffer();
8067 }
8068
8069 app
8070 };
8071
8072 let result = app.run();
8073
8074 let _ = std::panic::take_hook(); result
8079}
8080
8081pub fn run_enhanced_tui(api_url: &str, data_file: Option<&str>) -> Result<()> {
8082 let files = if let Some(file) = data_file {
8084 vec![file]
8085 } else {
8086 vec![]
8087 };
8088 run_enhanced_tui_multi(api_url, files)
8089}