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.key_sequence_renderer.clear_chord_mode();
1885 self.handle_results_input(single_key)
1887 }
1888 }
1889 }
1890
1891 fn try_handle_mode_dispatch(&mut self, key: crossterm::event::KeyEvent) -> Result<bool> {
1893 let mode = self.shadow_state.borrow().get_mode();
1894 debug!(
1895 "try_handle_mode_dispatch: mode={:?}, key={:?}",
1896 mode, key.code
1897 );
1898 match mode {
1899 AppMode::Command => self.handle_command_input(key),
1900 AppMode::Results => {
1901 self.try_handle_chord_processing(key)
1903 }
1904 AppMode::Search | AppMode::Filter | AppMode::FuzzyFilter | AppMode::ColumnSearch => {
1905 self.handle_search_modes_input(key)
1906 }
1907 AppMode::Help => self.handle_help_input(key),
1908 AppMode::History => self.handle_history_input(key),
1909 AppMode::Debug => self.handle_debug_input(key),
1910 AppMode::PrettyQuery => self.handle_pretty_query_input(key),
1911 AppMode::JumpToRow => self.handle_jump_to_row_input(key),
1912 AppMode::ColumnStats => self.handle_column_stats_input(key),
1913 }
1914 }
1915
1916 fn try_handle_key_event<B: Backend>(
1918 &mut self,
1919 terminal: &mut Terminal<B>,
1920 key: crossterm::event::KeyEvent,
1921 ) -> Result<bool> {
1922 if key.kind != crossterm::event::KeyEventKind::Press {
1925 return Ok(false);
1926 }
1927
1928 if key.code == KeyCode::Char('c') && key.modifiers.contains(KeyModifiers::CONTROL) {
1931 info!(target: "app", "Ctrl-C detected, forcing exit");
1932 return Ok(true);
1933 }
1934
1935 let key_display = format_key_for_display(&key);
1937 self.key_indicator.record_key(key_display.clone());
1938 self.key_sequence_renderer.record_key(key_display);
1939
1940 let should_exit = self.try_handle_mode_dispatch(key)?;
1942
1943 if should_exit {
1944 return Ok(true);
1945 }
1946
1947 if self.table_widget_manager.borrow().needs_render() {
1949 info!("TableWidgetManager needs render after key event");
1950 }
1951 terminal.draw(|f| self.ui(f))?;
1952 self.table_widget_manager.borrow_mut().rendered();
1953
1954 Ok(false)
1955 }
1956
1957 fn try_handle_events<B: Backend>(&mut self, terminal: &mut Terminal<B>) -> Result<bool> {
1959 if event::poll(std::time::Duration::from_millis(50))? {
1961 if let Event::Key(key) = event::read()? {
1962 if self.try_handle_key_event(terminal, key)? {
1963 return Ok(true);
1964 }
1965 } else {
1966 }
1968 } else {
1969 if self.key_chord_handler.clear_if_timed_out() {
1972 self.key_sequence_renderer.clear_chord_mode();
1973 self.state_container
1974 .set_status_message("Chord cancelled (timeout)".to_string());
1975 terminal.draw(|f| self.ui(f))?;
1976 return Ok(false);
1977 }
1978
1979 if self.search_modes_widget.is_active()
1981 || self.table_widget_manager.borrow().needs_render()
1982 {
1983 if self.table_widget_manager.borrow().needs_render() {
1984 info!("TableWidgetManager needs periodic render");
1985 }
1986 terminal.draw(|f| self.ui(f))?;
1987 self.table_widget_manager.borrow_mut().rendered();
1988 }
1989 }
1990 Ok(false)
1991 }
1992
1993 fn run_app<B: Backend>(&mut self, terminal: &mut Terminal<B>) -> Result<()> {
1994 self.initialize_viewport(terminal)?;
1995
1996 loop {
1997 if self.try_handle_debounced_actions(terminal)? {
1999 break;
2000 }
2001
2002 if self.try_handle_events(terminal)? {
2004 break;
2005 }
2006 }
2007 Ok(())
2008 }
2009
2010 fn handle_command_input(&mut self, key: crossterm::event::KeyEvent) -> Result<bool> {
2011 let normalized_key = self.normalize_and_log_key(key);
2013
2014 let is_special_combo = if let KeyCode::Char(c) = normalized_key.code {
2021 (normalized_key.modifiers.contains(KeyModifiers::CONTROL) && matches!(c,
2023 'x' | 'X' | 'p' | 'P' | 'n' | 'N' | 'r' | 'R' | 'j' | 'J' | 'o' | 'O' | 'b' | 'B' | 'l' | 'L' )) ||
2032 (normalized_key.modifiers.contains(KeyModifiers::ALT) && matches!(c,
2034 'x' | 'X' | '[' | ']' | ',' | '.' ))
2038 } else {
2039 false
2040 };
2041
2042 let should_try_command_editor = !is_special_combo
2043 && matches!(
2044 normalized_key.code,
2045 KeyCode::Char(_)
2046 | KeyCode::Backspace
2047 | KeyCode::Delete
2048 | KeyCode::Left
2049 | KeyCode::Right
2050 | KeyCode::Home
2051 | KeyCode::End
2052 );
2053
2054 if should_try_command_editor {
2055 let before_text = self.input.value().to_string();
2058 let before_cursor = self.input.cursor();
2059
2060 if self.command_editor.get_text() != before_text {
2061 self.command_editor.set_text(before_text.clone());
2062 }
2063 if self.command_editor.get_cursor() != before_cursor {
2064 self.command_editor.set_cursor(before_cursor);
2065 }
2066
2067 let result = self.command_editor.handle_input(
2069 normalized_key,
2070 &mut self.state_container,
2071 &self.shadow_state,
2072 )?;
2073
2074 let new_text = self.command_editor.get_text();
2077 let new_cursor = self.command_editor.get_cursor();
2078
2079 if new_text != before_text || new_cursor != before_cursor {
2081 debug!(
2082 "CommandEditor changed input: '{}' -> '{}', cursor: {} -> {}",
2083 before_text, new_text, before_cursor, new_cursor
2084 );
2085 }
2086
2087 self.input = tui_input::Input::from(new_text.clone()).with_cursor(new_cursor);
2089
2090 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
2093 buffer.set_input_text(new_text.clone());
2094 buffer.set_input_cursor_position(new_cursor);
2095 }
2096
2097 self.state_container.set_input_text(new_text);
2099 self.state_container.set_input_cursor_position(new_cursor);
2100
2101 if result {
2102 return Ok(true);
2103 }
2104
2105 return Ok(false);
2107 }
2108 if let Some(result) = self.try_action_system(normalized_key)? {
2112 return Ok(result);
2113 }
2114
2115 if let Some(result) = self.try_editor_widget(normalized_key)? {
2117 return Ok(result);
2118 }
2119
2120 if let Some(result) = self.try_handle_history_navigation(&normalized_key)? {
2124 return Ok(result);
2125 }
2126
2127 let old_cursor = self.get_input_cursor();
2129
2130 trace!(target: "input", "Key: {:?} Modifiers: {:?}", key.code, key.modifiers);
2132
2133 if let Some(result) = self.try_handle_buffer_operations(&key)? {
2138 return Ok(result);
2139 }
2140
2141 if let Some(result) = self.try_handle_function_keys(&key)? {
2143 return Ok(result);
2144 }
2145
2146 if let Some(result) = self.try_handle_text_editing(&key)? {
2148 return Ok(result);
2149 }
2150
2151 if let Some(result) = self.try_handle_mode_transitions(&key, old_cursor)? {
2153 return Ok(result);
2154 }
2155
2156 Ok(false)
2160 }
2161
2162 fn normalize_and_log_key(
2167 &mut self,
2168 key: crossterm::event::KeyEvent,
2169 ) -> crossterm::event::KeyEvent {
2170 let normalized = self.state_container.normalize_key(key);
2171
2172 let action = self
2174 .key_dispatcher
2175 .get_command_action(&normalized)
2176 .map(std::string::ToString::to_string);
2177
2178 if normalized != key {
2180 self.state_container
2181 .log_key_press(key, Some(format!("normalized to {normalized:?}")));
2182 }
2183 self.state_container.log_key_press(normalized, action);
2184
2185 normalized
2186 }
2187
2188 fn try_action_system(
2190 &mut self,
2191 normalized_key: crossterm::event::KeyEvent,
2192 ) -> Result<Option<bool>> {
2193 let action_context = self.build_action_context();
2194 if let Some(action) = self.key_mapper.map_key(normalized_key, &action_context) {
2195 info!(
2196 "✓ Action system (Command): key {:?} -> action {:?}",
2197 normalized_key.code, action
2198 );
2199 if let Ok(result) = self.try_handle_action(action, &action_context) {
2200 match result {
2201 ActionResult::Handled => {
2202 debug!("Action handled by new system in Command mode");
2203 return Ok(Some(false));
2204 }
2205 ActionResult::Exit => {
2206 return Ok(Some(true));
2207 }
2208 ActionResult::NotHandled => {
2209 }
2211 _ => {}
2212 }
2213 }
2214 }
2215 Ok(None)
2216 }
2217
2218 fn try_editor_widget(
2220 &mut self,
2221 normalized_key: crossterm::event::KeyEvent,
2222 ) -> Result<Option<bool>> {
2223 let key_dispatcher = self.key_dispatcher.clone();
2224 let editor_result = if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
2225 self.editor_widget
2226 .handle_key(normalized_key, &key_dispatcher, buffer)?
2227 } else {
2228 EditorAction::PassToMainApp(normalized_key)
2229 };
2230
2231 match editor_result {
2232 EditorAction::Quit => return Ok(Some(true)),
2233 EditorAction::ExecuteQuery => {
2234 return self.handle_execute_query().map(Some);
2235 }
2236 EditorAction::BufferAction(buffer_action) => {
2237 return self.handle_buffer_action(buffer_action).map(Some);
2238 }
2239 EditorAction::ExpandAsterisk => {
2240 return self.handle_expand_asterisk().map(Some);
2241 }
2242 EditorAction::ShowHelp => {
2243 self.state_container.set_help_visible(true);
2244 self.set_mode_via_shadow_state(AppMode::Help, "help_requested");
2246 return Ok(Some(false));
2247 }
2248 EditorAction::ShowDebug => {
2249 return Ok(Some(false));
2251 }
2252 EditorAction::ShowPrettyQuery => {
2253 self.show_pretty_query();
2254 return Ok(Some(false));
2255 }
2256 EditorAction::SwitchMode(mode) => {
2257 self.handle_editor_mode_switch(mode);
2258 return Ok(Some(false));
2259 }
2260 EditorAction::PassToMainApp(_) => {
2261 }
2263 EditorAction::Continue => return Ok(Some(false)),
2264 }
2265
2266 Ok(None)
2267 }
2268
2269 fn handle_editor_mode_switch(&mut self, mode: AppMode) {
2271 debug!(target: "shadow_state", "EditorAction::SwitchMode to {:?}", mode);
2272 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
2273 let trigger = match mode {
2275 AppMode::Results => "enter_results_mode",
2276 AppMode::Command => "enter_command_mode",
2277 AppMode::History => "enter_history_mode",
2278 _ => "switch_mode",
2279 };
2280 debug!(target: "shadow_state", "Setting mode via shadow state to {:?} with trigger {}", mode, trigger);
2281 self.shadow_state
2282 .borrow_mut()
2283 .set_mode(mode.clone(), buffer, trigger);
2284 } else {
2285 debug!(target: "shadow_state", "No buffer available for mode switch!");
2286 }
2287
2288 if mode == AppMode::History {
2290 eprintln!("[DEBUG] Using AppStateContainer for history search");
2291 let current_input = self.get_input_text();
2292
2293 self.state_container.start_history_search(current_input);
2295
2296 self.update_history_matches_in_container();
2298
2299 let match_count = self.state_container.history_search().matches.len();
2301
2302 self.state_container
2303 .set_status_message(format!("History search: {match_count} matches"));
2304 }
2305 }
2306
2307 fn try_handle_function_keys(
2309 &mut self,
2310 key: &crossterm::event::KeyEvent,
2311 ) -> Result<Option<bool>> {
2312 match key.code {
2313 KeyCode::F(1) | KeyCode::Char('?') => {
2314 if self.shadow_state.borrow().is_in_help_mode() {
2316 let mode = if self.state_container.has_dataview() {
2318 AppMode::Results
2319 } else {
2320 AppMode::Command
2321 };
2322 self.set_mode_via_shadow_state(mode, "exit_help");
2324 self.state_container.set_help_visible(false);
2325 self.help_widget.on_exit();
2326 } else {
2327 self.state_container.set_help_visible(true);
2329 self.set_mode_via_shadow_state(AppMode::Help, "help_requested");
2331 self.help_widget.on_enter();
2332 }
2333 Ok(Some(false))
2334 }
2335 KeyCode::F(3) => {
2336 self.show_pretty_query();
2338 Ok(Some(false))
2339 }
2340 KeyCode::F(5) => {
2341 self.toggle_debug_mode();
2343 Ok(Some(false))
2344 }
2345 KeyCode::F(6) => {
2346 let current = self.state_container.is_show_row_numbers();
2348 self.state_container.set_show_row_numbers(!current);
2349 self.state_container.set_status_message(format!(
2350 "Row numbers: {}",
2351 if current { "OFF" } else { "ON" }
2352 ));
2353 Ok(Some(false))
2354 }
2355 KeyCode::F(7) => {
2356 let current_mode = self.state_container.is_compact_mode();
2358 self.state_container.set_compact_mode(!current_mode);
2359 let message = if current_mode {
2360 "Compact mode disabled"
2361 } else {
2362 "Compact mode enabled"
2363 };
2364 self.state_container.set_status_message(message.to_string());
2365 Ok(Some(false))
2366 }
2367 KeyCode::F(8) => {
2368 let current = self.state_container.is_case_insensitive();
2370 self.state_container.set_case_insensitive(!current);
2371 self.state_container.set_status_message(format!(
2372 "Case-insensitive string comparisons: {}",
2373 if current { "OFF" } else { "ON" }
2374 ));
2375 Ok(Some(false))
2376 }
2377 KeyCode::F(9) => {
2378 use crate::ui::traits::input_ops::InputBehavior;
2380 InputBehavior::kill_line(self);
2381 let message = if self.state_container.is_kill_ring_empty() {
2382 "Killed to end of line".to_string()
2383 } else {
2384 format!(
2385 "Killed to end of line ('{}' saved to kill ring)",
2386 self.state_container.get_kill_ring()
2387 )
2388 };
2389 self.state_container.set_status_message(message);
2390 Ok(Some(false))
2391 }
2392 KeyCode::F(10) => {
2393 use crate::ui::traits::input_ops::InputBehavior;
2395 InputBehavior::kill_line_backward(self);
2396 let message = if self.state_container.is_kill_ring_empty() {
2397 "Killed to beginning of line".to_string()
2398 } else {
2399 format!(
2400 "Killed to beginning of line ('{}' saved to kill ring)",
2401 self.state_container.get_kill_ring()
2402 )
2403 };
2404 self.state_container.set_status_message(message);
2405 Ok(Some(false))
2406 }
2407 KeyCode::F(12) => {
2408 let enabled = !self.key_indicator.enabled;
2410 self.key_indicator.set_enabled(enabled);
2411 self.key_sequence_renderer.set_enabled(enabled);
2412 self.state_container.set_status_message(format!(
2413 "Key press indicator {}",
2414 if enabled { "enabled" } else { "disabled" }
2415 ));
2416 Ok(Some(false))
2417 }
2418 _ => Ok(None), }
2420 }
2421
2422 fn try_handle_buffer_operations(
2424 &mut self,
2425 key: &crossterm::event::KeyEvent,
2426 ) -> Result<Option<bool>> {
2427 if let Some(action) = self.key_dispatcher.get_command_action(key) {
2428 match action {
2429 "quit" => return Ok(Some(true)),
2430 "next_buffer" => {
2431 self.save_viewport_to_current_buffer();
2433
2434 let message = self
2435 .buffer_handler
2436 .next_buffer(self.state_container.buffers_mut());
2437 debug!("{}", message);
2438
2439 self.sync_after_buffer_switch();
2441 return Ok(Some(false));
2442 }
2443 "previous_buffer" => {
2444 self.save_viewport_to_current_buffer();
2446
2447 let message = self
2448 .buffer_handler
2449 .previous_buffer(self.state_container.buffers_mut());
2450 debug!("{}", message);
2451
2452 self.sync_after_buffer_switch();
2454 return Ok(Some(false));
2455 }
2456 "quick_switch_buffer" => {
2457 self.save_viewport_to_current_buffer();
2459
2460 let message = self
2461 .buffer_handler
2462 .quick_switch(self.state_container.buffers_mut());
2463 debug!("{}", message);
2464
2465 self.sync_after_buffer_switch();
2467
2468 return Ok(Some(false));
2469 }
2470 "new_buffer" => {
2471 let message = self
2472 .buffer_handler
2473 .new_buffer(self.state_container.buffers_mut(), &self.config);
2474 debug!("{}", message);
2475 return Ok(Some(false));
2476 }
2477 "close_buffer" => {
2478 let (success, message) = self
2479 .buffer_handler
2480 .close_buffer(self.state_container.buffers_mut());
2481 debug!("{}", message);
2482 return Ok(Some(!success)); }
2484 "list_buffers" => {
2485 let buffer_list = self
2486 .buffer_handler
2487 .list_buffers(self.state_container.buffers());
2488 for line in &buffer_list {
2489 debug!("{}", line);
2490 }
2491 return Ok(Some(false));
2492 }
2493 action if action.starts_with("switch_to_buffer_") => {
2494 if let Some(buffer_num_str) = action.strip_prefix("switch_to_buffer_") {
2495 if let Ok(buffer_num) = buffer_num_str.parse::<usize>() {
2496 self.save_viewport_to_current_buffer();
2498
2499 let message = self.buffer_handler.switch_to_buffer(
2500 self.state_container.buffers_mut(),
2501 buffer_num - 1,
2502 );
2503 debug!("{}", message);
2504
2505 self.sync_after_buffer_switch();
2507 }
2508 }
2509 return Ok(Some(false));
2510 }
2511 _ => {} }
2513 }
2514 Ok(None)
2515 }
2516
2517 fn try_handle_history_navigation(
2519 &mut self,
2520 key: &crossterm::event::KeyEvent,
2521 ) -> Result<Option<bool>> {
2522 if let KeyCode::Char('r') = key.code {
2524 if key.modifiers.contains(KeyModifiers::CONTROL) {
2525 let current_input = self.get_input_text();
2527
2528 self.state_container.start_history_search(current_input);
2530
2531 self.update_history_matches_in_container();
2533
2534 let match_count = self.state_container.history_search().matches.len();
2536
2537 self.state_container.set_mode(AppMode::History);
2538 self.shadow_state
2539 .borrow_mut()
2540 .observe_mode_change(AppMode::History, "history_search_started");
2541 self.state_container.set_status_message(format!(
2542 "History search started (Ctrl+R) - {match_count} matches"
2543 ));
2544 return Ok(Some(false));
2545 }
2546 }
2547
2548 if let KeyCode::Char('p') = key.code {
2550 if key.modifiers.contains(KeyModifiers::CONTROL) {
2551 let history_entries = self
2552 .state_container
2553 .command_history()
2554 .get_navigation_entries();
2555 let history_commands: Vec<String> =
2556 history_entries.iter().map(|e| e.command.clone()).collect();
2557
2558 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
2559 if buffer.navigate_history_up(&history_commands) {
2560 self.sync_all_input_states();
2561 self.state_container
2562 .set_status_message("Previous command from history".to_string());
2563 }
2564 }
2565 return Ok(Some(false));
2566 }
2567 }
2568
2569 if let KeyCode::Char('n') = key.code {
2571 if key.modifiers.contains(KeyModifiers::CONTROL) {
2572 let history_entries = self
2573 .state_container
2574 .command_history()
2575 .get_navigation_entries();
2576 let history_commands: Vec<String> =
2577 history_entries.iter().map(|e| e.command.clone()).collect();
2578
2579 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
2580 if buffer.navigate_history_down(&history_commands) {
2581 self.sync_all_input_states();
2582 self.state_container
2583 .set_status_message("Next command from history".to_string());
2584 }
2585 }
2586 return Ok(Some(false));
2587 }
2588 }
2589
2590 match key.code {
2592 KeyCode::Up if key.modifiers.contains(KeyModifiers::ALT) => {
2593 let history_entries = self
2594 .state_container
2595 .command_history()
2596 .get_navigation_entries();
2597 let history_commands: Vec<String> =
2598 history_entries.iter().map(|e| e.command.clone()).collect();
2599
2600 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
2601 if buffer.navigate_history_up(&history_commands) {
2602 self.sync_all_input_states();
2603 self.state_container
2604 .set_status_message("Previous command (Alt+Up)".to_string());
2605 }
2606 }
2607 Ok(Some(false))
2608 }
2609 KeyCode::Down if key.modifiers.contains(KeyModifiers::ALT) => {
2610 let history_entries = self
2611 .state_container
2612 .command_history()
2613 .get_navigation_entries();
2614 let history_commands: Vec<String> =
2615 history_entries.iter().map(|e| e.command.clone()).collect();
2616
2617 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
2618 if buffer.navigate_history_down(&history_commands) {
2619 self.sync_all_input_states();
2620 self.state_container
2621 .set_status_message("Next command (Alt+Down)".to_string());
2622 }
2623 }
2624 Ok(Some(false))
2625 }
2626 _ => Ok(None),
2627 }
2628 }
2629
2630 fn try_handle_text_editing(
2632 &mut self,
2633 key: &crossterm::event::KeyEvent,
2634 ) -> Result<Option<bool>> {
2635 if let Some(action) = self.key_dispatcher.get_command_action(key) {
2637 match action {
2638 "expand_asterisk" => {
2639 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
2640 if buffer.expand_asterisk(&self.hybrid_parser) {
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 "expand_asterisk_visible" => {
2652 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
2653 if buffer.expand_asterisk_visible() {
2654 if buffer.get_edit_mode() == EditMode::SingleLine {
2656 let text = buffer.get_input_text();
2657 let cursor = buffer.get_input_cursor_position();
2658 self.set_input_text_with_cursor(text, cursor);
2659 }
2660 }
2661 }
2662 return Ok(Some(false));
2663 }
2664 "delete_word_backward" => {
2666 use crate::ui::traits::input_ops::InputBehavior;
2667 InputBehavior::delete_word_backward(self);
2668 return Ok(Some(false));
2669 }
2670 "delete_word_forward" => {
2672 use crate::ui::traits::input_ops::InputBehavior;
2673 InputBehavior::delete_word_forward(self);
2674 return Ok(Some(false));
2675 }
2676 "kill_line" => {
2678 use crate::ui::traits::input_ops::InputBehavior;
2679 InputBehavior::kill_line(self);
2680 return Ok(Some(false));
2681 }
2682 "kill_line_backward" => {
2684 use crate::ui::traits::input_ops::InputBehavior;
2685 InputBehavior::kill_line_backward(self);
2686 return Ok(Some(false));
2687 }
2688 "move_word_backward" => {
2690 self.move_cursor_word_backward();
2691 return Ok(Some(false));
2692 }
2693 "move_word_forward" => {
2695 self.move_cursor_word_forward();
2696 return Ok(Some(false));
2697 }
2698 _ => {} }
2700 }
2701
2702 match key.code {
2704 KeyCode::Char('k') if key.modifiers.contains(KeyModifiers::CONTROL) => {
2706 self.state_container
2708 .set_status_message("Ctrl+K pressed - killing to end of line".to_string());
2709 use crate::ui::traits::input_ops::InputBehavior;
2710 InputBehavior::kill_line(self);
2711 Ok(Some(false))
2712 }
2713 KeyCode::Char('k') if key.modifiers.contains(KeyModifiers::ALT) => {
2715 self.state_container
2717 .set_status_message("Alt+K - killing to end of line".to_string());
2718 use crate::ui::traits::input_ops::InputBehavior;
2719 InputBehavior::kill_line(self);
2720 Ok(Some(false))
2721 }
2722 KeyCode::Char('u') if key.modifiers.contains(KeyModifiers::CONTROL) => {
2724 use crate::ui::traits::input_ops::InputBehavior;
2726 InputBehavior::kill_line_backward(self);
2727 Ok(Some(false))
2728 }
2729 KeyCode::Char('v') if key.modifiers.contains(KeyModifiers::CONTROL) => {
2730 self.paste_from_clipboard();
2732 Ok(Some(false))
2733 }
2734 KeyCode::Char('y') if key.modifiers.contains(KeyModifiers::CONTROL) => {
2735 self.yank();
2737 Ok(Some(false))
2738 }
2739 KeyCode::Left if key.modifiers.contains(KeyModifiers::CONTROL) => {
2741 self.move_cursor_word_backward();
2743 Ok(Some(false))
2744 }
2745 KeyCode::Right if key.modifiers.contains(KeyModifiers::CONTROL) => {
2747 self.move_cursor_word_forward();
2749 Ok(Some(false))
2750 }
2751 KeyCode::Char('b') if key.modifiers.contains(KeyModifiers::ALT) => {
2753 self.move_cursor_word_backward();
2755 Ok(Some(false))
2756 }
2757 KeyCode::Char('f') if key.modifiers.contains(KeyModifiers::ALT) => {
2759 self.move_cursor_word_forward();
2761 Ok(Some(false))
2762 }
2763 _ => Ok(None), }
2765 }
2766
2767 fn try_handle_mode_transitions(
2769 &mut self,
2770 key: &crossterm::event::KeyEvent,
2771 old_cursor: usize,
2772 ) -> Result<Option<bool>> {
2773 match key.code {
2774 KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => {
2775 Ok(Some(true))
2777 }
2778 KeyCode::Char('d') if key.modifiers.contains(KeyModifiers::CONTROL) => {
2779 Ok(Some(true))
2781 }
2782 KeyCode::Enter => {
2783 let query = self.get_input_text().trim().to_string();
2785 debug!(target: "action", "Executing query: {}", query);
2786
2787 if query.is_empty() {
2788 self.state_container
2789 .set_status_message("Empty query - please enter a SQL command".to_string());
2790 } else {
2791 if query == ":help" {
2793 self.state_container.set_help_visible(true);
2794 self.set_mode_via_shadow_state(AppMode::Help, "help_requested");
2796 self.state_container
2797 .set_status_message("Help Mode - Press ESC to return".to_string());
2798 } else if query == ":exit" || query == ":quit" || query == ":q" {
2799 return Ok(Some(true));
2800 } else if query == ":tui" {
2801 self.state_container
2803 .set_status_message("Already in TUI mode".to_string());
2804 } else {
2805 self.state_container
2806 .set_status_message(format!("Processing query: '{query}'"));
2807 self.execute_query_v2(&query)?;
2808 }
2809 }
2810 Ok(Some(false))
2811 }
2812 KeyCode::Tab => {
2813 self.apply_completion();
2815 Ok(Some(false))
2816 }
2817 KeyCode::Down => {
2818 debug!(target: "shadow_state", "Down arrow pressed in Command mode. has_dataview={}, edit_mode={:?}",
2819 self.state_container.has_dataview(),
2820 self.state_container.get_edit_mode());
2821
2822 if self.state_container.has_dataview()
2823 && self.state_container.get_edit_mode() == Some(EditMode::SingleLine)
2824 {
2825 debug!(target: "shadow_state", "Down arrow conditions met, switching to Results via set_mode");
2826 self.state_container.set_mode(AppMode::Results);
2828 self.shadow_state
2829 .borrow_mut()
2830 .observe_mode_change(AppMode::Results, "down_arrow_to_results");
2831 let row = self.state_container.get_last_results_row().unwrap_or(0);
2833 self.state_container.set_table_selected_row(Some(row));
2834
2835 let last_offset = self.state_container.get_last_scroll_offset();
2837 self.state_container.set_scroll_offset(last_offset);
2838 Ok(Some(false))
2839 } else {
2840 debug!(target: "shadow_state", "Down arrow conditions not met, falling through");
2841 Ok(None)
2843 }
2844 }
2845 _ => {
2846 self.handle_input_key(*key);
2848
2849 self.state_container.clear_completion();
2851
2852 self.handle_completion();
2854
2855 if self.get_input_cursor() != old_cursor {
2857 self.update_horizontal_scroll(120); }
2859
2860 Ok(Some(false))
2861 }
2862 }
2863 }
2864
2865 fn try_handle_results_navigation(
2867 &mut self,
2868 key: &crossterm::event::KeyEvent,
2869 ) -> Result<Option<bool>> {
2870 match key.code {
2871 KeyCode::PageDown | KeyCode::Char('f')
2872 if key.modifiers.contains(KeyModifiers::CONTROL) =>
2873 {
2874 NavigationBehavior::page_down(self);
2875 Ok(Some(false))
2876 }
2877 KeyCode::PageUp | KeyCode::Char('b')
2878 if key.modifiers.contains(KeyModifiers::CONTROL) =>
2879 {
2880 NavigationBehavior::page_up(self);
2881 Ok(Some(false))
2882 }
2883 _ => Ok(None), }
2885 }
2886
2887 fn try_handle_results_clipboard(
2889 &mut self,
2890 key: &crossterm::event::KeyEvent,
2891 ) -> Result<Option<bool>> {
2892 match key.code {
2893 KeyCode::Char('y') => {
2894 let selection_mode = self.get_selection_mode();
2895 debug!("'y' key pressed - selection_mode={:?}", selection_mode);
2896 match selection_mode {
2897 SelectionMode::Cell => {
2898 debug!("Yanking cell in cell selection mode");
2900 self.state_container
2901 .set_status_message("Yanking cell...".to_string());
2902 YankBehavior::yank_cell(self);
2903 }
2905 SelectionMode::Row => {
2906 debug!("'y' pressed in row mode - waiting for chord completion");
2909 self.state_container.set_status_message(
2910 "Press second key for chord: yy=row, yc=column, ya=all, yv=cell"
2911 .to_string(),
2912 );
2913 }
2914 SelectionMode::Column => {
2915 debug!("Yanking column in column selection mode");
2917 self.state_container
2918 .set_status_message("Yanking column...".to_string());
2919 YankBehavior::yank_column(self);
2920 }
2921 }
2922 Ok(Some(false))
2923 }
2924 _ => Ok(None),
2925 }
2926 }
2927
2928 fn try_handle_results_export(
2930 &mut self,
2931 key: &crossterm::event::KeyEvent,
2932 ) -> Result<Option<bool>> {
2933 match key.code {
2934 KeyCode::Char('e') if key.modifiers.contains(KeyModifiers::CONTROL) => {
2935 self.export_to_csv();
2936 Ok(Some(false))
2937 }
2938 KeyCode::Char('j') if key.modifiers.contains(KeyModifiers::CONTROL) => {
2939 self.export_to_json();
2940 Ok(Some(false))
2941 }
2942 _ => Ok(None),
2943 }
2944 }
2945
2946 fn try_handle_results_help(
2948 &mut self,
2949 key: &crossterm::event::KeyEvent,
2950 ) -> Result<Option<bool>> {
2951 match key.code {
2952 KeyCode::F(1) | KeyCode::Char('?') => {
2953 self.state_container.set_help_visible(true);
2954 self.set_mode_via_shadow_state(AppMode::Help, "help_requested");
2956 self.help_widget.on_enter();
2957 Ok(Some(false))
2958 }
2959 _ => Ok(None),
2960 }
2961 }
2962
2963 fn handle_results_input(&mut self, key: crossterm::event::KeyEvent) -> Result<bool> {
2964 debug!(
2966 "handle_results_input: Processing key {:?} in Results mode",
2967 key
2968 );
2969
2970 let is_vim_navigating = self.vim_search_adapter.borrow().is_navigating();
2972 let vim_is_active = self.vim_search_adapter.borrow().is_active();
2973 let has_search_pattern = !self.state_container.get_search_pattern().is_empty();
2974
2975 debug!(
2976 "Search state check: vim_navigating={}, vim_active={}, has_pattern={}, pattern='{}'",
2977 is_vim_navigating,
2978 vim_is_active,
2979 has_search_pattern,
2980 self.state_container.get_search_pattern()
2981 );
2982
2983 if key.code == KeyCode::Esc {
2985 info!("ESCAPE KEY DETECTED in Results mode!");
2986
2987 if is_vim_navigating || vim_is_active || has_search_pattern {
2988 info!("Escape pressed with active search - clearing via StateCoordinator");
2989 debug!(
2990 "Pre-clear state: vim_navigating={}, vim_active={}, pattern='{}'",
2991 is_vim_navigating,
2992 vim_is_active,
2993 self.state_container.get_search_pattern()
2994 );
2995
2996 use crate::ui::state::state_coordinator::StateCoordinator;
2998 StateCoordinator::cancel_search_with_refs(
2999 &mut self.state_container,
3000 &self.shadow_state,
3001 Some(&self.vim_search_adapter),
3002 );
3003
3004 let post_pattern = self.state_container.get_search_pattern();
3006 let post_vim_active = self.vim_search_adapter.borrow().is_active();
3007 info!(
3008 "Post-clear state: pattern='{}', vim_active={}",
3009 post_pattern, post_vim_active
3010 );
3011
3012 self.state_container
3013 .set_status_message("Search cleared".to_string());
3014 return Ok(false);
3015 }
3016 info!("Escape pressed but no active search to clear");
3017 }
3018
3019 let selection_mode = self.state_container.get_selection_mode();
3020
3021 debug!(
3022 "handle_results_input: key={:?}, selection_mode={:?}",
3023 key, selection_mode
3024 );
3025
3026 let normalized = self.state_container.normalize_key(key);
3028
3029 let action_context = self.build_action_context();
3031 let mapped_action = self.key_mapper.map_key(normalized, &action_context);
3032 let action = mapped_action.as_ref().map(|a| format!("{a:?}"));
3033
3034 if normalized != key {
3036 self.state_container
3037 .log_key_press(key, Some(format!("normalized to {normalized:?}")));
3038 }
3039 self.state_container
3040 .log_key_press(normalized, action.clone());
3041
3042 let normalized_key = normalized;
3043
3044 let action_context = self.build_action_context();
3048 debug!(
3049 "Action context for key {:?}: mode={:?}",
3050 normalized_key.code, action_context.mode
3051 );
3052 if let Some(action) = self.key_mapper.map_key(normalized_key, &action_context) {
3053 info!(
3054 "✓ Action system: key {:?} -> action {:?}",
3055 normalized_key.code, action
3056 );
3057 if let Ok(result) = self.try_handle_action(action, &action_context) {
3058 match result {
3059 ActionResult::Handled => {
3060 debug!("Action handled by new system");
3061 return Ok(false);
3062 }
3063 ActionResult::Exit => {
3064 debug!("Action requested exit");
3065 return Ok(true);
3066 }
3067 ActionResult::SwitchMode(mode) => {
3068 debug!("Action requested mode switch to {:?}", mode);
3069 self.state_container.set_mode(mode);
3070 return Ok(false);
3071 }
3072 ActionResult::Error(err) => {
3073 warn!("Action error: {}", err);
3074 self.state_container
3075 .set_status_message(format!("Error: {err}"));
3076 return Ok(false);
3077 }
3078 ActionResult::NotHandled => {
3079 debug!("Action not handled, falling back to legacy system");
3081 }
3082 }
3083 }
3084 }
3085
3086 if matches!(key.code, KeyCode::Char('G')) {
3088 debug!("Detected uppercase G key press!");
3089 }
3090
3091 if mapped_action.is_none() {
3105 debug!(
3106 "No action mapping for key {:?} in Results mode",
3107 normalized_key
3108 );
3109 }
3110
3111 if let Some(result) = self.try_handle_results_navigation(&normalized_key)? {
3113 return Ok(result);
3114 }
3115
3116 if let Some(result) = self.try_handle_results_clipboard(&normalized_key)? {
3118 return Ok(result);
3119 }
3120
3121 if let Some(result) = self.try_handle_results_export(&normalized_key)? {
3123 return Ok(result);
3124 }
3125
3126 if let Some(result) = self.try_handle_results_help(&normalized_key)? {
3128 return Ok(result);
3129 }
3130
3131 Ok(false)
3136 }
3137 fn execute_search_action(&mut self, mode: SearchMode, pattern: String) {
3140 debug!(target: "search", "execute_search_action called: mode={:?}, pattern='{}', current_app_mode={:?}, thread={:?}",
3141 mode, pattern, self.shadow_state.borrow().get_mode(), std::thread::current().id());
3142 match mode {
3143 SearchMode::Search => {
3144 debug!(target: "search", "Executing search with pattern: '{}', app_mode={:?}", pattern, self.shadow_state.borrow().get_mode());
3145 debug!(target: "search", "Search: current results count={}",
3146 self.state_container.get_buffer_dataview().map_or(0, |v| v.source().row_count()));
3147
3148 self.state_container.start_search(pattern.clone());
3150
3151 self.state_container.set_search_pattern(pattern.clone());
3152 self.perform_search();
3153 let matches_count = self.state_container.search().matches.len();
3154 debug!(target: "search", "After perform_search, app_mode={:?}, matches_found={}",
3155 self.shadow_state.borrow().get_mode(),
3156 matches_count);
3157
3158 if matches_count > 0 {
3160 let matches_for_vim: Vec<(usize, usize)> = {
3162 let search_manager = self.search_manager.borrow();
3163 search_manager
3164 .all_matches()
3165 .iter()
3166 .map(|m| (m.row, m.column))
3167 .collect()
3168 };
3169
3170 if let Some(dataview) = self.state_container.get_buffer_dataview() {
3172 info!(target: "search", "Syncing {} matches to VimSearchManager for pattern '{}'",
3173 matches_for_vim.len(), pattern);
3174 self.vim_search_adapter
3175 .borrow_mut()
3176 .set_search_state_from_external(
3177 pattern.clone(),
3178 matches_for_vim,
3179 dataview,
3180 );
3181 }
3182 }
3183
3184 if matches_count > 0 {
3186 let (row, col) = {
3188 let search_manager = self.search_manager.borrow();
3189 if let Some(first_match) = search_manager.first_match() {
3190 (first_match.row, first_match.column)
3191 } else {
3192 let search_state = self.state_container.search();
3194 if let Some((row, col, _, _)) = search_state.matches.first() {
3195 (*row, *col)
3196 } else {
3197 (0, 0)
3198 }
3199 }
3200 };
3201
3202 info!(target: "search", "NAVIGATION START: Moving to first match at data row={}, col={}", row, col);
3203
3204 self.state_container.set_table_selected_row(Some(row));
3207 self.state_container.set_selected_row(Some(row));
3208 info!(target: "search", " Set row position to {}", row);
3209
3210 {
3212 let mut nav = self.state_container.navigation_mut();
3213 nav.selected_column = col;
3214 }
3215 self.state_container.set_current_column_buffer(col);
3216 info!(target: "search", " Set column position to {}", col);
3217
3218 info!(target: "search", "Updating TableWidgetManager for debounced search to ({}, {})", row, col);
3220 self.table_widget_manager
3221 .borrow_mut()
3222 .on_debounced_search(row, col);
3223
3224 {
3226 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
3227 if let Some(ref mut viewport_manager) = *viewport_manager_borrow {
3228 let viewport_height = self.state_container.navigation().viewport_rows;
3230 let viewport_width = self.state_container.navigation().viewport_columns;
3231 let current_scroll = self.state_container.navigation().scroll_offset.0;
3232
3233 info!(target: "search", " Viewport dimensions: {}x{}, current_scroll: {}",
3234 viewport_height, viewport_width, current_scroll);
3235
3236 let new_row_offset = if row < current_scroll {
3238 info!(target: "search", " Match is above viewport, scrolling up to row {}", row);
3239 row } else if row >= current_scroll + viewport_height.saturating_sub(1) {
3241 let centered = row.saturating_sub(viewport_height / 2);
3242 info!(target: "search", " Match is below viewport, centering at row {}", centered);
3243 centered } else {
3245 info!(target: "search", " Match is already visible, keeping scroll at {}", current_scroll);
3246 current_scroll };
3248
3249 let current_col_scroll =
3251 self.state_container.navigation().scroll_offset.1;
3252 let new_col_offset = if col < current_col_scroll {
3253 info!(target: "search", " Match column {} is left of viewport (scroll={}), scrolling left", col, current_col_scroll);
3254 col } else if col >= current_col_scroll + viewport_width.saturating_sub(1) {
3256 let centered = col.saturating_sub(viewport_width / 4);
3257 info!(target: "search", " Match column {} is right of viewport (scroll={}, width={}), scrolling to {}",
3258 col, current_col_scroll, viewport_width, centered);
3259 centered } else {
3261 info!(target: "search", " Match column {} is visible, keeping scroll at {}", col, current_col_scroll);
3262 current_col_scroll };
3264
3265 viewport_manager.set_viewport(
3267 new_row_offset,
3268 new_col_offset,
3269 viewport_width as u16,
3270 viewport_height as u16,
3271 );
3272 info!(target: "search", " Set viewport to row_offset={}, col_offset={}", new_row_offset, new_col_offset);
3273
3274 let crosshair_row = row - new_row_offset;
3276 let crosshair_col = col - new_col_offset;
3277 viewport_manager.set_crosshair(crosshair_row, crosshair_col);
3278 info!(target: "search", " Set crosshair to viewport-relative ({}, {}), absolute was ({}, {})",
3279 crosshair_row, crosshair_col, row, col);
3280
3281 let mut nav = self.state_container.navigation_mut();
3283 nav.scroll_offset.0 = new_row_offset;
3284 nav.scroll_offset.1 = new_col_offset;
3285 }
3286 }
3287
3288 self.state_container.set_current_match(Some((row, col)));
3290
3291 {
3294 let mut nav = self.state_container.navigation_mut();
3295 nav.selected_row = row;
3296 nav.selected_column = col;
3297 }
3298 info!(target: "search", " Forced navigation state to row={}, col={}", row, col);
3299
3300 self.state_container.set_status_message(format!(
3302 "Match 1/{} at row {}, col {}",
3303 matches_count,
3304 row + 1,
3305 col + 1
3306 ));
3307 }
3308 }
3309 SearchMode::Filter => {
3310 use crate::ui::state::state_coordinator::StateCoordinator;
3311
3312 StateCoordinator::apply_filter_search_with_refs(
3314 &mut self.state_container,
3315 &self.shadow_state,
3316 &pattern,
3317 );
3318
3319 self.apply_filter(&pattern);
3321
3322 debug!(target: "search", "After apply_filter, filtered_count={}",
3323 self.state_container.get_buffer_dataview().map_or(0, super::super::data::data_view::DataView::row_count));
3324 }
3325 SearchMode::FuzzyFilter => {
3326 use crate::ui::state::state_coordinator::StateCoordinator;
3327
3328 StateCoordinator::apply_fuzzy_filter_search_with_refs(
3330 &mut self.state_container,
3331 &self.shadow_state,
3332 &pattern,
3333 );
3334
3335 self.apply_fuzzy_filter();
3337
3338 let indices_count = self.state_container.get_fuzzy_filter_indices().len();
3339 debug!(target: "search", "After apply_fuzzy_filter, matched_indices={}", indices_count);
3340 }
3341 SearchMode::ColumnSearch => {
3342 use crate::ui::state::state_coordinator::StateCoordinator;
3343
3344 debug!(target: "search", "Executing column search with pattern: '{}'", pattern);
3345
3346 StateCoordinator::apply_column_search_with_refs(
3348 &mut self.state_container,
3349 &self.shadow_state,
3350 &pattern,
3351 );
3352
3353 self.search_columns();
3355
3356 debug!(target: "search", "After search_columns, app_mode={:?}", self.shadow_state.borrow().get_mode());
3357 }
3358 }
3359 }
3360
3361 fn enter_search_mode(&mut self, mode: SearchMode) {
3362 debug!(target: "search", "enter_search_mode called for {:?}, current_mode={:?}, input_text='{}'",
3363 mode, self.shadow_state.borrow().get_mode(), self.state_container.get_input_text());
3364
3365 let current_sql = if self.shadow_state.borrow().is_in_results_mode() {
3367 let last_query = self.state_container.get_last_query();
3369 let input_text = self.state_container.get_input_text();
3370 debug!(target: "search", "COLUMN_SEARCH_SAVE_DEBUG: last_query='{}', input_text='{}'", last_query, input_text);
3371
3372 if !last_query.is_empty() {
3373 debug!(target: "search", "Using last_query for search mode: '{}'", last_query);
3374 last_query
3375 } else if !input_text.is_empty() {
3376 debug!(target: "search", "No last_query, using input_text as fallback: '{}'", input_text);
3379 input_text
3380 } else {
3381 warn!(target: "search", "No last_query or input_text found when entering search mode from Results!");
3383 String::new()
3384 }
3385 } else {
3386 self.get_input_text()
3388 };
3389
3390 let cursor_pos = current_sql.len();
3391
3392 debug!(
3393 "Entering {} mode, saving SQL: '{}', cursor: {}",
3394 mode.title(),
3395 current_sql,
3396 cursor_pos
3397 );
3398
3399 self.search_modes_widget
3401 .enter_mode(mode.clone(), current_sql, cursor_pos);
3402
3403 debug!(target: "mode", "Setting app mode from {:?} to {:?}", self.shadow_state.borrow().get_mode(), mode.to_app_mode());
3405 let trigger = match mode {
3406 SearchMode::ColumnSearch => "backslash_column_search",
3407 SearchMode::Search => "data_search_started",
3408 SearchMode::FuzzyFilter => "fuzzy_filter_started",
3409 SearchMode::Filter => "filter_started",
3410 };
3411 self.sync_mode(mode.to_app_mode(), trigger);
3412
3413 let search_type = match mode {
3415 SearchMode::ColumnSearch => crate::ui::state::shadow_state::SearchType::Column,
3416 SearchMode::Search => crate::ui::state::shadow_state::SearchType::Data,
3417 SearchMode::FuzzyFilter | SearchMode::Filter => {
3418 crate::ui::state::shadow_state::SearchType::Fuzzy
3419 }
3420 };
3421 self.shadow_state
3422 .borrow_mut()
3423 .observe_search_start(search_type, trigger);
3424
3425 match mode {
3427 SearchMode::Search => {
3428 self.state_container.clear_search();
3430 self.state_container.set_search_pattern(String::new());
3431 }
3432 SearchMode::Filter => {
3433 self.state_container.set_filter_pattern(String::new());
3434 self.state_container.filter_mut().clear();
3435 }
3436 SearchMode::FuzzyFilter => {
3437 self.state_container.set_fuzzy_filter_pattern(String::new());
3438 self.state_container.set_fuzzy_filter_indices(Vec::new());
3439 self.state_container.set_fuzzy_filter_active(false);
3440 }
3441 SearchMode::ColumnSearch => {
3442 self.state_container.clear_column_search();
3444 if let Some(dataview) = self.state_container.get_buffer_dataview_mut() {
3445 dataview.clear_column_search();
3446 }
3447
3448 }
3450 }
3451
3452 self.input = tui_input::Input::default();
3454 }
3456
3457 fn handle_search_modes_input(&mut self, key: crossterm::event::KeyEvent) -> Result<bool> {
3458 if key.code == KeyCode::Char('c') && key.modifiers.contains(KeyModifiers::CONTROL) {
3460 return Ok(true); }
3462
3463 let action = self.search_modes_widget.handle_key(key);
3466
3467 match action {
3468 SearchModesAction::Continue => {
3469 }
3471 SearchModesAction::InputChanged(mode, pattern) => {
3472 self.set_input_text_with_cursor(pattern.clone(), pattern.len());
3474
3475 match mode {
3477 SearchMode::Search => {
3478 self.state_container.set_search_pattern(pattern);
3479 }
3480 SearchMode::Filter => {
3481 self.state_container.set_filter_pattern(pattern.clone());
3482 let mut filter = self.state_container.filter_mut();
3483 filter.pattern = pattern.clone();
3484 filter.is_active = true;
3485 }
3486 SearchMode::FuzzyFilter => {
3487 self.state_container.set_fuzzy_filter_pattern(pattern);
3488 }
3489 SearchMode::ColumnSearch => {
3490 }
3492 }
3493 }
3494 SearchModesAction::ExecuteDebounced(mode, pattern) => {
3495 self.execute_search_action(mode, pattern);
3498 }
3500 SearchModesAction::Apply(mode, pattern) => {
3501 debug!(target: "search", "Apply action triggered for {:?} with pattern '{}'", mode, pattern);
3502 match mode {
3504 SearchMode::Search => {
3505 debug!(target: "search", "Search Apply: Applying search with pattern '{}'", pattern);
3506 self.execute_search_action(SearchMode::Search, pattern);
3508 debug!(target: "search", "Search Apply: last_query='{}', will restore saved SQL from widget", self.state_container.get_last_query());
3509 }
3511 SearchMode::Filter => {
3512 debug!(target: "search", "Filter Apply: Applying filter with pattern '{}'", pattern);
3513 self.state_container.set_filter_pattern(pattern.clone());
3514 {
3515 let mut filter = self.state_container.filter_mut();
3516 filter.pattern = pattern.clone();
3517 filter.is_active = true;
3518 } self.apply_filter(&pattern); debug!(target: "search", "Filter Apply: last_query='{}', will restore saved SQL from widget", self.state_container.get_last_query());
3521 }
3522 SearchMode::FuzzyFilter => {
3523 debug!(target: "search", "FuzzyFilter Apply: Applying filter with pattern '{}'", pattern);
3524 self.state_container.set_fuzzy_filter_pattern(pattern);
3525 self.apply_fuzzy_filter();
3526 debug!(target: "search", "FuzzyFilter Apply: last_query='{}', will restore saved SQL from widget", self.state_container.get_last_query());
3527 }
3528 SearchMode::ColumnSearch => {
3529 let column_info = {
3532 let column_search = self.state_container.column_search();
3533 if column_search.matching_columns.is_empty() {
3534 None
3535 } else {
3536 let current_match = column_search.current_match;
3537 Some(column_search.matching_columns[current_match].clone())
3538 }
3539 };
3540
3541 if let Some((col_idx, col_name)) = column_info {
3542 self.state_container.set_current_column(col_idx);
3543 self.state_container.set_current_column_buffer(col_idx);
3544
3545 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
3547 if let Some(viewport_manager) = viewport_manager_borrow.as_mut() {
3548 viewport_manager.set_current_column(col_idx);
3549 }
3550 drop(viewport_manager_borrow);
3551
3552 debug!(target: "column_search_sync", "ColumnSearch Apply: About to call sync_navigation_with_viewport() for column: {}", col_name);
3555 debug!(target: "column_search_sync", "ColumnSearch Apply: Pre-sync - viewport current_column: {}",
3556 if let Ok(vm) = self.viewport_manager.try_borrow() {
3557 vm.as_ref().map_or(0, super::viewport_manager::ViewportManager::get_crosshair_col)
3558 } else { 0 });
3559 self.sync_navigation_with_viewport();
3560 debug!(target: "column_search_sync", "ColumnSearch Apply: Post-sync - navigation current_column: {}",
3561 self.state_container.navigation().selected_column);
3562 debug!(target: "column_search_sync", "ColumnSearch Apply: sync_navigation_with_viewport() completed for column: {}", col_name);
3563
3564 self.state_container
3565 .set_status_message(format!("Jumped to column: {col_name}"));
3566 }
3567
3568 debug!(target: "search", "ColumnSearch Apply: Exiting without modifying input_text");
3571 debug!(target: "search", "ColumnSearch Apply: last_query='{}', will restore saved SQL from widget", self.state_container.get_last_query());
3572 }
3574 }
3575
3576 let saved_state = self.search_modes_widget.exit_mode();
3579
3580 if let Some((sql, cursor)) = saved_state {
3581 debug!(target: "search", "Exiting search mode. Original SQL was: '{}', cursor: {}", sql, cursor);
3582 debug!(target: "buffer", "Returning to Results mode, preserving last_query: '{}'",
3583 self.state_container.get_last_query());
3584
3585 debug!(target: "search", "Restoring saved SQL to input_text: '{}'", sql);
3588 self.set_input_text_with_cursor(sql, cursor);
3590 } else {
3591 if mode == SearchMode::ColumnSearch {
3593 let last_query = self.state_container.get_last_query();
3595 if last_query.is_empty() {
3596 debug!(target: "search", "Column search: No saved state or last_query, clearing input");
3597 self.set_input_text(String::new());
3598 } else {
3599 debug!(target: "search", "Column search: No saved state, restoring last_query: '{}'", last_query);
3600 self.set_input_text(last_query);
3601 }
3602 } else {
3603 debug!(target: "search", "No saved state from widget, keeping current SQL");
3604 }
3605 }
3606
3607 use crate::ui::state::state_coordinator::StateCoordinator;
3609
3610 if mode == SearchMode::ColumnSearch {
3613 debug!(target: "column_search_sync", "ColumnSearch Apply: Canceling column search completely with cancel_search_with_refs()");
3614
3615 if let Some(dataview) = self.state_container.get_buffer_dataview_mut() {
3617 dataview.clear_column_search();
3618 debug!(target: "column_search_sync", "ColumnSearch Apply: Cleared column search in DataView");
3619 }
3620
3621 StateCoordinator::cancel_search_with_refs(
3622 &mut self.state_container,
3623 &self.shadow_state,
3624 Some(&self.vim_search_adapter),
3625 );
3626 debug!(target: "column_search_sync", "ColumnSearch Apply: Column search canceled and mode switched to Results");
3628 } else {
3629 debug!(target: "column_search_sync", "Search Apply: About to call StateCoordinator::complete_search_with_refs() for mode: {:?}", mode);
3631 StateCoordinator::complete_search_with_refs(
3632 &mut self.state_container,
3633 &self.shadow_state,
3634 Some(&self.vim_search_adapter),
3635 AppMode::Results,
3636 "search_applied",
3637 );
3638 debug!(target: "column_search_sync", "Search Apply: StateCoordinator::complete_search_with_refs() completed - should now be in Results mode");
3639 }
3640
3641 let filter_msg = match mode {
3643 SearchMode::FuzzyFilter => {
3644 let query = self.state_container.get_last_query();
3645 format!(
3646 "Fuzzy filter applied. Query: '{}'. Press 'f' again to modify.",
3647 if query.len() > 30 {
3648 format!("{}...", &query[..30])
3649 } else {
3650 query
3651 }
3652 )
3653 }
3654 SearchMode::Filter => "Filter applied. Press 'F' again to modify.".to_string(),
3655 SearchMode::Search => {
3656 let matches = self.state_container.search().matches.len();
3657 if matches > 0 {
3658 format!("Found {matches} matches. Use n/N to navigate.")
3659 } else {
3660 "No matches found.".to_string()
3661 }
3662 }
3663 SearchMode::ColumnSearch => "Column search complete.".to_string(),
3664 };
3665 self.state_container.set_status_message(filter_msg);
3666 }
3667 SearchModesAction::Cancel => {
3668 let mode = self.shadow_state.borrow().get_mode();
3670 match mode {
3671 AppMode::FuzzyFilter => {
3672 debug!(target: "search", "FuzzyFilter Cancel: Clearing fuzzy filter");
3674 self.state_container.set_fuzzy_filter_pattern(String::new());
3675 self.apply_fuzzy_filter(); self.state_container.set_fuzzy_filter_indices(Vec::new());
3677 self.state_container.set_fuzzy_filter_active(false);
3678 }
3679 AppMode::Filter => {
3680 debug!(target: "search", "Filter Cancel: Clearing filter pattern and state");
3682 self.state_container.filter_mut().clear();
3683 self.state_container.set_filter_pattern(String::new());
3684 self.state_container.set_filter_active(false);
3685 self.apply_filter("");
3687 }
3688 AppMode::ColumnSearch => {
3689 self.state_container.clear_column_search();
3691 debug!(target: "search", "ColumnSearch Cancel: Exiting without modifying input_text");
3693 debug!(target: "search", "ColumnSearch Cancel: last_query='{}', will restore saved SQL from widget", self.state_container.get_last_query());
3694 }
3695 _ => {}
3696 }
3697
3698 if let Some((sql, cursor)) = self.search_modes_widget.exit_mode() {
3700 debug!(target: "search", "Cancel: Restoring saved SQL: '{}', cursor: {}", sql, cursor);
3701 if !sql.is_empty() {
3702 self.set_input_text_with_cursor(sql, cursor);
3704 }
3705 } else {
3706 debug!(target: "search", "Cancel: No saved SQL from widget");
3707 }
3708
3709 use crate::ui::state::state_coordinator::StateCoordinator;
3712 StateCoordinator::cancel_search_with_refs(
3713 &mut self.state_container,
3714 &self.shadow_state,
3715 Some(&self.vim_search_adapter),
3716 );
3717 }
3718 SearchModesAction::NextMatch => {
3719 debug!(target: "search", "NextMatch action, current_mode={:?}, widget_mode={:?}",
3720 self.shadow_state.borrow().get_mode(), self.search_modes_widget.current_mode());
3721
3722 if self.shadow_state.borrow().is_in_column_search()
3724 || self.search_modes_widget.current_mode() == Some(SearchMode::ColumnSearch)
3725 {
3726 debug!(target: "search", "Calling next_column_match");
3727 if !self.shadow_state.borrow().is_in_column_search() {
3729 debug!(target: "search", "WARNING: Mode mismatch - fixing");
3730 self.state_container.set_mode(AppMode::ColumnSearch);
3731 self.shadow_state.borrow_mut().observe_search_start(
3732 crate::ui::state::shadow_state::SearchType::Column,
3733 "column_search_mode_fix_next",
3734 );
3735 }
3736 self.next_column_match();
3737 } else {
3738 debug!(target: "search", "Not in ColumnSearch mode, skipping next_column_match");
3739 }
3740 }
3741 SearchModesAction::PreviousMatch => {
3742 debug!(target: "search", "PreviousMatch action, current_mode={:?}, widget_mode={:?}",
3743 self.shadow_state.borrow().get_mode(), self.search_modes_widget.current_mode());
3744
3745 if self.shadow_state.borrow().get_mode() == AppMode::ColumnSearch
3747 || self.search_modes_widget.current_mode() == Some(SearchMode::ColumnSearch)
3748 {
3749 debug!(target: "search", "Calling previous_column_match");
3750 if self.shadow_state.borrow().get_mode() != AppMode::ColumnSearch {
3752 debug!(target: "search", "WARNING: Mode mismatch - fixing");
3753 self.state_container.set_mode(AppMode::ColumnSearch);
3754 self.shadow_state.borrow_mut().observe_search_start(
3755 crate::ui::state::shadow_state::SearchType::Column,
3756 "column_search_mode_fix_prev",
3757 );
3758 }
3759 self.previous_column_match();
3760 } else {
3761 debug!(target: "search", "Not in ColumnSearch mode, skipping previous_column_match");
3762 }
3763 }
3764 SearchModesAction::PassThrough => {}
3765 }
3766
3767 Ok(false)
3770 }
3771
3772 fn handle_help_input(&mut self, key: crossterm::event::KeyEvent) -> Result<bool> {
3773 match key.code {
3776 crossterm::event::KeyCode::Esc | crossterm::event::KeyCode::Char('q') => {
3777 self.help_widget.on_exit();
3778 self.state_container.set_help_visible(false);
3779
3780 let target_mode = if self.state_container.has_dataview() {
3782 AppMode::Results
3783 } else {
3784 AppMode::Command
3785 };
3786
3787 self.set_mode_via_shadow_state(target_mode, "escape_from_help");
3789
3790 Ok(false)
3792 }
3793 _ => {
3794 self.help_widget.handle_key(key);
3796 Ok(false)
3797 }
3798 }
3799 }
3800
3801 fn handle_history_input(&mut self, key: crossterm::event::KeyEvent) -> Result<bool> {
3804 use crossterm::event::{KeyCode, KeyModifiers};
3806
3807 let result = match key.code {
3808 KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => {
3809 crate::ui::input::history_input_handler::HistoryInputResult::Exit
3810 }
3811 KeyCode::Esc => {
3812 let original_input = self.state_container.cancel_history_search();
3814 if let Some(buffer) = self.state_container.current_buffer_mut() {
3815 self.shadow_state.borrow_mut().set_mode(
3816 crate::buffer::AppMode::Command,
3817 buffer,
3818 "history_cancelled",
3819 );
3820 buffer.set_status_message("History search cancelled".to_string());
3821 }
3822 crate::ui::input::history_input_handler::HistoryInputResult::SwitchToCommand(Some(
3823 (original_input, 0),
3824 ))
3825 }
3826 KeyCode::Enter => {
3827 if let Some(command) = self.state_container.accept_history_search() {
3829 if let Some(buffer) = self.state_container.current_buffer_mut() {
3830 self.shadow_state.borrow_mut().set_mode(
3831 crate::buffer::AppMode::Command,
3832 buffer,
3833 "history_accepted",
3834 );
3835 buffer.set_status_message(
3836 "Command loaded from history (cursor at start)".to_string(),
3837 );
3838 }
3839 crate::ui::input::history_input_handler::HistoryInputResult::SwitchToCommand(
3841 Some((command, 0)),
3842 )
3843 } else {
3844 crate::ui::input::history_input_handler::HistoryInputResult::Continue
3845 }
3846 }
3847 KeyCode::Up => {
3848 self.state_container.history_search_previous();
3849 crate::ui::input::history_input_handler::HistoryInputResult::Continue
3850 }
3851 KeyCode::Down => {
3852 self.state_container.history_search_next();
3853 crate::ui::input::history_input_handler::HistoryInputResult::Continue
3854 }
3855 KeyCode::Char('r') if key.modifiers.contains(KeyModifiers::CONTROL) => {
3856 self.state_container.history_search_next();
3858 crate::ui::input::history_input_handler::HistoryInputResult::Continue
3859 }
3860 KeyCode::Backspace => {
3861 self.state_container.history_search_backspace();
3862 crate::ui::input::history_input_handler::HistoryInputResult::Continue
3863 }
3864 KeyCode::Char(c) => {
3865 self.state_container.history_search_add_char(c);
3866 crate::ui::input::history_input_handler::HistoryInputResult::Continue
3867 }
3868 _ => crate::ui::input::history_input_handler::HistoryInputResult::Continue,
3869 };
3870
3871 match result {
3873 crate::ui::input::history_input_handler::HistoryInputResult::Exit => return Ok(true),
3874 crate::ui::input::history_input_handler::HistoryInputResult::SwitchToCommand(
3875 input_data,
3876 ) => {
3877 if let Some((text, cursor_pos)) = input_data {
3878 self.set_input_text_with_cursor(text, cursor_pos);
3879 self.sync_all_input_states();
3881 }
3882 }
3883 crate::ui::input::history_input_handler::HistoryInputResult::Continue => {
3884 if crate::ui::input::history_input_handler::key_updates_search(key) {
3886 self.update_history_matches_in_container();
3887 }
3888 }
3889 }
3890
3891 Ok(false)
3892 }
3893
3894 fn update_history_matches_in_container(&mut self) {
3896 let (current_columns, current_source_str) =
3898 if let Some(dataview) = self.state_container.get_buffer_dataview() {
3899 (
3900 dataview.column_names(), Some(dataview.source().name.clone()), )
3903 } else {
3904 (vec![], None)
3905 };
3906
3907 let current_source = current_source_str.as_deref();
3908 let query = self.state_container.history_search().query.clone();
3909
3910 self.state_container.update_history_search_with_schema(
3911 query,
3912 ¤t_columns,
3913 current_source,
3914 );
3915 }
3916
3917 fn handle_debug_input(&mut self, key: crossterm::event::KeyEvent) -> Result<bool> {
3918 let mut ctx = crate::ui::input::input_handlers::DebugInputContext {
3920 buffer_manager: self.state_container.buffers_mut(),
3921 debug_widget: &mut self.debug_widget,
3922 shadow_state: &self.shadow_state,
3923 };
3924
3925 let should_quit = crate::ui::input::input_handlers::handle_debug_input(&mut ctx, key)?;
3926
3927 if !should_quit {
3930 match key.code {
3931 KeyCode::Char('t') if key.modifiers.contains(KeyModifiers::CONTROL) => {
3932 self.yank_as_test_case();
3934 }
3935 KeyCode::Char('y') if key.modifiers.contains(KeyModifiers::SHIFT) => {
3936 self.yank_debug_with_context();
3938 }
3939 _ => {}
3940 }
3941 }
3942
3943 Ok(should_quit)
3944 }
3946
3947 fn handle_pretty_query_input(&mut self, key: crossterm::event::KeyEvent) -> Result<bool> {
3948 let mut ctx = crate::ui::input::input_handlers::DebugInputContext {
3950 buffer_manager: self.state_container.buffers_mut(),
3951 debug_widget: &mut self.debug_widget,
3952 shadow_state: &self.shadow_state,
3953 };
3954
3955 crate::ui::input::input_handlers::handle_pretty_query_input(&mut ctx, key)
3956 }
3957
3958 pub fn execute_query_v2(&mut self, query: &str) -> Result<()> {
3959 let context = self.query_orchestrator.execute_query(
3961 query,
3962 &mut self.state_container,
3963 &self.vim_search_adapter,
3964 );
3965
3966 match context {
3967 Ok(ctx) => {
3968 self.state_container
3970 .set_dataview(Some(ctx.result.dataview.clone()));
3971
3972 self.update_viewport_manager(Some(ctx.result.dataview.clone()));
3974
3975 self.state_container
3977 .update_data_size(ctx.result.stats.row_count, ctx.result.stats.column_count);
3978
3979 self.calculate_optimal_column_widths();
3981
3982 self.state_container
3984 .set_status_message(ctx.result.status_message());
3985
3986 self.state_container
3988 .command_history_mut()
3989 .add_entry_with_schema(
3990 ctx.query.clone(),
3991 true,
3992 Some(ctx.result.stats.execution_time.as_millis() as u64),
3993 ctx.result.column_names(),
3994 Some(ctx.result.table_name()),
3995 )?;
3996
3997 self.sync_mode(AppMode::Results, "execute_query_success");
3999
4000 self.reset_table_state();
4002
4003 Ok(())
4004 }
4005 Err(e) => {
4006 let error_msg = format!("Query error: {e}");
4007 self.state_container.set_status_message(error_msg.clone());
4008
4009 self.state_container
4011 .command_history_mut()
4012 .add_entry_with_schema(query.to_string(), false, None, vec![], None)?;
4013
4014 Err(e)
4015 }
4016 }
4017 }
4018
4019 fn handle_completion(&mut self) {
4020 let cursor_pos = self.get_input_cursor();
4021 let query_str = self.get_input_text();
4022 let query = query_str.as_str();
4023
4024 let hybrid_result = self.hybrid_parser.get_completions(query, cursor_pos);
4025 if !hybrid_result.suggestions.is_empty() {
4026 self.state_container.set_status_message(format!(
4027 "Suggestions: {}",
4028 hybrid_result.suggestions.join(", ")
4029 ));
4030 }
4031 }
4032
4033 fn apply_completion(&mut self) {
4034 let cursor_pos = self.get_input_cursor();
4035 let query = self.get_input_text();
4036
4037 let suggestion = match self.get_or_refresh_completion(&query, cursor_pos) {
4039 Some(s) => s,
4040 None => return,
4041 };
4042
4043 self.apply_completion_to_input(&query, cursor_pos, &suggestion);
4045 }
4046
4047 fn get_or_refresh_completion(&mut self, query: &str, cursor_pos: usize) -> Option<String> {
4050 let is_same_context = self
4051 .state_container
4052 .is_same_completion_context(query, cursor_pos);
4053
4054 if !is_same_context {
4055 let hybrid_result = self.hybrid_parser.get_completions(query, cursor_pos);
4057 if hybrid_result.suggestions.is_empty() {
4058 self.state_container
4059 .set_status_message("No completions available".to_string());
4060 return None;
4061 }
4062
4063 self.state_container
4064 .set_completion_suggestions(hybrid_result.suggestions);
4065 } else if self.state_container.is_completion_active() {
4066 self.state_container.next_completion();
4068 } else {
4069 self.state_container
4070 .set_status_message("No completions available".to_string());
4071 return None;
4072 }
4073
4074 if let Some(sugg) = self.state_container.get_current_completion() {
4076 Some(sugg)
4077 } else {
4078 self.state_container
4079 .set_status_message("No completion selected".to_string());
4080 None
4081 }
4082 }
4083
4084 fn apply_completion_to_input(&mut self, query: &str, cursor_pos: usize, suggestion: &str) {
4086 let partial_word =
4087 crate::ui::utils::text_operations::extract_partial_word_at_cursor(query, cursor_pos);
4088
4089 if let Some(partial) = partial_word {
4090 self.apply_partial_completion(query, cursor_pos, &partial, suggestion);
4091 } else {
4092 self.apply_full_insertion(query, cursor_pos, suggestion);
4093 }
4094 }
4095
4096 fn apply_partial_completion(
4098 &mut self,
4099 query: &str,
4100 cursor_pos: usize,
4101 partial: &str,
4102 suggestion: &str,
4103 ) {
4104 let result = crate::ui::utils::text_operations::apply_completion_to_text(
4106 query, cursor_pos, partial, suggestion,
4107 );
4108
4109 self.set_input_text_with_cursor(result.new_text.clone(), result.new_cursor_position);
4111
4112 self.state_container
4114 .update_completion_context(result.new_text.clone(), result.new_cursor_position);
4115
4116 let completion = self.state_container.completion();
4118 let suggestion_info = if completion.suggestions.len() > 1 {
4119 format!(
4120 "Completed: {} ({}/{} - Tab for next)",
4121 suggestion,
4122 completion.current_index + 1,
4123 completion.suggestions.len()
4124 )
4125 } else {
4126 format!("Completed: {suggestion}")
4127 };
4128 drop(completion);
4129 self.state_container.set_status_message(suggestion_info);
4130 }
4131
4132 fn apply_full_insertion(&mut self, query: &str, cursor_pos: usize, suggestion: &str) {
4134 let before_cursor = &query[..cursor_pos];
4136 let after_cursor = &query[cursor_pos..];
4137 let new_query = format!("{before_cursor}{suggestion}{after_cursor}");
4138
4139 let cursor_pos_new = if suggestion.ends_with("('')") {
4141 cursor_pos + suggestion.len() - 2
4143 } else {
4144 cursor_pos + suggestion.len()
4145 };
4146
4147 self.set_input_text(new_query.clone());
4149
4150 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
4152 buffer.set_input_cursor_position(cursor_pos_new);
4153 self.sync_all_input_states();
4155 }
4156
4157 self.state_container
4159 .update_completion_context(new_query, cursor_pos_new);
4160
4161 self.state_container
4162 .set_status_message(format!("Inserted: {suggestion}"));
4163 }
4164
4165 fn get_column_count(&self) -> usize {
4172 if let Some(provider) = self.get_data_provider() {
4174 provider.get_column_count()
4175 } else {
4176 0
4177 }
4178 }
4179
4180 fn get_column_names_via_provider(&self) -> Vec<String> {
4185 if let Some(provider) = self.get_data_provider() {
4186 provider.get_column_names()
4187 } else {
4188 Vec::new()
4189 }
4190 }
4191
4192 fn toggle_column_pin_impl(&mut self) {
4197 let visual_col_idx = if let Some(ref viewport_manager) = *self.viewport_manager.borrow() {
4199 viewport_manager.get_crosshair_col()
4200 } else {
4201 0
4202 };
4203
4204 let column_name = if let Some(dataview) = self.state_container.get_buffer_dataview() {
4206 let all_columns = dataview.column_names();
4207 all_columns.get(visual_col_idx).cloned()
4208 } else {
4209 None
4210 };
4211
4212 if let Some(col_name) = column_name {
4213 if let Some(dataview) = self.state_container.get_buffer_dataview_mut() {
4214 let pinned_names = dataview.get_pinned_column_names();
4216 if pinned_names.contains(&col_name) {
4217 dataview.unpin_column_by_name(&col_name);
4219 self.state_container
4220 .set_status_message(format!("Column '{col_name}' unpinned"));
4221 } else {
4222 match dataview.pin_column_by_name(&col_name) {
4224 Ok(()) => {
4225 self.state_container
4226 .set_status_message(format!("Column '{col_name}' pinned [P]"));
4227 }
4228 Err(e) => {
4229 self.state_container.set_status_message(e.to_string());
4230 }
4231 }
4232 }
4233
4234 if let Some(updated_dataview) = self.state_container.get_buffer_dataview() {
4236 self.update_viewport_manager(Some(updated_dataview.clone()));
4237 }
4238 }
4239 } else {
4240 self.state_container
4241 .set_status_message("No column to pin at current position".to_string());
4242 }
4243 }
4244
4245 fn clear_all_pinned_columns_impl(&mut self) {
4246 if let Some(dataview) = self.state_container.get_buffer_dataview_mut() {
4247 dataview.clear_pinned_columns();
4248 }
4249 self.state_container
4250 .set_status_message("All columns unpinned".to_string());
4251
4252 if let Some(updated_dataview) = self.state_container.get_buffer_dataview() {
4254 self.update_viewport_manager(Some(updated_dataview.clone()));
4255 }
4256 }
4257
4258 fn calculate_column_statistics(&mut self) {
4259 use std::time::Instant;
4260
4261 let start_total = Instant::now();
4262
4263 let (column_name, data_to_analyze) = {
4265 let headers = self.get_column_names_via_provider();
4267 if headers.is_empty() {
4268 return;
4269 }
4270
4271 let current_column = self.state_container.get_current_column();
4272 if current_column >= headers.len() {
4273 return;
4274 }
4275
4276 let column_name = headers[current_column].clone();
4277
4278 let data_to_analyze: Vec<String> = if let Some(provider) = self.get_data_provider() {
4280 let row_count = provider.get_row_count();
4281 let mut column_data = Vec::with_capacity(row_count);
4282
4283 for row_idx in 0..row_count {
4284 if let Some(row) = provider.get_row(row_idx) {
4285 if current_column < row.len() {
4286 column_data.push(row[current_column].clone());
4287 } else {
4288 column_data.push(String::new());
4290 }
4291 }
4292 }
4293
4294 column_data
4295 } else {
4296 return;
4298 };
4299
4300 (column_name, data_to_analyze)
4301 };
4302
4303 let data_refs: Vec<&str> = data_to_analyze
4305 .iter()
4306 .map(std::string::String::as_str)
4307 .collect();
4308
4309 let analyzer_stats = self
4311 .data_analyzer
4312 .calculate_column_statistics(&column_name, &data_refs);
4313
4314 let stats = ColumnStatistics {
4316 column_name: analyzer_stats.column_name,
4317 column_type: match analyzer_stats.data_type {
4318 data_analyzer::ColumnType::Integer | data_analyzer::ColumnType::Float => {
4319 ColumnType::Numeric
4320 }
4321 data_analyzer::ColumnType::String
4322 | data_analyzer::ColumnType::Boolean
4323 | data_analyzer::ColumnType::Date => ColumnType::String,
4324 data_analyzer::ColumnType::Mixed => ColumnType::Mixed,
4325 data_analyzer::ColumnType::Unknown => ColumnType::Mixed,
4326 },
4327 total_count: analyzer_stats.total_values,
4328 null_count: analyzer_stats.null_values,
4329 unique_count: analyzer_stats.unique_values,
4330 frequency_map: analyzer_stats.frequency_map.clone(),
4331 min: analyzer_stats
4333 .min_value
4334 .as_ref()
4335 .and_then(|s| s.parse::<f64>().ok()),
4336 max: analyzer_stats
4337 .max_value
4338 .as_ref()
4339 .and_then(|s| s.parse::<f64>().ok()),
4340 sum: analyzer_stats.sum_value,
4341 mean: analyzer_stats.avg_value,
4342 median: analyzer_stats.median_value,
4343 };
4344
4345 let elapsed = start_total.elapsed();
4347
4348 self.state_container.set_column_stats(Some(stats));
4349
4350 self.state_container.set_status_message(format!(
4352 "Column stats: {:.1}ms for {} values ({} unique)",
4353 elapsed.as_secs_f64() * 1000.0,
4354 data_to_analyze.len(),
4355 analyzer_stats.unique_values
4356 ));
4357
4358 self.state_container.set_mode(AppMode::ColumnStats);
4359 self.shadow_state
4360 .borrow_mut()
4361 .observe_mode_change(AppMode::ColumnStats, "column_stats_requested");
4362 }
4363
4364 fn check_parser_error(&self, query: &str) -> Option<String> {
4365 crate::ui::operations::simple_operations::check_parser_error(query)
4366 }
4367
4368 fn update_viewport_size(&mut self) {
4369 if let Ok((width, height)) = crossterm::terminal::size() {
4371 let data_rows_available = Self::calculate_available_data_rows(height);
4373
4374 let visible_rows = {
4376 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
4377 let viewport_manager = viewport_manager_borrow
4378 .as_mut()
4379 .expect("ViewportManager must exist for viewport size update");
4380 viewport_manager.update_terminal_size(width, data_rows_available)
4381 };
4382
4383 self.state_container.set_last_visible_rows(visible_rows);
4385
4386 self.state_container
4388 .navigation_mut()
4389 .set_viewport_size(visible_rows, width as usize);
4390
4391 info!(target: "navigation", "update_viewport_size - viewport set to: {}x{} rows", visible_rows, width);
4392 }
4393 }
4394
4395 fn perform_search(&mut self) {
4399 if let Some(dataview) = self.get_current_data() {
4400 let data: Vec<Vec<String>> = (0..dataview.row_count())
4402 .filter_map(|i| dataview.get_row(i))
4403 .map(|row| {
4404 row.values
4405 .iter()
4406 .map(std::string::ToString::to_string)
4407 .collect()
4408 })
4409 .collect();
4410
4411 let pattern = self.state_container.get_search_pattern().to_string();
4413
4414 info!(target: "search", "=== SEARCH START ===");
4415 info!(target: "search", "Pattern: '{}', case_insensitive: {}",
4416 pattern, self.state_container.is_case_insensitive());
4417 info!(target: "search", "Data dimensions: {} rows x {} columns",
4418 data.len(), data.first().map_or(0, std::vec::Vec::len));
4419
4420 let column_names = dataview.column_names();
4422 info!(target: "search", "Column names (first 5): {:?}",
4423 column_names.iter().take(5).collect::<Vec<_>>());
4424
4425 for (i, row) in data.iter().take(10).enumerate() {
4427 info!(target: "search", " Data row {}: [{}]", i,
4428 row.iter().take(5).map(|s| format!("'{s}'")).collect::<Vec<_>>().join(", "));
4429 }
4430
4431 let visible_columns = None;
4433
4434 let match_count = {
4436 let mut search_manager = self.search_manager.borrow_mut();
4437
4438 search_manager.clear();
4440 info!(target: "search", "Cleared previous search results");
4441
4442 search_manager.set_case_sensitive(!self.state_container.is_case_insensitive());
4444 info!(target: "search", "Set case_sensitive to {}", !self.state_container.is_case_insensitive());
4445
4446 let count = search_manager.search(&pattern, &data, visible_columns);
4448 info!(target: "search", "SearchManager.search() returned {} matches", count);
4449 count
4450 };
4451
4452 info!(target: "search", "SearchManager found {} matches", match_count);
4453
4454 if match_count > 0 {
4456 let (first_row, first_col) = {
4458 let search_manager = self.search_manager.borrow();
4459 if let Some(first_match) = search_manager.first_match() {
4460 info!(target: "search", "FIRST MATCH DETAILS:");
4461 info!(target: "search", " Data coordinates: row={}, col={}",
4462 first_match.row, first_match.column);
4463 info!(target: "search", " Matched value: '{}'", first_match.value);
4464 info!(target: "search", " Highlight range: {:?}", first_match.highlight_range);
4465
4466 for (i, m) in search_manager.all_matches().iter().take(5).enumerate() {
4468 info!(target: "search", " Match #{}: row={}, col={}, value='{}'",
4469 i + 1, m.row, m.column, m.value);
4470 }
4471
4472 (first_match.row, first_match.column)
4473 } else {
4474 warn!(target: "search", "SearchManager reported matches but first_match() is None!");
4475 (0, 0)
4476 }
4477 };
4478
4479 self.state_container.set_table_selected_row(Some(first_row));
4481
4482 info!(target: "search", "Updating TableWidgetManager to navigate to ({}, {})", first_row, first_col);
4484 self.table_widget_manager
4485 .borrow_mut()
4486 .navigate_to_search_match(first_row, first_col);
4487
4488 if let Some(dataview) = self.get_current_data() {
4490 if let Some(row_data) = dataview.get_row(first_row) {
4491 if first_col < row_data.values.len() {
4492 info!(target: "search", "VALUE AT NAVIGATION TARGET ({}, {}): '{}'",
4493 first_row, first_col, row_data.values[first_col]);
4494 }
4495 }
4496 }
4497
4498 let buffer_matches: Vec<(usize, usize)> = {
4500 let search_manager = self.search_manager.borrow();
4501 search_manager
4502 .all_matches()
4503 .iter()
4504 .map(|m| (m.row, m.column))
4505 .collect()
4506 };
4507
4508 let state_matches: Vec<(usize, usize, usize, usize)> = {
4511 let search_manager = self.search_manager.borrow();
4512 search_manager
4513 .all_matches()
4514 .iter()
4515 .map(|m| {
4516 (m.row, m.column, m.row, m.column)
4518 })
4519 .collect()
4520 };
4521 self.state_container.search_mut().matches = state_matches;
4522
4523 self.state_container
4524 .set_search_matches_with_index(buffer_matches.clone(), 0);
4525 self.state_container
4526 .set_current_match(Some((first_row, first_col)));
4527 self.state_container
4528 .set_status_message(format!("Found {match_count} matches"));
4529
4530 info!(target: "search", "Search found {} matches for pattern '{}'", match_count, pattern);
4531 } else {
4532 self.state_container.search_mut().matches.clear();
4534 self.state_container.clear_search_state();
4535 self.state_container.set_current_match(None);
4536
4537 info!(target: "search", "No matches found for pattern '{}'", pattern);
4538 }
4539 }
4540 }
4541
4542 fn start_vim_search(&mut self) {
4546 info!(target: "vim_search", "Starting vim search mode");
4547
4548 self.vim_search_adapter.borrow_mut().start_search();
4550
4551 self.shadow_state.borrow_mut().observe_search_start(
4553 crate::ui::state::shadow_state::SearchType::Vim,
4554 "slash_key_pressed",
4555 );
4556
4557 self.enter_search_mode(SearchMode::Search);
4559 }
4560
4561 fn vim_search_next(&mut self) {
4563 if !self.vim_search_adapter.borrow().is_navigating() {
4564 let resumed = {
4566 let mut viewport_borrow = self.viewport_manager.borrow_mut();
4567 if let Some(ref mut viewport) = *viewport_borrow {
4568 if let Some(dataview) = self.state_container.get_buffer_dataview() {
4569 self.vim_search_adapter
4570 .borrow_mut()
4571 .resume_last_search(dataview, viewport)
4572 } else {
4573 false
4574 }
4575 } else {
4576 false
4577 }
4578 };
4579
4580 if !resumed {
4581 self.state_container
4582 .set_status_message("No previous search pattern".to_string());
4583 return;
4584 }
4585 }
4586
4587 let result = {
4589 let mut viewport_borrow = self.viewport_manager.borrow_mut();
4590 if let Some(ref mut viewport) = *viewport_borrow {
4591 let search_match = self.vim_search_adapter.borrow_mut().next_match(viewport);
4592 if search_match.is_some() {
4593 let match_info = self.vim_search_adapter.borrow().get_match_info();
4594 search_match.map(|m| (m, match_info))
4595 } else {
4596 None
4597 }
4598 } else {
4599 None
4600 }
4601 }; if let Some((ref search_match, _)) = result {
4605 info!(target: "search",
4607 "=== UPDATING TUI STATE FOR VIM SEARCH ===");
4608 info!(target: "search",
4609 "Setting selected row to: {}", search_match.row);
4610 info!(target: "search",
4611 "Setting selected column to: {} (visual col)", search_match.col);
4612
4613 if let Some(dataview) = self.state_container.get_buffer_dataview() {
4615 info!(target: "search",
4617 "DEBUG: Fetching row {} from dataview with {} total rows",
4618 search_match.row, dataview.row_count());
4619
4620 if let Some(row_data) = dataview.get_row(search_match.row) {
4621 info!(target: "search",
4623 "Row {} has {} values, first 5: {:?}",
4624 search_match.row, row_data.values.len(),
4625 row_data.values.iter().take(5).map(std::string::ToString::to_string).collect::<Vec<_>>());
4626
4627 if search_match.col < row_data.values.len() {
4628 let actual_value = &row_data.values[search_match.col];
4629 info!(target: "search",
4630 "Actual value at row {} col {}: '{}'",
4631 search_match.row, search_match.col, actual_value);
4632
4633 let pattern = self
4635 .vim_search_adapter
4636 .borrow()
4637 .get_pattern()
4638 .unwrap_or_default();
4639 let contains_pattern = actual_value
4640 .to_string()
4641 .to_lowercase()
4642 .contains(&pattern.to_lowercase());
4643 if contains_pattern {
4644 info!(target: "search",
4645 "✓ Confirmed: Cell contains pattern '{}'", pattern);
4646 } else {
4647 warn!(target: "search",
4648 "WARNING: Cell at ({}, {}) = '{}' does NOT contain pattern '{}'!",
4649 search_match.row, search_match.col, actual_value, pattern);
4650 }
4651 } else {
4652 warn!(target: "search",
4653 "Column {} is out of bounds for row {} (row has {} values)",
4654 search_match.col, search_match.row, row_data.values.len());
4655 }
4656 } else {
4657 warn!(target: "search",
4658 "Could not get row data for row {}", search_match.row);
4659 }
4660
4661 let display_columns = dataview.get_display_columns();
4663 info!(target: "search",
4664 "Display columns mapping (first 10): {:?}",
4665 display_columns.iter().take(10).collect::<Vec<_>>());
4666 }
4667
4668 self.state_container
4669 .set_table_selected_row(Some(search_match.row));
4670 self.state_container
4671 .set_selected_row(Some(search_match.row));
4672
4673 info!(target: "search",
4676 "Setting column to visual index {}",
4677 search_match.col);
4678
4679 self.state_container
4681 .set_current_column_buffer(search_match.col);
4682 self.state_container.navigation_mut().selected_column = search_match.col;
4683
4684 self.state_container.select_column(search_match.col);
4686 info!(target: "search",
4687 "Updated SelectionState column to: {}", search_match.col);
4688
4689 info!(target: "search",
4691 "Column state after update: nav.selected_column={}, buffer.current_column={}, selection.selected_column={}",
4692 self.state_container.navigation().selected_column,
4693 self.state_container.get_current_column(),
4694 self.state_container.selection().selected_column);
4695
4696 self.sync_navigation_with_viewport();
4699
4700 let scroll_offset = self.state_container.navigation().scroll_offset;
4702 self.state_container.set_scroll_offset(scroll_offset);
4703
4704 info!(target: "search", "Updating TableWidgetManager for vim search navigation to ({}, {})",
4706 search_match.row, search_match.col);
4707 self.table_widget_manager
4708 .borrow_mut()
4709 .navigate_to(search_match.row, search_match.col);
4710
4711 info!(target: "search",
4713 "After TableWidgetManager update: nav.selected_column={}, buffer.current_column={}, selection.selected_column={}",
4714 self.state_container.navigation().selected_column,
4715 self.state_container.get_current_column(),
4716 self.state_container.selection().selected_column);
4717
4718 }
4722
4723 if let Some((search_match, match_info)) = result {
4725 if let Some((current, total)) = match_info {
4726 self.state_container.set_status_message(format!(
4727 "Match {}/{} at ({}, {})",
4728 current,
4729 total,
4730 search_match.row + 1,
4731 search_match.col + 1
4732 ));
4733 }
4734
4735 info!(target: "search",
4737 "FINAL vim_search_next state: nav.selected_column={}, buffer.current_column={}, selection.selected_column={}",
4738 self.state_container.navigation().selected_column,
4739 self.state_container.get_current_column(),
4740 self.state_container.selection().selected_column);
4741
4742 if let Some(dataview) = self.state_container.get_buffer_dataview() {
4744 let final_row = self.state_container.navigation().selected_row;
4745 let final_col = self.state_container.navigation().selected_column;
4746
4747 if let Some(row_data) = dataview.get_row(final_row) {
4748 if final_col < row_data.values.len() {
4749 let actual_value = &row_data.values[final_col];
4750 info!(target: "search",
4751 "VERIFICATION: Cell at final position ({}, {}) contains: '{}'",
4752 final_row, final_col, actual_value);
4753
4754 let pattern = self
4755 .vim_search_adapter
4756 .borrow()
4757 .get_pattern()
4758 .unwrap_or_default();
4759 if !actual_value
4760 .to_string()
4761 .to_lowercase()
4762 .contains(&pattern.to_lowercase())
4763 {
4764 error!(target: "search",
4765 "ERROR: Final cell '{}' does NOT contain search pattern '{}'!",
4766 actual_value, pattern);
4767 }
4768 }
4769 }
4770 }
4771 }
4772 }
4773
4774 fn vim_search_previous(&mut self) {
4776 if !self.vim_search_adapter.borrow().is_navigating() {
4777 let resumed = {
4779 let mut viewport_borrow = self.viewport_manager.borrow_mut();
4780 if let Some(ref mut viewport) = *viewport_borrow {
4781 if let Some(dataview) = self.state_container.get_buffer_dataview() {
4782 self.vim_search_adapter
4783 .borrow_mut()
4784 .resume_last_search(dataview, viewport)
4785 } else {
4786 false
4787 }
4788 } else {
4789 false
4790 }
4791 };
4792
4793 if !resumed {
4794 self.state_container
4795 .set_status_message("No previous search pattern".to_string());
4796 return;
4797 }
4798 }
4799
4800 let result = {
4802 let mut viewport_borrow = self.viewport_manager.borrow_mut();
4803 if let Some(ref mut viewport) = *viewport_borrow {
4804 let search_match = self
4805 .vim_search_adapter
4806 .borrow_mut()
4807 .previous_match(viewport);
4808 if search_match.is_some() {
4809 let match_info = self.vim_search_adapter.borrow().get_match_info();
4810 search_match.map(|m| (m, match_info))
4811 } else {
4812 None
4813 }
4814 } else {
4815 None
4816 }
4817 }; if let Some((ref search_match, _)) = result {
4821 self.state_container
4822 .set_table_selected_row(Some(search_match.row));
4823 self.state_container
4824 .set_selected_row(Some(search_match.row));
4825
4826 info!(target: "search",
4829 "Setting column to visual index {}",
4830 search_match.col);
4831
4832 self.state_container
4834 .set_current_column_buffer(search_match.col);
4835 self.state_container.navigation_mut().selected_column = search_match.col;
4836
4837 self.state_container.select_column(search_match.col);
4839 info!(target: "search",
4840 "Updated SelectionState column to: {}", search_match.col);
4841
4842 info!(target: "search",
4844 "Column state after update: nav.selected_column={}, buffer.current_column={}, selection.selected_column={}",
4845 self.state_container.navigation().selected_column,
4846 self.state_container.get_current_column(),
4847 self.state_container.selection().selected_column);
4848
4849 self.sync_navigation_with_viewport();
4852
4853 let scroll_offset = self.state_container.navigation().scroll_offset;
4855 self.state_container.set_scroll_offset(scroll_offset);
4856
4857 info!(target: "search", "Updating TableWidgetManager for vim search navigation to ({}, {})",
4859 search_match.row, search_match.col);
4860 self.table_widget_manager
4861 .borrow_mut()
4862 .navigate_to(search_match.row, search_match.col);
4863
4864 info!(target: "search",
4866 "After TableWidgetManager update: nav.selected_column={}, buffer.current_column={}, selection.selected_column={}",
4867 self.state_container.navigation().selected_column,
4868 self.state_container.get_current_column(),
4869 self.state_container.selection().selected_column);
4870
4871 }
4875
4876 if let Some((search_match, match_info)) = result {
4878 if let Some((current, total)) = match_info {
4879 self.state_container.set_status_message(format!(
4880 "Match {}/{} at ({}, {})",
4881 current,
4882 total,
4883 search_match.row + 1,
4884 search_match.col + 1
4885 ));
4886 }
4887 }
4888 }
4890
4891 fn apply_filter(&mut self, pattern: &str) {
4892 use std::sync::atomic::{AtomicUsize, Ordering};
4893
4894 static FILTER_DEPTH: AtomicUsize = AtomicUsize::new(0);
4896 let depth = FILTER_DEPTH.fetch_add(1, Ordering::SeqCst);
4897 if depth > 0 {
4898 eprintln!(
4899 "WARNING: apply_filter re-entrancy detected! depth={}, pattern='{}', thread={:?}",
4900 depth,
4901 pattern,
4902 std::thread::current().id()
4903 );
4904 }
4905
4906 info!(
4907 "Applying filter: '{}' on thread {:?}",
4908 pattern,
4909 std::thread::current().id()
4910 );
4911
4912 use crate::ui::state::state_coordinator::StateCoordinator;
4914 let _rows_after =
4915 StateCoordinator::apply_text_filter_with_refs(&mut self.state_container, pattern);
4916
4917 self.sync_dataview_to_managers();
4920
4921 FILTER_DEPTH.fetch_sub(1, Ordering::SeqCst);
4923 }
4924 fn search_columns(&mut self) {
4925 static SEARCH_DEPTH: std::sync::atomic::AtomicUsize =
4927 std::sync::atomic::AtomicUsize::new(0);
4928 let depth = SEARCH_DEPTH.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
4929
4930 if depth > 10 {
4932 error!(target: "search", "Column search depth exceeded limit, aborting to prevent infinite loop");
4933 SEARCH_DEPTH.store(0, std::sync::atomic::Ordering::SeqCst);
4934 return;
4935 }
4936
4937 struct DepthGuard;
4939 impl Drop for DepthGuard {
4940 fn drop(&mut self) {
4941 SEARCH_DEPTH.fetch_sub(1, std::sync::atomic::Ordering::SeqCst);
4942 }
4943 }
4944 let _guard = DepthGuard;
4945
4946 let pattern = self.state_container.column_search().pattern.clone();
4947 debug!(target: "search", "search_columns called with pattern: '{}', depth: {}", pattern, depth);
4948
4949 if pattern.is_empty() {
4950 debug!(target: "search", "Pattern is empty, skipping column search");
4951 return;
4952 }
4953
4954 let matching_columns = if let Some(dataview) =
4956 self.state_container.get_buffer_dataview_mut()
4957 {
4958 dataview.search_columns(&pattern);
4959
4960 let matches = dataview.get_matching_columns().to_vec();
4962 debug!(target: "search", "DataView found {} matching columns", matches.len());
4963 if !matches.is_empty() {
4964 for (idx, (col_idx, col_name)) in matches.iter().enumerate() {
4965 debug!(target: "search", " Match {}: '{}' at visual index {}", idx + 1, col_name, col_idx);
4966 }
4967 }
4968
4969 let columns: Vec<(String, usize)> = matches
4971 .iter()
4972 .map(|(idx, name)| (name.clone(), *idx))
4973 .collect();
4974 self.state_container
4975 .update_column_search_matches(&columns, &pattern);
4976
4977 matches
4978 } else {
4979 debug!(target: "search", "No DataView available for column search");
4980 Vec::new()
4981 };
4982
4983 if matching_columns.is_empty() {
4984 let status_msg = format!("No columns matching '{pattern}'");
4985 debug!(target: "search", "Setting status: {}", status_msg);
4986 self.state_container.set_status_message(status_msg);
4987 } else {
4988 let first_match_visual_idx = matching_columns[0].0;
4990 let first_match_name = &matching_columns[0].1;
4991
4992 let datatable_idx = if let Some(dataview) = self.state_container.get_buffer_dataview() {
4994 let display_columns = dataview.get_display_columns();
4995 if first_match_visual_idx < display_columns.len() {
4996 display_columns[first_match_visual_idx]
4997 } else {
4998 first_match_visual_idx }
5000 } else {
5001 first_match_visual_idx
5002 };
5003
5004 self.state_container.set_current_column(datatable_idx);
5005 self.state_container
5006 .set_current_column_buffer(datatable_idx);
5007
5008 {
5011 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
5012 if let Some(viewport_manager) = viewport_manager_borrow.as_mut() {
5013 let viewport_changed =
5014 viewport_manager.set_current_column(first_match_visual_idx);
5015
5016 if viewport_changed {
5018 let new_viewport = viewport_manager.viewport_cols().clone();
5019 let pinned_count =
5020 if let Some(dv) = self.state_container.get_buffer_dataview() {
5021 dv.get_pinned_columns().len()
5022 } else {
5023 0
5024 };
5025 let scrollable_offset = new_viewport.start.saturating_sub(pinned_count);
5026 self.state_container.navigation_mut().scroll_offset.1 = scrollable_offset;
5027
5028 debug!(target: "navigation",
5029 "Column search initial: Jumped to column {} '{}', viewport adjusted to {:?}",
5030 first_match_visual_idx, first_match_name, new_viewport);
5031 }
5032 }
5033 }
5034
5035 debug!(target: "search", "Setting current column to visual index {} ('{}')",
5036 first_match_visual_idx, first_match_name);
5037 let status_msg = format!(
5038 "Found {} columns matching '{}'. Tab/Shift-Tab to navigate.",
5039 matching_columns.len(),
5040 pattern
5041 );
5042 debug!(target: "search", "Setting status: {}", status_msg);
5043 self.state_container.set_status_message(status_msg);
5044
5045 }
5047
5048 }
5050
5051 fn next_column_match(&mut self) {
5052 let column_match_data =
5055 if let Some(dataview) = self.state_container.get_buffer_dataview_mut() {
5056 if let Some(visual_idx) = dataview.next_column_match() {
5057 let matching_columns = dataview.get_matching_columns();
5059 let current_match_index = dataview.current_column_match_index();
5060 let current_match = current_match_index + 1;
5061 let total_matches = matching_columns.len();
5062 let col_name = matching_columns
5063 .get(current_match_index)
5064 .map(|(_, name)| name.clone())
5065 .unwrap_or_default();
5066
5067 let display_columns = dataview.get_display_columns();
5070 let datatable_idx = if visual_idx < display_columns.len() {
5071 display_columns[visual_idx]
5072 } else {
5073 visual_idx };
5075
5076 Some((
5077 visual_idx,
5078 datatable_idx,
5079 col_name,
5080 current_match,
5081 total_matches,
5082 current_match_index,
5083 ))
5084 } else {
5085 None
5086 }
5087 } else {
5088 None
5089 };
5090
5091 if let Some((
5093 visual_idx,
5094 datatable_idx,
5095 col_name,
5096 current_match,
5097 total_matches,
5098 current_match_index,
5099 )) = column_match_data
5100 {
5101 self.state_container.set_current_column(datatable_idx);
5103 self.state_container
5104 .set_current_column_buffer(datatable_idx);
5105
5106 {
5109 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
5110 if let Some(viewport_manager) = viewport_manager_borrow.as_mut() {
5111 viewport_manager.set_current_column(visual_idx);
5112 }
5113 }
5114
5115 debug!(target: "column_search_sync", "next_column_match: About to call sync_navigation_with_viewport() - visual_idx: {}, datatable_idx: {}", visual_idx, datatable_idx);
5117 debug!(target: "column_search_sync", "next_column_match: Pre-sync - viewport current_column: {}",
5118 if let Ok(vm) = self.viewport_manager.try_borrow() {
5119 vm.as_ref().map_or(0, super::viewport_manager::ViewportManager::get_crosshair_col)
5120 } else { 0 });
5121 self.sync_navigation_with_viewport();
5122 debug!(target: "column_search_sync", "next_column_match: Post-sync - navigation current_column: {}",
5123 self.state_container.navigation().selected_column);
5124 debug!(target: "column_search_sync", "next_column_match: sync_navigation_with_viewport() completed");
5125
5126 debug!(target: "navigation",
5127 "Column search: Jumped to visual column {} (datatable: {}) '{}', synced with viewport",
5128 visual_idx, datatable_idx, col_name);
5129
5130 {
5133 let mut column_search = self.state_container.column_search_mut();
5134 column_search.current_match = current_match_index;
5135 debug!(target: "column_search_sync", "next_column_match: Updated AppStateContainer column_search.current_match to {}", column_search.current_match);
5136 }
5137
5138 self.state_container.set_status_message(format!(
5139 "Column {current_match}/{total_matches}: {col_name} - Tab/Shift-Tab to navigate"
5140 ));
5141 }
5142 }
5143
5144 fn previous_column_match(&mut self) {
5145 let column_match_data =
5148 if let Some(dataview) = self.state_container.get_buffer_dataview_mut() {
5149 if let Some(visual_idx) = dataview.prev_column_match() {
5150 let matching_columns = dataview.get_matching_columns();
5152 let current_match_index = dataview.current_column_match_index();
5153 let current_match = current_match_index + 1;
5154 let total_matches = matching_columns.len();
5155 let col_name = matching_columns
5156 .get(current_match_index)
5157 .map(|(_, name)| name.clone())
5158 .unwrap_or_default();
5159
5160 let display_columns = dataview.get_display_columns();
5163 let datatable_idx = if visual_idx < display_columns.len() {
5164 display_columns[visual_idx]
5165 } else {
5166 visual_idx };
5168
5169 Some((
5170 visual_idx,
5171 datatable_idx,
5172 col_name,
5173 current_match,
5174 total_matches,
5175 current_match_index,
5176 ))
5177 } else {
5178 None
5179 }
5180 } else {
5181 None
5182 };
5183
5184 if let Some((
5186 visual_idx,
5187 datatable_idx,
5188 col_name,
5189 current_match,
5190 total_matches,
5191 current_match_index,
5192 )) = column_match_data
5193 {
5194 self.state_container.set_current_column(datatable_idx);
5196 self.state_container
5197 .set_current_column_buffer(datatable_idx);
5198
5199 {
5202 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
5203 if let Some(viewport_manager) = viewport_manager_borrow.as_mut() {
5204 viewport_manager.set_current_column(visual_idx);
5205 }
5206 }
5207
5208 debug!(target: "column_search_sync", "previous_column_match: About to call sync_navigation_with_viewport() - visual_idx: {}, datatable_idx: {}", visual_idx, datatable_idx);
5210 debug!(target: "column_search_sync", "previous_column_match: Pre-sync - viewport current_column: {}",
5211 if let Ok(vm) = self.viewport_manager.try_borrow() {
5212 vm.as_ref().map_or(0, super::viewport_manager::ViewportManager::get_crosshair_col)
5213 } else { 0 });
5214 self.sync_navigation_with_viewport();
5215 debug!(target: "column_search_sync", "previous_column_match: Post-sync - navigation current_column: {}",
5216 self.state_container.navigation().selected_column);
5217 debug!(target: "column_search_sync", "previous_column_match: sync_navigation_with_viewport() completed");
5218
5219 debug!(target: "navigation",
5220 "Column search (prev): Jumped to visual column {} (datatable: {}) '{}', synced with viewport",
5221 visual_idx, datatable_idx, col_name);
5222
5223 {
5226 let mut column_search = self.state_container.column_search_mut();
5227 column_search.current_match = current_match_index;
5228 debug!(target: "column_search_sync", "previous_column_match: Updated AppStateContainer column_search.current_match to {}", column_search.current_match);
5229 }
5230
5231 self.state_container.set_status_message(format!(
5232 "Column {current_match}/{total_matches}: {col_name} - Tab/Shift-Tab to navigate"
5233 ));
5234 }
5235 }
5236
5237 fn apply_fuzzy_filter(&mut self) {
5238 info!(
5239 "apply_fuzzy_filter called on thread {:?}",
5240 std::thread::current().id()
5241 );
5242
5243 use crate::ui::state::state_coordinator::StateCoordinator;
5245 let (_match_count, indices) = StateCoordinator::apply_fuzzy_filter_with_refs(
5246 &mut self.state_container,
5247 &self.viewport_manager,
5248 );
5249
5250 self.state_container.set_fuzzy_filter_indices(indices);
5252
5253 self.sync_dataview_to_managers();
5256 }
5257
5258 fn toggle_sort_current_column(&mut self) {
5259 let visual_col_idx = if let Some(ref viewport_manager) = *self.viewport_manager.borrow() {
5261 viewport_manager.get_crosshair_col()
5262 } else {
5263 0
5264 };
5265
5266 if let Some(dataview) = self.state_container.get_buffer_dataview_mut() {
5267 let column_names = dataview.column_names();
5270 let col_name = column_names
5271 .get(visual_col_idx)
5272 .cloned()
5273 .unwrap_or_else(|| format!("Column {visual_col_idx}"));
5274
5275 debug!(
5276 "toggle_sort_current_column: visual_idx={}, column_name={}",
5277 visual_col_idx, col_name
5278 );
5279
5280 if let Err(e) = dataview.toggle_sort(visual_col_idx) {
5281 self.state_container
5282 .set_status_message(format!("Sort error: {e}"));
5283 } else {
5284 let sort_state = dataview.get_sort_state();
5286 let message = match sort_state.order {
5287 crate::data::data_view::SortOrder::Ascending => {
5288 format!("Sorted '{col_name}' ascending ↑")
5289 }
5290 crate::data::data_view::SortOrder::Descending => {
5291 format!("Sorted '{col_name}' descending ↓")
5292 }
5293 crate::data::data_view::SortOrder::None => {
5294 format!("Cleared sort on '{col_name}'")
5295 }
5296 };
5297 self.state_container.set_status_message(message);
5298
5299 if let Some(updated_dataview) = self.state_container.get_buffer_dataview() {
5301 self.table_widget_manager
5303 .borrow_mut()
5304 .set_dataview(Arc::new(updated_dataview.clone()));
5305
5306 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
5307 if let Some(ref mut viewport_manager) = *viewport_manager_borrow {
5308 viewport_manager.set_dataview(Arc::new(updated_dataview.clone()));
5309 debug!("Updated ViewportManager with sorted DataView");
5310 }
5311 }
5312 }
5313 } else {
5314 self.state_container
5316 .set_status_message("Error: Invalid column position".to_string());
5317 }
5318 }
5319
5320 fn get_current_data(&self) -> Option<&DataView> {
5321 self.state_container.get_buffer_dataview()
5322 }
5323
5324 fn get_row_count(&self) -> usize {
5325 if self.state_container.is_fuzzy_filter_active() {
5327 self.state_container.get_fuzzy_filter_indices().len()
5329 } else if let Some(dataview) = self.state_container.get_buffer_dataview() {
5330 dataview.row_count()
5332 } else if let Some(provider) = self.get_data_provider() {
5333 provider.get_row_count()
5335 } else {
5336 0
5337 }
5338 }
5339
5340 fn sync_dataview_to_managers(&self) {
5342 if let Some(dataview) = self.state_container.get_buffer_dataview() {
5343 let arc_dataview = Arc::new(dataview.clone());
5344
5345 if let Some(ref mut viewport_manager) = *self.viewport_manager.borrow_mut() {
5347 viewport_manager.set_dataview(arc_dataview.clone());
5348 debug!(
5349 "Updated ViewportManager with DataView (row_count={})",
5350 arc_dataview.row_count()
5351 );
5352 }
5353
5354 self.table_widget_manager
5356 .borrow_mut()
5357 .set_dataview(arc_dataview);
5358 debug!("Updated TableWidgetManager with DataView");
5359 }
5360 }
5361
5362 pub fn reset_table_state(&mut self) {
5363 use crate::ui::state::state_coordinator::StateCoordinator;
5365 StateCoordinator::reset_table_state_with_refs(
5366 &mut self.state_container,
5367 &self.viewport_manager,
5368 );
5369 }
5370
5371 fn update_parser_for_current_buffer(&mut self) {
5372 self.sync_all_input_states();
5374
5375 use crate::ui::state::state_coordinator::StateCoordinator;
5377 StateCoordinator::update_parser_with_refs(&self.state_container, &mut self.hybrid_parser);
5378 }
5379
5380 fn sync_after_buffer_switch(&mut self) {
5385 self.restore_viewport_from_current_buffer();
5387
5388 self.update_parser_for_current_buffer();
5390 }
5391
5392 fn update_viewport_manager(&mut self, dataview: Option<DataView>) {
5394 if let Some(dv) = dataview {
5395 let current_column = self.state_container.get_current_column();
5397
5398 let mut new_viewport_manager = ViewportManager::new(Arc::new(dv));
5400
5401 if let Ok((width, height)) = crossterm::terminal::size() {
5403 let data_rows_available = Self::calculate_available_data_rows(height);
5405 new_viewport_manager.update_terminal_size(width, data_rows_available);
5406 debug!(
5407 "Updated new ViewportManager terminal size: {}x{} (data rows)",
5408 width, data_rows_available
5409 );
5410 }
5411
5412 if current_column < new_viewport_manager.dataview().column_count() {
5415 new_viewport_manager.set_current_column(current_column);
5416 } else {
5417 new_viewport_manager.set_current_column(0);
5419 self.state_container.set_current_column_buffer(0);
5420 }
5421
5422 *self.viewport_manager.borrow_mut() = Some(new_viewport_manager);
5423 debug!(
5424 "ViewportManager updated with new DataView, current_column={}",
5425 current_column
5426 );
5427 } else {
5428 *self.viewport_manager.borrow_mut() = None;
5430 debug!("ViewportManager cleared (no DataView)");
5431 }
5432 }
5433
5434 pub fn calculate_optimal_column_widths(&mut self) {
5435 let widths_from_viewport = {
5437 let mut viewport_opt = self.viewport_manager.borrow_mut();
5438 (*viewport_opt)
5439 .as_mut()
5440 .map(super::viewport_manager::ViewportManager::calculate_optimal_column_widths)
5441 };
5442
5443 if let Some(widths) = widths_from_viewport {
5444 self.state_container.set_column_widths(widths);
5445 }
5446 }
5447
5448 pub fn set_status_message(&mut self, message: impl Into<String>) {
5451 let msg = message.into();
5452 debug!("Status: {}", msg);
5453 self.state_container.set_status_message(msg.clone());
5454 }
5457
5458 fn set_error_status(&mut self, context: &str, error: impl std::fmt::Display) {
5460 let msg = format!("{context}: {error}");
5461 debug!("Error status: {}", msg);
5462 self.set_status_message(msg);
5463 }
5464
5465 fn export_to_csv(&mut self) {
5466 let result = {
5467 let ctx = crate::ui::operations::data_export_operations::DataExportContext {
5468 data_provider: self.get_data_provider(),
5469 };
5470 crate::ui::operations::data_export_operations::export_to_csv(&ctx)
5471 };
5472
5473 match result {
5474 crate::ui::operations::data_export_operations::ExportResult::Success(message) => {
5475 self.set_status_message(message);
5476 }
5477 crate::ui::operations::data_export_operations::ExportResult::Error(error) => {
5478 self.set_error_status("Export failed", error);
5479 }
5480 }
5481 }
5482
5483 fn paste_from_clipboard(&mut self) {
5490 match self.state_container.read_from_clipboard() {
5492 Ok(text) => {
5493 let mode = self.shadow_state.borrow().get_mode();
5494 match mode {
5495 AppMode::Command => {
5496 let cursor_pos = self.get_input_cursor();
5499 let current_value = self.get_input_text();
5500
5501 let mut new_value = String::new();
5503 new_value.push_str(¤t_value[..cursor_pos]);
5504 new_value.push_str(&text);
5505 new_value.push_str(¤t_value[cursor_pos..]);
5506
5507 self.set_input_text_with_cursor(new_value, cursor_pos + text.len());
5508
5509 self.state_container
5510 .set_status_message(format!("Pasted {} characters", text.len()));
5511 }
5512 AppMode::Filter
5513 | AppMode::FuzzyFilter
5514 | AppMode::Search
5515 | AppMode::ColumnSearch => {
5516 let cursor_pos = self.get_input_cursor();
5518 let current_value = self.get_input_text();
5519
5520 let mut new_value = String::new();
5521 new_value.push_str(¤t_value[..cursor_pos]);
5522 new_value.push_str(&text);
5523 new_value.push_str(¤t_value[cursor_pos..]);
5524
5525 self.set_input_text_with_cursor(new_value, cursor_pos + text.len());
5526
5527 match mode {
5529 AppMode::Filter => {
5530 let pattern = self.get_input_text();
5531 self.state_container.filter_mut().pattern = pattern.clone();
5532 self.apply_filter(&pattern);
5533 }
5534 AppMode::FuzzyFilter => {
5535 let input_text = self.get_input_text();
5536 self.state_container.set_fuzzy_filter_pattern(input_text);
5537 self.apply_fuzzy_filter();
5538 }
5539 AppMode::Search => {
5540 let search_text = self.get_input_text();
5541 self.state_container.set_search_pattern(search_text);
5542 }
5544 AppMode::ColumnSearch => {
5545 let input_text = self.get_input_text();
5546 self.state_container.start_column_search(input_text);
5547 }
5549 _ => {}
5550 }
5551 }
5552 _ => {
5553 self.state_container
5554 .set_status_message("Paste not available in this mode".to_string());
5555 }
5556 }
5557 }
5558 Err(e) => {
5559 self.state_container
5560 .set_status_message(format!("Failed to paste: {e}"));
5561 }
5562 }
5563 }
5564
5565 fn export_to_json(&mut self) {
5566 let result = {
5568 let ctx = crate::ui::operations::data_export_operations::DataExportContext {
5569 data_provider: self.get_data_provider(),
5570 };
5571 crate::ui::operations::data_export_operations::export_to_json(&ctx)
5572 };
5573
5574 match result {
5575 crate::ui::operations::data_export_operations::ExportResult::Success(message) => {
5576 self.set_status_message(message);
5577 }
5578 crate::ui::operations::data_export_operations::ExportResult::Error(error) => {
5579 self.set_error_status("Export failed", error);
5580 }
5581 }
5582 }
5583
5584 fn get_horizontal_scroll_offset(&self) -> u16 {
5585 let (horizontal, _vertical) = self.cursor_manager.scroll_offsets();
5587 horizontal
5588 }
5589
5590 fn update_horizontal_scroll(&mut self, terminal_width: u16) {
5591 let inner_width = terminal_width.saturating_sub(3) as usize; let cursor_pos = self.get_input_cursor();
5593
5594 self.cursor_manager
5596 .update_horizontal_scroll(cursor_pos, terminal_width.saturating_sub(3));
5597
5598 let mut scroll = self.state_container.scroll_mut();
5600 if cursor_pos < scroll.input_scroll_offset as usize {
5601 scroll.input_scroll_offset = cursor_pos as u16;
5602 }
5603 else if cursor_pos >= scroll.input_scroll_offset as usize + inner_width {
5605 scroll.input_scroll_offset = (cursor_pos + 1).saturating_sub(inner_width) as u16;
5606 }
5607 }
5608
5609 fn get_cursor_token_position(&self) -> (usize, usize) {
5610 let ctx = crate::ui::operations::simple_operations::TextNavigationContext {
5611 query: &self.get_input_text(),
5612 cursor_pos: self.get_input_cursor(),
5613 };
5614 crate::ui::operations::simple_operations::get_cursor_token_position(&ctx)
5615 }
5616
5617 fn get_token_at_cursor(&self) -> Option<String> {
5618 let ctx = crate::ui::operations::simple_operations::TextNavigationContext {
5619 query: &self.get_input_text(),
5620 cursor_pos: self.get_input_cursor(),
5621 };
5622 crate::ui::operations::simple_operations::get_token_at_cursor(&ctx)
5623 }
5624
5625 #[allow(dead_code)]
5627 fn ui(&mut self, f: &mut Frame) {
5628 let input_height = INPUT_AREA_HEIGHT;
5630
5631 let buffer_count = self.state_container.buffers().all_buffers().len();
5633 let tab_bar_height = 2; let chunks = Layout::default()
5636 .direction(Direction::Vertical)
5637 .constraints(
5638 [
5639 Constraint::Length(tab_bar_height), Constraint::Length(input_height), Constraint::Min(0), Constraint::Length(STATUS_BAR_HEIGHT), ]
5644 .as_ref(),
5645 )
5646 .split(f.area());
5647
5648 if buffer_count > 0 {
5650 let buffer_names: Vec<String> = self
5651 .state_container
5652 .buffers()
5653 .all_buffers()
5654 .iter()
5655 .map(super::super::buffer::BufferAPI::get_name)
5656 .collect();
5657 let current_index = self.state_container.buffers().current_index();
5658
5659 let tab_widget = TabBarWidget::new(current_index, buffer_names);
5660 tab_widget.render(f, chunks[0]);
5661 }
5662
5663 let input_chunk_idx = 1;
5665 let results_chunk_idx = 2;
5666 let status_chunk_idx = 3;
5667
5668 self.update_horizontal_scroll(chunks[input_chunk_idx].width);
5670
5671 let input_text_for_count = self.get_input_text();
5674 let char_count = input_text_for_count.len();
5675 let cursor_pos = self.get_input_cursor();
5676 let char_count_display = if char_count > 0 {
5677 format!(" [{cursor_pos}/{char_count} chars]")
5678 } else {
5679 String::new()
5680 };
5681
5682 let scroll_offset = self.get_horizontal_scroll_offset();
5683 let scroll_indicator = if scroll_offset > 0 {
5684 " ◀ " } else {
5686 ""
5687 };
5688
5689 let input_title = match self.shadow_state.borrow().get_mode() {
5690 AppMode::Command => format!("SQL Query{char_count_display}{scroll_indicator}"),
5691 AppMode::Results => format!(
5692 "SQL Query (Results Mode - Press ↑ to edit){char_count_display}{scroll_indicator}"
5693 ),
5694 AppMode::Search => format!("Search Pattern{char_count_display}{scroll_indicator}"),
5695 AppMode::Filter => format!("Filter Pattern{char_count_display}{scroll_indicator}"),
5696 AppMode::FuzzyFilter => {
5697 format!("Fuzzy Filter{char_count_display}{scroll_indicator}")
5698 }
5699 AppMode::ColumnSearch => {
5700 format!("Column Search{char_count_display}{scroll_indicator}")
5701 }
5702 AppMode::Help => "Help".to_string(),
5703 AppMode::History => {
5704 let query = self.state_container.history_search().query.clone();
5705 format!("History Search: '{query}' (Esc to cancel)")
5706 }
5707 AppMode::Debug => "Parser Debug (F5)".to_string(),
5708 AppMode::PrettyQuery => "Pretty Query View (F6)".to_string(),
5709 AppMode::JumpToRow => format!("Jump to row: {}", self.get_jump_to_row_input()),
5710 AppMode::ColumnStats => "Column Statistics (S to close)".to_string(),
5711 };
5712
5713 let input_block = Block::default().borders(Borders::ALL).title(input_title);
5714
5715 let use_search_widget = matches!(
5717 self.shadow_state.borrow().get_mode(),
5718 AppMode::Search | AppMode::Filter | AppMode::FuzzyFilter | AppMode::ColumnSearch
5719 ) && self.search_modes_widget.is_active();
5720
5721 if use_search_widget {
5722 self.search_modes_widget.render(f, chunks[input_chunk_idx]);
5724 } else {
5725 let input_text_string = self.get_input_text();
5727
5728 trace!(target: "render", "Rendering input: text='{}', mode={:?}, cursor={}",
5730 if input_text_string.len() > 50 {
5731 format!("{}...", &input_text_string[..50])
5732 } else {
5733 input_text_string.clone()
5734 },
5735 self.shadow_state.borrow().get_mode(),
5736 self.get_input_cursor());
5737
5738 let history_query_string = if self.shadow_state.borrow().is_in_history_mode() {
5740 self.state_container.history_search().query.clone()
5741 } else {
5742 String::new()
5743 };
5744
5745 let input_text = match self.shadow_state.borrow().get_mode() {
5746 AppMode::History => &history_query_string,
5747 _ => &input_text_string,
5748 };
5749
5750 let input_paragraph = match self.shadow_state.borrow().get_mode() {
5751 AppMode::Command => {
5752 match self.state_container.get_edit_mode() {
5753 Some(EditMode::SingleLine) => {
5754 let highlighted_line =
5756 self.sql_highlighter.simple_sql_highlight(input_text);
5757 Paragraph::new(Text::from(vec![highlighted_line]))
5758 .block(input_block)
5759 .scroll((0, self.get_horizontal_scroll_offset()))
5760 }
5761 Some(EditMode::MultiLine) => {
5762 let highlighted_line =
5764 self.sql_highlighter.simple_sql_highlight(input_text);
5765 Paragraph::new(Text::from(vec![highlighted_line]))
5766 .block(input_block)
5767 .scroll((0, self.get_horizontal_scroll_offset()))
5768 }
5769 None => {
5770 let highlighted_line =
5772 self.sql_highlighter.simple_sql_highlight(input_text);
5773 Paragraph::new(Text::from(vec![highlighted_line]))
5774 .block(input_block)
5775 .scroll((0, self.get_horizontal_scroll_offset()))
5776 }
5777 }
5778 }
5779 _ => {
5780 Paragraph::new(input_text.as_str())
5782 .block(input_block)
5783 .style(match self.shadow_state.borrow().get_mode() {
5784 AppMode::Results => Style::default().fg(Color::DarkGray),
5785 AppMode::Search => Style::default().fg(Color::Yellow),
5786 AppMode::Filter => Style::default().fg(Color::Cyan),
5787 AppMode::FuzzyFilter => Style::default().fg(Color::Magenta),
5788 AppMode::ColumnSearch => Style::default().fg(Color::Green),
5789 AppMode::Help => Style::default().fg(Color::DarkGray),
5790 AppMode::History => Style::default().fg(Color::Magenta),
5791 AppMode::Debug => Style::default().fg(Color::Yellow),
5792 AppMode::PrettyQuery => Style::default().fg(Color::Green),
5793 AppMode::JumpToRow => Style::default().fg(Color::Magenta),
5794 AppMode::ColumnStats => Style::default().fg(Color::Cyan),
5795 _ => Style::default(),
5796 })
5797 .scroll((0, self.get_horizontal_scroll_offset()))
5798 }
5799 };
5800
5801 f.render_widget(input_paragraph, chunks[input_chunk_idx]);
5803 }
5804 let results_area = chunks[results_chunk_idx];
5805
5806 if !use_search_widget {
5808 match self.shadow_state.borrow().get_mode() {
5809 AppMode::Command => {
5810 let inner_width = chunks[input_chunk_idx].width.saturating_sub(2) as usize;
5813 let cursor_pos = self.get_visual_cursor().1; let scroll_offset = self.get_horizontal_scroll_offset() as usize;
5815
5816 if cursor_pos >= scroll_offset && cursor_pos < scroll_offset + inner_width {
5818 let visible_pos = cursor_pos - scroll_offset;
5819 f.set_cursor_position((
5820 chunks[input_chunk_idx].x + visible_pos as u16 + 1,
5821 chunks[input_chunk_idx].y + 1,
5822 ));
5823 }
5824 }
5825 AppMode::Search => {
5826 f.set_cursor_position((
5827 chunks[input_chunk_idx].x + self.get_input_cursor() as u16 + 1,
5828 chunks[input_chunk_idx].y + 1,
5829 ));
5830 }
5831 AppMode::Filter => {
5832 f.set_cursor_position((
5833 chunks[input_chunk_idx].x + self.get_input_cursor() as u16 + 1,
5834 chunks[input_chunk_idx].y + 1,
5835 ));
5836 }
5837 AppMode::FuzzyFilter => {
5838 f.set_cursor_position((
5839 chunks[input_chunk_idx].x + self.get_input_cursor() as u16 + 1,
5840 chunks[input_chunk_idx].y + 1,
5841 ));
5842 }
5843 AppMode::ColumnSearch => {
5844 f.set_cursor_position((
5845 chunks[input_chunk_idx].x + self.get_input_cursor() as u16 + 1,
5846 chunks[input_chunk_idx].y + 1,
5847 ));
5848 }
5849 AppMode::JumpToRow => {
5850 f.set_cursor_position((
5851 chunks[input_chunk_idx].x + self.get_jump_to_row_input().len() as u16 + 1,
5852 chunks[input_chunk_idx].y + 1,
5853 ));
5854 }
5855 AppMode::History => {
5856 let query_len = self.state_container.history_search().query.len();
5857 f.set_cursor_position((
5858 chunks[input_chunk_idx].x + query_len as u16 + 1,
5859 chunks[input_chunk_idx].y + 1,
5860 ));
5861 }
5862 _ => {}
5863 }
5864 }
5865
5866 let mode = self.shadow_state.borrow().get_mode();
5868 match mode {
5869 AppMode::Help => self.render_help(f, results_area),
5870 AppMode::History => self.render_history(f, results_area),
5871 AppMode::Debug => self.render_debug(f, results_area),
5872 AppMode::PrettyQuery => self.render_pretty_query(f, results_area),
5873 AppMode::ColumnStats => self.render_column_stats(f, results_area),
5874 _ if self.state_container.has_dataview() => {
5875 if let Some(provider) = self.get_data_provider() {
5878 self.render_table_with_provider(f, results_area, provider.as_ref());
5879 }
5880 }
5881 _ => {
5882 let placeholder = Paragraph::new("Enter SQL query and press Enter\n\nTip: Use Tab for completion, Ctrl+R for history")
5884 .block(Block::default().borders(Borders::ALL).title("Results"))
5885 .style(Style::default().fg(Color::DarkGray));
5886 f.render_widget(placeholder, results_area);
5887 }
5888 }
5889
5890 self.render_status_line(f, chunks[status_chunk_idx]);
5892 }
5894
5895 fn add_mode_styling(&self, spans: &mut Vec<Span>) -> (Style, Color) {
5897 let (status_style, mode_color) = match self.shadow_state.borrow().get_mode() {
5899 AppMode::Command => (Style::default().fg(Color::Green), Color::Green),
5900 AppMode::Results => (Style::default().fg(Color::Blue), Color::Blue),
5901 AppMode::Search => (Style::default().fg(Color::Yellow), Color::Yellow),
5902 AppMode::Filter => (Style::default().fg(Color::Cyan), Color::Cyan),
5903 AppMode::FuzzyFilter => (Style::default().fg(Color::Magenta), Color::Magenta),
5904 AppMode::ColumnSearch => (Style::default().fg(Color::Green), Color::Green),
5905 AppMode::Help => (Style::default().fg(Color::Magenta), Color::Magenta),
5906 AppMode::History => (Style::default().fg(Color::Magenta), Color::Magenta),
5907 AppMode::Debug => (Style::default().fg(Color::Yellow), Color::Yellow),
5908 AppMode::PrettyQuery => (Style::default().fg(Color::Green), Color::Green),
5909 AppMode::JumpToRow => (Style::default().fg(Color::Magenta), Color::Magenta),
5910 AppMode::ColumnStats => (Style::default().fg(Color::Cyan), Color::Cyan),
5911 };
5912
5913 let mode_indicator = match self.shadow_state.borrow().get_mode() {
5914 AppMode::Command => "CMD",
5915 AppMode::Results => "NAV",
5916 AppMode::Search => "SEARCH",
5917 AppMode::Filter => "FILTER",
5918 AppMode::FuzzyFilter => "FUZZY",
5919 AppMode::ColumnSearch => "COL",
5920 AppMode::Help => "HELP",
5921 AppMode::History => "HISTORY",
5922 AppMode::Debug => "DEBUG",
5923 AppMode::PrettyQuery => "PRETTY",
5924 AppMode::JumpToRow => "JUMP",
5925 AppMode::ColumnStats => "STATS",
5926 };
5927
5928 spans.push(Span::styled(
5930 format!("[{mode_indicator}]"),
5931 Style::default().fg(mode_color).add_modifier(Modifier::BOLD),
5932 ));
5933
5934 (status_style, mode_color)
5935 }
5936
5937 fn add_data_source_display(&self, _spans: &mut Vec<Span>) {
5939 }
5942
5943 fn add_buffer_information(&self, spans: &mut Vec<Span>) {
5945 let index = self.state_container.buffers().current_index();
5946 let total = self.state_container.buffers().all_buffers().len();
5947
5948 if total > 1 {
5950 spans.push(Span::raw(" "));
5951 spans.push(Span::styled(
5952 format!("[{}/{}]", index + 1, total),
5953 Style::default().fg(Color::Yellow),
5954 ));
5955 }
5956
5957 if let Some(buffer) = self.state_container.buffers().current() {
5960 let query = buffer.get_input_text();
5961 if let Some(from_pos) = query.to_uppercase().find(" FROM ") {
5963 let after_from = &query[from_pos + 6..];
5964 if let Some(table_name) = after_from.split_whitespace().next() {
5966 let clean_name = table_name
5968 .trim_matches('"')
5969 .trim_matches('\'')
5970 .trim_matches('`');
5971
5972 spans.push(Span::raw(" "));
5973 spans.push(Span::styled(
5974 clean_name.to_string(),
5975 Style::default().fg(Color::Cyan),
5976 ));
5977 }
5978 }
5979 }
5980 }
5981
5982 fn add_mode_specific_info(&self, spans: &mut Vec<Span>, mode_color: Color, area: Rect) {
5984 match self.shadow_state.borrow().get_mode() {
5985 AppMode::Command => {
5986 if !self.get_input_text().trim().is_empty() {
5988 let (token_pos, total_tokens) = self.get_cursor_token_position();
5989 spans.push(Span::raw(" | "));
5990 spans.push(Span::styled(
5991 format!("Token {token_pos}/{total_tokens}"),
5992 Style::default().fg(Color::DarkGray),
5993 ));
5994
5995 if let Some(token) = self.get_token_at_cursor() {
5997 spans.push(Span::raw(" "));
5998 spans.push(Span::styled(
5999 format!("[{token}]"),
6000 Style::default().fg(Color::Cyan),
6001 ));
6002 }
6003
6004 if let Some(error_msg) = self.check_parser_error(&self.get_input_text()) {
6006 spans.push(Span::raw(" | "));
6007 spans.push(Span::styled(
6008 format!("{} {}", self.config.display.icons.warning, error_msg),
6009 Style::default().fg(Color::Red).add_modifier(Modifier::BOLD),
6010 ));
6011 }
6012 }
6013 }
6014 AppMode::Results => {
6015 self.add_results_mode_info(spans, area);
6017 }
6018 AppMode::Search | AppMode::Filter | AppMode::FuzzyFilter | AppMode::ColumnSearch => {
6019 let pattern = self.get_input_text();
6021 if !pattern.is_empty() {
6022 spans.push(Span::raw(" | Pattern: "));
6023 spans.push(Span::styled(pattern, Style::default().fg(mode_color)));
6024 }
6025 }
6026 _ => {}
6027 }
6028 }
6029
6030 fn add_results_mode_info(&self, spans: &mut Vec<Span>, area: Rect) {
6032 let total_rows = self.get_row_count();
6033 if total_rows > 0 {
6034 let selected = self.state_container.navigation().selected_row + 1;
6036 spans.push(Span::raw(" | "));
6037
6038 let selection_mode = self.get_selection_mode();
6040 let mode_text = match selection_mode {
6041 SelectionMode::Cell => "CELL",
6042 SelectionMode::Row => "ROW",
6043 SelectionMode::Column => "COL",
6044 };
6045 spans.push(Span::styled(
6046 format!("[{mode_text}]"),
6047 Style::default()
6048 .fg(Color::Cyan)
6049 .add_modifier(Modifier::BOLD),
6050 ));
6051
6052 spans.push(Span::raw(" "));
6053 spans.push(Span::styled(
6054 format!("Row {selected}/{total_rows}"),
6055 Style::default().fg(Color::White),
6056 ));
6057
6058 let visual_col_display =
6061 if let Some(ref viewport_manager) = *self.viewport_manager.borrow() {
6062 viewport_manager.get_crosshair_col() + 1
6063 } else {
6064 1
6065 };
6066 spans.push(Span::raw(" "));
6067 spans.push(Span::styled(
6068 format!("({visual_col_display},{selected})"),
6069 Style::default().fg(Color::DarkGray),
6070 ));
6071
6072 if let Some(ref mut viewport_manager) = *self.viewport_manager.borrow_mut() {
6074 let available_width = area.width.saturating_sub(TABLE_BORDER_WIDTH);
6075 let visual_col = viewport_manager.get_crosshair_col();
6077 if let Some(x_pos) =
6078 viewport_manager.get_column_x_position(visual_col, available_width)
6079 {
6080 let terminal_x = x_pos + 2;
6082 let terminal_y = (selected as u16)
6083 .saturating_sub(self.state_container.get_scroll_offset().0 as u16)
6084 + 3;
6085 spans.push(Span::raw(" "));
6086 spans.push(Span::styled(
6087 format!("[{terminal_x}x{terminal_y}]"),
6088 Style::default().fg(Color::DarkGray),
6089 ));
6090 }
6091 }
6092
6093 if let Some(dataview) = self.state_container.get_buffer_dataview() {
6095 let headers = dataview.column_names();
6096
6097 let (visual_row, visual_col) =
6100 if let Some(ref viewport_manager) = *self.viewport_manager.borrow() {
6101 (
6102 viewport_manager.get_crosshair_row(),
6103 viewport_manager.get_crosshair_col(),
6104 )
6105 } else {
6106 (0, 0)
6107 };
6108
6109 if visual_col < headers.len() {
6111 spans.push(Span::raw(" | Col: "));
6112 spans.push(Span::styled(
6113 headers[visual_col].clone(),
6114 Style::default().fg(Color::Cyan),
6115 ));
6116
6117 let viewport_info =
6119 if let Some(ref viewport_manager) = *self.viewport_manager.borrow() {
6120 let viewport_rows = viewport_manager.get_viewport_rows();
6121 let viewport_height = viewport_rows.end - viewport_rows.start;
6122 format!("[V:{visual_row},{visual_col} @ {viewport_height}r]")
6123 } else {
6124 format!("[V:{visual_row},{visual_col}]")
6125 };
6126 spans.push(Span::raw(" "));
6127 spans.push(Span::styled(
6128 viewport_info,
6129 Style::default().fg(Color::Magenta),
6130 ));
6131 }
6132 }
6133 }
6134 }
6135
6136 fn render_status_line(&self, f: &mut Frame, area: Rect) {
6137 let mut spans = Vec::new();
6138
6139 let (status_style, mode_color) = self.add_mode_styling(&mut spans);
6141
6142 self.add_data_source_display(&mut spans);
6144
6145 self.add_buffer_information(&mut spans);
6147
6148 self.add_mode_specific_info(&mut spans, mode_color, area);
6150
6151 self.add_query_source_indicator(&mut spans);
6153
6154 self.add_case_sensitivity_indicator(&mut spans);
6156
6157 self.add_column_packing_indicator(&mut spans);
6159
6160 self.add_status_message(&mut spans);
6162
6163 let help_text = self.get_help_text_for_mode();
6165
6166 self.add_global_indicators(&mut spans);
6167
6168 self.add_shadow_state_display(&mut spans);
6170
6171 self.add_help_text_display(&mut spans, help_text, area);
6172
6173 let status_line = Line::from(spans);
6174 let status = Paragraph::new(status_line)
6175 .block(Block::default().borders(Borders::ALL))
6176 .style(status_style);
6177 f.render_widget(status, area);
6178 }
6179
6180 fn build_table_context(
6183 &self,
6184 area: Rect,
6185 provider: &dyn DataProvider,
6186 ) -> crate::ui::rendering::table_render_context::TableRenderContext {
6187 use crate::ui::rendering::table_render_context::TableRenderContextBuilder;
6188
6189 let row_count = provider.get_row_count();
6190 let available_width = area.width.saturating_sub(TABLE_BORDER_WIDTH);
6191 let available_height = area.height;
6194
6195 let headers = {
6197 let viewport_manager = self.viewport_manager.borrow();
6198 let viewport_manager = viewport_manager
6199 .as_ref()
6200 .expect("ViewportManager must exist");
6201 viewport_manager.get_column_names_ordered()
6202 };
6203
6204 {
6206 let mut viewport_opt = self.viewport_manager.borrow_mut();
6207 if let Some(ref mut viewport_manager) = *viewport_opt {
6208 let data_rows = Self::calculate_table_data_rows(available_height);
6210 viewport_manager.update_terminal_size(available_width, data_rows);
6211 let _ = viewport_manager.get_column_widths(); }
6213 }
6214
6215 let (pinned_visual_positions, crosshair_column_position, _) = {
6217 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
6218 let viewport_manager = viewport_manager_borrow
6219 .as_mut()
6220 .expect("ViewportManager must exist for rendering");
6221 let info = viewport_manager.get_visible_columns_info(available_width);
6222
6223 let visible_indices = &info.0;
6227 let pinned_source_indices = &info.1;
6228
6229 let mut pinned_visual_positions = Vec::new();
6232 for &source_idx in pinned_source_indices {
6233 if let Some(visual_pos) = visible_indices.iter().position(|&x| x == source_idx) {
6234 pinned_visual_positions.push(visual_pos);
6235 }
6236 }
6237
6238 let crosshair_column_position =
6242 if let Some((_, col_pos)) = viewport_manager.get_crosshair_viewport_position() {
6243 col_pos
6244 } else {
6245 0
6247 };
6248
6249 let crosshair_visual = viewport_manager.get_crosshair_col();
6250
6251 (
6252 pinned_visual_positions,
6253 crosshair_column_position,
6254 crosshair_visual,
6255 )
6256 };
6257
6258 let row_viewport_start = self
6260 .state_container
6261 .navigation()
6262 .scroll_offset
6263 .0
6264 .min(row_count.saturating_sub(1));
6265 let row_viewport_end = (row_viewport_start + available_height as usize).min(row_count);
6266 let visible_row_indices: Vec<usize> = (row_viewport_start..row_viewport_end).collect();
6267
6268 let (column_headers, data_to_display, column_widths_visual) = {
6270 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
6271 if let Some(ref mut viewport_manager) = *viewport_manager_borrow {
6272 viewport_manager.get_visual_display(available_width, &visible_row_indices)
6273 } else {
6274 let visible_rows = provider
6276 .get_visible_rows(row_viewport_start, row_viewport_end - row_viewport_start);
6277 let widths = vec![15u16; headers.len()];
6278 (headers.clone(), visible_rows, widths)
6279 }
6280 };
6281
6282 let sort_state = self
6284 .buffer()
6285 .get_dataview()
6286 .map(|dv| dv.get_sort_state().clone());
6287
6288 let fuzzy_filter_pattern = if self.state_container.is_fuzzy_filter_active() {
6290 let pattern = self.state_container.get_fuzzy_filter_pattern();
6291 if pattern.is_empty() {
6292 None
6293 } else {
6294 Some(pattern)
6295 }
6296 } else {
6297 None
6298 };
6299
6300 let selected_row = self.state_container.navigation().selected_row;
6302 let selected_col = crosshair_column_position;
6303
6304 trace!(target: "search", "Building TableRenderContext: selected_row={}, selected_col={}, mode={:?}",
6306 selected_row, selected_col, self.state_container.get_selection_mode());
6307
6308 TableRenderContextBuilder::new()
6309 .row_count(row_count)
6310 .visible_rows(visible_row_indices.clone(), data_to_display)
6311 .columns(column_headers, column_widths_visual)
6312 .pinned_columns(pinned_visual_positions)
6313 .selection(
6314 selected_row,
6315 selected_col,
6316 self.state_container.get_selection_mode(),
6317 )
6318 .row_viewport(row_viewport_start..row_viewport_end)
6319 .sort_state(sort_state)
6320 .display_options(
6321 self.state_container.is_show_row_numbers(),
6322 self.shadow_state.borrow().get_mode(),
6323 )
6324 .filter(
6325 fuzzy_filter_pattern,
6326 self.state_container.is_case_insensitive(),
6327 )
6328 .dimensions(available_width, available_height)
6329 .build()
6330 }
6331
6332 fn render_table_with_provider(&self, f: &mut Frame, area: Rect, provider: &dyn DataProvider) {
6335 let context = self.build_table_context(area, provider);
6337
6338 crate::ui::rendering::table_renderer::render_table(f, area, &context);
6340 }
6341
6342 fn render_help(&mut self, f: &mut Frame, area: Rect) {
6343 self.help_widget.render(f, area);
6345 }
6346
6347 fn render_help_two_column(&self, f: &mut Frame, area: Rect) {
6348 let chunks = Layout::default()
6350 .direction(Direction::Horizontal)
6351 .constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
6352 .split(area);
6353
6354 let left_content = HelpText::left_column();
6356 let right_content = HelpText::right_column();
6357
6358 let visible_height = area.height.saturating_sub(2) as usize; let left_total_lines = left_content.len();
6361 let right_total_lines = right_content.len();
6362 let max_lines = left_total_lines.max(right_total_lines);
6363
6364 let scroll_offset = { self.state_container.help_scroll_offset() as usize };
6366
6367 let left_visible: Vec<Line> = left_content
6369 .into_iter()
6370 .skip(scroll_offset)
6371 .take(visible_height)
6372 .collect();
6373
6374 let right_visible: Vec<Line> = right_content
6375 .into_iter()
6376 .skip(scroll_offset)
6377 .take(visible_height)
6378 .collect();
6379
6380 let scroll_indicator = if max_lines > visible_height {
6382 format!(
6383 " (↓/↑ to scroll, {}/{})",
6384 scroll_offset + 1,
6385 max_lines.saturating_sub(visible_height) + 1
6386 )
6387 } else {
6388 String::new()
6389 };
6390
6391 let left_paragraph = Paragraph::new(Text::from(left_visible))
6393 .block(
6394 Block::default()
6395 .borders(Borders::ALL)
6396 .title(format!("Help - Commands{scroll_indicator}")),
6397 )
6398 .style(Style::default());
6399
6400 let right_paragraph = Paragraph::new(Text::from(right_visible))
6402 .block(
6403 Block::default()
6404 .borders(Borders::ALL)
6405 .title("Help - Navigation & Features"),
6406 )
6407 .style(Style::default());
6408
6409 f.render_widget(left_paragraph, chunks[0]);
6410 f.render_widget(right_paragraph, chunks[1]);
6411 }
6412
6413 fn render_debug(&self, f: &mut Frame, area: Rect) {
6414 <Self as DebugContext>::render_debug(self, f, area);
6415 }
6416
6417 fn render_pretty_query(&self, f: &mut Frame, area: Rect) {
6418 <Self as DebugContext>::render_pretty_query(self, f, area);
6419 }
6420
6421 fn render_history(&self, f: &mut Frame, area: Rect) {
6422 let history_search = self.state_container.history_search();
6424 let matches_empty = history_search.matches.is_empty();
6425 let search_query_empty = history_search.query.is_empty();
6426
6427 if matches_empty {
6428 let no_history = if search_query_empty {
6429 "No command history found.\nExecute some queries to build history."
6430 } else {
6431 "No matches found for your search.\nTry a different search term."
6432 };
6433
6434 let placeholder = Paragraph::new(no_history)
6435 .block(
6436 Block::default()
6437 .borders(Borders::ALL)
6438 .title("Command History"),
6439 )
6440 .style(Style::default().fg(Color::DarkGray));
6441 f.render_widget(placeholder, area);
6442 return;
6443 }
6444
6445 let chunks = Layout::default()
6447 .direction(Direction::Vertical)
6448 .constraints([
6449 Constraint::Percentage(50), Constraint::Percentage(50), ])
6452 .split(area);
6453
6454 self.render_history_list(f, chunks[0]);
6455 self.render_selected_command_preview(f, chunks[1]);
6456 }
6457
6458 fn render_history_list(&self, f: &mut Frame, area: Rect) {
6459 let history_search = self.state_container.history_search();
6461 let matches = history_search.matches.clone();
6462 let selected_index = history_search.selected_index;
6463 let match_count = matches.len();
6464
6465 let history_items: Vec<Line> = matches
6467 .iter()
6468 .enumerate()
6469 .map(|(i, history_match)| {
6470 let entry = &history_match.entry;
6471 let is_selected = i == selected_index;
6472
6473 let success_indicator = if entry.success { "✓" } else { "✗" };
6474 let time_ago = {
6475 let elapsed = chrono::Utc::now() - entry.timestamp;
6476 if elapsed.num_days() > 0 {
6477 format!("{}d", elapsed.num_days())
6478 } else if elapsed.num_hours() > 0 {
6479 format!("{}h", elapsed.num_hours())
6480 } else if elapsed.num_minutes() > 0 {
6481 format!("{}m", elapsed.num_minutes())
6482 } else {
6483 "now".to_string()
6484 }
6485 };
6486
6487 let terminal_width = area.width as usize;
6489 let metadata_space = 15; let available_for_command = terminal_width.saturating_sub(metadata_space).max(50);
6491
6492 let command_text = if entry.command.len() > available_for_command {
6493 format!(
6494 "{}…",
6495 &entry.command[..available_for_command.saturating_sub(1)]
6496 )
6497 } else {
6498 entry.command.clone()
6499 };
6500
6501 let line_text = format!(
6502 "{} {} {} {}x {}",
6503 if is_selected { "►" } else { " " },
6504 command_text,
6505 success_indicator,
6506 entry.execution_count,
6507 time_ago
6508 );
6509
6510 let mut style = Style::default();
6511 if is_selected {
6512 style = style.bg(Color::DarkGray).add_modifier(Modifier::BOLD);
6513 }
6514 if !entry.success {
6515 style = style.fg(Color::Red);
6516 }
6517
6518 if !history_match.indices.is_empty() && is_selected {
6520 style = style.fg(Color::Yellow);
6521 }
6522
6523 Line::from(line_text).style(style)
6524 })
6525 .collect();
6526
6527 let history_paragraph = Paragraph::new(history_items)
6528 .block(Block::default().borders(Borders::ALL).title(format!(
6529 "History ({match_count} matches) - j/k to navigate, Enter to select"
6530 )))
6531 .wrap(ratatui::widgets::Wrap { trim: false });
6532
6533 f.render_widget(history_paragraph, area);
6534 }
6535
6536 fn render_selected_command_preview(&self, f: &mut Frame, area: Rect) {
6537 let history_search = self.state_container.history_search();
6539 let selected_match = history_search
6540 .matches
6541 .get(history_search.selected_index)
6542 .cloned();
6543
6544 if let Some(selected_match) = selected_match {
6545 let entry = &selected_match.entry;
6546
6547 use crate::recursive_parser::format_sql_pretty_compact;
6549
6550 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);
6556
6557 let max_lines = area.height.saturating_sub(2) as usize; if pretty_lines.len() > max_lines && cols_per_line < 12 {
6560 pretty_lines = format_sql_pretty_compact(&entry.command, 15);
6562 }
6563
6564 let mut highlighted_lines = Vec::new();
6566 for line in pretty_lines {
6567 highlighted_lines.push(self.sql_highlighter.simple_sql_highlight(&line));
6568 }
6569
6570 let preview_text = Text::from(highlighted_lines);
6571
6572 let duration_text = entry
6573 .duration_ms
6574 .map_or_else(|| "?ms".to_string(), |d| format!("{d}ms"));
6575
6576 let success_text = if entry.success {
6577 "✓ Success"
6578 } else {
6579 "✗ Failed"
6580 };
6581
6582 let preview = Paragraph::new(preview_text)
6583 .block(Block::default().borders(Borders::ALL).title(format!(
6584 "Pretty SQL Preview: {} | {} | Used {}x",
6585 success_text, duration_text, entry.execution_count
6586 )))
6587 .scroll((0, 0)); f.render_widget(preview, area);
6590 } else {
6591 let empty_preview = Paragraph::new("No command selected")
6592 .block(Block::default().borders(Borders::ALL).title("Preview"))
6593 .style(Style::default().fg(Color::DarkGray));
6594 f.render_widget(empty_preview, area);
6595 }
6596 }
6597
6598 fn handle_column_stats_input(&mut self, key: crossterm::event::KeyEvent) -> Result<bool> {
6599 let mut ctx = crate::ui::input::input_handlers::StatsInputContext {
6601 buffer_manager: self.state_container.buffers_mut(),
6602 stats_widget: &mut self.stats_widget,
6603 shadow_state: &self.shadow_state,
6604 };
6605
6606 let result = crate::ui::input::input_handlers::handle_column_stats_input(&mut ctx, key)?;
6607
6608 if self.shadow_state.borrow().get_mode() == AppMode::Results {
6610 self.shadow_state
6611 .borrow_mut()
6612 .observe_mode_change(AppMode::Results, "column_stats_closed");
6613 }
6614
6615 Ok(result)
6616 }
6617
6618 fn handle_jump_to_row_input(&mut self, key: crossterm::event::KeyEvent) -> Result<bool> {
6619 match key.code {
6620 KeyCode::Enter => {
6621 let input = self.get_jump_to_row_input();
6623 self.complete_jump_to_row(&input);
6624 }
6625 _ => {
6626 self.process_jump_to_row_key(key);
6628 }
6629 }
6630 Ok(false)
6631 }
6632
6633 fn render_column_stats(&self, f: &mut Frame, area: Rect) {
6634 self.stats_widget.render(
6636 f,
6637 area,
6638 self.state_container
6639 .current_buffer()
6640 .expect("Buffer should exist"),
6641 );
6642 }
6643
6644 fn handle_execute_query(&mut self) -> Result<bool> {
6650 use crate::ui::state::state_coordinator::StateCoordinator;
6651
6652 let query = self.get_input_text().trim().to_string();
6653 debug!(target: "action", "Executing query: {}", query);
6654
6655 let should_exit = StateCoordinator::handle_execute_query_with_refs(
6657 &mut self.state_container,
6658 &self.shadow_state,
6659 &query,
6660 )?;
6661
6662 if should_exit {
6663 return Ok(true);
6664 }
6665
6666 if !query.is_empty() && !query.starts_with(':') {
6668 if let Err(e) = self.execute_query_v2(&query) {
6669 self.state_container
6670 .set_status_message(format!("Error executing query: {e}"));
6671 }
6672 }
6674
6675 Ok(false) }
6677
6678 fn handle_buffer_action(&mut self, action: BufferAction) -> Result<bool> {
6679 match action {
6680 BufferAction::NextBuffer => {
6681 let message = self
6682 .buffer_handler
6683 .next_buffer(self.state_container.buffers_mut());
6684 debug!("{}", message);
6685 self.sync_after_buffer_switch();
6687 Ok(false)
6688 }
6689 BufferAction::PreviousBuffer => {
6690 let message = self
6691 .buffer_handler
6692 .previous_buffer(self.state_container.buffers_mut());
6693 debug!("{}", message);
6694 self.sync_after_buffer_switch();
6696 Ok(false)
6697 }
6698 BufferAction::QuickSwitch => {
6699 let message = self
6700 .buffer_handler
6701 .quick_switch(self.state_container.buffers_mut());
6702 debug!("{}", message);
6703 self.sync_after_buffer_switch();
6705 Ok(false)
6706 }
6707 BufferAction::NewBuffer => {
6708 let message = self
6709 .buffer_handler
6710 .new_buffer(self.state_container.buffers_mut(), &self.config);
6711 debug!("{}", message);
6712 Ok(false)
6713 }
6714 BufferAction::CloseBuffer => {
6715 let (success, message) = self
6716 .buffer_handler
6717 .close_buffer(self.state_container.buffers_mut());
6718 debug!("{}", message);
6719 Ok(!success) }
6721 BufferAction::ListBuffers => {
6722 let buffer_list = self
6723 .buffer_handler
6724 .list_buffers(self.state_container.buffers());
6725 for line in &buffer_list {
6727 debug!("{}", line);
6728 }
6729 Ok(false)
6730 }
6731 BufferAction::SwitchToBuffer(buffer_index) => {
6732 let message = self
6733 .buffer_handler
6734 .switch_to_buffer(self.state_container.buffers_mut(), buffer_index);
6735 debug!("{}", message);
6736
6737 self.sync_after_buffer_switch();
6739
6740 Ok(false)
6741 }
6742 }
6743 }
6744
6745 fn handle_expand_asterisk(&mut self) -> Result<bool> {
6746 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
6747 if buffer.expand_asterisk(&self.hybrid_parser) {
6748 if buffer.get_edit_mode() == EditMode::SingleLine {
6750 let text = buffer.get_input_text();
6751 let cursor = buffer.get_input_cursor_position();
6752 self.set_input_text_with_cursor(text, cursor);
6753 }
6754 }
6755 }
6756 Ok(false)
6757 }
6758
6759 pub(crate) fn toggle_debug_mode(&mut self) {
6760 DebugContext::toggle_debug_mode(self);
6762 }
6763
6764 pub(crate) fn debug_generate_parser_info(&self, query: &str) -> String {
6769 self.hybrid_parser
6770 .get_detailed_debug_info(query, query.len())
6771 }
6772
6773 fn debug_generate_navigation_state(&self) -> String {
6774 let mut debug_info = String::new();
6775 debug_info.push_str("\n========== NAVIGATION DEBUG ==========\n");
6776 let current_column = self.state_container.get_current_column();
6777 let scroll_offset = self.state_container.get_scroll_offset();
6778 let nav_state = self.state_container.navigation();
6779
6780 debug_info.push_str(&format!("Buffer Column Position: {current_column}\n"));
6781 debug_info.push_str(&format!(
6782 "Buffer Scroll Offset: row={}, col={}\n",
6783 scroll_offset.0, scroll_offset.1
6784 ));
6785 debug_info.push_str(&format!(
6786 "NavigationState Column: {}\n",
6787 nav_state.selected_column
6788 ));
6789 debug_info.push_str(&format!(
6790 "NavigationState Row: {:?}\n",
6791 nav_state.selected_row
6792 ));
6793 debug_info.push_str(&format!(
6794 "NavigationState Scroll Offset: row={}, col={}\n",
6795 nav_state.scroll_offset.0, nav_state.scroll_offset.1
6796 ));
6797
6798 if current_column != nav_state.selected_column {
6800 debug_info.push_str(&format!(
6801 "⚠️ WARNING: Column mismatch! Buffer={}, Nav={}\n",
6802 current_column, nav_state.selected_column
6803 ));
6804 }
6805 if scroll_offset.1 != nav_state.scroll_offset.1 {
6806 debug_info.push_str(&format!(
6807 "⚠️ WARNING: Scroll column mismatch! Buffer={}, Nav={}\n",
6808 scroll_offset.1, nav_state.scroll_offset.1
6809 ));
6810 }
6811
6812 debug_info.push_str("\n--- Navigation Flow ---\n");
6813 debug_info.push_str(
6814 "(Enable RUST_LOG=sql_cli::ui::viewport_manager=debug,navigation=debug to see flow)\n",
6815 );
6816
6817 if let Some(dataview) = self.state_container.get_buffer_dataview() {
6819 let pinned_count = dataview.get_pinned_columns().len();
6820 let pinned_names = dataview.get_pinned_column_names();
6821 debug_info.push_str(&format!("Pinned Column Count: {pinned_count}\n"));
6822 if !pinned_names.is_empty() {
6823 debug_info.push_str(&format!("Pinned Column Names: {pinned_names:?}\n"));
6824 }
6825 debug_info.push_str(&format!("First Scrollable Column: {pinned_count}\n"));
6826
6827 if current_column < pinned_count {
6829 debug_info.push_str(&format!(
6830 "Current Position: PINNED area (column {current_column})\n"
6831 ));
6832 } else {
6833 debug_info.push_str(&format!(
6834 "Current Position: SCROLLABLE area (column {}, scrollable index {})\n",
6835 current_column,
6836 current_column - pinned_count
6837 ));
6838 }
6839
6840 let display_columns = dataview.get_display_columns();
6842 debug_info.push_str("\n--- COLUMN ORDERING ---\n");
6843 debug_info.push_str(&format!(
6844 "Display column order (first 10): {:?}\n",
6845 &display_columns[..display_columns.len().min(10)]
6846 ));
6847 if display_columns.len() > 10 {
6848 debug_info.push_str(&format!(
6849 "... and {} more columns\n",
6850 display_columns.len() - 10
6851 ));
6852 }
6853
6854 if let Some(display_idx) = display_columns
6856 .iter()
6857 .position(|&idx| idx == current_column)
6858 {
6859 debug_info.push_str(&format!(
6860 "Current column {} is at display index {}/{}\n",
6861 current_column,
6862 display_idx,
6863 display_columns.len()
6864 ));
6865
6866 if display_idx + 1 < display_columns.len() {
6868 let next_col = display_columns[display_idx + 1];
6869 debug_info.push_str(&format!(
6870 "Next 'l' press should move to column {} (display index {})\n",
6871 next_col,
6872 display_idx + 1
6873 ));
6874 } else {
6875 debug_info.push_str("Next 'l' press should wrap to first column\n");
6876 }
6877 } else {
6878 debug_info.push_str(&format!(
6879 "WARNING: Current column {current_column} not found in display order!\n"
6880 ));
6881 }
6882 }
6883 debug_info.push_str("==========================================\n");
6884 debug_info
6885 }
6886
6887 fn debug_generate_column_search_state(&self) -> String {
6888 let mut debug_info = String::new();
6889 let show_column_search = self.shadow_state.borrow().get_mode() == AppMode::ColumnSearch
6890 || !self.state_container.column_search().pattern.is_empty();
6891 if show_column_search {
6892 let column_search = self.state_container.column_search();
6893 debug_info.push_str("\n========== COLUMN SEARCH STATE ==========\n");
6894 debug_info.push_str(&format!("Pattern: '{}'\n", column_search.pattern));
6895 debug_info.push_str(&format!(
6896 "Matching Columns: {} found\n",
6897 column_search.matching_columns.len()
6898 ));
6899 if !column_search.matching_columns.is_empty() {
6900 debug_info.push_str("Matches:\n");
6901 for (idx, (col_idx, col_name)) in column_search.matching_columns.iter().enumerate()
6902 {
6903 let marker = if idx == column_search.current_match {
6904 " <--"
6905 } else {
6906 ""
6907 };
6908 debug_info
6909 .push_str(&format!(" [{idx}] {col_name} (index {col_idx}){marker}\n"));
6910 }
6911 }
6912 debug_info.push_str(&format!(
6913 "Current Match Index: {}\n",
6914 column_search.current_match
6915 ));
6916 debug_info.push_str(&format!(
6917 "Current Column: {}\n",
6918 self.state_container.get_current_column()
6919 ));
6920 debug_info.push_str("==========================================\n");
6921 }
6922 debug_info
6923 }
6924
6925 pub(crate) fn debug_generate_trace_logs(&self) -> String {
6926 let mut debug_info = String::from("\n========== TRACE LOGS ==========\n");
6927 debug_info.push_str("(Most recent at bottom, last 100 entries)\n");
6928
6929 if let Some(ref log_buffer) = self.log_buffer {
6930 let recent_logs = log_buffer.get_recent(100);
6931 for entry in recent_logs {
6932 debug_info.push_str(&entry.format_for_display());
6933 debug_info.push('\n');
6934 }
6935 debug_info.push_str(&format!("Total log entries: {}\n", log_buffer.len()));
6936 } else {
6937 debug_info.push_str("Log buffer not initialized\n");
6938 }
6939 debug_info.push_str("================================\n");
6940
6941 debug_info
6942 }
6943
6944 pub(crate) fn debug_generate_state_logs(&self) -> String {
6946 let mut debug_info = String::new();
6947
6948 if let Some(ref debug_service) = self.debug_service {
6949 debug_info.push_str("\n========== STATE CHANGE LOGS ==========\n");
6950 debug_info.push_str("(Most recent at bottom, from DebugService)\n");
6951 let debug_entries = debug_service.get_entries();
6952 let recent = debug_entries.iter().rev().take(50).rev();
6953 for entry in recent {
6954 debug_info.push_str(&format!(
6955 "[{}] {:?} [{}]: {}\n",
6956 entry.timestamp, entry.level, entry.component, entry.message
6957 ));
6958 }
6959 debug_info.push_str(&format!(
6960 "Total state change entries: {}\n",
6961 debug_entries.len()
6962 ));
6963 debug_info.push_str("================================\n");
6964 } else {
6965 debug_info.push_str("\n========== STATE CHANGE LOGS ==========\n");
6966 debug_info.push_str("DebugService not available (service_container is None)\n");
6967 debug_info.push_str("================================\n");
6968 }
6969
6970 debug_info
6971 }
6972
6973 pub(crate) fn debug_extract_timing(&self, s: &str) -> Option<f64> {
6975 crate::ui::rendering::ui_layout_utils::extract_timing_from_debug_string(s)
6976 }
6977
6978 fn show_pretty_query(&mut self) {
6979 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
6980 self.shadow_state.borrow_mut().set_mode(
6981 AppMode::PrettyQuery,
6982 buffer,
6983 "pretty_query_show",
6984 );
6985 let query = buffer.get_input_text();
6986 self.debug_widget.generate_pretty_sql(&query);
6987 }
6988 }
6989
6990 fn add_global_indicators(&self, spans: &mut Vec<Span>) {
6992 if self.key_sequence_renderer.has_content() {
6993 let key_display = self.key_sequence_renderer.get_display();
6994 if !key_display.is_empty() {
6995 spans.push(Span::raw(" | Keys: "));
6996 spans.push(Span::styled(
6997 key_display,
6998 Style::default()
6999 .fg(Color::Cyan)
7000 .add_modifier(Modifier::ITALIC),
7001 ));
7002 }
7003 }
7004 }
7005
7006 fn add_query_source_indicator(&self, spans: &mut Vec<Span>) {
7007 if let Some(source) = self.state_container.get_last_query_source() {
7008 spans.push(Span::raw(" | "));
7009 let (icon, label, color) = match source.as_str() {
7010 "cache" => (
7011 &self.config.display.icons.cache,
7012 "CACHE".to_string(),
7013 Color::Cyan,
7014 ),
7015 "file" | "FileDataSource" => (
7016 &self.config.display.icons.file,
7017 "FILE".to_string(),
7018 Color::Green,
7019 ),
7020 "SqlServerDataSource" => (
7021 &self.config.display.icons.database,
7022 "SQL".to_string(),
7023 Color::Blue,
7024 ),
7025 "PublicApiDataSource" => (
7026 &self.config.display.icons.api,
7027 "API".to_string(),
7028 Color::Yellow,
7029 ),
7030 _ => (
7031 &self.config.display.icons.api,
7032 source.clone(),
7033 Color::Magenta,
7034 ),
7035 };
7036 spans.push(Span::raw(format!("{icon} ")));
7037 spans.push(Span::styled(label, Style::default().fg(color)));
7038 }
7039 }
7040
7041 fn add_case_sensitivity_indicator(&self, spans: &mut Vec<Span>) {
7042 let case_insensitive = self.state_container.is_case_insensitive();
7043 if case_insensitive {
7044 spans.push(Span::raw(" | "));
7045 let icon = self.config.display.icons.case_insensitive.clone();
7046 spans.push(Span::styled(
7047 format!("{icon} CASE"),
7048 Style::default().fg(Color::Cyan),
7049 ));
7050 }
7051 }
7052
7053 fn add_column_packing_indicator(&self, spans: &mut Vec<Span>) {
7054 if let Some(ref viewport_manager) = *self.viewport_manager.borrow() {
7055 let packing_mode = viewport_manager.get_packing_mode();
7056 spans.push(Span::raw(" | "));
7057 let (text, color) = match packing_mode {
7058 ColumnPackingMode::DataFocus => ("DATA", Color::Cyan),
7059 ColumnPackingMode::HeaderFocus => ("HEADER", Color::Yellow),
7060 ColumnPackingMode::Balanced => ("BALANCED", Color::Green),
7061 };
7062 spans.push(Span::styled(text, Style::default().fg(color)));
7063 }
7064 }
7065
7066 fn add_status_message(&self, spans: &mut Vec<Span>) {
7067 let status_msg = self.state_container.get_buffer_status_message();
7068 if !status_msg.is_empty() {
7069 spans.push(Span::raw(" | "));
7070 spans.push(Span::styled(
7071 status_msg,
7072 Style::default()
7073 .fg(Color::Yellow)
7074 .add_modifier(Modifier::BOLD),
7075 ));
7076 }
7077 }
7078
7079 fn get_help_text_for_mode(&self) -> &str {
7080 match self.shadow_state.borrow().get_mode() {
7081 AppMode::Command => "Enter:Run | Tab:Complete | ↓:Results | F1:Help",
7082 AppMode::Results => match self.get_selection_mode() {
7083 SelectionMode::Cell => "v:Row mode | y:Yank cell | ↑:Edit | F1:Help",
7084 SelectionMode::Row => "v:Cell mode | y:Yank | f:Filter | ↑:Edit | F1:Help",
7085 SelectionMode::Column => "v:Cell mode | y:Yank col | ↑:Edit | F1:Help",
7086 },
7087 AppMode::Search | AppMode::Filter | AppMode::FuzzyFilter | AppMode::ColumnSearch => {
7088 "Enter:Apply | Esc:Cancel"
7089 }
7090 AppMode::Help | AppMode::Debug | AppMode::PrettyQuery | AppMode::ColumnStats => {
7091 "Esc:Close"
7092 }
7093 AppMode::History => "Enter:Select | Esc:Cancel",
7094 AppMode::JumpToRow => "Enter:Jump | Esc:Cancel",
7095 }
7096 }
7097
7098 fn add_shadow_state_display(&self, spans: &mut Vec<Span>) {
7099 let shadow_display = self.shadow_state.borrow().status_display();
7100 spans.push(Span::raw(" "));
7101 spans.push(Span::styled(
7102 shadow_display,
7103 Style::default().fg(Color::Cyan),
7104 ));
7105 }
7106
7107 fn add_help_text_display<'a>(&self, spans: &mut Vec<Span<'a>>, help_text: &'a str, area: Rect) {
7109 let current_length: usize = spans.iter().map(|s| s.content.len()).sum();
7110 let available_width = area.width.saturating_sub(TABLE_BORDER_WIDTH) as usize;
7111 let help_length = help_text.len();
7112
7113 if current_length + help_length + 3 < available_width {
7114 let padding = available_width - current_length - help_length - 3;
7115 spans.push(Span::raw(" ".repeat(padding)));
7116 spans.push(Span::raw(" | "));
7117 spans.push(Span::styled(
7118 help_text,
7119 Style::default().fg(Color::DarkGray),
7120 ));
7121 }
7122 }
7123}
7124
7125impl ActionHandlerContext for EnhancedTuiApp {
7127 fn previous_row(&mut self) {
7129 <Self as NavigationBehavior>::previous_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", "previous_row: Updated TableWidgetManager to ({}, {})", current_row, current_col);
7138 }
7139
7140 fn next_row(&mut self) {
7141 info!(target: "navigation", "next_row called - calling NavigationBehavior::next_row");
7142 <Self as NavigationBehavior>::next_row(self);
7143
7144 let current_row = self.state_container.navigation().selected_row;
7146 let current_col = self.state_container.navigation().selected_column;
7147 self.table_widget_manager
7148 .borrow_mut()
7149 .navigate_to(current_row, current_col);
7150 info!(target: "navigation", "next_row: Updated TableWidgetManager to ({}, {})", current_row, current_col);
7151 }
7152
7153 fn move_column_left(&mut self) {
7154 <Self as ColumnBehavior>::move_column_left(self);
7155
7156 let current_row = self.state_container.navigation().selected_row;
7158 let current_col = self.state_container.navigation().selected_column;
7159 self.table_widget_manager
7160 .borrow_mut()
7161 .navigate_to(current_row, current_col);
7162 info!(target: "navigation", "move_column_left: Updated TableWidgetManager to ({}, {})", current_row, current_col);
7163 }
7164
7165 fn move_column_right(&mut self) {
7166 <Self as ColumnBehavior>::move_column_right(self);
7167
7168 let current_row = self.state_container.navigation().selected_row;
7170 let current_col = self.state_container.navigation().selected_column;
7171 self.table_widget_manager
7172 .borrow_mut()
7173 .navigate_to(current_row, current_col);
7174 info!(target: "navigation", "move_column_right: Updated TableWidgetManager to ({}, {})", current_row, current_col);
7175 }
7176
7177 fn page_up(&mut self) {
7178 <Self as NavigationBehavior>::page_up(self);
7179 }
7180
7181 fn page_down(&mut self) {
7182 <Self as NavigationBehavior>::page_down(self);
7183 }
7184
7185 fn goto_first_row(&mut self) {
7186 use crate::ui::state::state_coordinator::StateCoordinator;
7187
7188 <Self as NavigationBehavior>::goto_first_row(self);
7190
7191 StateCoordinator::goto_first_row_with_refs(
7193 &mut self.state_container,
7194 Some(&self.vim_search_adapter),
7195 Some(&self.viewport_manager),
7196 );
7197 }
7198
7199 fn goto_last_row(&mut self) {
7200 use crate::ui::state::state_coordinator::StateCoordinator;
7201
7202 <Self as NavigationBehavior>::goto_last_row(self);
7204
7205 StateCoordinator::goto_last_row_with_refs(&mut self.state_container);
7207 }
7208
7209 fn goto_first_column(&mut self) {
7210 <Self as ColumnBehavior>::goto_first_column(self);
7211 }
7212
7213 fn goto_last_column(&mut self) {
7214 <Self as ColumnBehavior>::goto_last_column(self);
7215 }
7216
7217 fn goto_row(&mut self, row: usize) {
7218 use crate::ui::state::state_coordinator::StateCoordinator;
7219
7220 <Self as NavigationBehavior>::goto_line(self, row + 1); StateCoordinator::goto_row_with_refs(&mut self.state_container, row);
7225 }
7226
7227 fn goto_column(&mut self, col: usize) {
7228 let current_col = self.state_container.get_current_column();
7231 if col < current_col {
7232 for _ in 0..(current_col - col) {
7233 <Self as ColumnBehavior>::move_column_left(self);
7234 }
7235 } else if col > current_col {
7236 for _ in 0..(col - current_col) {
7237 <Self as ColumnBehavior>::move_column_right(self);
7238 }
7239 }
7240 }
7241
7242 fn set_mode(&mut self, mode: AppMode) {
7244 self.set_mode_via_shadow_state(mode, "action_handler");
7246 }
7247
7248 fn get_mode(&self) -> AppMode {
7249 self.shadow_state.borrow().get_mode()
7250 }
7251
7252 fn set_status_message(&mut self, message: String) {
7253 self.state_container.set_status_message(message);
7254 }
7255
7256 fn toggle_column_pin(&mut self) {
7258 self.toggle_column_pin_impl();
7260 }
7261
7262 fn hide_current_column(&mut self) {
7263 <Self as ColumnBehavior>::hide_current_column(self);
7264 }
7265
7266 fn unhide_all_columns(&mut self) {
7267 <Self as ColumnBehavior>::unhide_all_columns(self);
7268 }
7269
7270 fn clear_all_pinned_columns(&mut self) {
7271 self.clear_all_pinned_columns_impl();
7273 }
7274
7275 fn export_to_csv(&mut self) {
7277 self.state_container
7279 .set_status_message("CSV export not yet implemented".to_string());
7280 }
7281
7282 fn export_to_json(&mut self) {
7283 self.state_container
7285 .set_status_message("JSON export not yet implemented".to_string());
7286 }
7287
7288 fn yank_cell(&mut self) {
7290 YankBehavior::yank_cell(self);
7291 }
7292
7293 fn yank_row(&mut self) {
7294 YankBehavior::yank_row(self);
7295 }
7296
7297 fn yank_column(&mut self) {
7298 YankBehavior::yank_column(self);
7299 }
7300
7301 fn yank_all(&mut self) {
7302 YankBehavior::yank_all(self);
7303 }
7304
7305 fn yank_query(&mut self) {
7306 YankBehavior::yank_query(self);
7307 }
7308
7309 fn toggle_selection_mode(&mut self) {
7311 self.state_container.toggle_selection_mode();
7312 let new_mode = self.state_container.get_selection_mode();
7313 let msg = match new_mode {
7314 SelectionMode::Cell => "Cell mode - Navigate to select individual cells",
7315 SelectionMode::Row => "Row mode - Navigate to select rows",
7316 SelectionMode::Column => "Column mode - Navigate to select columns",
7317 };
7318 self.state_container.set_status_message(msg.to_string());
7319 }
7320
7321 fn toggle_row_numbers(&mut self) {
7322 let current = self.state_container.is_show_row_numbers();
7323 self.state_container.set_show_row_numbers(!current);
7324 let message = if current {
7325 "Row numbers: OFF".to_string()
7326 } else {
7327 "Row numbers: ON (showing line numbers)".to_string()
7328 };
7329 self.state_container.set_status_message(message);
7330 self.calculate_optimal_column_widths();
7332 }
7333
7334 fn toggle_compact_mode(&mut self) {
7335 let current_mode = self.state_container.is_compact_mode();
7336 self.state_container.set_compact_mode(!current_mode);
7337 let message = if current_mode {
7338 "Compact mode disabled"
7339 } else {
7340 "Compact mode enabled"
7341 };
7342 self.state_container.set_status_message(message.to_string());
7343 }
7344
7345 fn toggle_case_insensitive(&mut self) {
7346 let current = self.state_container.is_case_insensitive();
7347 self.state_container.set_case_insensitive(!current);
7348 self.state_container.set_status_message(format!(
7349 "Case-insensitive string comparisons: {}",
7350 if current { "OFF" } else { "ON" }
7351 ));
7352 }
7353
7354 fn toggle_key_indicator(&mut self) {
7355 let enabled = !self.key_indicator.enabled;
7356 self.key_indicator.set_enabled(enabled);
7357 self.key_sequence_renderer.set_enabled(enabled);
7358 self.state_container.set_status_message(format!(
7359 "Key press indicator {}",
7360 if enabled { "enabled" } else { "disabled" }
7361 ));
7362 }
7363
7364 fn clear_filter(&mut self) {
7366 if let Some(dataview) = self.state_container.get_buffer_dataview() {
7368 if dataview.has_filter() {
7369 if let Some(dataview_mut) = self.state_container.get_buffer_dataview_mut() {
7371 dataview_mut.clear_filter();
7372 self.state_container
7373 .set_status_message("Filter cleared".to_string());
7374 }
7375
7376 self.sync_dataview_to_managers();
7379 } else {
7380 self.state_container
7381 .set_status_message("No active filter to clear".to_string());
7382 }
7383 } else {
7384 self.state_container
7385 .set_status_message("No data loaded".to_string());
7386 }
7387 }
7388
7389 fn clear_line(&mut self) {
7390 self.state_container.clear_line();
7391 }
7392
7393 fn start_search(&mut self) {
7395 self.start_vim_search();
7396 }
7397
7398 fn start_column_search(&mut self) {
7399 self.enter_search_mode(SearchMode::ColumnSearch);
7400 }
7401
7402 fn start_filter(&mut self) {
7403 self.enter_search_mode(SearchMode::Filter);
7404 }
7405
7406 fn start_fuzzy_filter(&mut self) {
7407 self.enter_search_mode(SearchMode::FuzzyFilter);
7408 }
7409
7410 fn exit_current_mode(&mut self) {
7411 let mode = self.shadow_state.borrow().get_mode();
7419 match mode {
7420 AppMode::Results => {
7421 self.set_mode_via_shadow_state(AppMode::Command, "escape_from_results");
7424 }
7425 AppMode::Command => {
7426 self.set_mode_via_shadow_state(AppMode::Results, "escape_from_command");
7427 }
7428 AppMode::Help => {
7429 self.set_mode_via_shadow_state(AppMode::Results, "escape_from_help");
7430 }
7431 AppMode::JumpToRow => {
7432 self.set_mode_via_shadow_state(AppMode::Results, "escape_from_jump_to_row");
7433 <Self as InputBehavior>::clear_jump_to_row_input(self);
7434 self.state_container.jump_to_row_mut().is_active = false;
7436 self.state_container
7437 .set_status_message("Jump to row cancelled".to_string());
7438 }
7439 _ => {
7440 self.set_mode_via_shadow_state(AppMode::Results, "escape_to_results");
7442 }
7443 }
7444 }
7445
7446 fn toggle_debug_mode(&mut self) {
7447 <Self as DebugContext>::toggle_debug_mode(self);
7449 }
7450
7451 fn move_current_column_left(&mut self) {
7453 <Self as ColumnBehavior>::move_current_column_left(self);
7454 }
7455
7456 fn move_current_column_right(&mut self) {
7457 <Self as ColumnBehavior>::move_current_column_right(self);
7458 }
7459
7460 fn next_search_match(&mut self) {
7462 self.vim_search_next();
7463 }
7464
7465 fn previous_search_match(&mut self) {
7466 if self.vim_search_adapter.borrow().is_active()
7467 || self.vim_search_adapter.borrow().get_pattern().is_some()
7468 {
7469 self.vim_search_previous();
7470 }
7471 }
7472
7473 fn show_column_statistics(&mut self) {
7475 self.calculate_column_statistics();
7476 }
7477
7478 fn cycle_column_packing(&mut self) {
7479 let message = {
7480 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
7481 let viewport_manager = viewport_manager_borrow
7482 .as_mut()
7483 .expect("ViewportManager must exist");
7484 let new_mode = viewport_manager.cycle_packing_mode();
7485 format!("Column packing: {}", new_mode.display_name())
7486 };
7487 self.state_container.set_status_message(message);
7488 }
7489
7490 fn navigate_to_viewport_top(&mut self) {
7492 let result = {
7493 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
7494 (*viewport_manager_borrow)
7495 .as_mut()
7496 .map(super::viewport_manager::ViewportManager::navigate_to_viewport_top)
7497 };
7498
7499 if let Some(result) = result {
7500 self.sync_navigation_with_viewport();
7502
7503 self.state_container
7505 .set_selected_row(Some(result.row_position));
7506
7507 if result.viewport_changed {
7509 let scroll_offset = self.state_container.navigation().scroll_offset;
7510 self.state_container.set_scroll_offset(scroll_offset);
7511 }
7512 }
7513 }
7514
7515 fn navigate_to_viewport_middle(&mut self) {
7516 let result = {
7517 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
7518 (*viewport_manager_borrow)
7519 .as_mut()
7520 .map(super::viewport_manager::ViewportManager::navigate_to_viewport_middle)
7521 };
7522
7523 if let Some(result) = result {
7524 self.sync_navigation_with_viewport();
7526
7527 self.state_container
7529 .set_selected_row(Some(result.row_position));
7530
7531 if result.viewport_changed {
7533 let scroll_offset = self.state_container.navigation().scroll_offset;
7534 self.state_container.set_scroll_offset(scroll_offset);
7535 }
7536 }
7537 }
7538
7539 fn navigate_to_viewport_bottom(&mut self) {
7540 let result = {
7541 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
7542 (*viewport_manager_borrow)
7543 .as_mut()
7544 .map(super::viewport_manager::ViewportManager::navigate_to_viewport_bottom)
7545 };
7546
7547 if let Some(result) = result {
7548 self.sync_navigation_with_viewport();
7550
7551 self.state_container
7553 .set_selected_row(Some(result.row_position));
7554
7555 if result.viewport_changed {
7557 let scroll_offset = self.state_container.navigation().scroll_offset;
7558 self.state_container.set_scroll_offset(scroll_offset);
7559 }
7560 }
7561 }
7562
7563 fn move_input_cursor_left(&mut self) {
7565 self.state_container.move_input_cursor_left();
7566 }
7567
7568 fn move_input_cursor_right(&mut self) {
7569 self.state_container.move_input_cursor_right();
7570 }
7571
7572 fn move_input_cursor_home(&mut self) {
7573 self.state_container.set_input_cursor_position(0);
7574 }
7575
7576 fn move_input_cursor_end(&mut self) {
7577 let text_len = self.state_container.get_input_text().chars().count();
7578 self.state_container.set_input_cursor_position(text_len);
7579 }
7580
7581 fn backspace(&mut self) {
7582 self.state_container.backspace();
7583 }
7584
7585 fn delete(&mut self) {
7586 self.state_container.delete();
7587 }
7588
7589 fn undo(&mut self) {
7590 self.state_container.perform_undo();
7591 }
7592
7593 fn redo(&mut self) {
7594 self.state_container.perform_redo();
7595 }
7596
7597 fn start_jump_to_row(&mut self) {
7598 self.state_container.set_mode(AppMode::JumpToRow);
7599 self.shadow_state
7600 .borrow_mut()
7601 .observe_mode_change(AppMode::JumpToRow, "jump_to_row_requested");
7602 <Self as InputBehavior>::clear_jump_to_row_input(self);
7603
7604 self.state_container.jump_to_row_mut().is_active = true;
7606
7607 self.state_container
7608 .set_status_message("Enter row number (1-based):".to_string());
7609 }
7610
7611 fn clear_jump_to_row_input(&mut self) {
7612 <Self as InputBehavior>::clear_jump_to_row_input(self);
7613 }
7614
7615 fn toggle_cursor_lock(&mut self) {
7616 let is_locked = {
7618 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
7619 if let Some(ref mut viewport_manager) = *viewport_manager_borrow {
7620 viewport_manager.toggle_cursor_lock();
7621 Some(viewport_manager.is_cursor_locked())
7622 } else {
7623 None
7624 }
7625 };
7626
7627 if let Some(is_locked) = is_locked {
7628 let msg = if is_locked {
7629 "Cursor lock ON - cursor stays in viewport position while scrolling"
7630 } else {
7631 "Cursor lock OFF"
7632 };
7633 self.state_container.set_status_message(msg.to_string());
7634
7635 info!(target: "shadow_state",
7637 "Cursor lock toggled: {} (in {:?} mode)",
7638 if is_locked { "ON" } else { "OFF" },
7639 self.shadow_state.borrow().get_mode()
7640 );
7641 }
7642 }
7643
7644 fn toggle_viewport_lock(&mut self) {
7645 let is_locked = {
7647 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
7648 if let Some(ref mut viewport_manager) = *viewport_manager_borrow {
7649 viewport_manager.toggle_viewport_lock();
7650 Some(viewport_manager.is_viewport_locked())
7651 } else {
7652 None
7653 }
7654 };
7655
7656 if let Some(is_locked) = is_locked {
7657 let msg = if is_locked {
7658 "Viewport lock ON - navigation constrained to current viewport"
7659 } else {
7660 "Viewport lock OFF"
7661 };
7662 self.state_container.set_status_message(msg.to_string());
7663
7664 info!(target: "shadow_state",
7666 "Viewport lock toggled: {} (in {:?} mode)",
7667 if is_locked { "ON" } else { "OFF" },
7668 self.shadow_state.borrow().get_mode()
7669 );
7670 }
7671 }
7672
7673 fn show_debug_info(&mut self) {
7675 <Self as DebugContext>::toggle_debug_mode(self);
7676 }
7677
7678 fn show_pretty_query(&mut self) {
7679 self.show_pretty_query();
7680 }
7681
7682 fn show_help(&mut self) {
7683 self.state_container.set_help_visible(true);
7684 self.set_mode_via_shadow_state(AppMode::Help, "help_requested");
7685 self.help_widget.on_enter();
7686 }
7687
7688 fn kill_line(&mut self) {
7690 use crate::ui::traits::input_ops::InputBehavior;
7691 InputBehavior::kill_line(self);
7692 let message = if self.state_container.is_kill_ring_empty() {
7693 "Kill line - nothing to kill".to_string()
7694 } else {
7695 let kill_ring = self.state_container.get_kill_ring();
7696 format!(
7697 "Killed to end of line - {} chars in kill ring",
7698 kill_ring.len()
7699 )
7700 };
7701 self.state_container.set_status_message(message);
7702 }
7703
7704 fn kill_line_backward(&mut self) {
7705 use crate::ui::traits::input_ops::InputBehavior;
7706 InputBehavior::kill_line_backward(self);
7707 let message = if self.state_container.is_kill_ring_empty() {
7708 "Kill line backward - nothing to kill".to_string()
7709 } else {
7710 let kill_ring = self.state_container.get_kill_ring();
7711 format!(
7712 "Killed to beginning of line - {} chars in kill ring",
7713 kill_ring.len()
7714 )
7715 };
7716 self.state_container.set_status_message(message);
7717 }
7718
7719 fn delete_word_backward(&mut self) {
7720 use crate::ui::traits::input_ops::InputBehavior;
7721 InputBehavior::delete_word_backward(self);
7722 }
7723
7724 fn delete_word_forward(&mut self) {
7725 use crate::ui::traits::input_ops::InputBehavior;
7726 InputBehavior::delete_word_forward(self);
7727 }
7728
7729 fn jump_to_prev_token(&mut self) {
7730 use crate::ui::traits::input_ops::InputBehavior;
7731 InputBehavior::jump_to_prev_token(self);
7732 }
7733
7734 fn jump_to_next_token(&mut self) {
7735 use crate::ui::traits::input_ops::InputBehavior;
7736 InputBehavior::jump_to_next_token(self);
7737 }
7738
7739 fn expand_asterisk(&mut self) {
7740 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
7741 if buffer.expand_asterisk(&self.hybrid_parser) {
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 expand_asterisk_visible(&mut self) {
7753 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
7754 if buffer.expand_asterisk_visible() {
7755 if buffer.get_edit_mode() == EditMode::SingleLine {
7757 let text = buffer.get_input_text();
7758 let cursor = buffer.get_input_cursor_position();
7759 self.set_input_text_with_cursor(text, cursor);
7760 }
7761 }
7762 }
7763 }
7764
7765 fn previous_history_command(&mut self) {
7766 let history_entries = self
7767 .state_container
7768 .command_history()
7769 .get_navigation_entries();
7770 let history_commands: Vec<String> =
7771 history_entries.iter().map(|e| e.command.clone()).collect();
7772
7773 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
7774 if buffer.navigate_history_up(&history_commands) {
7775 self.sync_all_input_states();
7776 self.state_container
7777 .set_status_message("Previous command from history".to_string());
7778 }
7779 }
7780 }
7781
7782 fn next_history_command(&mut self) {
7783 let history_entries = self
7784 .state_container
7785 .command_history()
7786 .get_navigation_entries();
7787 let history_commands: Vec<String> =
7788 history_entries.iter().map(|e| e.command.clone()).collect();
7789
7790 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
7791 if buffer.navigate_history_down(&history_commands) {
7792 self.sync_all_input_states();
7793 self.state_container
7794 .set_status_message("Next command from history".to_string());
7795 }
7796 }
7797 }
7798}
7799
7800impl NavigationBehavior for EnhancedTuiApp {
7802 fn viewport_manager(&self) -> &RefCell<Option<ViewportManager>> {
7803 &self.viewport_manager
7804 }
7805
7806 fn buffer_mut(&mut self) -> &mut dyn BufferAPI {
7807 self.state_container
7808 .current_buffer_mut()
7809 .expect("Buffer should exist")
7810 }
7811
7812 fn buffer(&self) -> &dyn BufferAPI {
7813 self.state_container
7814 .current_buffer()
7815 .expect("Buffer should exist")
7816 }
7817
7818 fn state_container(&self) -> &AppStateContainer {
7819 &self.state_container
7820 }
7821
7822 fn state_container_mut(&mut self) -> &mut AppStateContainer {
7823 &mut self.state_container
7824 }
7825
7826 fn get_row_count(&self) -> usize {
7827 self.get_row_count()
7828 }
7829
7830 fn set_mode_with_sync(&mut self, mode: AppMode, trigger: &str) {
7831 self.set_mode_via_shadow_state(mode, trigger);
7833 }
7834}
7835
7836impl ColumnBehavior for EnhancedTuiApp {
7838 fn viewport_manager(&self) -> &RefCell<Option<ViewportManager>> {
7839 &self.viewport_manager
7840 }
7841
7842 fn buffer_mut(&mut self) -> &mut dyn BufferAPI {
7843 self.state_container
7844 .current_buffer_mut()
7845 .expect("Buffer should exist")
7846 }
7847
7848 fn buffer(&self) -> &dyn BufferAPI {
7849 self.state_container
7850 .current_buffer()
7851 .expect("Buffer should exist")
7852 }
7853
7854 fn state_container(&self) -> &AppStateContainer {
7855 &self.state_container
7856 }
7857
7858 fn is_in_results_mode(&self) -> bool {
7859 self.shadow_state.borrow().is_in_results_mode()
7860 }
7861}
7862
7863impl InputBehavior for EnhancedTuiApp {
7864 fn buffer_manager(&mut self) -> &mut BufferManager {
7865 self.state_container.buffers_mut()
7866 }
7867
7868 fn cursor_manager(&mut self) -> &mut CursorManager {
7869 &mut self.cursor_manager
7870 }
7871
7872 fn set_input_text_with_cursor(&mut self, text: String, cursor: usize) {
7873 self.set_input_text_with_cursor(text, cursor);
7874 }
7875
7876 fn state_container(&self) -> &AppStateContainer {
7877 &self.state_container
7878 }
7879
7880 fn state_container_mut(&mut self) -> &mut AppStateContainer {
7881 &mut self.state_container
7882 }
7883
7884 fn buffer_mut(&mut self) -> &mut dyn BufferAPI {
7885 self.state_container
7886 .current_buffer_mut()
7887 .expect("Buffer should exist")
7888 }
7889
7890 fn set_mode_with_sync(&mut self, mode: AppMode, trigger: &str) {
7891 self.set_mode_via_shadow_state(mode, trigger);
7893 }
7894}
7895
7896impl YankBehavior for EnhancedTuiApp {
7897 fn buffer(&self) -> &dyn BufferAPI {
7898 self.state_container
7899 .current_buffer()
7900 .expect("Buffer should exist")
7901 }
7902
7903 fn buffer_mut(&mut self) -> &mut dyn BufferAPI {
7904 self.state_container
7905 .current_buffer_mut()
7906 .expect("Buffer should exist")
7907 }
7908
7909 fn state_container(&self) -> &AppStateContainer {
7910 &self.state_container
7911 }
7912
7913 fn set_status_message(&mut self, message: String) {
7914 self.state_container.set_status_message(message);
7915 }
7916
7917 fn set_error_status(&mut self, prefix: &str, error: anyhow::Error) {
7918 self.set_error_status(prefix, error);
7919 }
7920}
7921
7922impl BufferManagementBehavior for EnhancedTuiApp {
7923 fn buffer_manager(&mut self) -> &mut BufferManager {
7924 self.state_container.buffers_mut()
7925 }
7926
7927 fn buffer_handler(&mut self) -> &mut BufferHandler {
7928 &mut self.buffer_handler
7929 }
7930
7931 fn buffer(&self) -> &dyn BufferAPI {
7932 self.state_container
7933 .current_buffer()
7934 .expect("Buffer should exist")
7935 }
7936
7937 fn buffer_mut(&mut self) -> &mut dyn BufferAPI {
7938 self.state_container
7939 .current_buffer_mut()
7940 .expect("Buffer should exist")
7941 }
7942
7943 fn config(&self) -> &Config {
7944 &self.config
7945 }
7946
7947 fn cursor_manager(&mut self) -> &mut CursorManager {
7948 &mut self.cursor_manager
7949 }
7950
7951 fn set_input_text_with_cursor(&mut self, text: String, cursor: usize) {
7952 self.set_input_text_with_cursor(text, cursor);
7953 }
7954
7955 fn next_buffer(&mut self) -> String {
7956 self.save_viewport_to_current_buffer();
7958
7959 let result = self
7960 .buffer_handler
7961 .next_buffer(self.state_container.buffers_mut());
7962
7963 self.sync_after_buffer_switch();
7965
7966 result
7967 }
7968
7969 fn previous_buffer(&mut self) -> String {
7970 self.save_viewport_to_current_buffer();
7972
7973 let result = self
7974 .buffer_handler
7975 .previous_buffer(self.state_container.buffers_mut());
7976
7977 self.sync_after_buffer_switch();
7979
7980 result
7981 }
7982
7983 fn quick_switch_buffer(&mut self) -> String {
7984 self.save_viewport_to_current_buffer();
7986
7987 let result = self
7988 .buffer_handler
7989 .quick_switch(self.state_container.buffers_mut());
7990
7991 self.sync_after_buffer_switch();
7993
7994 result
7995 }
7996
7997 fn close_buffer(&mut self) -> (bool, String) {
7998 self.buffer_handler
7999 .close_buffer(self.state_container.buffers_mut())
8000 }
8001
8002 fn switch_to_buffer(&mut self, index: usize) -> String {
8003 self.save_viewport_to_current_buffer();
8005
8006 let result = self
8008 .buffer_handler
8009 .switch_to_buffer(self.state_container.buffers_mut(), index);
8010
8011 self.sync_after_buffer_switch();
8013
8014 result
8015 }
8016
8017 fn buffer_count(&self) -> usize {
8018 self.state_container.buffers().all_buffers().len()
8019 }
8020
8021 fn current_buffer_index(&self) -> usize {
8022 self.state_container.buffers().current_index()
8023 }
8024}
8025
8026pub fn run_enhanced_tui_multi(api_url: &str, data_files: Vec<&str>) -> Result<()> {
8027 let original_hook = std::panic::take_hook();
8029 std::panic::set_hook(Box::new(move |panic_info| {
8030 let _ = disable_raw_mode();
8032 let _ = execute!(
8033 io::stdout(),
8034 LeaveAlternateScreen,
8035 DisableMouseCapture,
8036 crossterm::cursor::Show
8037 );
8038
8039 original_hook(panic_info);
8041 }));
8042
8043 let app = if data_files.is_empty() {
8044 EnhancedTuiApp::new(api_url)
8045 } else {
8046 use crate::services::ApplicationOrchestrator;
8048
8049 let config = Config::default();
8051 let orchestrator = ApplicationOrchestrator::new(
8052 config.behavior.case_insensitive_default,
8053 config.behavior.hide_empty_columns,
8054 );
8055
8056 let mut app = orchestrator.create_tui_with_file(data_files[0])?;
8058
8059 for file_path in data_files.iter().skip(1) {
8061 if let Err(e) = orchestrator.load_additional_file(&mut app, file_path) {
8062 app.state_container
8063 .set_status_message(format!("Error loading {file_path}: {e}"));
8064 continue;
8065 }
8066 }
8067
8068 if data_files.len() > 1 {
8070 app.state_container.buffers_mut().switch_to(0);
8071 app.sync_after_buffer_switch();
8073 app.state_container.set_status_message(format!(
8074 "Loaded {} files into separate buffers. Use Alt+Tab to switch.",
8075 data_files.len()
8076 ));
8077 } else if data_files.len() == 1 {
8078 app.update_parser_for_current_buffer();
8080 }
8081
8082 app
8083 };
8084
8085 let result = app.run();
8086
8087 let _ = std::panic::take_hook(); result
8092}
8093
8094pub fn run_enhanced_tui(api_url: &str, data_file: Option<&str>) -> Result<()> {
8095 let files = if let Some(file) = data_file {
8097 vec![file]
8098 } else {
8099 vec![]
8100 };
8101 run_enhanced_tui_multi(api_url, files)
8102}