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 = StateCoordinator::apply_text_filter_with_refs(
4915 &mut self.state_container,
4916 &self.viewport_manager,
4917 pattern,
4918 );
4919
4920 self.sync_dataview_to_managers();
4923
4924 FILTER_DEPTH.fetch_sub(1, Ordering::SeqCst);
4926 }
4927 fn search_columns(&mut self) {
4928 static SEARCH_DEPTH: std::sync::atomic::AtomicUsize =
4930 std::sync::atomic::AtomicUsize::new(0);
4931 let depth = SEARCH_DEPTH.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
4932
4933 if depth > 10 {
4935 error!(target: "search", "Column search depth exceeded limit, aborting to prevent infinite loop");
4936 SEARCH_DEPTH.store(0, std::sync::atomic::Ordering::SeqCst);
4937 return;
4938 }
4939
4940 struct DepthGuard;
4942 impl Drop for DepthGuard {
4943 fn drop(&mut self) {
4944 SEARCH_DEPTH.fetch_sub(1, std::sync::atomic::Ordering::SeqCst);
4945 }
4946 }
4947 let _guard = DepthGuard;
4948
4949 let pattern = self.state_container.column_search().pattern.clone();
4950 debug!(target: "search", "search_columns called with pattern: '{}', depth: {}", pattern, depth);
4951
4952 if pattern.is_empty() {
4953 debug!(target: "search", "Pattern is empty, skipping column search");
4954 return;
4955 }
4956
4957 let matching_columns = if let Some(dataview) =
4959 self.state_container.get_buffer_dataview_mut()
4960 {
4961 dataview.search_columns(&pattern);
4962
4963 let matches = dataview.get_matching_columns().to_vec();
4965 debug!(target: "search", "DataView found {} matching columns", matches.len());
4966 if !matches.is_empty() {
4967 for (idx, (col_idx, col_name)) in matches.iter().enumerate() {
4968 debug!(target: "search", " Match {}: '{}' at visual index {}", idx + 1, col_name, col_idx);
4969 }
4970 }
4971
4972 let columns: Vec<(String, usize)> = matches
4974 .iter()
4975 .map(|(idx, name)| (name.clone(), *idx))
4976 .collect();
4977 self.state_container
4978 .update_column_search_matches(&columns, &pattern);
4979
4980 matches
4981 } else {
4982 debug!(target: "search", "No DataView available for column search");
4983 Vec::new()
4984 };
4985
4986 if matching_columns.is_empty() {
4987 let status_msg = format!("No columns matching '{pattern}'");
4988 debug!(target: "search", "Setting status: {}", status_msg);
4989 self.state_container.set_status_message(status_msg);
4990 } else {
4991 let first_match_visual_idx = matching_columns[0].0;
4993 let first_match_name = &matching_columns[0].1;
4994
4995 let datatable_idx = if let Some(dataview) = self.state_container.get_buffer_dataview() {
4997 let display_columns = dataview.get_display_columns();
4998 if first_match_visual_idx < display_columns.len() {
4999 display_columns[first_match_visual_idx]
5000 } else {
5001 first_match_visual_idx }
5003 } else {
5004 first_match_visual_idx
5005 };
5006
5007 self.state_container.set_current_column(datatable_idx);
5008 self.state_container
5009 .set_current_column_buffer(datatable_idx);
5010
5011 {
5014 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
5015 if let Some(viewport_manager) = viewport_manager_borrow.as_mut() {
5016 let viewport_changed =
5017 viewport_manager.set_current_column(first_match_visual_idx);
5018
5019 if viewport_changed {
5021 let new_viewport = viewport_manager.viewport_cols().clone();
5022 let pinned_count =
5023 if let Some(dv) = self.state_container.get_buffer_dataview() {
5024 dv.get_pinned_columns().len()
5025 } else {
5026 0
5027 };
5028 let scrollable_offset = new_viewport.start.saturating_sub(pinned_count);
5029 self.state_container.navigation_mut().scroll_offset.1 = scrollable_offset;
5030
5031 debug!(target: "navigation",
5032 "Column search initial: Jumped to column {} '{}', viewport adjusted to {:?}",
5033 first_match_visual_idx, first_match_name, new_viewport);
5034 }
5035 }
5036 }
5037
5038 debug!(target: "search", "Setting current column to visual index {} ('{}')",
5039 first_match_visual_idx, first_match_name);
5040 let status_msg = format!(
5041 "Found {} columns matching '{}'. Tab/Shift-Tab to navigate.",
5042 matching_columns.len(),
5043 pattern
5044 );
5045 debug!(target: "search", "Setting status: {}", status_msg);
5046 self.state_container.set_status_message(status_msg);
5047
5048 }
5050
5051 }
5053
5054 fn next_column_match(&mut self) {
5055 let column_match_data =
5058 if let Some(dataview) = self.state_container.get_buffer_dataview_mut() {
5059 if let Some(visual_idx) = dataview.next_column_match() {
5060 let matching_columns = dataview.get_matching_columns();
5062 let current_match_index = dataview.current_column_match_index();
5063 let current_match = current_match_index + 1;
5064 let total_matches = matching_columns.len();
5065 let col_name = matching_columns
5066 .get(current_match_index)
5067 .map(|(_, name)| name.clone())
5068 .unwrap_or_default();
5069
5070 let display_columns = dataview.get_display_columns();
5073 let datatable_idx = if visual_idx < display_columns.len() {
5074 display_columns[visual_idx]
5075 } else {
5076 visual_idx };
5078
5079 Some((
5080 visual_idx,
5081 datatable_idx,
5082 col_name,
5083 current_match,
5084 total_matches,
5085 current_match_index,
5086 ))
5087 } else {
5088 None
5089 }
5090 } else {
5091 None
5092 };
5093
5094 if let Some((
5096 visual_idx,
5097 datatable_idx,
5098 col_name,
5099 current_match,
5100 total_matches,
5101 current_match_index,
5102 )) = column_match_data
5103 {
5104 self.state_container.set_current_column(datatable_idx);
5106 self.state_container
5107 .set_current_column_buffer(datatable_idx);
5108
5109 {
5112 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
5113 if let Some(viewport_manager) = viewport_manager_borrow.as_mut() {
5114 viewport_manager.set_current_column(visual_idx);
5115 }
5116 }
5117
5118 debug!(target: "column_search_sync", "next_column_match: About to call sync_navigation_with_viewport() - visual_idx: {}, datatable_idx: {}", visual_idx, datatable_idx);
5120 debug!(target: "column_search_sync", "next_column_match: Pre-sync - viewport current_column: {}",
5121 if let Ok(vm) = self.viewport_manager.try_borrow() {
5122 vm.as_ref().map_or(0, super::viewport_manager::ViewportManager::get_crosshair_col)
5123 } else { 0 });
5124 self.sync_navigation_with_viewport();
5125 debug!(target: "column_search_sync", "next_column_match: Post-sync - navigation current_column: {}",
5126 self.state_container.navigation().selected_column);
5127 debug!(target: "column_search_sync", "next_column_match: sync_navigation_with_viewport() completed");
5128
5129 debug!(target: "navigation",
5130 "Column search: Jumped to visual column {} (datatable: {}) '{}', synced with viewport",
5131 visual_idx, datatable_idx, col_name);
5132
5133 {
5136 let mut column_search = self.state_container.column_search_mut();
5137 column_search.current_match = current_match_index;
5138 debug!(target: "column_search_sync", "next_column_match: Updated AppStateContainer column_search.current_match to {}", column_search.current_match);
5139 }
5140
5141 self.state_container.set_status_message(format!(
5142 "Column {current_match}/{total_matches}: {col_name} - Tab/Shift-Tab to navigate"
5143 ));
5144 }
5145 }
5146
5147 fn previous_column_match(&mut self) {
5148 let column_match_data =
5151 if let Some(dataview) = self.state_container.get_buffer_dataview_mut() {
5152 if let Some(visual_idx) = dataview.prev_column_match() {
5153 let matching_columns = dataview.get_matching_columns();
5155 let current_match_index = dataview.current_column_match_index();
5156 let current_match = current_match_index + 1;
5157 let total_matches = matching_columns.len();
5158 let col_name = matching_columns
5159 .get(current_match_index)
5160 .map(|(_, name)| name.clone())
5161 .unwrap_or_default();
5162
5163 let display_columns = dataview.get_display_columns();
5166 let datatable_idx = if visual_idx < display_columns.len() {
5167 display_columns[visual_idx]
5168 } else {
5169 visual_idx };
5171
5172 Some((
5173 visual_idx,
5174 datatable_idx,
5175 col_name,
5176 current_match,
5177 total_matches,
5178 current_match_index,
5179 ))
5180 } else {
5181 None
5182 }
5183 } else {
5184 None
5185 };
5186
5187 if let Some((
5189 visual_idx,
5190 datatable_idx,
5191 col_name,
5192 current_match,
5193 total_matches,
5194 current_match_index,
5195 )) = column_match_data
5196 {
5197 self.state_container.set_current_column(datatable_idx);
5199 self.state_container
5200 .set_current_column_buffer(datatable_idx);
5201
5202 {
5205 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
5206 if let Some(viewport_manager) = viewport_manager_borrow.as_mut() {
5207 viewport_manager.set_current_column(visual_idx);
5208 }
5209 }
5210
5211 debug!(target: "column_search_sync", "previous_column_match: About to call sync_navigation_with_viewport() - visual_idx: {}, datatable_idx: {}", visual_idx, datatable_idx);
5213 debug!(target: "column_search_sync", "previous_column_match: Pre-sync - viewport current_column: {}",
5214 if let Ok(vm) = self.viewport_manager.try_borrow() {
5215 vm.as_ref().map_or(0, super::viewport_manager::ViewportManager::get_crosshair_col)
5216 } else { 0 });
5217 self.sync_navigation_with_viewport();
5218 debug!(target: "column_search_sync", "previous_column_match: Post-sync - navigation current_column: {}",
5219 self.state_container.navigation().selected_column);
5220 debug!(target: "column_search_sync", "previous_column_match: sync_navigation_with_viewport() completed");
5221
5222 debug!(target: "navigation",
5223 "Column search (prev): Jumped to visual column {} (datatable: {}) '{}', synced with viewport",
5224 visual_idx, datatable_idx, col_name);
5225
5226 {
5229 let mut column_search = self.state_container.column_search_mut();
5230 column_search.current_match = current_match_index;
5231 debug!(target: "column_search_sync", "previous_column_match: Updated AppStateContainer column_search.current_match to {}", column_search.current_match);
5232 }
5233
5234 self.state_container.set_status_message(format!(
5235 "Column {current_match}/{total_matches}: {col_name} - Tab/Shift-Tab to navigate"
5236 ));
5237 }
5238 }
5239
5240 fn apply_fuzzy_filter(&mut self) {
5241 info!(
5242 "apply_fuzzy_filter called on thread {:?}",
5243 std::thread::current().id()
5244 );
5245
5246 use crate::ui::state::state_coordinator::StateCoordinator;
5248 let (_match_count, indices) = StateCoordinator::apply_fuzzy_filter_with_refs(
5249 &mut self.state_container,
5250 &self.viewport_manager,
5251 );
5252
5253 self.state_container.set_fuzzy_filter_indices(indices);
5255
5256 self.sync_dataview_to_managers();
5259 }
5260
5261 fn toggle_sort_current_column(&mut self) {
5262 let visual_col_idx = if let Some(ref viewport_manager) = *self.viewport_manager.borrow() {
5264 viewport_manager.get_crosshair_col()
5265 } else {
5266 0
5267 };
5268
5269 if let Some(dataview) = self.state_container.get_buffer_dataview_mut() {
5270 let column_names = dataview.column_names();
5273 let col_name = column_names
5274 .get(visual_col_idx)
5275 .cloned()
5276 .unwrap_or_else(|| format!("Column {visual_col_idx}"));
5277
5278 debug!(
5279 "toggle_sort_current_column: visual_idx={}, column_name={}",
5280 visual_col_idx, col_name
5281 );
5282
5283 if let Err(e) = dataview.toggle_sort(visual_col_idx) {
5284 self.state_container
5285 .set_status_message(format!("Sort error: {e}"));
5286 } else {
5287 let sort_state = dataview.get_sort_state();
5289 let message = match sort_state.order {
5290 crate::data::data_view::SortOrder::Ascending => {
5291 format!("Sorted '{col_name}' ascending ↑")
5292 }
5293 crate::data::data_view::SortOrder::Descending => {
5294 format!("Sorted '{col_name}' descending ↓")
5295 }
5296 crate::data::data_view::SortOrder::None => {
5297 format!("Cleared sort on '{col_name}'")
5298 }
5299 };
5300 self.state_container.set_status_message(message);
5301
5302 if let Some(updated_dataview) = self.state_container.get_buffer_dataview() {
5304 self.table_widget_manager
5306 .borrow_mut()
5307 .set_dataview(Arc::new(updated_dataview.clone()));
5308
5309 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
5310 if let Some(ref mut viewport_manager) = *viewport_manager_borrow {
5311 viewport_manager.set_dataview(Arc::new(updated_dataview.clone()));
5312 debug!("Updated ViewportManager with sorted DataView");
5313 }
5314 }
5315 }
5316 } else {
5317 self.state_container
5319 .set_status_message("Error: Invalid column position".to_string());
5320 }
5321 }
5322
5323 fn get_current_data(&self) -> Option<&DataView> {
5324 self.state_container.get_buffer_dataview()
5325 }
5326
5327 fn get_row_count(&self) -> usize {
5328 if self.state_container.is_fuzzy_filter_active() {
5330 self.state_container.get_fuzzy_filter_indices().len()
5332 } else if let Some(dataview) = self.state_container.get_buffer_dataview() {
5333 dataview.row_count()
5335 } else if let Some(provider) = self.get_data_provider() {
5336 provider.get_row_count()
5338 } else {
5339 0
5340 }
5341 }
5342
5343 fn sync_dataview_to_managers(&self) {
5345 if let Some(dataview) = self.state_container.get_buffer_dataview() {
5346 let arc_dataview = Arc::new(dataview.clone());
5347
5348 if let Some(ref mut viewport_manager) = *self.viewport_manager.borrow_mut() {
5350 viewport_manager.set_dataview(arc_dataview.clone());
5351 debug!(
5352 "Updated ViewportManager with DataView (row_count={})",
5353 arc_dataview.row_count()
5354 );
5355 }
5356
5357 self.table_widget_manager
5359 .borrow_mut()
5360 .set_dataview(arc_dataview);
5361 debug!("Updated TableWidgetManager with DataView");
5362 }
5363 }
5364
5365 pub fn reset_table_state(&mut self) {
5366 use crate::ui::state::state_coordinator::StateCoordinator;
5368 StateCoordinator::reset_table_state_with_refs(
5369 &mut self.state_container,
5370 &self.viewport_manager,
5371 );
5372 }
5373
5374 fn update_parser_for_current_buffer(&mut self) {
5375 self.sync_all_input_states();
5377
5378 use crate::ui::state::state_coordinator::StateCoordinator;
5380 StateCoordinator::update_parser_with_refs(&self.state_container, &mut self.hybrid_parser);
5381 }
5382
5383 fn sync_after_buffer_switch(&mut self) {
5388 self.restore_viewport_from_current_buffer();
5390
5391 self.update_parser_for_current_buffer();
5393 }
5394
5395 fn update_viewport_manager(&mut self, dataview: Option<DataView>) {
5397 if let Some(dv) = dataview {
5398 let current_column = self.state_container.get_current_column();
5400
5401 let mut new_viewport_manager = ViewportManager::new(Arc::new(dv));
5403
5404 if let Ok((width, height)) = crossterm::terminal::size() {
5406 let data_rows_available = Self::calculate_available_data_rows(height);
5408 new_viewport_manager.update_terminal_size(width, data_rows_available);
5409 debug!(
5410 "Updated new ViewportManager terminal size: {}x{} (data rows)",
5411 width, data_rows_available
5412 );
5413 }
5414
5415 if current_column < new_viewport_manager.dataview().column_count() {
5418 new_viewport_manager.set_current_column(current_column);
5419 } else {
5420 new_viewport_manager.set_current_column(0);
5422 self.state_container.set_current_column_buffer(0);
5423 }
5424
5425 *self.viewport_manager.borrow_mut() = Some(new_viewport_manager);
5426 debug!(
5427 "ViewportManager updated with new DataView, current_column={}",
5428 current_column
5429 );
5430 } else {
5431 *self.viewport_manager.borrow_mut() = None;
5433 debug!("ViewportManager cleared (no DataView)");
5434 }
5435 }
5436
5437 pub fn calculate_optimal_column_widths(&mut self) {
5438 let widths_from_viewport = {
5440 let mut viewport_opt = self.viewport_manager.borrow_mut();
5441 (*viewport_opt)
5442 .as_mut()
5443 .map(super::viewport_manager::ViewportManager::calculate_optimal_column_widths)
5444 };
5445
5446 if let Some(widths) = widths_from_viewport {
5447 self.state_container.set_column_widths(widths);
5448 }
5449 }
5450
5451 pub fn set_status_message(&mut self, message: impl Into<String>) {
5454 let msg = message.into();
5455 debug!("Status: {}", msg);
5456 self.state_container.set_status_message(msg.clone());
5457 }
5460
5461 fn set_error_status(&mut self, context: &str, error: impl std::fmt::Display) {
5463 let msg = format!("{context}: {error}");
5464 debug!("Error status: {}", msg);
5465 self.set_status_message(msg);
5466 }
5467
5468 fn export_to_csv(&mut self) {
5469 let result = {
5470 let ctx = crate::ui::operations::data_export_operations::DataExportContext {
5471 data_provider: self.get_data_provider(),
5472 };
5473 crate::ui::operations::data_export_operations::export_to_csv(&ctx)
5474 };
5475
5476 match result {
5477 crate::ui::operations::data_export_operations::ExportResult::Success(message) => {
5478 self.set_status_message(message);
5479 }
5480 crate::ui::operations::data_export_operations::ExportResult::Error(error) => {
5481 self.set_error_status("Export failed", error);
5482 }
5483 }
5484 }
5485
5486 fn paste_from_clipboard(&mut self) {
5493 match self.state_container.read_from_clipboard() {
5495 Ok(text) => {
5496 let mode = self.shadow_state.borrow().get_mode();
5497 match mode {
5498 AppMode::Command => {
5499 let cursor_pos = self.get_input_cursor();
5502 let current_value = self.get_input_text();
5503
5504 let mut new_value = String::new();
5506 new_value.push_str(¤t_value[..cursor_pos]);
5507 new_value.push_str(&text);
5508 new_value.push_str(¤t_value[cursor_pos..]);
5509
5510 self.set_input_text_with_cursor(new_value, cursor_pos + text.len());
5511
5512 self.state_container
5513 .set_status_message(format!("Pasted {} characters", text.len()));
5514 }
5515 AppMode::Filter
5516 | AppMode::FuzzyFilter
5517 | AppMode::Search
5518 | AppMode::ColumnSearch => {
5519 let cursor_pos = self.get_input_cursor();
5521 let current_value = self.get_input_text();
5522
5523 let mut new_value = String::new();
5524 new_value.push_str(¤t_value[..cursor_pos]);
5525 new_value.push_str(&text);
5526 new_value.push_str(¤t_value[cursor_pos..]);
5527
5528 self.set_input_text_with_cursor(new_value, cursor_pos + text.len());
5529
5530 match mode {
5532 AppMode::Filter => {
5533 let pattern = self.get_input_text();
5534 self.state_container.filter_mut().pattern = pattern.clone();
5535 self.apply_filter(&pattern);
5536 }
5537 AppMode::FuzzyFilter => {
5538 let input_text = self.get_input_text();
5539 self.state_container.set_fuzzy_filter_pattern(input_text);
5540 self.apply_fuzzy_filter();
5541 }
5542 AppMode::Search => {
5543 let search_text = self.get_input_text();
5544 self.state_container.set_search_pattern(search_text);
5545 }
5547 AppMode::ColumnSearch => {
5548 let input_text = self.get_input_text();
5549 self.state_container.start_column_search(input_text);
5550 }
5552 _ => {}
5553 }
5554 }
5555 _ => {
5556 self.state_container
5557 .set_status_message("Paste not available in this mode".to_string());
5558 }
5559 }
5560 }
5561 Err(e) => {
5562 self.state_container
5563 .set_status_message(format!("Failed to paste: {e}"));
5564 }
5565 }
5566 }
5567
5568 fn export_to_json(&mut self) {
5569 let result = {
5571 let ctx = crate::ui::operations::data_export_operations::DataExportContext {
5572 data_provider: self.get_data_provider(),
5573 };
5574 crate::ui::operations::data_export_operations::export_to_json(&ctx)
5575 };
5576
5577 match result {
5578 crate::ui::operations::data_export_operations::ExportResult::Success(message) => {
5579 self.set_status_message(message);
5580 }
5581 crate::ui::operations::data_export_operations::ExportResult::Error(error) => {
5582 self.set_error_status("Export failed", error);
5583 }
5584 }
5585 }
5586
5587 fn get_horizontal_scroll_offset(&self) -> u16 {
5588 let (horizontal, _vertical) = self.cursor_manager.scroll_offsets();
5590 horizontal
5591 }
5592
5593 fn update_horizontal_scroll(&mut self, terminal_width: u16) {
5594 let inner_width = terminal_width.saturating_sub(3) as usize; let cursor_pos = self.get_input_cursor();
5596
5597 self.cursor_manager
5599 .update_horizontal_scroll(cursor_pos, terminal_width.saturating_sub(3));
5600
5601 let mut scroll = self.state_container.scroll_mut();
5603 if cursor_pos < scroll.input_scroll_offset as usize {
5604 scroll.input_scroll_offset = cursor_pos as u16;
5605 }
5606 else if cursor_pos >= scroll.input_scroll_offset as usize + inner_width {
5608 scroll.input_scroll_offset = (cursor_pos + 1).saturating_sub(inner_width) as u16;
5609 }
5610 }
5611
5612 fn get_cursor_token_position(&self) -> (usize, usize) {
5613 let ctx = crate::ui::operations::simple_operations::TextNavigationContext {
5614 query: &self.get_input_text(),
5615 cursor_pos: self.get_input_cursor(),
5616 };
5617 crate::ui::operations::simple_operations::get_cursor_token_position(&ctx)
5618 }
5619
5620 fn get_token_at_cursor(&self) -> Option<String> {
5621 let ctx = crate::ui::operations::simple_operations::TextNavigationContext {
5622 query: &self.get_input_text(),
5623 cursor_pos: self.get_input_cursor(),
5624 };
5625 crate::ui::operations::simple_operations::get_token_at_cursor(&ctx)
5626 }
5627
5628 #[allow(dead_code)]
5630 fn ui(&mut self, f: &mut Frame) {
5631 let input_height = INPUT_AREA_HEIGHT;
5633
5634 let buffer_count = self.state_container.buffers().all_buffers().len();
5636 let tab_bar_height = 2; let chunks = Layout::default()
5639 .direction(Direction::Vertical)
5640 .constraints(
5641 [
5642 Constraint::Length(tab_bar_height), Constraint::Length(input_height), Constraint::Min(0), Constraint::Length(STATUS_BAR_HEIGHT), ]
5647 .as_ref(),
5648 )
5649 .split(f.area());
5650
5651 if buffer_count > 0 {
5653 let buffer_names: Vec<String> = self
5654 .state_container
5655 .buffers()
5656 .all_buffers()
5657 .iter()
5658 .map(super::super::buffer::BufferAPI::get_name)
5659 .collect();
5660 let current_index = self.state_container.buffers().current_index();
5661
5662 let tab_widget = TabBarWidget::new(current_index, buffer_names);
5663 tab_widget.render(f, chunks[0]);
5664 }
5665
5666 let input_chunk_idx = 1;
5668 let results_chunk_idx = 2;
5669 let status_chunk_idx = 3;
5670
5671 self.update_horizontal_scroll(chunks[input_chunk_idx].width);
5673
5674 let input_text_for_count = self.get_input_text();
5677 let char_count = input_text_for_count.len();
5678 let cursor_pos = self.get_input_cursor();
5679 let char_count_display = if char_count > 0 {
5680 format!(" [{cursor_pos}/{char_count} chars]")
5681 } else {
5682 String::new()
5683 };
5684
5685 let scroll_offset = self.get_horizontal_scroll_offset();
5686 let scroll_indicator = if scroll_offset > 0 {
5687 " ◀ " } else {
5689 ""
5690 };
5691
5692 let input_title = match self.shadow_state.borrow().get_mode() {
5693 AppMode::Command => format!("SQL Query{char_count_display}{scroll_indicator}"),
5694 AppMode::Results => format!(
5695 "SQL Query (Results Mode - Press ↑ to edit){char_count_display}{scroll_indicator}"
5696 ),
5697 AppMode::Search => format!("Search Pattern{char_count_display}{scroll_indicator}"),
5698 AppMode::Filter => format!("Filter Pattern{char_count_display}{scroll_indicator}"),
5699 AppMode::FuzzyFilter => {
5700 format!("Fuzzy Filter{char_count_display}{scroll_indicator}")
5701 }
5702 AppMode::ColumnSearch => {
5703 format!("Column Search{char_count_display}{scroll_indicator}")
5704 }
5705 AppMode::Help => "Help".to_string(),
5706 AppMode::History => {
5707 let query = self.state_container.history_search().query.clone();
5708 format!("History Search: '{query}' (Esc to cancel)")
5709 }
5710 AppMode::Debug => "Parser Debug (F5)".to_string(),
5711 AppMode::PrettyQuery => "Pretty Query View (F6)".to_string(),
5712 AppMode::JumpToRow => format!("Jump to row: {}", self.get_jump_to_row_input()),
5713 AppMode::ColumnStats => "Column Statistics (S to close)".to_string(),
5714 };
5715
5716 let input_block = Block::default().borders(Borders::ALL).title(input_title);
5717
5718 let use_search_widget = matches!(
5720 self.shadow_state.borrow().get_mode(),
5721 AppMode::Search | AppMode::Filter | AppMode::FuzzyFilter | AppMode::ColumnSearch
5722 ) && self.search_modes_widget.is_active();
5723
5724 if use_search_widget {
5725 self.search_modes_widget.render(f, chunks[input_chunk_idx]);
5727 } else {
5728 let input_text_string = self.get_input_text();
5730
5731 trace!(target: "render", "Rendering input: text='{}', mode={:?}, cursor={}",
5733 if input_text_string.len() > 50 {
5734 format!("{}...", &input_text_string[..50])
5735 } else {
5736 input_text_string.clone()
5737 },
5738 self.shadow_state.borrow().get_mode(),
5739 self.get_input_cursor());
5740
5741 let history_query_string = if self.shadow_state.borrow().is_in_history_mode() {
5743 self.state_container.history_search().query.clone()
5744 } else {
5745 String::new()
5746 };
5747
5748 let input_text = match self.shadow_state.borrow().get_mode() {
5749 AppMode::History => &history_query_string,
5750 _ => &input_text_string,
5751 };
5752
5753 let input_paragraph = match self.shadow_state.borrow().get_mode() {
5754 AppMode::Command => {
5755 match self.state_container.get_edit_mode() {
5756 Some(EditMode::SingleLine) => {
5757 let highlighted_line =
5759 self.sql_highlighter.simple_sql_highlight(input_text);
5760 Paragraph::new(Text::from(vec![highlighted_line]))
5761 .block(input_block)
5762 .scroll((0, self.get_horizontal_scroll_offset()))
5763 }
5764 Some(EditMode::MultiLine) => {
5765 let highlighted_line =
5767 self.sql_highlighter.simple_sql_highlight(input_text);
5768 Paragraph::new(Text::from(vec![highlighted_line]))
5769 .block(input_block)
5770 .scroll((0, self.get_horizontal_scroll_offset()))
5771 }
5772 None => {
5773 let highlighted_line =
5775 self.sql_highlighter.simple_sql_highlight(input_text);
5776 Paragraph::new(Text::from(vec![highlighted_line]))
5777 .block(input_block)
5778 .scroll((0, self.get_horizontal_scroll_offset()))
5779 }
5780 }
5781 }
5782 _ => {
5783 Paragraph::new(input_text.as_str())
5785 .block(input_block)
5786 .style(match self.shadow_state.borrow().get_mode() {
5787 AppMode::Results => Style::default().fg(Color::DarkGray),
5788 AppMode::Search => Style::default().fg(Color::Yellow),
5789 AppMode::Filter => Style::default().fg(Color::Cyan),
5790 AppMode::FuzzyFilter => Style::default().fg(Color::Magenta),
5791 AppMode::ColumnSearch => Style::default().fg(Color::Green),
5792 AppMode::Help => Style::default().fg(Color::DarkGray),
5793 AppMode::History => Style::default().fg(Color::Magenta),
5794 AppMode::Debug => Style::default().fg(Color::Yellow),
5795 AppMode::PrettyQuery => Style::default().fg(Color::Green),
5796 AppMode::JumpToRow => Style::default().fg(Color::Magenta),
5797 AppMode::ColumnStats => Style::default().fg(Color::Cyan),
5798 _ => Style::default(),
5799 })
5800 .scroll((0, self.get_horizontal_scroll_offset()))
5801 }
5802 };
5803
5804 f.render_widget(input_paragraph, chunks[input_chunk_idx]);
5806 }
5807 let results_area = chunks[results_chunk_idx];
5808
5809 if !use_search_widget {
5811 match self.shadow_state.borrow().get_mode() {
5812 AppMode::Command => {
5813 let inner_width = chunks[input_chunk_idx].width.saturating_sub(2) as usize;
5816 let cursor_pos = self.get_visual_cursor().1; let scroll_offset = self.get_horizontal_scroll_offset() as usize;
5818
5819 if cursor_pos >= scroll_offset && cursor_pos < scroll_offset + inner_width {
5821 let visible_pos = cursor_pos - scroll_offset;
5822 f.set_cursor_position((
5823 chunks[input_chunk_idx].x + visible_pos as u16 + 1,
5824 chunks[input_chunk_idx].y + 1,
5825 ));
5826 }
5827 }
5828 AppMode::Search => {
5829 f.set_cursor_position((
5830 chunks[input_chunk_idx].x + self.get_input_cursor() as u16 + 1,
5831 chunks[input_chunk_idx].y + 1,
5832 ));
5833 }
5834 AppMode::Filter => {
5835 f.set_cursor_position((
5836 chunks[input_chunk_idx].x + self.get_input_cursor() as u16 + 1,
5837 chunks[input_chunk_idx].y + 1,
5838 ));
5839 }
5840 AppMode::FuzzyFilter => {
5841 f.set_cursor_position((
5842 chunks[input_chunk_idx].x + self.get_input_cursor() as u16 + 1,
5843 chunks[input_chunk_idx].y + 1,
5844 ));
5845 }
5846 AppMode::ColumnSearch => {
5847 f.set_cursor_position((
5848 chunks[input_chunk_idx].x + self.get_input_cursor() as u16 + 1,
5849 chunks[input_chunk_idx].y + 1,
5850 ));
5851 }
5852 AppMode::JumpToRow => {
5853 f.set_cursor_position((
5854 chunks[input_chunk_idx].x + self.get_jump_to_row_input().len() as u16 + 1,
5855 chunks[input_chunk_idx].y + 1,
5856 ));
5857 }
5858 AppMode::History => {
5859 let query_len = self.state_container.history_search().query.len();
5860 f.set_cursor_position((
5861 chunks[input_chunk_idx].x + query_len as u16 + 1,
5862 chunks[input_chunk_idx].y + 1,
5863 ));
5864 }
5865 _ => {}
5866 }
5867 }
5868
5869 let mode = self.shadow_state.borrow().get_mode();
5871 match mode {
5872 AppMode::Help => self.render_help(f, results_area),
5873 AppMode::History => self.render_history(f, results_area),
5874 AppMode::Debug => self.render_debug(f, results_area),
5875 AppMode::PrettyQuery => self.render_pretty_query(f, results_area),
5876 AppMode::ColumnStats => self.render_column_stats(f, results_area),
5877 _ if self.state_container.has_dataview() => {
5878 if let Some(provider) = self.get_data_provider() {
5881 self.render_table_with_provider(f, results_area, provider.as_ref());
5882 }
5883 }
5884 _ => {
5885 let placeholder = Paragraph::new("Enter SQL query and press Enter\n\nTip: Use Tab for completion, Ctrl+R for history")
5887 .block(Block::default().borders(Borders::ALL).title("Results"))
5888 .style(Style::default().fg(Color::DarkGray));
5889 f.render_widget(placeholder, results_area);
5890 }
5891 }
5892
5893 self.render_status_line(f, chunks[status_chunk_idx]);
5895 }
5897
5898 fn add_mode_styling(&self, spans: &mut Vec<Span>) -> (Style, Color) {
5900 let (status_style, mode_color) = match self.shadow_state.borrow().get_mode() {
5902 AppMode::Command => (Style::default().fg(Color::Green), Color::Green),
5903 AppMode::Results => (Style::default().fg(Color::Blue), Color::Blue),
5904 AppMode::Search => (Style::default().fg(Color::Yellow), Color::Yellow),
5905 AppMode::Filter => (Style::default().fg(Color::Cyan), Color::Cyan),
5906 AppMode::FuzzyFilter => (Style::default().fg(Color::Magenta), Color::Magenta),
5907 AppMode::ColumnSearch => (Style::default().fg(Color::Green), Color::Green),
5908 AppMode::Help => (Style::default().fg(Color::Magenta), Color::Magenta),
5909 AppMode::History => (Style::default().fg(Color::Magenta), Color::Magenta),
5910 AppMode::Debug => (Style::default().fg(Color::Yellow), Color::Yellow),
5911 AppMode::PrettyQuery => (Style::default().fg(Color::Green), Color::Green),
5912 AppMode::JumpToRow => (Style::default().fg(Color::Magenta), Color::Magenta),
5913 AppMode::ColumnStats => (Style::default().fg(Color::Cyan), Color::Cyan),
5914 };
5915
5916 let mode_indicator = match self.shadow_state.borrow().get_mode() {
5917 AppMode::Command => "CMD",
5918 AppMode::Results => "NAV",
5919 AppMode::Search => "SEARCH",
5920 AppMode::Filter => "FILTER",
5921 AppMode::FuzzyFilter => "FUZZY",
5922 AppMode::ColumnSearch => "COL",
5923 AppMode::Help => "HELP",
5924 AppMode::History => "HISTORY",
5925 AppMode::Debug => "DEBUG",
5926 AppMode::PrettyQuery => "PRETTY",
5927 AppMode::JumpToRow => "JUMP",
5928 AppMode::ColumnStats => "STATS",
5929 };
5930
5931 spans.push(Span::styled(
5933 format!("[{mode_indicator}]"),
5934 Style::default().fg(mode_color).add_modifier(Modifier::BOLD),
5935 ));
5936
5937 (status_style, mode_color)
5938 }
5939
5940 fn add_data_source_display(&self, _spans: &mut Vec<Span>) {
5942 }
5945
5946 fn add_buffer_information(&self, spans: &mut Vec<Span>) {
5948 let index = self.state_container.buffers().current_index();
5949 let total = self.state_container.buffers().all_buffers().len();
5950
5951 if total > 1 {
5953 spans.push(Span::raw(" "));
5954 spans.push(Span::styled(
5955 format!("[{}/{}]", index + 1, total),
5956 Style::default().fg(Color::Yellow),
5957 ));
5958 }
5959
5960 if let Some(buffer) = self.state_container.buffers().current() {
5963 let query = buffer.get_input_text();
5964 if let Some(from_pos) = query.to_uppercase().find(" FROM ") {
5966 let after_from = &query[from_pos + 6..];
5967 if let Some(table_name) = after_from.split_whitespace().next() {
5969 let clean_name = table_name
5971 .trim_matches('"')
5972 .trim_matches('\'')
5973 .trim_matches('`');
5974
5975 spans.push(Span::raw(" "));
5976 spans.push(Span::styled(
5977 clean_name.to_string(),
5978 Style::default().fg(Color::Cyan),
5979 ));
5980 }
5981 }
5982 }
5983 }
5984
5985 fn add_mode_specific_info(&self, spans: &mut Vec<Span>, mode_color: Color, area: Rect) {
5987 match self.shadow_state.borrow().get_mode() {
5988 AppMode::Command => {
5989 if !self.get_input_text().trim().is_empty() {
5991 let (token_pos, total_tokens) = self.get_cursor_token_position();
5992 spans.push(Span::raw(" | "));
5993 spans.push(Span::styled(
5994 format!("Token {token_pos}/{total_tokens}"),
5995 Style::default().fg(Color::DarkGray),
5996 ));
5997
5998 if let Some(token) = self.get_token_at_cursor() {
6000 spans.push(Span::raw(" "));
6001 spans.push(Span::styled(
6002 format!("[{token}]"),
6003 Style::default().fg(Color::Cyan),
6004 ));
6005 }
6006
6007 if let Some(error_msg) = self.check_parser_error(&self.get_input_text()) {
6009 spans.push(Span::raw(" | "));
6010 spans.push(Span::styled(
6011 format!("{} {}", self.config.display.icons.warning, error_msg),
6012 Style::default().fg(Color::Red).add_modifier(Modifier::BOLD),
6013 ));
6014 }
6015 }
6016 }
6017 AppMode::Results => {
6018 self.add_results_mode_info(spans, area);
6020 }
6021 AppMode::Search | AppMode::Filter | AppMode::FuzzyFilter | AppMode::ColumnSearch => {
6022 let pattern = self.get_input_text();
6024 if !pattern.is_empty() {
6025 spans.push(Span::raw(" | Pattern: "));
6026 spans.push(Span::styled(pattern, Style::default().fg(mode_color)));
6027 }
6028 }
6029 _ => {}
6030 }
6031 }
6032
6033 fn add_results_mode_info(&self, spans: &mut Vec<Span>, area: Rect) {
6035 let total_rows = self.get_row_count();
6036 if total_rows > 0 {
6037 let selected = self.state_container.navigation().selected_row + 1;
6039 spans.push(Span::raw(" | "));
6040
6041 let selection_mode = self.get_selection_mode();
6043 let mode_text = match selection_mode {
6044 SelectionMode::Cell => "CELL",
6045 SelectionMode::Row => "ROW",
6046 SelectionMode::Column => "COL",
6047 };
6048 spans.push(Span::styled(
6049 format!("[{mode_text}]"),
6050 Style::default()
6051 .fg(Color::Cyan)
6052 .add_modifier(Modifier::BOLD),
6053 ));
6054
6055 spans.push(Span::raw(" "));
6056 spans.push(Span::styled(
6057 format!("Row {selected}/{total_rows}"),
6058 Style::default().fg(Color::White),
6059 ));
6060
6061 let visual_col_display =
6064 if let Some(ref viewport_manager) = *self.viewport_manager.borrow() {
6065 viewport_manager.get_crosshair_col() + 1
6066 } else {
6067 1
6068 };
6069 spans.push(Span::raw(" "));
6070 spans.push(Span::styled(
6071 format!("({visual_col_display},{selected})"),
6072 Style::default().fg(Color::DarkGray),
6073 ));
6074
6075 if let Some(ref mut viewport_manager) = *self.viewport_manager.borrow_mut() {
6077 let available_width = area.width.saturating_sub(TABLE_BORDER_WIDTH);
6078 let visual_col = viewport_manager.get_crosshair_col();
6080 if let Some(x_pos) =
6081 viewport_manager.get_column_x_position(visual_col, available_width)
6082 {
6083 let terminal_x = x_pos + 2;
6085 let terminal_y = (selected as u16)
6086 .saturating_sub(self.state_container.get_scroll_offset().0 as u16)
6087 + 3;
6088 spans.push(Span::raw(" "));
6089 spans.push(Span::styled(
6090 format!("[{terminal_x}x{terminal_y}]"),
6091 Style::default().fg(Color::DarkGray),
6092 ));
6093 }
6094 }
6095
6096 if let Some(dataview) = self.state_container.get_buffer_dataview() {
6098 let headers = dataview.column_names();
6099
6100 let (visual_row, visual_col) =
6103 if let Some(ref viewport_manager) = *self.viewport_manager.borrow() {
6104 (
6105 viewport_manager.get_crosshair_row(),
6106 viewport_manager.get_crosshair_col(),
6107 )
6108 } else {
6109 (0, 0)
6110 };
6111
6112 if visual_col < headers.len() {
6114 spans.push(Span::raw(" | Col: "));
6115 spans.push(Span::styled(
6116 headers[visual_col].clone(),
6117 Style::default().fg(Color::Cyan),
6118 ));
6119
6120 let viewport_info =
6122 if let Some(ref viewport_manager) = *self.viewport_manager.borrow() {
6123 let viewport_rows = viewport_manager.get_viewport_rows();
6124 let viewport_height = viewport_rows.end - viewport_rows.start;
6125 format!("[V:{visual_row},{visual_col} @ {viewport_height}r]")
6126 } else {
6127 format!("[V:{visual_row},{visual_col}]")
6128 };
6129 spans.push(Span::raw(" "));
6130 spans.push(Span::styled(
6131 viewport_info,
6132 Style::default().fg(Color::Magenta),
6133 ));
6134 }
6135 }
6136 }
6137 }
6138
6139 fn render_status_line(&self, f: &mut Frame, area: Rect) {
6140 let mut spans = Vec::new();
6141
6142 let (status_style, mode_color) = self.add_mode_styling(&mut spans);
6144
6145 self.add_data_source_display(&mut spans);
6147
6148 self.add_buffer_information(&mut spans);
6150
6151 self.add_mode_specific_info(&mut spans, mode_color, area);
6153
6154 self.add_query_source_indicator(&mut spans);
6156
6157 self.add_case_sensitivity_indicator(&mut spans);
6159
6160 self.add_column_packing_indicator(&mut spans);
6162
6163 self.add_status_message(&mut spans);
6165
6166 let help_text = self.get_help_text_for_mode();
6168
6169 self.add_global_indicators(&mut spans);
6170
6171 self.add_shadow_state_display(&mut spans);
6173
6174 self.add_help_text_display(&mut spans, help_text, area);
6175
6176 let status_line = Line::from(spans);
6177 let status = Paragraph::new(status_line)
6178 .block(Block::default().borders(Borders::ALL))
6179 .style(status_style);
6180 f.render_widget(status, area);
6181 }
6182
6183 fn build_table_context(
6186 &self,
6187 area: Rect,
6188 provider: &dyn DataProvider,
6189 ) -> crate::ui::rendering::table_render_context::TableRenderContext {
6190 use crate::ui::rendering::table_render_context::TableRenderContextBuilder;
6191
6192 let row_count = provider.get_row_count();
6193 let available_width = area.width.saturating_sub(TABLE_BORDER_WIDTH);
6194 let available_height = area.height;
6197
6198 let headers = {
6200 let viewport_manager = self.viewport_manager.borrow();
6201 let viewport_manager = viewport_manager
6202 .as_ref()
6203 .expect("ViewportManager must exist");
6204 viewport_manager.get_column_names_ordered()
6205 };
6206
6207 {
6209 let mut viewport_opt = self.viewport_manager.borrow_mut();
6210 if let Some(ref mut viewport_manager) = *viewport_opt {
6211 let data_rows = Self::calculate_table_data_rows(available_height);
6213 viewport_manager.update_terminal_size(available_width, data_rows);
6214 let _ = viewport_manager.get_column_widths(); }
6216 }
6217
6218 let (pinned_visual_positions, crosshair_column_position, _) = {
6220 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
6221 let viewport_manager = viewport_manager_borrow
6222 .as_mut()
6223 .expect("ViewportManager must exist for rendering");
6224 let info = viewport_manager.get_visible_columns_info(available_width);
6225
6226 let visible_indices = &info.0;
6230 let pinned_source_indices = &info.1;
6231
6232 let mut pinned_visual_positions = Vec::new();
6235 for &source_idx in pinned_source_indices {
6236 if let Some(visual_pos) = visible_indices.iter().position(|&x| x == source_idx) {
6237 pinned_visual_positions.push(visual_pos);
6238 }
6239 }
6240
6241 let crosshair_column_position =
6245 if let Some((_, col_pos)) = viewport_manager.get_crosshair_viewport_position() {
6246 col_pos
6247 } else {
6248 0
6250 };
6251
6252 let crosshair_visual = viewport_manager.get_crosshair_col();
6253
6254 (
6255 pinned_visual_positions,
6256 crosshair_column_position,
6257 crosshair_visual,
6258 )
6259 };
6260
6261 let row_viewport_start = self
6263 .state_container
6264 .navigation()
6265 .scroll_offset
6266 .0
6267 .min(row_count.saturating_sub(1));
6268 let row_viewport_end = (row_viewport_start + available_height as usize).min(row_count);
6269 let visible_row_indices: Vec<usize> = (row_viewport_start..row_viewport_end).collect();
6270
6271 let (column_headers, data_to_display, column_widths_visual) = {
6273 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
6274 if let Some(ref mut viewport_manager) = *viewport_manager_borrow {
6275 viewport_manager.get_visual_display(available_width, &visible_row_indices)
6276 } else {
6277 let visible_rows = provider
6279 .get_visible_rows(row_viewport_start, row_viewport_end - row_viewport_start);
6280 let widths = vec![15u16; headers.len()];
6281 (headers.clone(), visible_rows, widths)
6282 }
6283 };
6284
6285 let sort_state = self
6287 .buffer()
6288 .get_dataview()
6289 .map(|dv| dv.get_sort_state().clone());
6290
6291 let fuzzy_filter_pattern = if self.state_container.is_fuzzy_filter_active() {
6293 let pattern = self.state_container.get_fuzzy_filter_pattern();
6294 if pattern.is_empty() {
6295 None
6296 } else {
6297 Some(pattern)
6298 }
6299 } else {
6300 None
6301 };
6302
6303 let selected_row = self.state_container.navigation().selected_row;
6305 let selected_col = crosshair_column_position;
6306
6307 trace!(target: "search", "Building TableRenderContext: selected_row={}, selected_col={}, mode={:?}",
6309 selected_row, selected_col, self.state_container.get_selection_mode());
6310
6311 TableRenderContextBuilder::new()
6312 .row_count(row_count)
6313 .visible_rows(visible_row_indices.clone(), data_to_display)
6314 .columns(column_headers, column_widths_visual)
6315 .pinned_columns(pinned_visual_positions)
6316 .selection(
6317 selected_row,
6318 selected_col,
6319 self.state_container.get_selection_mode(),
6320 )
6321 .row_viewport(row_viewport_start..row_viewport_end)
6322 .sort_state(sort_state)
6323 .display_options(
6324 self.state_container.is_show_row_numbers(),
6325 self.shadow_state.borrow().get_mode(),
6326 )
6327 .filter(
6328 fuzzy_filter_pattern,
6329 self.state_container.is_case_insensitive(),
6330 )
6331 .dimensions(available_width, available_height)
6332 .build()
6333 }
6334
6335 fn render_table_with_provider(&self, f: &mut Frame, area: Rect, provider: &dyn DataProvider) {
6338 let context = self.build_table_context(area, provider);
6340
6341 crate::ui::rendering::table_renderer::render_table(f, area, &context);
6343 }
6344
6345 fn render_help(&mut self, f: &mut Frame, area: Rect) {
6346 self.help_widget.render(f, area);
6348 }
6349
6350 fn render_help_two_column(&self, f: &mut Frame, area: Rect) {
6351 let chunks = Layout::default()
6353 .direction(Direction::Horizontal)
6354 .constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
6355 .split(area);
6356
6357 let left_content = HelpText::left_column();
6359 let right_content = HelpText::right_column();
6360
6361 let visible_height = area.height.saturating_sub(2) as usize; let left_total_lines = left_content.len();
6364 let right_total_lines = right_content.len();
6365 let max_lines = left_total_lines.max(right_total_lines);
6366
6367 let scroll_offset = { self.state_container.help_scroll_offset() as usize };
6369
6370 let left_visible: Vec<Line> = left_content
6372 .into_iter()
6373 .skip(scroll_offset)
6374 .take(visible_height)
6375 .collect();
6376
6377 let right_visible: Vec<Line> = right_content
6378 .into_iter()
6379 .skip(scroll_offset)
6380 .take(visible_height)
6381 .collect();
6382
6383 let scroll_indicator = if max_lines > visible_height {
6385 format!(
6386 " (↓/↑ to scroll, {}/{})",
6387 scroll_offset + 1,
6388 max_lines.saturating_sub(visible_height) + 1
6389 )
6390 } else {
6391 String::new()
6392 };
6393
6394 let left_paragraph = Paragraph::new(Text::from(left_visible))
6396 .block(
6397 Block::default()
6398 .borders(Borders::ALL)
6399 .title(format!("Help - Commands{scroll_indicator}")),
6400 )
6401 .style(Style::default());
6402
6403 let right_paragraph = Paragraph::new(Text::from(right_visible))
6405 .block(
6406 Block::default()
6407 .borders(Borders::ALL)
6408 .title("Help - Navigation & Features"),
6409 )
6410 .style(Style::default());
6411
6412 f.render_widget(left_paragraph, chunks[0]);
6413 f.render_widget(right_paragraph, chunks[1]);
6414 }
6415
6416 fn render_debug(&self, f: &mut Frame, area: Rect) {
6417 <Self as DebugContext>::render_debug(self, f, area);
6418 }
6419
6420 fn render_pretty_query(&self, f: &mut Frame, area: Rect) {
6421 <Self as DebugContext>::render_pretty_query(self, f, area);
6422 }
6423
6424 fn render_history(&self, f: &mut Frame, area: Rect) {
6425 let history_search = self.state_container.history_search();
6427 let matches_empty = history_search.matches.is_empty();
6428 let search_query_empty = history_search.query.is_empty();
6429
6430 if matches_empty {
6431 let no_history = if search_query_empty {
6432 "No command history found.\nExecute some queries to build history."
6433 } else {
6434 "No matches found for your search.\nTry a different search term."
6435 };
6436
6437 let placeholder = Paragraph::new(no_history)
6438 .block(
6439 Block::default()
6440 .borders(Borders::ALL)
6441 .title("Command History"),
6442 )
6443 .style(Style::default().fg(Color::DarkGray));
6444 f.render_widget(placeholder, area);
6445 return;
6446 }
6447
6448 let chunks = Layout::default()
6450 .direction(Direction::Vertical)
6451 .constraints([
6452 Constraint::Percentage(50), Constraint::Percentage(50), ])
6455 .split(area);
6456
6457 self.render_history_list(f, chunks[0]);
6458 self.render_selected_command_preview(f, chunks[1]);
6459 }
6460
6461 fn render_history_list(&self, f: &mut Frame, area: Rect) {
6462 let history_search = self.state_container.history_search();
6464 let matches = history_search.matches.clone();
6465 let selected_index = history_search.selected_index;
6466 let match_count = matches.len();
6467
6468 let history_items: Vec<Line> = matches
6470 .iter()
6471 .enumerate()
6472 .map(|(i, history_match)| {
6473 let entry = &history_match.entry;
6474 let is_selected = i == selected_index;
6475
6476 let success_indicator = if entry.success { "✓" } else { "✗" };
6477 let time_ago = {
6478 let elapsed = chrono::Utc::now() - entry.timestamp;
6479 if elapsed.num_days() > 0 {
6480 format!("{}d", elapsed.num_days())
6481 } else if elapsed.num_hours() > 0 {
6482 format!("{}h", elapsed.num_hours())
6483 } else if elapsed.num_minutes() > 0 {
6484 format!("{}m", elapsed.num_minutes())
6485 } else {
6486 "now".to_string()
6487 }
6488 };
6489
6490 let terminal_width = area.width as usize;
6492 let metadata_space = 15; let available_for_command = terminal_width.saturating_sub(metadata_space).max(50);
6494
6495 let command_text = if entry.command.len() > available_for_command {
6496 format!(
6497 "{}…",
6498 &entry.command[..available_for_command.saturating_sub(1)]
6499 )
6500 } else {
6501 entry.command.clone()
6502 };
6503
6504 let line_text = format!(
6505 "{} {} {} {}x {}",
6506 if is_selected { "►" } else { " " },
6507 command_text,
6508 success_indicator,
6509 entry.execution_count,
6510 time_ago
6511 );
6512
6513 let mut style = Style::default();
6514 if is_selected {
6515 style = style.bg(Color::DarkGray).add_modifier(Modifier::BOLD);
6516 }
6517 if !entry.success {
6518 style = style.fg(Color::Red);
6519 }
6520
6521 if !history_match.indices.is_empty() && is_selected {
6523 style = style.fg(Color::Yellow);
6524 }
6525
6526 Line::from(line_text).style(style)
6527 })
6528 .collect();
6529
6530 let history_paragraph = Paragraph::new(history_items)
6531 .block(Block::default().borders(Borders::ALL).title(format!(
6532 "History ({match_count} matches) - j/k to navigate, Enter to select"
6533 )))
6534 .wrap(ratatui::widgets::Wrap { trim: false });
6535
6536 f.render_widget(history_paragraph, area);
6537 }
6538
6539 fn render_selected_command_preview(&self, f: &mut Frame, area: Rect) {
6540 let history_search = self.state_container.history_search();
6542 let selected_match = history_search
6543 .matches
6544 .get(history_search.selected_index)
6545 .cloned();
6546
6547 if let Some(selected_match) = selected_match {
6548 let entry = &selected_match.entry;
6549
6550 use crate::recursive_parser::format_sql_pretty_compact;
6552
6553 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);
6559
6560 let max_lines = area.height.saturating_sub(2) as usize; if pretty_lines.len() > max_lines && cols_per_line < 12 {
6563 pretty_lines = format_sql_pretty_compact(&entry.command, 15);
6565 }
6566
6567 let mut highlighted_lines = Vec::new();
6569 for line in pretty_lines {
6570 highlighted_lines.push(self.sql_highlighter.simple_sql_highlight(&line));
6571 }
6572
6573 let preview_text = Text::from(highlighted_lines);
6574
6575 let duration_text = entry
6576 .duration_ms
6577 .map_or_else(|| "?ms".to_string(), |d| format!("{d}ms"));
6578
6579 let success_text = if entry.success {
6580 "✓ Success"
6581 } else {
6582 "✗ Failed"
6583 };
6584
6585 let preview = Paragraph::new(preview_text)
6586 .block(Block::default().borders(Borders::ALL).title(format!(
6587 "Pretty SQL Preview: {} | {} | Used {}x",
6588 success_text, duration_text, entry.execution_count
6589 )))
6590 .scroll((0, 0)); f.render_widget(preview, area);
6593 } else {
6594 let empty_preview = Paragraph::new("No command selected")
6595 .block(Block::default().borders(Borders::ALL).title("Preview"))
6596 .style(Style::default().fg(Color::DarkGray));
6597 f.render_widget(empty_preview, area);
6598 }
6599 }
6600
6601 fn handle_column_stats_input(&mut self, key: crossterm::event::KeyEvent) -> Result<bool> {
6602 let mut ctx = crate::ui::input::input_handlers::StatsInputContext {
6604 buffer_manager: self.state_container.buffers_mut(),
6605 stats_widget: &mut self.stats_widget,
6606 shadow_state: &self.shadow_state,
6607 };
6608
6609 let result = crate::ui::input::input_handlers::handle_column_stats_input(&mut ctx, key)?;
6610
6611 if self.shadow_state.borrow().get_mode() == AppMode::Results {
6613 self.shadow_state
6614 .borrow_mut()
6615 .observe_mode_change(AppMode::Results, "column_stats_closed");
6616 }
6617
6618 Ok(result)
6619 }
6620
6621 fn handle_jump_to_row_input(&mut self, key: crossterm::event::KeyEvent) -> Result<bool> {
6622 match key.code {
6623 KeyCode::Enter => {
6624 let input = self.get_jump_to_row_input();
6626 self.complete_jump_to_row(&input);
6627 }
6628 _ => {
6629 self.process_jump_to_row_key(key);
6631 }
6632 }
6633 Ok(false)
6634 }
6635
6636 fn render_column_stats(&self, f: &mut Frame, area: Rect) {
6637 self.stats_widget.render(
6639 f,
6640 area,
6641 self.state_container
6642 .current_buffer()
6643 .expect("Buffer should exist"),
6644 );
6645 }
6646
6647 fn handle_execute_query(&mut self) -> Result<bool> {
6653 use crate::ui::state::state_coordinator::StateCoordinator;
6654
6655 let query = self.get_input_text().trim().to_string();
6656 debug!(target: "action", "Executing query: {}", query);
6657
6658 let should_exit = StateCoordinator::handle_execute_query_with_refs(
6660 &mut self.state_container,
6661 &self.shadow_state,
6662 &query,
6663 )?;
6664
6665 if should_exit {
6666 return Ok(true);
6667 }
6668
6669 if !query.is_empty() && !query.starts_with(':') {
6671 if let Err(e) = self.execute_query_v2(&query) {
6672 self.state_container
6673 .set_status_message(format!("Error executing query: {e}"));
6674 }
6675 }
6677
6678 Ok(false) }
6680
6681 fn handle_buffer_action(&mut self, action: BufferAction) -> Result<bool> {
6682 match action {
6683 BufferAction::NextBuffer => {
6684 let message = self
6685 .buffer_handler
6686 .next_buffer(self.state_container.buffers_mut());
6687 debug!("{}", message);
6688 self.sync_after_buffer_switch();
6690 Ok(false)
6691 }
6692 BufferAction::PreviousBuffer => {
6693 let message = self
6694 .buffer_handler
6695 .previous_buffer(self.state_container.buffers_mut());
6696 debug!("{}", message);
6697 self.sync_after_buffer_switch();
6699 Ok(false)
6700 }
6701 BufferAction::QuickSwitch => {
6702 let message = self
6703 .buffer_handler
6704 .quick_switch(self.state_container.buffers_mut());
6705 debug!("{}", message);
6706 self.sync_after_buffer_switch();
6708 Ok(false)
6709 }
6710 BufferAction::NewBuffer => {
6711 let message = self
6712 .buffer_handler
6713 .new_buffer(self.state_container.buffers_mut(), &self.config);
6714 debug!("{}", message);
6715 Ok(false)
6716 }
6717 BufferAction::CloseBuffer => {
6718 let (success, message) = self
6719 .buffer_handler
6720 .close_buffer(self.state_container.buffers_mut());
6721 debug!("{}", message);
6722 Ok(!success) }
6724 BufferAction::ListBuffers => {
6725 let buffer_list = self
6726 .buffer_handler
6727 .list_buffers(self.state_container.buffers());
6728 for line in &buffer_list {
6730 debug!("{}", line);
6731 }
6732 Ok(false)
6733 }
6734 BufferAction::SwitchToBuffer(buffer_index) => {
6735 let message = self
6736 .buffer_handler
6737 .switch_to_buffer(self.state_container.buffers_mut(), buffer_index);
6738 debug!("{}", message);
6739
6740 self.sync_after_buffer_switch();
6742
6743 Ok(false)
6744 }
6745 }
6746 }
6747
6748 fn handle_expand_asterisk(&mut self) -> Result<bool> {
6749 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
6750 if buffer.expand_asterisk(&self.hybrid_parser) {
6751 if buffer.get_edit_mode() == EditMode::SingleLine {
6753 let text = buffer.get_input_text();
6754 let cursor = buffer.get_input_cursor_position();
6755 self.set_input_text_with_cursor(text, cursor);
6756 }
6757 }
6758 }
6759 Ok(false)
6760 }
6761
6762 pub(crate) fn toggle_debug_mode(&mut self) {
6763 DebugContext::toggle_debug_mode(self);
6765 }
6766
6767 pub(crate) fn debug_generate_parser_info(&self, query: &str) -> String {
6772 self.hybrid_parser
6773 .get_detailed_debug_info(query, query.len())
6774 }
6775
6776 fn debug_generate_navigation_state(&self) -> String {
6777 let mut debug_info = String::new();
6778 debug_info.push_str("\n========== NAVIGATION DEBUG ==========\n");
6779 let current_column = self.state_container.get_current_column();
6780 let scroll_offset = self.state_container.get_scroll_offset();
6781 let nav_state = self.state_container.navigation();
6782
6783 debug_info.push_str(&format!("Buffer Column Position: {current_column}\n"));
6784 debug_info.push_str(&format!(
6785 "Buffer Scroll Offset: row={}, col={}\n",
6786 scroll_offset.0, scroll_offset.1
6787 ));
6788 debug_info.push_str(&format!(
6789 "NavigationState Column: {}\n",
6790 nav_state.selected_column
6791 ));
6792 debug_info.push_str(&format!(
6793 "NavigationState Row: {:?}\n",
6794 nav_state.selected_row
6795 ));
6796 debug_info.push_str(&format!(
6797 "NavigationState Scroll Offset: row={}, col={}\n",
6798 nav_state.scroll_offset.0, nav_state.scroll_offset.1
6799 ));
6800
6801 if current_column != nav_state.selected_column {
6803 debug_info.push_str(&format!(
6804 "⚠️ WARNING: Column mismatch! Buffer={}, Nav={}\n",
6805 current_column, nav_state.selected_column
6806 ));
6807 }
6808 if scroll_offset.1 != nav_state.scroll_offset.1 {
6809 debug_info.push_str(&format!(
6810 "⚠️ WARNING: Scroll column mismatch! Buffer={}, Nav={}\n",
6811 scroll_offset.1, nav_state.scroll_offset.1
6812 ));
6813 }
6814
6815 debug_info.push_str("\n--- Navigation Flow ---\n");
6816 debug_info.push_str(
6817 "(Enable RUST_LOG=sql_cli::ui::viewport_manager=debug,navigation=debug to see flow)\n",
6818 );
6819
6820 if let Some(dataview) = self.state_container.get_buffer_dataview() {
6822 let pinned_count = dataview.get_pinned_columns().len();
6823 let pinned_names = dataview.get_pinned_column_names();
6824 debug_info.push_str(&format!("Pinned Column Count: {pinned_count}\n"));
6825 if !pinned_names.is_empty() {
6826 debug_info.push_str(&format!("Pinned Column Names: {pinned_names:?}\n"));
6827 }
6828 debug_info.push_str(&format!("First Scrollable Column: {pinned_count}\n"));
6829
6830 if current_column < pinned_count {
6832 debug_info.push_str(&format!(
6833 "Current Position: PINNED area (column {current_column})\n"
6834 ));
6835 } else {
6836 debug_info.push_str(&format!(
6837 "Current Position: SCROLLABLE area (column {}, scrollable index {})\n",
6838 current_column,
6839 current_column - pinned_count
6840 ));
6841 }
6842
6843 let display_columns = dataview.get_display_columns();
6845 debug_info.push_str("\n--- COLUMN ORDERING ---\n");
6846 debug_info.push_str(&format!(
6847 "Display column order (first 10): {:?}\n",
6848 &display_columns[..display_columns.len().min(10)]
6849 ));
6850 if display_columns.len() > 10 {
6851 debug_info.push_str(&format!(
6852 "... and {} more columns\n",
6853 display_columns.len() - 10
6854 ));
6855 }
6856
6857 if let Some(display_idx) = display_columns
6859 .iter()
6860 .position(|&idx| idx == current_column)
6861 {
6862 debug_info.push_str(&format!(
6863 "Current column {} is at display index {}/{}\n",
6864 current_column,
6865 display_idx,
6866 display_columns.len()
6867 ));
6868
6869 if display_idx + 1 < display_columns.len() {
6871 let next_col = display_columns[display_idx + 1];
6872 debug_info.push_str(&format!(
6873 "Next 'l' press should move to column {} (display index {})\n",
6874 next_col,
6875 display_idx + 1
6876 ));
6877 } else {
6878 debug_info.push_str("Next 'l' press should wrap to first column\n");
6879 }
6880 } else {
6881 debug_info.push_str(&format!(
6882 "WARNING: Current column {current_column} not found in display order!\n"
6883 ));
6884 }
6885 }
6886 debug_info.push_str("==========================================\n");
6887 debug_info
6888 }
6889
6890 fn debug_generate_column_search_state(&self) -> String {
6891 let mut debug_info = String::new();
6892 let show_column_search = self.shadow_state.borrow().get_mode() == AppMode::ColumnSearch
6893 || !self.state_container.column_search().pattern.is_empty();
6894 if show_column_search {
6895 let column_search = self.state_container.column_search();
6896 debug_info.push_str("\n========== COLUMN SEARCH STATE ==========\n");
6897 debug_info.push_str(&format!("Pattern: '{}'\n", column_search.pattern));
6898 debug_info.push_str(&format!(
6899 "Matching Columns: {} found\n",
6900 column_search.matching_columns.len()
6901 ));
6902 if !column_search.matching_columns.is_empty() {
6903 debug_info.push_str("Matches:\n");
6904 for (idx, (col_idx, col_name)) in column_search.matching_columns.iter().enumerate()
6905 {
6906 let marker = if idx == column_search.current_match {
6907 " <--"
6908 } else {
6909 ""
6910 };
6911 debug_info
6912 .push_str(&format!(" [{idx}] {col_name} (index {col_idx}){marker}\n"));
6913 }
6914 }
6915 debug_info.push_str(&format!(
6916 "Current Match Index: {}\n",
6917 column_search.current_match
6918 ));
6919 debug_info.push_str(&format!(
6920 "Current Column: {}\n",
6921 self.state_container.get_current_column()
6922 ));
6923 debug_info.push_str("==========================================\n");
6924 }
6925 debug_info
6926 }
6927
6928 pub(crate) fn debug_generate_trace_logs(&self) -> String {
6929 let mut debug_info = String::from("\n========== TRACE LOGS ==========\n");
6930 debug_info.push_str("(Most recent at bottom, last 100 entries)\n");
6931
6932 if let Some(ref log_buffer) = self.log_buffer {
6933 let recent_logs = log_buffer.get_recent(100);
6934 for entry in recent_logs {
6935 debug_info.push_str(&entry.format_for_display());
6936 debug_info.push('\n');
6937 }
6938 debug_info.push_str(&format!("Total log entries: {}\n", log_buffer.len()));
6939 } else {
6940 debug_info.push_str("Log buffer not initialized\n");
6941 }
6942 debug_info.push_str("================================\n");
6943
6944 debug_info
6945 }
6946
6947 pub(crate) fn debug_generate_state_logs(&self) -> String {
6949 let mut debug_info = String::new();
6950
6951 if let Some(ref debug_service) = self.debug_service {
6952 debug_info.push_str("\n========== STATE CHANGE LOGS ==========\n");
6953 debug_info.push_str("(Most recent at bottom, from DebugService)\n");
6954 let debug_entries = debug_service.get_entries();
6955 let recent = debug_entries.iter().rev().take(50).rev();
6956 for entry in recent {
6957 debug_info.push_str(&format!(
6958 "[{}] {:?} [{}]: {}\n",
6959 entry.timestamp, entry.level, entry.component, entry.message
6960 ));
6961 }
6962 debug_info.push_str(&format!(
6963 "Total state change entries: {}\n",
6964 debug_entries.len()
6965 ));
6966 debug_info.push_str("================================\n");
6967 } else {
6968 debug_info.push_str("\n========== STATE CHANGE LOGS ==========\n");
6969 debug_info.push_str("DebugService not available (service_container is None)\n");
6970 debug_info.push_str("================================\n");
6971 }
6972
6973 debug_info
6974 }
6975
6976 pub(crate) fn debug_extract_timing(&self, s: &str) -> Option<f64> {
6978 crate::ui::rendering::ui_layout_utils::extract_timing_from_debug_string(s)
6979 }
6980
6981 fn show_pretty_query(&mut self) {
6982 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
6983 self.shadow_state.borrow_mut().set_mode(
6984 AppMode::PrettyQuery,
6985 buffer,
6986 "pretty_query_show",
6987 );
6988 let query = buffer.get_input_text();
6989 self.debug_widget.generate_pretty_sql(&query);
6990 }
6991 }
6992
6993 fn add_global_indicators(&self, spans: &mut Vec<Span>) {
6995 if self.key_sequence_renderer.has_content() {
6996 let key_display = self.key_sequence_renderer.get_display();
6997 if !key_display.is_empty() {
6998 spans.push(Span::raw(" | Keys: "));
6999 spans.push(Span::styled(
7000 key_display,
7001 Style::default()
7002 .fg(Color::Cyan)
7003 .add_modifier(Modifier::ITALIC),
7004 ));
7005 }
7006 }
7007 }
7008
7009 fn add_query_source_indicator(&self, spans: &mut Vec<Span>) {
7010 if let Some(source) = self.state_container.get_last_query_source() {
7011 spans.push(Span::raw(" | "));
7012 let (icon, label, color) = match source.as_str() {
7013 "cache" => (
7014 &self.config.display.icons.cache,
7015 "CACHE".to_string(),
7016 Color::Cyan,
7017 ),
7018 "file" | "FileDataSource" => (
7019 &self.config.display.icons.file,
7020 "FILE".to_string(),
7021 Color::Green,
7022 ),
7023 "SqlServerDataSource" => (
7024 &self.config.display.icons.database,
7025 "SQL".to_string(),
7026 Color::Blue,
7027 ),
7028 "PublicApiDataSource" => (
7029 &self.config.display.icons.api,
7030 "API".to_string(),
7031 Color::Yellow,
7032 ),
7033 _ => (
7034 &self.config.display.icons.api,
7035 source.clone(),
7036 Color::Magenta,
7037 ),
7038 };
7039 spans.push(Span::raw(format!("{icon} ")));
7040 spans.push(Span::styled(label, Style::default().fg(color)));
7041 }
7042 }
7043
7044 fn add_case_sensitivity_indicator(&self, spans: &mut Vec<Span>) {
7045 let case_insensitive = self.state_container.is_case_insensitive();
7046 if case_insensitive {
7047 spans.push(Span::raw(" | "));
7048 let icon = self.config.display.icons.case_insensitive.clone();
7049 spans.push(Span::styled(
7050 format!("{icon} CASE"),
7051 Style::default().fg(Color::Cyan),
7052 ));
7053 }
7054 }
7055
7056 fn add_column_packing_indicator(&self, spans: &mut Vec<Span>) {
7057 if let Some(ref viewport_manager) = *self.viewport_manager.borrow() {
7058 let packing_mode = viewport_manager.get_packing_mode();
7059 spans.push(Span::raw(" | "));
7060 let (text, color) = match packing_mode {
7061 ColumnPackingMode::DataFocus => ("DATA", Color::Cyan),
7062 ColumnPackingMode::HeaderFocus => ("HEADER", Color::Yellow),
7063 ColumnPackingMode::Balanced => ("BALANCED", Color::Green),
7064 };
7065 spans.push(Span::styled(text, Style::default().fg(color)));
7066 }
7067 }
7068
7069 fn add_status_message(&self, spans: &mut Vec<Span>) {
7070 let status_msg = self.state_container.get_buffer_status_message();
7071 if !status_msg.is_empty() {
7072 spans.push(Span::raw(" | "));
7073 spans.push(Span::styled(
7074 status_msg,
7075 Style::default()
7076 .fg(Color::Yellow)
7077 .add_modifier(Modifier::BOLD),
7078 ));
7079 }
7080 }
7081
7082 fn get_help_text_for_mode(&self) -> &str {
7083 match self.shadow_state.borrow().get_mode() {
7084 AppMode::Command => "Enter:Run | Tab:Complete | ↓:Results | F1:Help",
7085 AppMode::Results => match self.get_selection_mode() {
7086 SelectionMode::Cell => "v:Row mode | y:Yank cell | ↑:Edit | F1:Help",
7087 SelectionMode::Row => "v:Cell mode | y:Yank | f:Filter | ↑:Edit | F1:Help",
7088 SelectionMode::Column => "v:Cell mode | y:Yank col | ↑:Edit | F1:Help",
7089 },
7090 AppMode::Search | AppMode::Filter | AppMode::FuzzyFilter | AppMode::ColumnSearch => {
7091 "Enter:Apply | Esc:Cancel"
7092 }
7093 AppMode::Help | AppMode::Debug | AppMode::PrettyQuery | AppMode::ColumnStats => {
7094 "Esc:Close"
7095 }
7096 AppMode::History => "Enter:Select | Esc:Cancel",
7097 AppMode::JumpToRow => "Enter:Jump | Esc:Cancel",
7098 }
7099 }
7100
7101 fn add_shadow_state_display(&self, spans: &mut Vec<Span>) {
7102 let shadow_display = self.shadow_state.borrow().status_display();
7103 spans.push(Span::raw(" "));
7104 spans.push(Span::styled(
7105 shadow_display,
7106 Style::default().fg(Color::Cyan),
7107 ));
7108 }
7109
7110 fn add_help_text_display<'a>(&self, spans: &mut Vec<Span<'a>>, help_text: &'a str, area: Rect) {
7112 let current_length: usize = spans.iter().map(|s| s.content.len()).sum();
7113 let available_width = area.width.saturating_sub(TABLE_BORDER_WIDTH) as usize;
7114 let help_length = help_text.len();
7115
7116 if current_length + help_length + 3 < available_width {
7117 let padding = available_width - current_length - help_length - 3;
7118 spans.push(Span::raw(" ".repeat(padding)));
7119 spans.push(Span::raw(" | "));
7120 spans.push(Span::styled(
7121 help_text,
7122 Style::default().fg(Color::DarkGray),
7123 ));
7124 }
7125 }
7126}
7127
7128impl ActionHandlerContext for EnhancedTuiApp {
7130 fn previous_row(&mut self) {
7132 <Self as NavigationBehavior>::previous_row(self);
7133
7134 let current_row = self.state_container.navigation().selected_row;
7136 let current_col = self.state_container.navigation().selected_column;
7137 self.table_widget_manager
7138 .borrow_mut()
7139 .navigate_to(current_row, current_col);
7140 info!(target: "navigation", "previous_row: Updated TableWidgetManager to ({}, {})", current_row, current_col);
7141 }
7142
7143 fn next_row(&mut self) {
7144 info!(target: "navigation", "next_row called - calling NavigationBehavior::next_row");
7145 <Self as NavigationBehavior>::next_row(self);
7146
7147 let current_row = self.state_container.navigation().selected_row;
7149 let current_col = self.state_container.navigation().selected_column;
7150 self.table_widget_manager
7151 .borrow_mut()
7152 .navigate_to(current_row, current_col);
7153 info!(target: "navigation", "next_row: Updated TableWidgetManager to ({}, {})", current_row, current_col);
7154 }
7155
7156 fn move_column_left(&mut self) {
7157 <Self as ColumnBehavior>::move_column_left(self);
7158
7159 let current_row = self.state_container.navigation().selected_row;
7161 let current_col = self.state_container.navigation().selected_column;
7162 self.table_widget_manager
7163 .borrow_mut()
7164 .navigate_to(current_row, current_col);
7165 info!(target: "navigation", "move_column_left: Updated TableWidgetManager to ({}, {})", current_row, current_col);
7166 }
7167
7168 fn move_column_right(&mut self) {
7169 <Self as ColumnBehavior>::move_column_right(self);
7170
7171 let current_row = self.state_container.navigation().selected_row;
7173 let current_col = self.state_container.navigation().selected_column;
7174 self.table_widget_manager
7175 .borrow_mut()
7176 .navigate_to(current_row, current_col);
7177 info!(target: "navigation", "move_column_right: Updated TableWidgetManager to ({}, {})", current_row, current_col);
7178 }
7179
7180 fn page_up(&mut self) {
7181 <Self as NavigationBehavior>::page_up(self);
7182 }
7183
7184 fn page_down(&mut self) {
7185 <Self as NavigationBehavior>::page_down(self);
7186 }
7187
7188 fn goto_first_row(&mut self) {
7189 use crate::ui::state::state_coordinator::StateCoordinator;
7190
7191 <Self as NavigationBehavior>::goto_first_row(self);
7193
7194 StateCoordinator::goto_first_row_with_refs(
7196 &mut self.state_container,
7197 Some(&self.vim_search_adapter),
7198 Some(&self.viewport_manager),
7199 );
7200 }
7201
7202 fn goto_last_row(&mut self) {
7203 use crate::ui::state::state_coordinator::StateCoordinator;
7204
7205 <Self as NavigationBehavior>::goto_last_row(self);
7207
7208 StateCoordinator::goto_last_row_with_refs(&mut self.state_container);
7210 }
7211
7212 fn goto_first_column(&mut self) {
7213 <Self as ColumnBehavior>::goto_first_column(self);
7214 }
7215
7216 fn goto_last_column(&mut self) {
7217 <Self as ColumnBehavior>::goto_last_column(self);
7218 }
7219
7220 fn goto_row(&mut self, row: usize) {
7221 use crate::ui::state::state_coordinator::StateCoordinator;
7222
7223 <Self as NavigationBehavior>::goto_line(self, row + 1); StateCoordinator::goto_row_with_refs(&mut self.state_container, row);
7228 }
7229
7230 fn goto_column(&mut self, col: usize) {
7231 let current_col = self.state_container.get_current_column();
7234 if col < current_col {
7235 for _ in 0..(current_col - col) {
7236 <Self as ColumnBehavior>::move_column_left(self);
7237 }
7238 } else if col > current_col {
7239 for _ in 0..(col - current_col) {
7240 <Self as ColumnBehavior>::move_column_right(self);
7241 }
7242 }
7243 }
7244
7245 fn set_mode(&mut self, mode: AppMode) {
7247 self.set_mode_via_shadow_state(mode, "action_handler");
7249 }
7250
7251 fn get_mode(&self) -> AppMode {
7252 self.shadow_state.borrow().get_mode()
7253 }
7254
7255 fn set_status_message(&mut self, message: String) {
7256 self.state_container.set_status_message(message);
7257 }
7258
7259 fn toggle_column_pin(&mut self) {
7261 self.toggle_column_pin_impl();
7263 }
7264
7265 fn hide_current_column(&mut self) {
7266 <Self as ColumnBehavior>::hide_current_column(self);
7267 }
7268
7269 fn unhide_all_columns(&mut self) {
7270 <Self as ColumnBehavior>::unhide_all_columns(self);
7271 }
7272
7273 fn clear_all_pinned_columns(&mut self) {
7274 self.clear_all_pinned_columns_impl();
7276 }
7277
7278 fn export_to_csv(&mut self) {
7280 self.state_container
7282 .set_status_message("CSV export not yet implemented".to_string());
7283 }
7284
7285 fn export_to_json(&mut self) {
7286 self.state_container
7288 .set_status_message("JSON export not yet implemented".to_string());
7289 }
7290
7291 fn yank_cell(&mut self) {
7293 YankBehavior::yank_cell(self);
7294 }
7295
7296 fn yank_row(&mut self) {
7297 YankBehavior::yank_row(self);
7298 }
7299
7300 fn yank_column(&mut self) {
7301 YankBehavior::yank_column(self);
7302 }
7303
7304 fn yank_all(&mut self) {
7305 YankBehavior::yank_all(self);
7306 }
7307
7308 fn yank_query(&mut self) {
7309 YankBehavior::yank_query(self);
7310 }
7311
7312 fn toggle_selection_mode(&mut self) {
7314 self.state_container.toggle_selection_mode();
7315 let new_mode = self.state_container.get_selection_mode();
7316 let msg = match new_mode {
7317 SelectionMode::Cell => "Cell mode - Navigate to select individual cells",
7318 SelectionMode::Row => "Row mode - Navigate to select rows",
7319 SelectionMode::Column => "Column mode - Navigate to select columns",
7320 };
7321 self.state_container.set_status_message(msg.to_string());
7322 }
7323
7324 fn toggle_row_numbers(&mut self) {
7325 let current = self.state_container.is_show_row_numbers();
7326 self.state_container.set_show_row_numbers(!current);
7327 let message = if current {
7328 "Row numbers: OFF".to_string()
7329 } else {
7330 "Row numbers: ON (showing line numbers)".to_string()
7331 };
7332 self.state_container.set_status_message(message);
7333 self.calculate_optimal_column_widths();
7335 }
7336
7337 fn toggle_compact_mode(&mut self) {
7338 let current_mode = self.state_container.is_compact_mode();
7339 self.state_container.set_compact_mode(!current_mode);
7340 let message = if current_mode {
7341 "Compact mode disabled"
7342 } else {
7343 "Compact mode enabled"
7344 };
7345 self.state_container.set_status_message(message.to_string());
7346 }
7347
7348 fn toggle_case_insensitive(&mut self) {
7349 let current = self.state_container.is_case_insensitive();
7350 self.state_container.set_case_insensitive(!current);
7351 self.state_container.set_status_message(format!(
7352 "Case-insensitive string comparisons: {}",
7353 if current { "OFF" } else { "ON" }
7354 ));
7355 }
7356
7357 fn toggle_key_indicator(&mut self) {
7358 let enabled = !self.key_indicator.enabled;
7359 self.key_indicator.set_enabled(enabled);
7360 self.key_sequence_renderer.set_enabled(enabled);
7361 self.state_container.set_status_message(format!(
7362 "Key press indicator {}",
7363 if enabled { "enabled" } else { "disabled" }
7364 ));
7365 }
7366
7367 fn clear_filter(&mut self) {
7369 if let Some(dataview) = self.state_container.get_buffer_dataview() {
7371 if dataview.has_filter() {
7372 if let Some(dataview_mut) = self.state_container.get_buffer_dataview_mut() {
7374 dataview_mut.clear_filter();
7375 self.state_container
7376 .set_status_message("Filter cleared".to_string());
7377 }
7378
7379 self.sync_dataview_to_managers();
7382 } else {
7383 self.state_container
7384 .set_status_message("No active filter to clear".to_string());
7385 }
7386 } else {
7387 self.state_container
7388 .set_status_message("No data loaded".to_string());
7389 }
7390 }
7391
7392 fn clear_line(&mut self) {
7393 self.state_container.clear_line();
7394 }
7395
7396 fn start_search(&mut self) {
7398 self.start_vim_search();
7399 }
7400
7401 fn start_column_search(&mut self) {
7402 self.enter_search_mode(SearchMode::ColumnSearch);
7403 }
7404
7405 fn start_filter(&mut self) {
7406 self.enter_search_mode(SearchMode::Filter);
7407 }
7408
7409 fn start_fuzzy_filter(&mut self) {
7410 self.enter_search_mode(SearchMode::FuzzyFilter);
7411 }
7412
7413 fn exit_current_mode(&mut self) {
7414 let mode = self.shadow_state.borrow().get_mode();
7422 match mode {
7423 AppMode::Results => {
7424 self.set_mode_via_shadow_state(AppMode::Command, "escape_from_results");
7427 }
7428 AppMode::Command => {
7429 self.set_mode_via_shadow_state(AppMode::Results, "escape_from_command");
7430 }
7431 AppMode::Help => {
7432 self.set_mode_via_shadow_state(AppMode::Results, "escape_from_help");
7433 }
7434 AppMode::JumpToRow => {
7435 self.set_mode_via_shadow_state(AppMode::Results, "escape_from_jump_to_row");
7436 <Self as InputBehavior>::clear_jump_to_row_input(self);
7437 self.state_container.jump_to_row_mut().is_active = false;
7439 self.state_container
7440 .set_status_message("Jump to row cancelled".to_string());
7441 }
7442 _ => {
7443 self.set_mode_via_shadow_state(AppMode::Results, "escape_to_results");
7445 }
7446 }
7447 }
7448
7449 fn toggle_debug_mode(&mut self) {
7450 <Self as DebugContext>::toggle_debug_mode(self);
7452 }
7453
7454 fn move_current_column_left(&mut self) {
7456 <Self as ColumnBehavior>::move_current_column_left(self);
7457 }
7458
7459 fn move_current_column_right(&mut self) {
7460 <Self as ColumnBehavior>::move_current_column_right(self);
7461 }
7462
7463 fn next_search_match(&mut self) {
7465 self.vim_search_next();
7466 }
7467
7468 fn previous_search_match(&mut self) {
7469 if self.vim_search_adapter.borrow().is_active()
7470 || self.vim_search_adapter.borrow().get_pattern().is_some()
7471 {
7472 self.vim_search_previous();
7473 }
7474 }
7475
7476 fn show_column_statistics(&mut self) {
7478 self.calculate_column_statistics();
7479 }
7480
7481 fn cycle_column_packing(&mut self) {
7482 let message = {
7483 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
7484 let viewport_manager = viewport_manager_borrow
7485 .as_mut()
7486 .expect("ViewportManager must exist");
7487 let new_mode = viewport_manager.cycle_packing_mode();
7488 format!("Column packing: {}", new_mode.display_name())
7489 };
7490 self.state_container.set_status_message(message);
7491 }
7492
7493 fn navigate_to_viewport_top(&mut self) {
7495 let result = {
7496 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
7497 (*viewport_manager_borrow)
7498 .as_mut()
7499 .map(super::viewport_manager::ViewportManager::navigate_to_viewport_top)
7500 };
7501
7502 if let Some(result) = result {
7503 self.sync_navigation_with_viewport();
7505
7506 self.state_container
7508 .set_selected_row(Some(result.row_position));
7509
7510 if result.viewport_changed {
7512 let scroll_offset = self.state_container.navigation().scroll_offset;
7513 self.state_container.set_scroll_offset(scroll_offset);
7514 }
7515 }
7516 }
7517
7518 fn navigate_to_viewport_middle(&mut self) {
7519 let result = {
7520 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
7521 (*viewport_manager_borrow)
7522 .as_mut()
7523 .map(super::viewport_manager::ViewportManager::navigate_to_viewport_middle)
7524 };
7525
7526 if let Some(result) = result {
7527 self.sync_navigation_with_viewport();
7529
7530 self.state_container
7532 .set_selected_row(Some(result.row_position));
7533
7534 if result.viewport_changed {
7536 let scroll_offset = self.state_container.navigation().scroll_offset;
7537 self.state_container.set_scroll_offset(scroll_offset);
7538 }
7539 }
7540 }
7541
7542 fn navigate_to_viewport_bottom(&mut self) {
7543 let result = {
7544 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
7545 (*viewport_manager_borrow)
7546 .as_mut()
7547 .map(super::viewport_manager::ViewportManager::navigate_to_viewport_bottom)
7548 };
7549
7550 if let Some(result) = result {
7551 self.sync_navigation_with_viewport();
7553
7554 self.state_container
7556 .set_selected_row(Some(result.row_position));
7557
7558 if result.viewport_changed {
7560 let scroll_offset = self.state_container.navigation().scroll_offset;
7561 self.state_container.set_scroll_offset(scroll_offset);
7562 }
7563 }
7564 }
7565
7566 fn move_input_cursor_left(&mut self) {
7568 self.state_container.move_input_cursor_left();
7569 }
7570
7571 fn move_input_cursor_right(&mut self) {
7572 self.state_container.move_input_cursor_right();
7573 }
7574
7575 fn move_input_cursor_home(&mut self) {
7576 self.state_container.set_input_cursor_position(0);
7577 }
7578
7579 fn move_input_cursor_end(&mut self) {
7580 let text_len = self.state_container.get_input_text().chars().count();
7581 self.state_container.set_input_cursor_position(text_len);
7582 }
7583
7584 fn backspace(&mut self) {
7585 self.state_container.backspace();
7586 }
7587
7588 fn delete(&mut self) {
7589 self.state_container.delete();
7590 }
7591
7592 fn undo(&mut self) {
7593 self.state_container.perform_undo();
7594 }
7595
7596 fn redo(&mut self) {
7597 self.state_container.perform_redo();
7598 }
7599
7600 fn start_jump_to_row(&mut self) {
7601 self.state_container.set_mode(AppMode::JumpToRow);
7602 self.shadow_state
7603 .borrow_mut()
7604 .observe_mode_change(AppMode::JumpToRow, "jump_to_row_requested");
7605 <Self as InputBehavior>::clear_jump_to_row_input(self);
7606
7607 self.state_container.jump_to_row_mut().is_active = true;
7609
7610 self.state_container
7611 .set_status_message("Enter row number (1-based):".to_string());
7612 }
7613
7614 fn clear_jump_to_row_input(&mut self) {
7615 <Self as InputBehavior>::clear_jump_to_row_input(self);
7616 }
7617
7618 fn toggle_cursor_lock(&mut self) {
7619 let is_locked = {
7621 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
7622 if let Some(ref mut viewport_manager) = *viewport_manager_borrow {
7623 viewport_manager.toggle_cursor_lock();
7624 Some(viewport_manager.is_cursor_locked())
7625 } else {
7626 None
7627 }
7628 };
7629
7630 if let Some(is_locked) = is_locked {
7631 let msg = if is_locked {
7632 "Cursor lock ON - cursor stays in viewport position while scrolling"
7633 } else {
7634 "Cursor lock OFF"
7635 };
7636 self.state_container.set_status_message(msg.to_string());
7637
7638 info!(target: "shadow_state",
7640 "Cursor lock toggled: {} (in {:?} mode)",
7641 if is_locked { "ON" } else { "OFF" },
7642 self.shadow_state.borrow().get_mode()
7643 );
7644 }
7645 }
7646
7647 fn toggle_viewport_lock(&mut self) {
7648 let is_locked = {
7650 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
7651 if let Some(ref mut viewport_manager) = *viewport_manager_borrow {
7652 viewport_manager.toggle_viewport_lock();
7653 Some(viewport_manager.is_viewport_locked())
7654 } else {
7655 None
7656 }
7657 };
7658
7659 if let Some(is_locked) = is_locked {
7660 let msg = if is_locked {
7661 "Viewport lock ON - navigation constrained to current viewport"
7662 } else {
7663 "Viewport lock OFF"
7664 };
7665 self.state_container.set_status_message(msg.to_string());
7666
7667 info!(target: "shadow_state",
7669 "Viewport lock toggled: {} (in {:?} mode)",
7670 if is_locked { "ON" } else { "OFF" },
7671 self.shadow_state.borrow().get_mode()
7672 );
7673 }
7674 }
7675
7676 fn show_debug_info(&mut self) {
7678 <Self as DebugContext>::toggle_debug_mode(self);
7679 }
7680
7681 fn show_pretty_query(&mut self) {
7682 self.show_pretty_query();
7683 }
7684
7685 fn show_help(&mut self) {
7686 self.state_container.set_help_visible(true);
7687 self.set_mode_via_shadow_state(AppMode::Help, "help_requested");
7688 self.help_widget.on_enter();
7689 }
7690
7691 fn kill_line(&mut self) {
7693 use crate::ui::traits::input_ops::InputBehavior;
7694 InputBehavior::kill_line(self);
7695 let message = if self.state_container.is_kill_ring_empty() {
7696 "Kill line - nothing to kill".to_string()
7697 } else {
7698 let kill_ring = self.state_container.get_kill_ring();
7699 format!(
7700 "Killed to end of line - {} chars in kill ring",
7701 kill_ring.len()
7702 )
7703 };
7704 self.state_container.set_status_message(message);
7705 }
7706
7707 fn kill_line_backward(&mut self) {
7708 use crate::ui::traits::input_ops::InputBehavior;
7709 InputBehavior::kill_line_backward(self);
7710 let message = if self.state_container.is_kill_ring_empty() {
7711 "Kill line backward - nothing to kill".to_string()
7712 } else {
7713 let kill_ring = self.state_container.get_kill_ring();
7714 format!(
7715 "Killed to beginning of line - {} chars in kill ring",
7716 kill_ring.len()
7717 )
7718 };
7719 self.state_container.set_status_message(message);
7720 }
7721
7722 fn delete_word_backward(&mut self) {
7723 use crate::ui::traits::input_ops::InputBehavior;
7724 InputBehavior::delete_word_backward(self);
7725 }
7726
7727 fn delete_word_forward(&mut self) {
7728 use crate::ui::traits::input_ops::InputBehavior;
7729 InputBehavior::delete_word_forward(self);
7730 }
7731
7732 fn jump_to_prev_token(&mut self) {
7733 use crate::ui::traits::input_ops::InputBehavior;
7734 InputBehavior::jump_to_prev_token(self);
7735 }
7736
7737 fn jump_to_next_token(&mut self) {
7738 use crate::ui::traits::input_ops::InputBehavior;
7739 InputBehavior::jump_to_next_token(self);
7740 }
7741
7742 fn expand_asterisk(&mut self) {
7743 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
7744 if buffer.expand_asterisk(&self.hybrid_parser) {
7745 if buffer.get_edit_mode() == EditMode::SingleLine {
7747 let text = buffer.get_input_text();
7748 let cursor = buffer.get_input_cursor_position();
7749 self.set_input_text_with_cursor(text, cursor);
7750 }
7751 }
7752 }
7753 }
7754
7755 fn expand_asterisk_visible(&mut self) {
7756 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
7757 if buffer.expand_asterisk_visible() {
7758 if buffer.get_edit_mode() == EditMode::SingleLine {
7760 let text = buffer.get_input_text();
7761 let cursor = buffer.get_input_cursor_position();
7762 self.set_input_text_with_cursor(text, cursor);
7763 }
7764 }
7765 }
7766 }
7767
7768 fn previous_history_command(&mut self) {
7769 let history_entries = self
7770 .state_container
7771 .command_history()
7772 .get_navigation_entries();
7773 let history_commands: Vec<String> =
7774 history_entries.iter().map(|e| e.command.clone()).collect();
7775
7776 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
7777 if buffer.navigate_history_up(&history_commands) {
7778 self.sync_all_input_states();
7779 self.state_container
7780 .set_status_message("Previous command from history".to_string());
7781 }
7782 }
7783 }
7784
7785 fn next_history_command(&mut self) {
7786 let history_entries = self
7787 .state_container
7788 .command_history()
7789 .get_navigation_entries();
7790 let history_commands: Vec<String> =
7791 history_entries.iter().map(|e| e.command.clone()).collect();
7792
7793 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
7794 if buffer.navigate_history_down(&history_commands) {
7795 self.sync_all_input_states();
7796 self.state_container
7797 .set_status_message("Next command from history".to_string());
7798 }
7799 }
7800 }
7801}
7802
7803impl NavigationBehavior for EnhancedTuiApp {
7805 fn viewport_manager(&self) -> &RefCell<Option<ViewportManager>> {
7806 &self.viewport_manager
7807 }
7808
7809 fn buffer_mut(&mut self) -> &mut dyn BufferAPI {
7810 self.state_container
7811 .current_buffer_mut()
7812 .expect("Buffer should exist")
7813 }
7814
7815 fn buffer(&self) -> &dyn BufferAPI {
7816 self.state_container
7817 .current_buffer()
7818 .expect("Buffer should exist")
7819 }
7820
7821 fn state_container(&self) -> &AppStateContainer {
7822 &self.state_container
7823 }
7824
7825 fn state_container_mut(&mut self) -> &mut AppStateContainer {
7826 &mut self.state_container
7827 }
7828
7829 fn get_row_count(&self) -> usize {
7830 self.get_row_count()
7831 }
7832
7833 fn set_mode_with_sync(&mut self, mode: AppMode, trigger: &str) {
7834 self.set_mode_via_shadow_state(mode, trigger);
7836 }
7837}
7838
7839impl ColumnBehavior for EnhancedTuiApp {
7841 fn viewport_manager(&self) -> &RefCell<Option<ViewportManager>> {
7842 &self.viewport_manager
7843 }
7844
7845 fn buffer_mut(&mut self) -> &mut dyn BufferAPI {
7846 self.state_container
7847 .current_buffer_mut()
7848 .expect("Buffer should exist")
7849 }
7850
7851 fn buffer(&self) -> &dyn BufferAPI {
7852 self.state_container
7853 .current_buffer()
7854 .expect("Buffer should exist")
7855 }
7856
7857 fn state_container(&self) -> &AppStateContainer {
7858 &self.state_container
7859 }
7860
7861 fn is_in_results_mode(&self) -> bool {
7862 self.shadow_state.borrow().is_in_results_mode()
7863 }
7864}
7865
7866impl InputBehavior for EnhancedTuiApp {
7867 fn buffer_manager(&mut self) -> &mut BufferManager {
7868 self.state_container.buffers_mut()
7869 }
7870
7871 fn cursor_manager(&mut self) -> &mut CursorManager {
7872 &mut self.cursor_manager
7873 }
7874
7875 fn set_input_text_with_cursor(&mut self, text: String, cursor: usize) {
7876 self.set_input_text_with_cursor(text, cursor);
7877 }
7878
7879 fn state_container(&self) -> &AppStateContainer {
7880 &self.state_container
7881 }
7882
7883 fn state_container_mut(&mut self) -> &mut AppStateContainer {
7884 &mut self.state_container
7885 }
7886
7887 fn buffer_mut(&mut self) -> &mut dyn BufferAPI {
7888 self.state_container
7889 .current_buffer_mut()
7890 .expect("Buffer should exist")
7891 }
7892
7893 fn set_mode_with_sync(&mut self, mode: AppMode, trigger: &str) {
7894 self.set_mode_via_shadow_state(mode, trigger);
7896 }
7897}
7898
7899impl YankBehavior for EnhancedTuiApp {
7900 fn buffer(&self) -> &dyn BufferAPI {
7901 self.state_container
7902 .current_buffer()
7903 .expect("Buffer should exist")
7904 }
7905
7906 fn buffer_mut(&mut self) -> &mut dyn BufferAPI {
7907 self.state_container
7908 .current_buffer_mut()
7909 .expect("Buffer should exist")
7910 }
7911
7912 fn state_container(&self) -> &AppStateContainer {
7913 &self.state_container
7914 }
7915
7916 fn set_status_message(&mut self, message: String) {
7917 self.state_container.set_status_message(message);
7918 }
7919
7920 fn set_error_status(&mut self, prefix: &str, error: anyhow::Error) {
7921 self.set_error_status(prefix, error);
7922 }
7923}
7924
7925impl BufferManagementBehavior for EnhancedTuiApp {
7926 fn buffer_manager(&mut self) -> &mut BufferManager {
7927 self.state_container.buffers_mut()
7928 }
7929
7930 fn buffer_handler(&mut self) -> &mut BufferHandler {
7931 &mut self.buffer_handler
7932 }
7933
7934 fn buffer(&self) -> &dyn BufferAPI {
7935 self.state_container
7936 .current_buffer()
7937 .expect("Buffer should exist")
7938 }
7939
7940 fn buffer_mut(&mut self) -> &mut dyn BufferAPI {
7941 self.state_container
7942 .current_buffer_mut()
7943 .expect("Buffer should exist")
7944 }
7945
7946 fn config(&self) -> &Config {
7947 &self.config
7948 }
7949
7950 fn cursor_manager(&mut self) -> &mut CursorManager {
7951 &mut self.cursor_manager
7952 }
7953
7954 fn set_input_text_with_cursor(&mut self, text: String, cursor: usize) {
7955 self.set_input_text_with_cursor(text, cursor);
7956 }
7957
7958 fn next_buffer(&mut self) -> String {
7959 self.save_viewport_to_current_buffer();
7961
7962 let result = self
7963 .buffer_handler
7964 .next_buffer(self.state_container.buffers_mut());
7965
7966 self.sync_after_buffer_switch();
7968
7969 result
7970 }
7971
7972 fn previous_buffer(&mut self) -> String {
7973 self.save_viewport_to_current_buffer();
7975
7976 let result = self
7977 .buffer_handler
7978 .previous_buffer(self.state_container.buffers_mut());
7979
7980 self.sync_after_buffer_switch();
7982
7983 result
7984 }
7985
7986 fn quick_switch_buffer(&mut self) -> String {
7987 self.save_viewport_to_current_buffer();
7989
7990 let result = self
7991 .buffer_handler
7992 .quick_switch(self.state_container.buffers_mut());
7993
7994 self.sync_after_buffer_switch();
7996
7997 result
7998 }
7999
8000 fn close_buffer(&mut self) -> (bool, String) {
8001 self.buffer_handler
8002 .close_buffer(self.state_container.buffers_mut())
8003 }
8004
8005 fn switch_to_buffer(&mut self, index: usize) -> String {
8006 self.save_viewport_to_current_buffer();
8008
8009 let result = self
8011 .buffer_handler
8012 .switch_to_buffer(self.state_container.buffers_mut(), index);
8013
8014 self.sync_after_buffer_switch();
8016
8017 result
8018 }
8019
8020 fn buffer_count(&self) -> usize {
8021 self.state_container.buffers().all_buffers().len()
8022 }
8023
8024 fn current_buffer_index(&self) -> usize {
8025 self.state_container.buffers().current_index()
8026 }
8027}
8028
8029pub fn run_enhanced_tui_multi(api_url: &str, data_files: Vec<&str>) -> Result<()> {
8030 let original_hook = std::panic::take_hook();
8032 std::panic::set_hook(Box::new(move |panic_info| {
8033 let _ = disable_raw_mode();
8035 let _ = execute!(
8036 io::stdout(),
8037 LeaveAlternateScreen,
8038 DisableMouseCapture,
8039 crossterm::cursor::Show
8040 );
8041
8042 original_hook(panic_info);
8044 }));
8045
8046 let app = if data_files.is_empty() {
8047 EnhancedTuiApp::new(api_url)
8048 } else {
8049 use crate::services::ApplicationOrchestrator;
8051
8052 let config = Config::default();
8054 let orchestrator = ApplicationOrchestrator::new(
8055 config.behavior.case_insensitive_default,
8056 config.behavior.hide_empty_columns,
8057 );
8058
8059 let mut app = orchestrator.create_tui_with_file(data_files[0])?;
8061
8062 for file_path in data_files.iter().skip(1) {
8064 if let Err(e) = orchestrator.load_additional_file(&mut app, file_path) {
8065 app.state_container
8066 .set_status_message(format!("Error loading {file_path}: {e}"));
8067 continue;
8068 }
8069 }
8070
8071 if data_files.len() > 1 {
8073 app.state_container.buffers_mut().switch_to(0);
8074 app.sync_after_buffer_switch();
8076 app.state_container.set_status_message(format!(
8077 "Loaded {} files into separate buffers. Use Alt+Tab to switch.",
8078 data_files.len()
8079 ));
8080 } else if data_files.len() == 1 {
8081 app.update_parser_for_current_buffer();
8083 }
8084
8085 app
8086 };
8087
8088 let result = app.run();
8089
8090 let _ = std::panic::take_hook(); result
8095}
8096
8097pub fn run_enhanced_tui(api_url: &str, data_file: Option<&str>) -> Result<()> {
8098 let files = if let Some(file) = data_file {
8100 vec![file]
8101 } else {
8102 vec![]
8103 };
8104 run_enhanced_tui_multi(api_url, files)
8105}