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
550 .get_dataview()
551 .map(|v| v.source().row_count())
552 .unwrap_or(0),
553 buffer.get_dataview().map(|v| v.row_count()).unwrap_or(0),
554 )
555 }
556 } else {
557 (
558 AppMode::Command,
559 String::new(),
560 String::new(),
561 None,
562 0,
563 0,
564 0,
565 )
566 }
567 }
568
569 fn format_buffer_manager_state(&self) -> String {
570 let buffer_names: Vec<String> = self
571 .state_container
572 .buffers()
573 .all_buffers()
574 .iter()
575 .map(|b| b.get_name())
576 .collect();
577 let buffer_count = self.state_container.buffers().all_buffers().len();
578 let buffer_index = self.state_container.buffers().current_index();
579
580 format!(
581 "\n========== BUFFER MANAGER STATE ==========\n\
582 Number of Buffers: {}\n\
583 Current Buffer Index: {}\n\
584 Buffer Names: {}\n",
585 buffer_count,
586 buffer_index,
587 buffer_names.join(", ")
588 )
589 }
590
591 fn debug_generate_viewport_efficiency(&self) -> String {
592 if let Some(ref efficiency) = *self.viewport_efficiency.borrow() {
593 let mut result = String::from("\n========== VIEWPORT EFFICIENCY ==========\n");
594 result.push_str(&efficiency.to_debug_string());
595 result.push_str("\n==========================================\n");
596 result
597 } else {
598 String::new()
599 }
600 }
601
602 fn debug_generate_key_chord_info(&self) -> String {
603 let mut result = String::from("\n");
604 result.push_str(&self.key_chord_handler.format_debug_info());
605 result.push_str("========================================\n");
606 result
607 }
608
609 fn debug_generate_search_modes_info(&self) -> String {
610 let mut result = String::from("\n");
611 result.push_str(&self.search_modes_widget.debug_info());
612 result
613 }
614
615 fn debug_generate_state_container_info(&self) -> String {
616 let mut result = String::from("\n");
617 result.push_str(&self.state_container.debug_dump());
618 result.push_str("\n");
619 result
620 }
621
622 fn collect_debug_info(&self) -> String {
623 let mut debug_info = String::new();
625 debug_info
626 .push_str(&self.debug_generate_parser_info(&self.state_container.get_input_text()));
627 debug_info.push_str(&self.debug_generate_memory_info());
628 debug_info
629 }
630
631 fn debug_generate_parser_info(&self, query: &str) -> String {
633 EnhancedTuiApp::debug_generate_parser_info(self, query)
634 }
635
636 fn debug_generate_navigation_state(&self) -> String {
637 Self::debug_generate_navigation_state(self)
639 }
640
641 fn debug_generate_column_search_state(&self) -> String {
642 Self::debug_generate_column_search_state(self)
644 }
645
646 fn debug_generate_trace_logs(&self) -> String {
647 Self::debug_generate_trace_logs(self)
649 }
650
651 fn debug_generate_state_logs(&self) -> String {
652 Self::debug_generate_state_logs(self)
654 }
655}
656
657impl EnhancedTuiApp {
658 pub(crate) fn state_container(&self) -> &AppStateContainer {
661 &self.state_container
662 }
663
664 pub(crate) fn state_container_mut(&mut self) -> &mut AppStateContainer {
666 &mut self.state_container
667 }
668
669 pub(crate) fn sync_navigation_with_viewport(&self) {
674 debug!(target: "column_search_sync", "sync_navigation_with_viewport: ENTRY");
675 let viewport_borrow = self.viewport_manager.borrow();
676
677 if let Some(ref viewport) = viewport_borrow.as_ref() {
678 let mut nav = self.state_container.navigation_mut();
679
680 debug!(target: "column_search_sync", "sync_navigation_with_viewport: BEFORE - nav.selected_column: {}, viewport.crosshair_col: {}",
682 nav.selected_column, viewport.get_crosshair_col());
683 debug!(target: "column_search_sync", "sync_navigation_with_viewport: BEFORE - nav.selected_row: {}, viewport.crosshair_row: {}",
684 nav.selected_row, viewport.get_crosshair_row());
685
686 nav.selected_row = viewport.get_selected_row();
688 nav.selected_column = viewport.get_selected_column();
689 nav.scroll_offset = viewport.get_scroll_offset();
690
691 debug!(target: "column_search_sync", "sync_navigation_with_viewport: AFTER - nav.selected_column: {}, nav.selected_row: {}",
693 nav.selected_column, nav.selected_row);
694 debug!(target: "column_search_sync", "sync_navigation_with_viewport: Successfully synced NavigationState with ViewportManager");
695 } else {
696 debug!(target: "column_search_sync", "sync_navigation_with_viewport: No ViewportManager available to sync with");
697 }
698 debug!(target: "column_search_sync", "sync_navigation_with_viewport: EXIT");
699 }
700
701 fn sync_mode(&mut self, mode: AppMode, trigger: &str) {
704 debug!(target: "column_search_sync", "sync_mode: ENTRY - mode: {:?}, trigger: '{}'", mode, trigger);
705 use crate::ui::state::state_coordinator::StateCoordinator;
707 StateCoordinator::sync_mode_with_refs(
708 &mut self.state_container,
709 &self.shadow_state,
710 mode,
711 trigger,
712 );
713 debug!(target: "column_search_sync", "sync_mode: EXIT - StateCoordinator::sync_mode_with_refs completed");
714 }
715
716 fn save_viewport_to_current_buffer(&mut self) {
718 let viewport_borrow = self.viewport_manager.borrow();
719
720 if let Some(ref viewport) = viewport_borrow.as_ref() {
721 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
722 buffer.set_selected_row(Some(viewport.get_selected_row()));
724 buffer.set_current_column(viewport.get_selected_column());
725
726 buffer.set_scroll_offset(viewport.get_scroll_offset());
728 }
729 }
730 }
731
732 fn restore_viewport_from_current_buffer(&mut self) {
734 if let Some(buffer) = self.state_container.buffers().current() {
735 if let Some(dataview) = buffer.get_dataview() {
737 let needs_new_viewport = {
739 let viewport_borrow = self.viewport_manager.borrow();
740 viewport_borrow.is_none()
741 };
742
743 if needs_new_viewport {
744 self.viewport_manager =
746 RefCell::new(Some(ViewportManager::new(Arc::new(dataview.clone()))));
747 } else {
748 let mut viewport_borrow = self.viewport_manager.borrow_mut();
750 if let Some(ref mut viewport) = viewport_borrow.as_mut() {
751 viewport.set_dataview(Arc::new(dataview.clone()));
752 }
753 }
754
755 let mut viewport_borrow = self.viewport_manager.borrow_mut();
757 if let Some(ref mut viewport) = viewport_borrow.as_mut() {
758 let row = buffer.get_selected_row().unwrap_or(0);
763 let col = buffer.get_current_column();
764 viewport.set_crosshair(row, col);
765
766 let scroll_offset = buffer.get_scroll_offset();
768 viewport.set_scroll_offset(scroll_offset.0, scroll_offset.1);
769
770 let term_width = viewport.get_terminal_width();
772 let term_height = viewport.get_terminal_height() as u16;
773 viewport.update_terminal_size(term_width, term_height);
774
775 drop(viewport_borrow);
777 self.sync_navigation_with_viewport();
778 }
779
780 let mut table_manager = self.table_widget_manager.borrow_mut();
782 table_manager.set_dataview(Arc::new(dataview.clone()));
783 table_manager.force_render(); }
785 }
786 }
787
788 fn calculate_available_data_rows(terminal_height: u16) -> u16 {
793 crate::ui::rendering::ui_layout_utils::calculate_available_data_rows(terminal_height)
794 }
795
796 fn calculate_table_data_rows(table_area_height: u16) -> u16 {
799 crate::ui::rendering::ui_layout_utils::calculate_table_data_rows(table_area_height)
800 }
801
802 fn current_buffer(&self) -> Option<&dyn buffer::BufferAPI> {
806 self.state_container
807 .buffers()
808 .current()
809 .map(|b| b as &dyn buffer::BufferAPI)
810 }
811
812 fn buffer(&self) -> &dyn buffer::BufferAPI {
815 self.current_buffer()
816 .expect("No buffer available - this should not happen")
817 }
818
819 fn build_action_context(&self) -> ActionContext {
823 let nav = self.state_container.navigation();
824 let dataview = self.state_container.get_buffer_dataview();
825
826 ActionContext {
827 mode: self.state_container.get_mode(),
828 selection_mode: self.state_container.get_selection_mode(),
829 has_results: dataview.is_some(),
830 has_filter: !self.state_container.get_filter_pattern().is_empty()
831 || !self.state_container.get_fuzzy_filter_pattern().is_empty(),
832 has_search: !self.state_container.get_search_pattern().is_empty()
833 || self
834 .vim_search_adapter
835 .borrow()
836 .should_handle_key(&self.state_container)
837 || self.state_container.column_search().is_active,
838 row_count: dataview.as_ref().map_or(0, |v| v.row_count()),
839 column_count: dataview.as_ref().map_or(0, |v| v.column_count()),
840 current_row: nav.selected_row,
841 current_column: nav.selected_column,
842 }
843 }
844
845 fn try_handle_action(
849 &mut self,
850 action: Action,
851 context: &ActionContext,
852 ) -> Result<ActionResult> {
853 let temp_dispatcher = crate::ui::input::action_handlers::ActionDispatcher::new();
856 match temp_dispatcher.dispatch(&action, context, self) {
857 Ok(ActionResult::NotHandled) => {
858 }
861 result => {
862 return result;
864 }
865 }
866
867 use Action::*;
868
869 match action {
871 Sort(_column_idx) => {
884 self.toggle_sort_current_column();
886 Ok(ActionResult::Handled)
887 }
888 HideEmptyColumns => {
890 tracing::info!("HideEmptyColumns action triggered");
891
892 let (count, updated_dataview) = {
894 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
895 if let Some(ref mut viewport_manager) = *viewport_manager_borrow {
896 tracing::debug!("ViewportManager available, checking for empty columns");
897 let count = viewport_manager.hide_empty_columns();
898 if count > 0 {
899 (count, Some(viewport_manager.clone_dataview()))
900 } else {
901 (count, None)
902 }
903 } else {
904 tracing::warn!("No ViewportManager available to hide columns");
905 (0, None)
906 }
907 };
908
909 if let Some(updated_dataview) = updated_dataview {
911 self.state_container.set_dataview(Some(updated_dataview));
912 }
913
914 tracing::info!("Hidden {} empty columns", count);
915 let message = if count > 0 {
916 format!(
917 "Hidden {} empty columns (press Ctrl+Shift+H to unhide)",
918 count
919 )
920 } else {
921 "No empty columns found".to_string()
922 };
923 self.state_container.set_status_message(message);
924 Ok(ActionResult::Handled)
925 }
926 ExitCurrentMode => {
929 match context.mode {
931 AppMode::Results => {
932 if let Some(selected) = self.state_container.get_table_selected_row() {
936 self.state_container.set_last_results_row(Some(selected));
937 let scroll_offset = self.state_container.get_scroll_offset();
938 self.state_container.set_last_scroll_offset(scroll_offset);
939 }
940
941 let last_query = self.state_container.get_last_query();
943 let current_input = self.state_container.get_input_text();
944 debug!(target: "mode", "Exiting Results mode: current input_text='{}', last_query='{}'", current_input, last_query);
945
946 if !last_query.is_empty() {
947 debug!(target: "buffer", "Restoring last_query to input_text: '{}'", last_query);
948 self.set_input_text(last_query.clone());
950 } else if !current_input.is_empty() {
951 debug!(target: "buffer", "No last_query but input_text has content, keeping: '{}'", current_input);
952 } else {
953 debug!(target: "buffer", "No last_query to restore when exiting Results mode");
954 }
955
956 debug!(target: "mode", "Switching from Results to Command mode");
957 self.state_container.set_mode(AppMode::Command);
958 self.shadow_state
959 .borrow_mut()
960 .observe_mode_change(AppMode::Command, "escape_from_results");
961 self.state_container.set_table_selected_row(None);
962 }
963 AppMode::Help => {
964 self.set_mode_via_shadow_state(AppMode::Results, "escape_from_help");
967 self.state_container.set_help_visible(false);
968 }
969 AppMode::Debug => {
970 self.set_mode_via_shadow_state(AppMode::Results, "escape_from_debug");
973 }
974 _ => {
975 self.set_mode_via_shadow_state(AppMode::Command, "escape_to_command");
978 }
979 }
980 Ok(ActionResult::Handled)
981 }
982 SwitchMode(target_mode) => {
983 if target_mode == AppMode::Results && !context.has_results {
986 self.state_container.set_status_message(
988 "No results to display. Run a query first.".to_string(),
989 );
990 Ok(ActionResult::Handled)
991 } else {
992 self.state_container.set_mode(target_mode.clone());
993
994 let trigger = match target_mode {
996 AppMode::Command => "switch_to_command",
997 AppMode::Results => "switch_to_results",
998 AppMode::Help => "switch_to_help",
999 AppMode::History => "switch_to_history",
1000 _ => "switch_mode",
1001 };
1002 self.shadow_state
1003 .borrow_mut()
1004 .observe_mode_change(target_mode.clone(), trigger);
1005
1006 let msg = match target_mode {
1007 AppMode::Command => "Command mode - Enter SQL queries",
1008 AppMode::Results => {
1009 "Results mode - Navigate with arrows/hjkl, Tab for command"
1010 }
1011 _ => "",
1012 };
1013 if !msg.is_empty() {
1014 self.state_container.set_status_message(msg.to_string());
1015 }
1016 Ok(ActionResult::Handled)
1017 }
1018 }
1019 SwitchModeWithCursor(target_mode, cursor_position) => {
1020 use crate::ui::input::actions::{CursorPosition, SqlClause};
1021
1022 self.state_container.set_mode(target_mode.clone());
1024
1025 let trigger = match cursor_position {
1027 CursorPosition::End => "a_key_pressed",
1028 CursorPosition::Current => "i_key_pressed",
1029 CursorPosition::AfterClause(_) => "clause_navigation",
1030 };
1031 self.shadow_state
1032 .borrow_mut()
1033 .observe_mode_change(target_mode.clone(), trigger);
1034
1035 match cursor_position {
1037 CursorPosition::Current => {
1038 }
1040 CursorPosition::End => {
1041 let text = self.state_container.get_buffer_input_text();
1043 let text_len = text.len();
1044 self.state_container.set_input_cursor_position(text_len);
1045 self.input = tui_input::Input::new(text).with_cursor(text_len);
1047 }
1048 CursorPosition::AfterClause(clause) => {
1049 let input_text = self.state_container.get_input_text();
1051
1052 use crate::sql::recursive_parser::Lexer;
1054 let mut lexer = Lexer::new(&input_text);
1055 let tokens = lexer.tokenize_all_with_positions();
1056
1057 let mut cursor_pos = None;
1059 for i in 0..tokens.len() {
1060 let (_, end_pos, ref token) = tokens[i];
1061
1062 use crate::sql::recursive_parser::Token;
1064 let clause_matched = match (&clause, token) {
1065 (SqlClause::Select, Token::Select) => true,
1066 (SqlClause::From, Token::From) => true,
1067 (SqlClause::Where, Token::Where) => true,
1068 (SqlClause::OrderBy, Token::OrderBy) => true,
1069 (SqlClause::GroupBy, Token::GroupBy) => true,
1070 (SqlClause::Having, Token::Having) => true,
1071 (SqlClause::Limit, Token::Limit) => true,
1072 _ => false,
1073 };
1074
1075 if clause_matched {
1076 let mut clause_end = end_pos;
1078
1079 for j in (i + 1)..tokens.len() {
1081 let (_, token_end, ref next_token) = tokens[j];
1082
1083 match next_token {
1085 Token::Select
1086 | Token::From
1087 | Token::Where
1088 | Token::OrderBy
1089 | Token::GroupBy
1090 | Token::Having
1091 | Token::Limit => break,
1092 _ => clause_end = token_end,
1093 }
1094 }
1095
1096 cursor_pos = Some(clause_end);
1097 break;
1098 }
1099 }
1100
1101 if let Some(pos) = cursor_pos {
1104 self.state_container.set_input_cursor_position(pos);
1105 let text = self.state_container.get_buffer_input_text();
1107 self.input = tui_input::Input::new(text).with_cursor(pos);
1108 } else {
1109 let clause_text = match clause {
1111 SqlClause::Where => " WHERE ",
1112 SqlClause::OrderBy => " ORDER BY ",
1113 SqlClause::GroupBy => " GROUP BY ",
1114 SqlClause::Having => " HAVING ",
1115 SqlClause::Limit => " LIMIT ",
1116 SqlClause::Select => "SELECT ",
1117 SqlClause::From => " FROM ",
1118 };
1119
1120 let mut new_text = self.state_container.get_input_text();
1121 new_text.push_str(clause_text);
1122 let cursor_pos = new_text.len();
1123 self.set_input_text_with_cursor(new_text, cursor_pos);
1125 }
1126 }
1127 }
1128
1129 let msg = match target_mode {
1131 AppMode::Command => "Command mode - Enter SQL queries",
1132 _ => "",
1133 };
1134 if !msg.is_empty() {
1135 self.state_container.set_status_message(msg.to_string());
1136 }
1137
1138 Ok(ActionResult::Handled)
1139 }
1140
1141 MoveCursorLeft => Ok(ActionResult::NotHandled),
1144 MoveCursorRight => Ok(ActionResult::NotHandled),
1146 MoveCursorHome => Ok(ActionResult::NotHandled),
1148 MoveCursorEnd => Ok(ActionResult::NotHandled),
1150 Backspace => Ok(ActionResult::NotHandled),
1152 Delete => Ok(ActionResult::NotHandled),
1154 Undo => Ok(ActionResult::NotHandled),
1157 Redo => Ok(ActionResult::NotHandled),
1159 ExecuteQuery => {
1160 if context.mode == AppMode::Command {
1161 self.handle_execute_query()?;
1163 Ok(ActionResult::Handled)
1164 } else {
1165 Ok(ActionResult::NotHandled)
1166 }
1167 }
1168 InsertChar(c) => {
1169 if context.mode == AppMode::Command {
1170 self.state_container.insert_char_at_cursor(c);
1171
1172 self.state_container.clear_completion();
1174
1175 self.handle_completion();
1177
1178 Ok(ActionResult::Handled)
1179 } else {
1180 Ok(ActionResult::NotHandled)
1181 }
1182 }
1183
1184 NextSearchMatch => {
1185 debug!(target: "column_search_sync", "NextSearchMatch: 'n' key pressed, checking if search navigation should be handled");
1187 use crate::ui::state::state_coordinator::StateCoordinator;
1189 let should_handle = StateCoordinator::should_handle_next_match(
1190 &self.state_container,
1191 Some(&self.vim_search_adapter),
1192 );
1193 debug!(target: "column_search_sync", "NextSearchMatch: StateCoordinator::should_handle_next_match returned: {}", should_handle);
1194 if should_handle {
1195 debug!(target: "column_search_sync", "NextSearchMatch: Calling vim_search_next()");
1196 self.vim_search_next();
1197 debug!(target: "column_search_sync", "NextSearchMatch: vim_search_next() completed");
1198 } else {
1199 debug!(target: "column_search_sync", "NextSearchMatch: No active search (or cancelled with Escape), ignoring 'n' key");
1200 }
1201 Ok(ActionResult::Handled)
1202 }
1203 PreviousSearchMatch => {
1204 use crate::ui::state::state_coordinator::StateCoordinator;
1207 if StateCoordinator::should_handle_previous_match(
1208 &self.state_container,
1209 Some(&self.vim_search_adapter),
1210 ) {
1211 self.vim_search_previous();
1212 } else {
1213 return self.try_handle_action(Action::ToggleRowNumbers, context);
1215 }
1216 Ok(ActionResult::Handled)
1217 }
1218 ShowColumnStatistics => {
1219 self.calculate_column_statistics();
1220 Ok(ActionResult::Handled)
1221 }
1222 StartHistorySearch => {
1223 use crate::ui::state::state_coordinator::StateCoordinator;
1224
1225 let current_input = self.get_input_text();
1227
1228 let (input_to_use, _match_count) = StateCoordinator::start_history_search_with_refs(
1230 &mut self.state_container,
1231 &self.shadow_state,
1232 current_input,
1233 );
1234
1235 if input_to_use != self.get_input_text() {
1237 self.set_input_text(input_to_use);
1238 }
1239
1240 self.update_history_matches_in_container();
1242
1243 Ok(ActionResult::Handled)
1244 }
1245 CycleColumnPacking => {
1246 let message = {
1247 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
1248 let viewport_manager = viewport_manager_borrow
1249 .as_mut()
1250 .expect("ViewportManager must exist");
1251 let new_mode = viewport_manager.cycle_packing_mode();
1252 format!("Column packing: {}", new_mode.display_name())
1253 };
1254 self.state_container.set_status_message(message);
1255 Ok(ActionResult::Handled)
1256 }
1257 _ => {
1258 Ok(ActionResult::NotHandled)
1260 }
1261 }
1262 }
1263
1264 fn get_data_provider(&self) -> Option<Box<dyn DataProvider + '_>> {
1269 if let Some(buffer) = self.state_container.buffers().current() {
1272 if buffer.has_dataview() {
1274 return Some(Box::new(BufferAdapter::new(buffer)));
1275 }
1276 }
1277 None
1278 }
1279
1280 fn get_input_text(&self) -> String {
1286 if self.shadow_state.borrow().is_in_search_mode() {
1288 self.input.value().to_string() } else {
1291 self.state_container.get_buffer_input_text()
1293 }
1294 }
1295
1296 fn get_input_cursor(&self) -> usize {
1298 if self.shadow_state.borrow().is_in_search_mode() {
1300 self.input.cursor()
1302 } else {
1303 self.state_container.get_input_cursor_position()
1305 }
1306 }
1307
1308 fn set_input_text(&mut self, text: String) {
1310 let old_text = self.state_container.get_input_text();
1311 let mode = self.shadow_state.borrow().get_mode();
1312
1313 info!(target: "input", "SET_INPUT_TEXT: '{}' -> '{}' (mode: {:?})",
1315 if old_text.len() > 50 { format!("{}...", &old_text[..50]) } else { old_text.clone() },
1316 if text.len() > 50 { format!("{}...", &text[..50]) } else { text.clone() },
1317 mode);
1318
1319 self.state_container.set_buffer_input_text(text.clone());
1321
1322 self.input = tui_input::Input::new(text.clone()).with_cursor(text.len());
1325 }
1326
1327 fn set_input_text_with_cursor(&mut self, text: String, cursor_pos: usize) {
1329 let old_text = self.state_container.get_buffer_input_text();
1330 let old_cursor = self.state_container.get_input_cursor_position();
1331 let mode = self.state_container.get_mode();
1332
1333 info!(target: "input", "SET_INPUT_TEXT_WITH_CURSOR: '{}' (cursor {}) -> '{}' (cursor {}) (mode: {:?})",
1335 if old_text.len() > 50 { format!("{}...", &old_text[..50]) } else { old_text.clone() },
1336 old_cursor,
1337 if text.len() > 50 { format!("{}...", &text[..50]) } else { text.clone() },
1338 cursor_pos,
1339 mode);
1340
1341 self.state_container
1343 .set_buffer_input_text_with_cursor(text.clone(), cursor_pos);
1344
1345 self.input = tui_input::Input::new(text.clone()).with_cursor(cursor_pos);
1348 }
1349
1350 fn sync_all_input_states(&mut self) {
1356 let text = self.state_container.get_input_text();
1357 let cursor = self.state_container.get_input_cursor_position();
1358 let mode = self.state_container.get_mode();
1359
1360 let backtrace_str = std::backtrace::Backtrace::capture().to_string();
1362 let caller = backtrace_str
1363 .lines()
1364 .skip(3) .find(|line| line.contains("enhanced_tui") && !line.contains("sync_all_input_states"))
1366 .and_then(|line| line.split("::").last())
1367 .unwrap_or("unknown");
1368
1369 self.input = tui_input::Input::new(text.clone()).with_cursor(cursor);
1371
1372 self.state_container
1374 .set_input_text_with_cursor(text.clone(), cursor);
1375
1376 self.cursor_manager.reset_horizontal_scroll();
1380 self.state_container.scroll_mut().input_scroll_offset = 0;
1381
1382 self.update_horizontal_scroll(120); info!(target: "input", "SYNC_ALL [{}]: text='{}', cursor={}, mode={:?}, scroll_reset",
1386 caller,
1387 if text.len() > 50 { format!("{}...", &text[..50]) } else { text.clone() },
1388 cursor,
1389 mode);
1390 }
1391
1392 fn handle_input_key(&mut self, key: KeyEvent) -> bool {
1394 let mode = self.shadow_state.borrow().get_mode();
1396 match mode {
1397 AppMode::Search | AppMode::Filter | AppMode::FuzzyFilter | AppMode::ColumnSearch => {
1398 self.input.handle_event(&Event::Key(key));
1399 false
1400 }
1401 _ => {
1402 self.state_container.handle_input_key(key)
1404 }
1405 }
1406 }
1407 fn get_visual_cursor(&self) -> (usize, usize) {
1411 let mode = self.state_container.get_mode();
1413 let (text, cursor) = match mode {
1414 AppMode::Search | AppMode::Filter | AppMode::FuzzyFilter | AppMode::ColumnSearch => {
1415 (self.input.value().to_string(), self.input.cursor())
1417 }
1418 _ => {
1419 (
1421 self.state_container.get_input_text(),
1422 self.state_container.get_input_cursor_position(),
1423 )
1424 }
1425 };
1426
1427 let lines: Vec<&str> = text.split('\n').collect();
1428 let mut current_pos = 0;
1429 for (row, line) in lines.iter().enumerate() {
1430 if current_pos + line.len() >= cursor {
1431 return (row, cursor - current_pos);
1432 }
1433 current_pos += line.len() + 1; }
1435 (0, cursor)
1436 }
1437
1438 fn get_selection_mode(&self) -> SelectionMode {
1439 self.state_container.get_selection_mode()
1440 }
1442
1443 pub fn new(api_url: &str) -> Self {
1444 let config = Config::load().unwrap_or_else(|_e| {
1446 Config::default()
1448 });
1449
1450 let data_source = if !api_url.is_empty() {
1452 Some(api_url.to_string())
1453 } else {
1454 None
1455 };
1456
1457 if let Some(logger) = dual_logging::get_dual_logger() {
1459 logger.log(
1460 "INFO",
1461 "EnhancedTuiApp",
1462 &format!("Initializing TUI with API URL: {}", api_url),
1463 );
1464 }
1465
1466 let mut buffer_manager = BufferManager::new();
1468 let mut buffer = buffer::Buffer::new(1);
1469 buffer.set_case_insensitive(config.behavior.case_insensitive_default);
1471 buffer.set_compact_mode(config.display.compact_mode);
1472 buffer.set_show_row_numbers(config.display.show_row_numbers);
1473 buffer_manager.add_buffer(buffer);
1474
1475 let state_container = match AppStateContainer::new(buffer_manager) {
1477 Ok(container) => container,
1478 Err(e) => {
1479 panic!("Failed to initialize AppStateContainer: {}", e);
1480 }
1481 };
1482
1483 let debug_service = DebugService::new(1000); debug_service.set_enabled(true); state_container.set_debug_service(debug_service.clone_service());
1487
1488 let help_widget = HelpWidget::new();
1490 let mut app = Self {
1493 state_container,
1494 debug_service: Some(debug_service),
1495 input: Input::default(),
1496 command_editor: CommandEditor::new(),
1497 cursor_manager: CursorManager::new(),
1498 data_analyzer: DataAnalyzer::new(),
1499 hybrid_parser: HybridParser::new(),
1500 config: config.clone(),
1501 sql_highlighter: SqlHighlighter::new(),
1502 debug_widget: DebugWidget::new(),
1503 editor_widget: EditorWidget::new(),
1504 stats_widget: StatsWidget::new(),
1505 help_widget,
1506 search_modes_widget: SearchModesWidget::new(),
1507 vim_search_adapter: RefCell::new(VimSearchAdapter::new()),
1508 state_dispatcher: RefCell::new(StateDispatcher::new()),
1509 search_manager: RefCell::new({
1510 let mut search_config = SearchConfig::default();
1511 search_config.case_sensitive = !config.behavior.case_insensitive_default;
1512 SearchManager::with_config(search_config)
1513 }),
1514 key_chord_handler: KeyChordHandler::new(),
1515 key_dispatcher: KeyDispatcher::new(),
1516 key_mapper: KeyMapper::new(),
1517 buffer_handler: BufferHandler::new(),
1520 navigation_timings: Vec::new(),
1521 render_timings: Vec::new(),
1522 log_buffer: dual_logging::get_dual_logger().map(|logger| logger.ring_buffer().clone()),
1523 data_source,
1524 key_indicator: {
1525 let mut indicator = KeyPressIndicator::new();
1526 if config.display.show_key_indicator {
1527 indicator.set_enabled(true);
1528 }
1529 indicator
1530 },
1531 key_sequence_renderer: {
1532 let mut renderer = KeySequenceRenderer::new();
1533 if config.display.show_key_indicator {
1534 renderer.set_enabled(true);
1535 }
1536 renderer
1537 },
1538 viewport_manager: RefCell::new(None), viewport_efficiency: RefCell::new(None),
1540 shadow_state: RefCell::new(crate::ui::state::shadow_state::ShadowStateManager::new()),
1541 table_widget_manager: RefCell::new(TableWidgetManager::new()),
1542 query_orchestrator: QueryOrchestrator::with_behavior_config(config.behavior.clone()),
1543 debug_registry: DebugRegistry::new(),
1544 memory_tracker: MemoryTracker::new(100),
1545 };
1546
1547 app.setup_state_coordination();
1549
1550 app
1551 }
1552
1553 fn setup_state_coordination(&mut self) {
1555 if let Some(current_buffer) = self.state_container.buffers().current() {
1557 let buffer_rc = std::rc::Rc::new(std::cell::RefCell::new(current_buffer.clone()));
1558 self.state_dispatcher.borrow_mut().set_buffer(buffer_rc);
1559 }
1560
1561 info!("State coordination setup complete");
1566 }
1567
1568 pub fn new_with_dataview(dataview: DataView, source_name: &str) -> Result<Self> {
1571 let mut app = Self::new("");
1573
1574 app.data_source = Some(source_name.to_string());
1576
1577 app.state_container.buffers_mut().clear_all();
1579 let mut buffer = buffer::Buffer::new(1);
1580
1581 let source_table = dataview.source_arc();
1584
1585 use crate::utils::memory_audit;
1587 use crate::utils::memory_tracker;
1588
1589 memory_tracker::track_memory("before_arc_share");
1590
1591 buffer.set_datatable(Some(source_table));
1593
1594 memory_tracker::track_memory("after_arc_share");
1595
1596 let audits = memory_audit::perform_memory_audit(
1598 buffer.get_datatable(),
1599 buffer.get_original_source(),
1600 None,
1601 );
1602 memory_audit::log_memory_audit(&audits);
1603
1604 buffer.set_dataview(Some(dataview.clone()));
1606 let buffer_name = std::path::Path::new(source_name)
1608 .file_name()
1609 .and_then(|s| s.to_str())
1610 .unwrap_or(source_name)
1611 .to_string();
1612 buffer.set_name(buffer_name);
1613
1614 buffer.set_case_insensitive(app.config.behavior.case_insensitive_default);
1616 buffer.set_compact_mode(app.config.display.compact_mode);
1617 buffer.set_show_row_numbers(app.config.display.show_row_numbers);
1618
1619 app.state_container.buffers_mut().add_buffer(buffer);
1621
1622 app.state_container.set_dataview(Some(dataview.clone()));
1624
1625 app.viewport_manager = RefCell::new(Some(ViewportManager::new(Arc::new(dataview.clone()))));
1627
1628 app.calculate_optimal_column_widths();
1630
1631 let row_count = dataview.row_count();
1633 let column_count = dataview.column_count();
1634 app.state_container
1635 .update_data_size(row_count, column_count);
1636
1637 Ok(app)
1638 }
1639
1640 pub fn add_dataview(&mut self, dataview: DataView, source_name: &str) -> Result<()> {
1642 use crate::ui::state::state_coordinator::StateCoordinator;
1644
1645 StateCoordinator::add_dataview_with_refs(
1646 &mut self.state_container,
1647 &self.viewport_manager,
1648 dataview,
1649 source_name,
1650 &self.config,
1651 )?;
1652
1653 self.calculate_optimal_column_widths();
1655
1656 Ok(())
1657 }
1658
1659 pub fn update_viewport_with_dataview(&mut self, dataview: DataView) {
1661 self.viewport_manager = RefCell::new(Some(ViewportManager::new(Arc::new(dataview))));
1662 }
1663
1664 pub fn vim_search_adapter(&self) -> &RefCell<VimSearchAdapter> {
1666 &self.vim_search_adapter
1667 }
1668
1669 pub fn set_sql_query(&mut self, table_name: &str, raw_table_name: &str) {
1671 use crate::ui::state::state_coordinator::StateCoordinator;
1672
1673 let auto_query = StateCoordinator::set_sql_query_with_refs(
1675 &mut self.state_container,
1676 &self.shadow_state,
1677 &mut self.hybrid_parser,
1678 table_name,
1679 raw_table_name,
1680 &self.config,
1681 );
1682
1683 self.set_input_text(auto_query);
1685 }
1686
1687 pub fn run(mut self) -> Result<()> {
1688 if let Err(e) = enable_raw_mode() {
1690 return Err(anyhow::anyhow!(
1691 "Failed to enable raw mode: {}. Try running with --classic flag.",
1692 e
1693 ));
1694 }
1695
1696 let mut stdout = io::stdout();
1697 if let Err(e) = execute!(stdout, EnterAlternateScreen, EnableMouseCapture) {
1698 let _ = disable_raw_mode();
1699 return Err(anyhow::anyhow!(
1700 "Failed to setup terminal: {}. Try running with --classic flag.",
1701 e
1702 ));
1703 }
1704
1705 let backend = CrosstermBackend::new(stdout);
1706 let mut terminal = match Terminal::new(backend) {
1707 Ok(t) => t,
1708 Err(e) => {
1709 let _ = disable_raw_mode();
1710 return Err(anyhow::anyhow!(
1711 "Failed to create terminal: {}. Try running with --classic flag.",
1712 e
1713 ));
1714 }
1715 };
1716
1717 let res = self.run_app(&mut terminal);
1718
1719 let _ = disable_raw_mode();
1721 let _ = execute!(
1722 terminal.backend_mut(),
1723 LeaveAlternateScreen,
1724 DisableMouseCapture
1725 );
1726 let _ = terminal.show_cursor();
1727
1728 match res {
1729 Ok(_) => Ok(()),
1730 Err(e) => Err(anyhow::anyhow!("TUI error: {}", e)),
1731 }
1732 }
1733
1734 fn initialize_viewport<B: Backend>(&mut self, terminal: &mut Terminal<B>) -> Result<()> {
1736 self.update_viewport_size();
1737 info!(target: "navigation", "Initial viewport size update completed");
1738 terminal.draw(|f| self.ui(f))?;
1739 Ok(())
1740 }
1741
1742 fn try_handle_debounced_actions<B: Backend>(
1744 &mut self,
1745 terminal: &mut Terminal<B>,
1746 ) -> Result<bool> {
1747 if !self.search_modes_widget.is_active() {
1748 return Ok(false);
1749 }
1750
1751 if let Some(action) = self.search_modes_widget.check_debounce() {
1752 let mut needs_redraw = false;
1753 match action {
1754 SearchModesAction::ExecuteDebounced(mode, pattern) => {
1755 info!(target: "search", "=== DEBOUNCED SEARCH EXECUTING ===");
1756 info!(target: "search", "Mode: {:?}, Pattern: '{}', AppMode: {:?}",
1757 mode, pattern, self.shadow_state.borrow().get_mode());
1758
1759 {
1761 let nav = self.state_container.navigation();
1762 info!(target: "search", "BEFORE: nav.selected_row={}, nav.selected_column={}",
1763 nav.selected_row, nav.selected_column);
1764 info!(target: "search", "BEFORE: buffer.selected_row={:?}, buffer.current_column={}",
1765 self.state_container.get_buffer_selected_row(), self.state_container.get_current_column());
1766 }
1767
1768 self.execute_search_action(mode, pattern);
1769
1770 {
1772 let nav = self.state_container.navigation();
1773 info!(target: "search", "AFTER: nav.selected_row={}, nav.selected_column={}",
1774 nav.selected_row, nav.selected_column);
1775 info!(target: "search", "AFTER: buffer.selected_row={:?}, buffer.current_column={}",
1776 self.state_container.get_buffer_selected_row(), self.state_container.get_current_column());
1777
1778 let viewport_manager = self.viewport_manager.borrow();
1780 if let Some(ref vm) = *viewport_manager {
1781 info!(target: "search", "AFTER: ViewportManager crosshair=({}, {})",
1782 vm.get_crosshair_row(), vm.get_crosshair_col());
1783 }
1784 }
1785
1786 info!(target: "search", "=== FORCING REDRAW ===");
1787 needs_redraw = true;
1789 }
1790 _ => {}
1791 }
1792
1793 if needs_redraw || self.table_widget_manager.borrow().needs_render() {
1795 info!(target: "search", "Triggering redraw: needs_redraw={}, table_needs_render={}",
1796 needs_redraw, self.table_widget_manager.borrow().needs_render());
1797 terminal.draw(|f| self.ui(f))?;
1798 self.table_widget_manager.borrow_mut().rendered();
1799 }
1800 }
1801 Ok(false)
1802 }
1803
1804 fn try_handle_chord_processing(&mut self, key: crossterm::event::KeyEvent) -> Result<bool> {
1806 if self
1809 .vim_search_adapter
1810 .borrow()
1811 .should_handle_key(&self.state_container)
1812 {
1813 let handled = self
1814 .vim_search_adapter
1815 .borrow_mut()
1816 .handle_key(key.code, &mut self.state_container);
1817 if handled {
1818 debug!("VimSearchAdapter handled key: {:?}", key.code);
1819 return Ok(false); }
1821 }
1822
1823 if let Some(result) = self.try_handle_buffer_operations(&key)? {
1825 return Ok(result);
1826 }
1827
1828 let chord_result = self.key_chord_handler.process_key(key);
1829 debug!("Chord handler returned: {:?}", chord_result);
1830
1831 match chord_result {
1832 ChordResult::CompleteChord(action) => {
1833 debug!("Chord completed: {:?}", action);
1835 self.key_sequence_renderer.clear_chord_mode();
1837
1838 let current_mode = self.shadow_state.borrow().get_mode();
1840
1841 self.try_handle_action(
1843 action,
1844 &ActionContext {
1845 mode: current_mode,
1846 selection_mode: self.state_container.get_selection_mode(),
1847 has_results: self.state_container.get_buffer_dataview().is_some(),
1848 has_filter: false,
1849 has_search: false,
1850 row_count: self.get_row_count(),
1851 column_count: self.get_column_count(),
1852 current_row: self.state_container.get_table_selected_row().unwrap_or(0),
1853 current_column: self.state_container.get_current_column(),
1854 },
1855 )?;
1856 Ok(false)
1857 }
1858 ChordResult::PartialChord(description) => {
1859 self.state_container.set_status_message(description.clone());
1861 if description.contains("y=row") {
1864 self.key_sequence_renderer
1865 .set_chord_mode(Some("y(a,c,q,r,v)".to_string()));
1866 } else {
1867 self.key_sequence_renderer
1868 .set_chord_mode(Some(description.clone()));
1869 }
1870 Ok(false) }
1872 ChordResult::Cancelled => {
1873 self.state_container
1874 .set_status_message("Chord cancelled".to_string());
1875 self.key_sequence_renderer.clear_chord_mode();
1877 Ok(false)
1878 }
1879 ChordResult::SingleKey(single_key) => {
1880 self.handle_results_input(single_key)
1882 }
1883 }
1884 }
1885
1886 fn try_handle_mode_dispatch(&mut self, key: crossterm::event::KeyEvent) -> Result<bool> {
1888 let mode = self.shadow_state.borrow().get_mode();
1889 debug!(
1890 "try_handle_mode_dispatch: mode={:?}, key={:?}",
1891 mode, key.code
1892 );
1893 match mode {
1894 AppMode::Command => self.handle_command_input(key),
1895 AppMode::Results => {
1896 self.try_handle_chord_processing(key)
1898 }
1899 AppMode::Search | AppMode::Filter | AppMode::FuzzyFilter | AppMode::ColumnSearch => {
1900 self.handle_search_modes_input(key)
1901 }
1902 AppMode::Help => self.handle_help_input(key),
1903 AppMode::History => self.handle_history_input(key),
1904 AppMode::Debug => self.handle_debug_input(key),
1905 AppMode::PrettyQuery => self.handle_pretty_query_input(key),
1906 AppMode::JumpToRow => self.handle_jump_to_row_input(key),
1907 AppMode::ColumnStats => self.handle_column_stats_input(key),
1908 }
1909 }
1910
1911 fn try_handle_key_event<B: Backend>(
1913 &mut self,
1914 terminal: &mut Terminal<B>,
1915 key: crossterm::event::KeyEvent,
1916 ) -> Result<bool> {
1917 if key.kind != crossterm::event::KeyEventKind::Press {
1920 return Ok(false);
1921 }
1922
1923 if key.code == KeyCode::Char('c') && key.modifiers.contains(KeyModifiers::CONTROL) {
1926 info!(target: "app", "Ctrl-C detected, forcing exit");
1927 return Ok(true);
1928 }
1929
1930 let key_display = format_key_for_display(&key);
1932 self.key_indicator.record_key(key_display.clone());
1933 self.key_sequence_renderer.record_key(key_display);
1934
1935 let should_exit = self.try_handle_mode_dispatch(key)?;
1937
1938 if should_exit {
1939 return Ok(true);
1940 }
1941
1942 if self.table_widget_manager.borrow().needs_render() {
1944 info!("TableWidgetManager needs render after key event");
1945 }
1946 terminal.draw(|f| self.ui(f))?;
1947 self.table_widget_manager.borrow_mut().rendered();
1948
1949 Ok(false)
1950 }
1951
1952 fn try_handle_events<B: Backend>(&mut self, terminal: &mut Terminal<B>) -> Result<bool> {
1954 if event::poll(std::time::Duration::from_millis(50))? {
1956 match event::read()? {
1957 Event::Key(key) => {
1958 if self.try_handle_key_event(terminal, key)? {
1959 return Ok(true);
1960 }
1961 }
1962 _ => {
1963 }
1965 }
1966 } else {
1967 if self.search_modes_widget.is_active()
1969 || self.table_widget_manager.borrow().needs_render()
1970 {
1971 if self.table_widget_manager.borrow().needs_render() {
1972 info!("TableWidgetManager needs periodic render");
1973 }
1974 terminal.draw(|f| self.ui(f))?;
1975 self.table_widget_manager.borrow_mut().rendered();
1976 }
1977 }
1978 Ok(false)
1979 }
1980
1981 fn run_app<B: Backend>(&mut self, terminal: &mut Terminal<B>) -> Result<()> {
1982 self.initialize_viewport(terminal)?;
1983
1984 loop {
1985 if self.try_handle_debounced_actions(terminal)? {
1987 break;
1988 }
1989
1990 if self.try_handle_events(terminal)? {
1992 break;
1993 }
1994 }
1995 Ok(())
1996 }
1997
1998 fn handle_command_input(&mut self, key: crossterm::event::KeyEvent) -> Result<bool> {
1999 let normalized_key = self.normalize_and_log_key(key);
2001
2002 let is_special_combo = if let KeyCode::Char(c) = normalized_key.code {
2009 (normalized_key.modifiers.contains(KeyModifiers::CONTROL) && matches!(c,
2011 'x' | 'X' | 'p' | 'P' | 'n' | 'N' | 'r' | 'R' | 'j' | 'J' | 'o' | 'O' | 'b' | 'B' | 'l' | 'L' )) ||
2020 (normalized_key.modifiers.contains(KeyModifiers::ALT) && matches!(c,
2022 'x' | 'X' ))
2024 } else {
2025 false
2026 };
2027
2028 let should_try_command_editor = !is_special_combo
2029 && matches!(
2030 normalized_key.code,
2031 KeyCode::Char(_)
2032 | KeyCode::Backspace
2033 | KeyCode::Delete
2034 | KeyCode::Left
2035 | KeyCode::Right
2036 | KeyCode::Home
2037 | KeyCode::End
2038 );
2039
2040 if should_try_command_editor {
2041 let before_text = self.input.value().to_string();
2044 let before_cursor = self.input.cursor();
2045
2046 if self.command_editor.get_text() != before_text {
2047 self.command_editor.set_text(before_text.clone());
2048 }
2049 if self.command_editor.get_cursor() != before_cursor {
2050 self.command_editor.set_cursor(before_cursor);
2051 }
2052
2053 let result = self.command_editor.handle_input(
2055 normalized_key.clone(),
2056 &mut self.state_container,
2057 &self.shadow_state,
2058 )?;
2059
2060 let new_text = self.command_editor.get_text();
2063 let new_cursor = self.command_editor.get_cursor();
2064
2065 if new_text != before_text || new_cursor != before_cursor {
2067 debug!(
2068 "CommandEditor changed input: '{}' -> '{}', cursor: {} -> {}",
2069 before_text, new_text, before_cursor, new_cursor
2070 );
2071 }
2072
2073 self.input = tui_input::Input::from(new_text.clone()).with_cursor(new_cursor);
2075
2076 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
2079 buffer.set_input_text(new_text.clone());
2080 buffer.set_input_cursor_position(new_cursor);
2081 }
2082
2083 self.state_container.set_input_text(new_text);
2085 self.state_container.set_input_cursor_position(new_cursor);
2086
2087 if result {
2088 return Ok(true);
2089 }
2090
2091 return Ok(false);
2093 }
2094 if let Some(result) = self.try_action_system(normalized_key.clone())? {
2098 return Ok(result);
2099 }
2100
2101 if let Some(result) = self.try_editor_widget(normalized_key.clone())? {
2103 return Ok(result);
2104 }
2105
2106 if let Some(result) = self.try_handle_history_navigation(&normalized_key)? {
2110 return Ok(result);
2111 }
2112
2113 let old_cursor = self.get_input_cursor();
2115
2116 trace!(target: "input", "Key: {:?} Modifiers: {:?}", key.code, key.modifiers);
2118
2119 if let Some(result) = self.try_handle_buffer_operations(&key)? {
2124 return Ok(result);
2125 }
2126
2127 if let Some(result) = self.try_handle_function_keys(&key)? {
2129 return Ok(result);
2130 }
2131
2132 if let Some(result) = self.try_handle_text_editing(&key)? {
2134 return Ok(result);
2135 }
2136
2137 if let Some(result) = self.try_handle_mode_transitions(&key, old_cursor)? {
2139 return Ok(result);
2140 }
2141
2142 Ok(false)
2146 }
2147
2148 fn normalize_and_log_key(
2153 &mut self,
2154 key: crossterm::event::KeyEvent,
2155 ) -> crossterm::event::KeyEvent {
2156 let normalized = self.state_container.normalize_key(key);
2157
2158 let action = self
2160 .key_dispatcher
2161 .get_command_action(&normalized)
2162 .map(|s| s.to_string());
2163
2164 if normalized != key {
2166 self.state_container
2167 .log_key_press(key, Some(format!("normalized to {:?}", normalized)));
2168 }
2169 self.state_container.log_key_press(normalized, action);
2170
2171 normalized
2172 }
2173
2174 fn try_action_system(
2176 &mut self,
2177 normalized_key: crossterm::event::KeyEvent,
2178 ) -> Result<Option<bool>> {
2179 let action_context = self.build_action_context();
2180 if let Some(action) = self
2181 .key_mapper
2182 .map_key(normalized_key.clone(), &action_context)
2183 {
2184 info!(
2185 "✓ Action system (Command): key {:?} -> action {:?}",
2186 normalized_key.code, action
2187 );
2188 if let Ok(result) = self.try_handle_action(action, &action_context) {
2189 match result {
2190 ActionResult::Handled => {
2191 debug!("Action handled by new system in Command mode");
2192 return Ok(Some(false));
2193 }
2194 ActionResult::Exit => {
2195 return Ok(Some(true));
2196 }
2197 ActionResult::NotHandled => {
2198 }
2200 _ => {}
2201 }
2202 }
2203 }
2204 Ok(None)
2205 }
2206
2207 fn try_editor_widget(
2209 &mut self,
2210 normalized_key: crossterm::event::KeyEvent,
2211 ) -> Result<Option<bool>> {
2212 let key_dispatcher = self.key_dispatcher.clone();
2213 let editor_result = if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
2214 self.editor_widget
2215 .handle_key(normalized_key.clone(), &key_dispatcher, buffer)?
2216 } else {
2217 EditorAction::PassToMainApp(normalized_key.clone())
2218 };
2219
2220 match editor_result {
2221 EditorAction::Quit => return Ok(Some(true)),
2222 EditorAction::ExecuteQuery => {
2223 return self.handle_execute_query().map(Some);
2224 }
2225 EditorAction::BufferAction(buffer_action) => {
2226 return self.handle_buffer_action(buffer_action).map(Some);
2227 }
2228 EditorAction::ExpandAsterisk => {
2229 return self.handle_expand_asterisk().map(Some);
2230 }
2231 EditorAction::ShowHelp => {
2232 self.state_container.set_help_visible(true);
2233 self.set_mode_via_shadow_state(AppMode::Help, "help_requested");
2235 return Ok(Some(false));
2236 }
2237 EditorAction::ShowDebug => {
2238 return Ok(Some(false));
2240 }
2241 EditorAction::ShowPrettyQuery => {
2242 self.show_pretty_query();
2243 return Ok(Some(false));
2244 }
2245 EditorAction::SwitchMode(mode) => {
2246 self.handle_editor_mode_switch(mode);
2247 return Ok(Some(false));
2248 }
2249 EditorAction::PassToMainApp(_) => {
2250 }
2252 EditorAction::Continue => return Ok(Some(false)),
2253 }
2254
2255 Ok(None)
2256 }
2257
2258 fn handle_editor_mode_switch(&mut self, mode: AppMode) {
2260 debug!(target: "shadow_state", "EditorAction::SwitchMode to {:?}", mode);
2261 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
2262 let trigger = match mode {
2264 AppMode::Results => "enter_results_mode",
2265 AppMode::Command => "enter_command_mode",
2266 AppMode::History => "enter_history_mode",
2267 _ => "switch_mode",
2268 };
2269 debug!(target: "shadow_state", "Setting mode via shadow state to {:?} with trigger {}", mode, trigger);
2270 self.shadow_state
2271 .borrow_mut()
2272 .set_mode(mode.clone(), buffer, trigger);
2273 } else {
2274 debug!(target: "shadow_state", "No buffer available for mode switch!");
2275 }
2276
2277 if mode == AppMode::History {
2279 eprintln!("[DEBUG] Using AppStateContainer for history search");
2280 let current_input = self.get_input_text();
2281
2282 self.state_container.start_history_search(current_input);
2284
2285 self.update_history_matches_in_container();
2287
2288 let match_count = self.state_container.history_search().matches.len();
2290
2291 self.state_container
2292 .set_status_message(format!("History search: {} matches", match_count));
2293 }
2294 }
2295
2296 fn try_handle_function_keys(
2298 &mut self,
2299 key: &crossterm::event::KeyEvent,
2300 ) -> Result<Option<bool>> {
2301 match key.code {
2302 KeyCode::F(1) | KeyCode::Char('?') => {
2303 if self.shadow_state.borrow().is_in_help_mode() {
2305 let mode = if self.state_container.has_dataview() {
2307 AppMode::Results
2308 } else {
2309 AppMode::Command
2310 };
2311 self.set_mode_via_shadow_state(mode, "exit_help");
2313 self.state_container.set_help_visible(false);
2314 self.help_widget.on_exit();
2315 } else {
2316 self.state_container.set_help_visible(true);
2318 self.set_mode_via_shadow_state(AppMode::Help, "help_requested");
2320 self.help_widget.on_enter();
2321 }
2322 Ok(Some(false))
2323 }
2324 KeyCode::F(3) => {
2325 self.show_pretty_query();
2327 Ok(Some(false))
2328 }
2329 KeyCode::F(5) => {
2330 self.toggle_debug_mode();
2332 Ok(Some(false))
2333 }
2334 KeyCode::F(6) => {
2335 let current = self.state_container.is_show_row_numbers();
2337 self.state_container.set_show_row_numbers(!current);
2338 self.state_container.set_status_message(format!(
2339 "Row numbers: {}",
2340 if !current { "ON" } else { "OFF" }
2341 ));
2342 Ok(Some(false))
2343 }
2344 KeyCode::F(7) => {
2345 let current_mode = self.state_container.is_compact_mode();
2347 self.state_container.set_compact_mode(!current_mode);
2348 let message = if !current_mode {
2349 "Compact mode enabled"
2350 } else {
2351 "Compact mode disabled"
2352 };
2353 self.state_container.set_status_message(message.to_string());
2354 Ok(Some(false))
2355 }
2356 KeyCode::F(8) => {
2357 let current = self.state_container.is_case_insensitive();
2359 self.state_container.set_case_insensitive(!current);
2360 self.state_container.set_status_message(format!(
2361 "Case-insensitive string comparisons: {}",
2362 if !current { "ON" } else { "OFF" }
2363 ));
2364 Ok(Some(false))
2365 }
2366 KeyCode::F(9) => {
2367 use crate::ui::traits::input_ops::InputBehavior;
2369 InputBehavior::kill_line(self);
2370 let message = if !self.state_container.is_kill_ring_empty() {
2371 format!(
2372 "Killed to end of line ('{}' saved to kill ring)",
2373 self.state_container.get_kill_ring()
2374 )
2375 } else {
2376 "Killed to end of line".to_string()
2377 };
2378 self.state_container.set_status_message(message);
2379 Ok(Some(false))
2380 }
2381 KeyCode::F(10) => {
2382 use crate::ui::traits::input_ops::InputBehavior;
2384 InputBehavior::kill_line_backward(self);
2385 let message = if !self.state_container.is_kill_ring_empty() {
2386 format!(
2387 "Killed to beginning of line ('{}' saved to kill ring)",
2388 self.state_container.get_kill_ring()
2389 )
2390 } else {
2391 "Killed to beginning of line".to_string()
2392 };
2393 self.state_container.set_status_message(message);
2394 Ok(Some(false))
2395 }
2396 KeyCode::F(12) => {
2397 let enabled = !self.key_indicator.enabled;
2399 self.key_indicator.set_enabled(enabled);
2400 self.key_sequence_renderer.set_enabled(enabled);
2401 self.state_container.set_status_message(format!(
2402 "Key press indicator {}",
2403 if enabled { "enabled" } else { "disabled" }
2404 ));
2405 Ok(Some(false))
2406 }
2407 _ => Ok(None), }
2409 }
2410
2411 fn try_handle_buffer_operations(
2413 &mut self,
2414 key: &crossterm::event::KeyEvent,
2415 ) -> Result<Option<bool>> {
2416 if let Some(action) = self.key_dispatcher.get_command_action(key) {
2417 match action {
2418 "quit" => return Ok(Some(true)),
2419 "next_buffer" => {
2420 self.save_viewport_to_current_buffer();
2422
2423 let message = self
2424 .buffer_handler
2425 .next_buffer(self.state_container.buffers_mut());
2426 debug!("{}", message);
2427
2428 self.sync_after_buffer_switch();
2430 return Ok(Some(false));
2431 }
2432 "previous_buffer" => {
2433 self.save_viewport_to_current_buffer();
2435
2436 let message = self
2437 .buffer_handler
2438 .previous_buffer(self.state_container.buffers_mut());
2439 debug!("{}", message);
2440
2441 self.sync_after_buffer_switch();
2443 return Ok(Some(false));
2444 }
2445 "quick_switch_buffer" => {
2446 self.save_viewport_to_current_buffer();
2448
2449 let message = self
2450 .buffer_handler
2451 .quick_switch(self.state_container.buffers_mut());
2452 debug!("{}", message);
2453
2454 self.sync_after_buffer_switch();
2456
2457 return Ok(Some(false));
2458 }
2459 "new_buffer" => {
2460 let message = self
2461 .buffer_handler
2462 .new_buffer(self.state_container.buffers_mut(), &self.config);
2463 debug!("{}", message);
2464 return Ok(Some(false));
2465 }
2466 "close_buffer" => {
2467 let (success, message) = self
2468 .buffer_handler
2469 .close_buffer(self.state_container.buffers_mut());
2470 debug!("{}", message);
2471 return Ok(Some(!success)); }
2473 "list_buffers" => {
2474 let buffer_list = self
2475 .buffer_handler
2476 .list_buffers(self.state_container.buffers());
2477 for line in &buffer_list {
2478 debug!("{}", line);
2479 }
2480 return Ok(Some(false));
2481 }
2482 action if action.starts_with("switch_to_buffer_") => {
2483 if let Some(buffer_num_str) = action.strip_prefix("switch_to_buffer_") {
2484 if let Ok(buffer_num) = buffer_num_str.parse::<usize>() {
2485 self.save_viewport_to_current_buffer();
2487
2488 let message = self.buffer_handler.switch_to_buffer(
2489 self.state_container.buffers_mut(),
2490 buffer_num - 1,
2491 );
2492 debug!("{}", message);
2493
2494 self.sync_after_buffer_switch();
2496 }
2497 }
2498 return Ok(Some(false));
2499 }
2500 _ => {} }
2502 }
2503 Ok(None)
2504 }
2505
2506 fn try_handle_history_navigation(
2508 &mut self,
2509 key: &crossterm::event::KeyEvent,
2510 ) -> Result<Option<bool>> {
2511 if let KeyCode::Char('r') = key.code {
2513 if key.modifiers.contains(KeyModifiers::CONTROL) {
2514 let current_input = self.get_input_text();
2516
2517 self.state_container.start_history_search(current_input);
2519
2520 self.update_history_matches_in_container();
2522
2523 let match_count = self.state_container.history_search().matches.len();
2525
2526 self.state_container.set_mode(AppMode::History);
2527 self.shadow_state
2528 .borrow_mut()
2529 .observe_mode_change(AppMode::History, "history_search_started");
2530 self.state_container.set_status_message(format!(
2531 "History search started (Ctrl+R) - {} matches",
2532 match_count
2533 ));
2534 return Ok(Some(false));
2535 }
2536 }
2537
2538 if let KeyCode::Char('p') = key.code {
2540 if key.modifiers.contains(KeyModifiers::CONTROL) {
2541 let history_entries = self
2542 .state_container
2543 .command_history()
2544 .get_navigation_entries();
2545 let history_commands: Vec<String> =
2546 history_entries.iter().map(|e| e.command.clone()).collect();
2547
2548 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
2549 if buffer.navigate_history_up(&history_commands) {
2550 self.sync_all_input_states();
2551 self.state_container
2552 .set_status_message("Previous command from history".to_string());
2553 }
2554 }
2555 return Ok(Some(false));
2556 }
2557 }
2558
2559 if let KeyCode::Char('n') = key.code {
2561 if key.modifiers.contains(KeyModifiers::CONTROL) {
2562 let history_entries = self
2563 .state_container
2564 .command_history()
2565 .get_navigation_entries();
2566 let history_commands: Vec<String> =
2567 history_entries.iter().map(|e| e.command.clone()).collect();
2568
2569 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
2570 if buffer.navigate_history_down(&history_commands) {
2571 self.sync_all_input_states();
2572 self.state_container
2573 .set_status_message("Next command from history".to_string());
2574 }
2575 }
2576 return Ok(Some(false));
2577 }
2578 }
2579
2580 match key.code {
2582 KeyCode::Up if key.modifiers.contains(KeyModifiers::ALT) => {
2583 let history_entries = self
2584 .state_container
2585 .command_history()
2586 .get_navigation_entries();
2587 let history_commands: Vec<String> =
2588 history_entries.iter().map(|e| e.command.clone()).collect();
2589
2590 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
2591 if buffer.navigate_history_up(&history_commands) {
2592 self.sync_all_input_states();
2593 self.state_container
2594 .set_status_message("Previous command (Alt+Up)".to_string());
2595 }
2596 }
2597 Ok(Some(false))
2598 }
2599 KeyCode::Down if key.modifiers.contains(KeyModifiers::ALT) => {
2600 let history_entries = self
2601 .state_container
2602 .command_history()
2603 .get_navigation_entries();
2604 let history_commands: Vec<String> =
2605 history_entries.iter().map(|e| e.command.clone()).collect();
2606
2607 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
2608 if buffer.navigate_history_down(&history_commands) {
2609 self.sync_all_input_states();
2610 self.state_container
2611 .set_status_message("Next command (Alt+Down)".to_string());
2612 }
2613 }
2614 Ok(Some(false))
2615 }
2616 _ => Ok(None),
2617 }
2618 }
2619
2620 fn try_handle_text_editing(
2622 &mut self,
2623 key: &crossterm::event::KeyEvent,
2624 ) -> Result<Option<bool>> {
2625 if let Some(action) = self.key_dispatcher.get_command_action(key) {
2627 match action {
2628 "expand_asterisk" => {
2629 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
2630 if buffer.expand_asterisk(&self.hybrid_parser) {
2631 if buffer.get_edit_mode() == EditMode::SingleLine {
2633 let text = buffer.get_input_text();
2634 let cursor = buffer.get_input_cursor_position();
2635 self.set_input_text_with_cursor(text, cursor);
2636 }
2637 }
2638 }
2639 return Ok(Some(false));
2640 }
2641 "expand_asterisk_visible" => {
2642 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
2643 if buffer.expand_asterisk_visible() {
2644 if buffer.get_edit_mode() == EditMode::SingleLine {
2646 let text = buffer.get_input_text();
2647 let cursor = buffer.get_input_cursor_position();
2648 self.set_input_text_with_cursor(text, cursor);
2649 }
2650 }
2651 }
2652 return Ok(Some(false));
2653 }
2654 "delete_word_backward" => {
2656 use crate::ui::traits::input_ops::InputBehavior;
2657 InputBehavior::delete_word_backward(self);
2658 return Ok(Some(false));
2659 }
2660 "delete_word_forward" => {
2662 use crate::ui::traits::input_ops::InputBehavior;
2663 InputBehavior::delete_word_forward(self);
2664 return Ok(Some(false));
2665 }
2666 "kill_line" => {
2668 use crate::ui::traits::input_ops::InputBehavior;
2669 InputBehavior::kill_line(self);
2670 return Ok(Some(false));
2671 }
2672 "kill_line_backward" => {
2674 use crate::ui::traits::input_ops::InputBehavior;
2675 InputBehavior::kill_line_backward(self);
2676 return Ok(Some(false));
2677 }
2678 "move_word_backward" => {
2680 self.move_cursor_word_backward();
2681 return Ok(Some(false));
2682 }
2683 "move_word_forward" => {
2685 self.move_cursor_word_forward();
2686 return Ok(Some(false));
2687 }
2688 _ => {} }
2690 }
2691
2692 match key.code {
2694 KeyCode::Char('k') if key.modifiers.contains(KeyModifiers::CONTROL) => {
2696 self.state_container
2698 .set_status_message("Ctrl+K pressed - killing to end of line".to_string());
2699 use crate::ui::traits::input_ops::InputBehavior;
2700 InputBehavior::kill_line(self);
2701 Ok(Some(false))
2702 }
2703 KeyCode::Char('k') if key.modifiers.contains(KeyModifiers::ALT) => {
2705 self.state_container
2707 .set_status_message("Alt+K - killing to end of line".to_string());
2708 use crate::ui::traits::input_ops::InputBehavior;
2709 InputBehavior::kill_line(self);
2710 Ok(Some(false))
2711 }
2712 KeyCode::Char('u') if key.modifiers.contains(KeyModifiers::CONTROL) => {
2714 use crate::ui::traits::input_ops::InputBehavior;
2716 InputBehavior::kill_line_backward(self);
2717 Ok(Some(false))
2718 }
2719 KeyCode::Char('v') if key.modifiers.contains(KeyModifiers::CONTROL) => {
2720 self.paste_from_clipboard();
2722 Ok(Some(false))
2723 }
2724 KeyCode::Char('y') if key.modifiers.contains(KeyModifiers::CONTROL) => {
2725 self.yank();
2727 Ok(Some(false))
2728 }
2729 KeyCode::Left if key.modifiers.contains(KeyModifiers::CONTROL) => {
2731 self.move_cursor_word_backward();
2733 Ok(Some(false))
2734 }
2735 KeyCode::Right if key.modifiers.contains(KeyModifiers::CONTROL) => {
2737 self.move_cursor_word_forward();
2739 Ok(Some(false))
2740 }
2741 KeyCode::Char('b') if key.modifiers.contains(KeyModifiers::ALT) => {
2743 self.move_cursor_word_backward();
2745 Ok(Some(false))
2746 }
2747 KeyCode::Char('f') if key.modifiers.contains(KeyModifiers::ALT) => {
2749 self.move_cursor_word_forward();
2751 Ok(Some(false))
2752 }
2753 _ => Ok(None), }
2755 }
2756
2757 fn try_handle_mode_transitions(
2759 &mut self,
2760 key: &crossterm::event::KeyEvent,
2761 old_cursor: usize,
2762 ) -> Result<Option<bool>> {
2763 match key.code {
2764 KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => {
2765 Ok(Some(true))
2767 }
2768 KeyCode::Char('d') if key.modifiers.contains(KeyModifiers::CONTROL) => {
2769 Ok(Some(true))
2771 }
2772 KeyCode::Enter => {
2773 let query = self.get_input_text().trim().to_string();
2775 debug!(target: "action", "Executing query: {}", query);
2776
2777 if !query.is_empty() {
2778 if query == ":help" {
2780 self.state_container.set_help_visible(true);
2781 self.set_mode_via_shadow_state(AppMode::Help, "help_requested");
2783 self.state_container
2784 .set_status_message("Help Mode - Press ESC to return".to_string());
2785 } else if query == ":exit" || query == ":quit" || query == ":q" {
2786 return Ok(Some(true));
2787 } else if query == ":tui" {
2788 self.state_container
2790 .set_status_message("Already in TUI mode".to_string());
2791 } else {
2792 self.state_container
2793 .set_status_message(format!("Processing query: '{}'", query));
2794 self.execute_query_v2(&query)?;
2795 }
2796 } else {
2797 self.state_container
2798 .set_status_message("Empty query - please enter a SQL command".to_string());
2799 }
2800 Ok(Some(false))
2801 }
2802 KeyCode::Tab => {
2803 self.apply_completion();
2805 Ok(Some(false))
2806 }
2807 KeyCode::Down => {
2808 debug!(target: "shadow_state", "Down arrow pressed in Command mode. has_dataview={}, edit_mode={:?}",
2809 self.state_container.has_dataview(),
2810 self.state_container.get_edit_mode());
2811
2812 if self.state_container.has_dataview()
2813 && self.state_container.get_edit_mode() == Some(EditMode::SingleLine)
2814 {
2815 debug!(target: "shadow_state", "Down arrow conditions met, switching to Results via set_mode");
2816 self.state_container.set_mode(AppMode::Results);
2818 self.shadow_state
2819 .borrow_mut()
2820 .observe_mode_change(AppMode::Results, "down_arrow_to_results");
2821 let row = self.state_container.get_last_results_row().unwrap_or(0);
2823 self.state_container.set_table_selected_row(Some(row));
2824
2825 let last_offset = self.state_container.get_last_scroll_offset();
2827 self.state_container.set_scroll_offset(last_offset);
2828 Ok(Some(false))
2829 } else {
2830 debug!(target: "shadow_state", "Down arrow conditions not met, falling through");
2831 Ok(None)
2833 }
2834 }
2835 _ => {
2836 self.handle_input_key(*key);
2838
2839 self.state_container.clear_completion();
2841
2842 self.handle_completion();
2844
2845 if self.get_input_cursor() != old_cursor {
2847 self.update_horizontal_scroll(120); }
2849
2850 Ok(Some(false))
2851 }
2852 }
2853 }
2854
2855 fn try_handle_results_navigation(
2857 &mut self,
2858 key: &crossterm::event::KeyEvent,
2859 ) -> Result<Option<bool>> {
2860 match key.code {
2861 KeyCode::PageDown | KeyCode::Char('f')
2862 if key.modifiers.contains(KeyModifiers::CONTROL) =>
2863 {
2864 NavigationBehavior::page_down(self);
2865 Ok(Some(false))
2866 }
2867 KeyCode::PageUp | KeyCode::Char('b')
2868 if key.modifiers.contains(KeyModifiers::CONTROL) =>
2869 {
2870 NavigationBehavior::page_up(self);
2871 Ok(Some(false))
2872 }
2873 _ => Ok(None), }
2875 }
2876
2877 fn try_handle_results_clipboard(
2879 &mut self,
2880 key: &crossterm::event::KeyEvent,
2881 ) -> Result<Option<bool>> {
2882 match key.code {
2883 KeyCode::Char('y') => {
2884 let selection_mode = self.get_selection_mode();
2885 debug!("'y' key pressed - selection_mode={:?}", selection_mode);
2886 match selection_mode {
2887 SelectionMode::Cell => {
2888 debug!("Yanking cell in cell selection mode");
2890 self.state_container
2891 .set_status_message("Yanking cell...".to_string());
2892 YankBehavior::yank_cell(self);
2893 }
2895 SelectionMode::Row => {
2896 debug!("'y' pressed in row mode - waiting for chord completion");
2899 self.state_container.set_status_message(
2900 "Press second key for chord: yy=row, yc=column, ya=all, yv=cell"
2901 .to_string(),
2902 );
2903 }
2904 SelectionMode::Column => {
2905 debug!("Yanking column in column selection mode");
2907 self.state_container
2908 .set_status_message("Yanking column...".to_string());
2909 YankBehavior::yank_column(self);
2910 }
2911 }
2912 Ok(Some(false))
2913 }
2914 _ => Ok(None),
2915 }
2916 }
2917
2918 fn try_handle_results_export(
2920 &mut self,
2921 key: &crossterm::event::KeyEvent,
2922 ) -> Result<Option<bool>> {
2923 match key.code {
2924 KeyCode::Char('e') if key.modifiers.contains(KeyModifiers::CONTROL) => {
2925 self.export_to_csv();
2926 Ok(Some(false))
2927 }
2928 KeyCode::Char('j') if key.modifiers.contains(KeyModifiers::CONTROL) => {
2929 self.export_to_json();
2930 Ok(Some(false))
2931 }
2932 _ => Ok(None),
2933 }
2934 }
2935
2936 fn try_handle_results_help(
2938 &mut self,
2939 key: &crossterm::event::KeyEvent,
2940 ) -> Result<Option<bool>> {
2941 match key.code {
2942 KeyCode::F(1) | KeyCode::Char('?') => {
2943 self.state_container.set_help_visible(true);
2944 self.set_mode_via_shadow_state(AppMode::Help, "help_requested");
2946 self.help_widget.on_enter();
2947 Ok(Some(false))
2948 }
2949 _ => Ok(None),
2950 }
2951 }
2952
2953 fn handle_results_input(&mut self, key: crossterm::event::KeyEvent) -> Result<bool> {
2954 debug!(
2956 "handle_results_input: Processing key {:?} in Results mode",
2957 key
2958 );
2959
2960 let is_vim_navigating = self.vim_search_adapter.borrow().is_navigating();
2962 let vim_is_active = self.vim_search_adapter.borrow().is_active();
2963 let has_search_pattern = !self.state_container.get_search_pattern().is_empty();
2964
2965 debug!(
2966 "Search state check: vim_navigating={}, vim_active={}, has_pattern={}, pattern='{}'",
2967 is_vim_navigating,
2968 vim_is_active,
2969 has_search_pattern,
2970 self.state_container.get_search_pattern()
2971 );
2972
2973 if key.code == KeyCode::Esc {
2975 info!("ESCAPE KEY DETECTED in Results mode!");
2976
2977 if is_vim_navigating || vim_is_active || has_search_pattern {
2978 info!("Escape pressed with active search - clearing via StateCoordinator");
2979 debug!(
2980 "Pre-clear state: vim_navigating={}, vim_active={}, pattern='{}'",
2981 is_vim_navigating,
2982 vim_is_active,
2983 self.state_container.get_search_pattern()
2984 );
2985
2986 use crate::ui::state::state_coordinator::StateCoordinator;
2988 StateCoordinator::cancel_search_with_refs(
2989 &mut self.state_container,
2990 &self.shadow_state,
2991 Some(&self.vim_search_adapter),
2992 );
2993
2994 let post_pattern = self.state_container.get_search_pattern();
2996 let post_vim_active = self.vim_search_adapter.borrow().is_active();
2997 info!(
2998 "Post-clear state: pattern='{}', vim_active={}",
2999 post_pattern, post_vim_active
3000 );
3001
3002 self.state_container
3003 .set_status_message("Search cleared".to_string());
3004 return Ok(false);
3005 } else {
3006 info!("Escape pressed but no active search to clear");
3007 }
3008 }
3009
3010 let selection_mode = self.state_container.get_selection_mode();
3011
3012 debug!(
3013 "handle_results_input: key={:?}, selection_mode={:?}",
3014 key, selection_mode
3015 );
3016
3017 let normalized = self.state_container.normalize_key(key);
3019
3020 let action_context = self.build_action_context();
3022 let mapped_action = self.key_mapper.map_key(normalized, &action_context);
3023 let action = mapped_action.as_ref().map(|a| format!("{:?}", a));
3024
3025 if normalized != key {
3027 self.state_container
3028 .log_key_press(key, Some(format!("normalized to {:?}", normalized)));
3029 }
3030 self.state_container
3031 .log_key_press(normalized, action.clone());
3032
3033 let normalized_key = normalized;
3034
3035 let action_context = self.build_action_context();
3039 debug!(
3040 "Action context for key {:?}: mode={:?}",
3041 normalized_key.code, action_context.mode
3042 );
3043 if let Some(action) = self
3044 .key_mapper
3045 .map_key(normalized_key.clone(), &action_context)
3046 {
3047 info!(
3048 "✓ Action system: key {:?} -> action {:?}",
3049 normalized_key.code, action
3050 );
3051 if let Ok(result) = self.try_handle_action(action, &action_context) {
3052 match result {
3053 ActionResult::Handled => {
3054 debug!("Action handled by new system");
3055 return Ok(false);
3056 }
3057 ActionResult::Exit => {
3058 debug!("Action requested exit");
3059 return Ok(true);
3060 }
3061 ActionResult::SwitchMode(mode) => {
3062 debug!("Action requested mode switch to {:?}", mode);
3063 self.state_container.set_mode(mode);
3064 return Ok(false);
3065 }
3066 ActionResult::Error(err) => {
3067 warn!("Action error: {}", err);
3068 self.state_container
3069 .set_status_message(format!("Error: {}", err));
3070 return Ok(false);
3071 }
3072 ActionResult::NotHandled => {
3073 debug!("Action not handled, falling back to legacy system");
3075 }
3076 }
3077 }
3078 }
3079
3080 if matches!(key.code, KeyCode::Char('G')) {
3082 debug!("Detected uppercase G key press!");
3083 }
3084
3085 if mapped_action.is_none() {
3099 debug!(
3100 "No action mapping for key {:?} in Results mode",
3101 normalized_key
3102 );
3103 }
3104
3105 if let Some(result) = self.try_handle_results_navigation(&normalized_key)? {
3107 return Ok(result);
3108 }
3109
3110 if let Some(result) = self.try_handle_results_clipboard(&normalized_key)? {
3112 return Ok(result);
3113 }
3114
3115 if let Some(result) = self.try_handle_results_export(&normalized_key)? {
3117 return Ok(result);
3118 }
3119
3120 if let Some(result) = self.try_handle_results_help(&normalized_key)? {
3122 return Ok(result);
3123 }
3124
3125 Ok(false)
3130 }
3131 fn execute_search_action(&mut self, mode: SearchMode, pattern: String) {
3134 debug!(target: "search", "execute_search_action called: mode={:?}, pattern='{}', current_app_mode={:?}, thread={:?}",
3135 mode, pattern, self.shadow_state.borrow().get_mode(), std::thread::current().id());
3136 match mode {
3137 SearchMode::Search => {
3138 debug!(target: "search", "Executing search with pattern: '{}', app_mode={:?}", pattern, self.shadow_state.borrow().get_mode());
3139 debug!(target: "search", "Search: current results count={}",
3140 self.state_container.get_buffer_dataview().map(|v| v.source().row_count()).unwrap_or(0));
3141
3142 self.state_container.start_search(pattern.clone());
3144
3145 self.state_container.set_search_pattern(pattern.clone());
3146 self.perform_search();
3147 let matches_count = self.state_container.search().matches.len();
3148 debug!(target: "search", "After perform_search, app_mode={:?}, matches_found={}",
3149 self.shadow_state.borrow().get_mode(),
3150 matches_count);
3151
3152 if matches_count > 0 {
3154 let matches_for_vim: Vec<(usize, usize)> = {
3156 let search_manager = self.search_manager.borrow();
3157 search_manager
3158 .all_matches()
3159 .iter()
3160 .map(|m| (m.row, m.column))
3161 .collect()
3162 };
3163
3164 if let Some(dataview) = self.state_container.get_buffer_dataview() {
3166 info!(target: "search", "Syncing {} matches to VimSearchManager for pattern '{}'",
3167 matches_for_vim.len(), pattern);
3168 self.vim_search_adapter
3169 .borrow_mut()
3170 .set_search_state_from_external(
3171 pattern.clone(),
3172 matches_for_vim,
3173 dataview,
3174 );
3175 }
3176 }
3177
3178 if matches_count > 0 {
3180 let (row, col) = {
3182 let search_manager = self.search_manager.borrow();
3183 if let Some(first_match) = search_manager.first_match() {
3184 (first_match.row, first_match.column)
3185 } else {
3186 let search_state = self.state_container.search();
3188 if let Some((row, col, _, _)) = search_state.matches.first() {
3189 (*row, *col)
3190 } else {
3191 (0, 0)
3192 }
3193 }
3194 };
3195
3196 info!(target: "search", "NAVIGATION START: Moving to first match at data row={}, col={}", row, col);
3197
3198 self.state_container.set_table_selected_row(Some(row));
3201 self.state_container.set_selected_row(Some(row));
3202 info!(target: "search", " Set row position to {}", row);
3203
3204 {
3206 let mut nav = self.state_container.navigation_mut();
3207 nav.selected_column = col;
3208 }
3209 self.state_container.set_current_column_buffer(col);
3210 info!(target: "search", " Set column position to {}", col);
3211
3212 info!(target: "search", "Updating TableWidgetManager for debounced search to ({}, {})", row, col);
3214 self.table_widget_manager
3215 .borrow_mut()
3216 .on_debounced_search(row, col);
3217
3218 {
3220 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
3221 if let Some(ref mut viewport_manager) = *viewport_manager_borrow {
3222 let viewport_height = self.state_container.navigation().viewport_rows;
3224 let viewport_width = self.state_container.navigation().viewport_columns;
3225 let current_scroll = self.state_container.navigation().scroll_offset.0;
3226
3227 info!(target: "search", " Viewport dimensions: {}x{}, current_scroll: {}",
3228 viewport_height, viewport_width, current_scroll);
3229
3230 let new_row_offset = if row < current_scroll {
3232 info!(target: "search", " Match is above viewport, scrolling up to row {}", row);
3233 row } else if row >= current_scroll + viewport_height.saturating_sub(1) {
3235 let centered = row.saturating_sub(viewport_height / 2);
3236 info!(target: "search", " Match is below viewport, centering at row {}", centered);
3237 centered } else {
3239 info!(target: "search", " Match is already visible, keeping scroll at {}", current_scroll);
3240 current_scroll };
3242
3243 let current_col_scroll =
3245 self.state_container.navigation().scroll_offset.1;
3246 let new_col_offset = if col < current_col_scroll {
3247 info!(target: "search", " Match column {} is left of viewport (scroll={}), scrolling left", col, current_col_scroll);
3248 col } else if col >= current_col_scroll + viewport_width.saturating_sub(1) {
3250 let centered = col.saturating_sub(viewport_width / 4);
3251 info!(target: "search", " Match column {} is right of viewport (scroll={}, width={}), scrolling to {}",
3252 col, current_col_scroll, viewport_width, centered);
3253 centered } else {
3255 info!(target: "search", " Match column {} is visible, keeping scroll at {}", col, current_col_scroll);
3256 current_col_scroll };
3258
3259 viewport_manager.set_viewport(
3261 new_row_offset,
3262 new_col_offset,
3263 viewport_width as u16,
3264 viewport_height as u16,
3265 );
3266 info!(target: "search", " Set viewport to row_offset={}, col_offset={}", new_row_offset, new_col_offset);
3267
3268 let crosshair_row = row - new_row_offset;
3270 let crosshair_col = col - new_col_offset;
3271 viewport_manager.set_crosshair(crosshair_row, crosshair_col);
3272 info!(target: "search", " Set crosshair to viewport-relative ({}, {}), absolute was ({}, {})",
3273 crosshair_row, crosshair_col, row, col);
3274
3275 let mut nav = self.state_container.navigation_mut();
3277 nav.scroll_offset.0 = new_row_offset;
3278 nav.scroll_offset.1 = new_col_offset;
3279 }
3280 }
3281
3282 self.state_container.set_current_match(Some((row, col)));
3284
3285 {
3288 let mut nav = self.state_container.navigation_mut();
3289 nav.selected_row = row;
3290 nav.selected_column = col;
3291 }
3292 info!(target: "search", " Forced navigation state to row={}, col={}", row, col);
3293
3294 self.state_container.set_status_message(format!(
3296 "Match 1/{} at row {}, col {}",
3297 matches_count,
3298 row + 1,
3299 col + 1
3300 ));
3301 }
3302 }
3303 SearchMode::Filter => {
3304 use crate::ui::state::state_coordinator::StateCoordinator;
3305
3306 StateCoordinator::apply_filter_search_with_refs(
3308 &mut self.state_container,
3309 &self.shadow_state,
3310 &pattern,
3311 );
3312
3313 self.apply_filter(&pattern);
3315
3316 debug!(target: "search", "After apply_filter, filtered_count={}",
3317 self.state_container.get_buffer_dataview().map(|v| v.row_count()).unwrap_or(0));
3318 }
3319 SearchMode::FuzzyFilter => {
3320 use crate::ui::state::state_coordinator::StateCoordinator;
3321
3322 StateCoordinator::apply_fuzzy_filter_search_with_refs(
3324 &mut self.state_container,
3325 &self.shadow_state,
3326 &pattern,
3327 );
3328
3329 self.apply_fuzzy_filter();
3331
3332 let indices_count = self.state_container.get_fuzzy_filter_indices().len();
3333 debug!(target: "search", "After apply_fuzzy_filter, matched_indices={}", indices_count);
3334 }
3335 SearchMode::ColumnSearch => {
3336 use crate::ui::state::state_coordinator::StateCoordinator;
3337
3338 debug!(target: "search", "Executing column search with pattern: '{}'", pattern);
3339
3340 StateCoordinator::apply_column_search_with_refs(
3342 &mut self.state_container,
3343 &self.shadow_state,
3344 &pattern,
3345 );
3346
3347 self.search_columns();
3349
3350 debug!(target: "search", "After search_columns, app_mode={:?}", self.shadow_state.borrow().get_mode());
3351 }
3352 }
3353 }
3354
3355 fn enter_search_mode(&mut self, mode: SearchMode) {
3356 debug!(target: "search", "enter_search_mode called for {:?}, current_mode={:?}, input_text='{}'",
3357 mode, self.shadow_state.borrow().get_mode(), self.state_container.get_input_text());
3358
3359 let current_sql = if self.shadow_state.borrow().is_in_results_mode() {
3361 let last_query = self.state_container.get_last_query();
3363 let input_text = self.state_container.get_input_text();
3364 debug!(target: "search", "COLUMN_SEARCH_SAVE_DEBUG: last_query='{}', input_text='{}'", last_query, input_text);
3365
3366 if !last_query.is_empty() {
3367 debug!(target: "search", "Using last_query for search mode: '{}'", last_query);
3368 last_query
3369 } else if !input_text.is_empty() {
3370 debug!(target: "search", "No last_query, using input_text as fallback: '{}'", input_text);
3373 input_text
3374 } else {
3375 warn!(target: "search", "No last_query or input_text found when entering search mode from Results!");
3377 String::new()
3378 }
3379 } else {
3380 self.get_input_text()
3382 };
3383
3384 let cursor_pos = current_sql.len();
3385
3386 debug!(
3387 "Entering {} mode, saving SQL: '{}', cursor: {}",
3388 mode.title(),
3389 current_sql,
3390 cursor_pos
3391 );
3392
3393 self.search_modes_widget
3395 .enter_mode(mode.clone(), current_sql, cursor_pos);
3396
3397 debug!(target: "mode", "Setting app mode from {:?} to {:?}", self.shadow_state.borrow().get_mode(), mode.to_app_mode());
3399 let trigger = match mode {
3400 SearchMode::ColumnSearch => "backslash_column_search",
3401 SearchMode::Search => "data_search_started",
3402 SearchMode::FuzzyFilter => "fuzzy_filter_started",
3403 SearchMode::Filter => "filter_started",
3404 };
3405 self.sync_mode(mode.to_app_mode(), trigger);
3406
3407 let search_type = match mode {
3409 SearchMode::ColumnSearch => crate::ui::state::shadow_state::SearchType::Column,
3410 SearchMode::Search => crate::ui::state::shadow_state::SearchType::Data,
3411 SearchMode::FuzzyFilter | SearchMode::Filter => {
3412 crate::ui::state::shadow_state::SearchType::Fuzzy
3413 }
3414 };
3415 self.shadow_state
3416 .borrow_mut()
3417 .observe_search_start(search_type, trigger);
3418
3419 match mode {
3421 SearchMode::Search => {
3422 self.state_container.clear_search();
3424 self.state_container.set_search_pattern(String::new());
3425 }
3426 SearchMode::Filter => {
3427 self.state_container.set_filter_pattern(String::new());
3428 self.state_container.filter_mut().clear();
3429 }
3430 SearchMode::FuzzyFilter => {
3431 self.state_container.set_fuzzy_filter_pattern(String::new());
3432 self.state_container.set_fuzzy_filter_indices(Vec::new());
3433 self.state_container.set_fuzzy_filter_active(false);
3434 }
3435 SearchMode::ColumnSearch => {
3436 self.state_container.clear_column_search();
3438 if let Some(dataview) = self.state_container.get_buffer_dataview_mut() {
3439 dataview.clear_column_search();
3440 }
3441
3442 }
3444 }
3445
3446 self.input = tui_input::Input::default();
3448 }
3450
3451 fn handle_search_modes_input(&mut self, key: crossterm::event::KeyEvent) -> Result<bool> {
3452 if key.code == KeyCode::Char('c') && key.modifiers.contains(KeyModifiers::CONTROL) {
3454 return Ok(true); }
3456
3457 let action = self.search_modes_widget.handle_key(key);
3460
3461 match action {
3462 SearchModesAction::Continue => {
3463 }
3465 SearchModesAction::InputChanged(mode, pattern) => {
3466 self.set_input_text_with_cursor(pattern.clone(), pattern.len());
3468
3469 match mode {
3471 SearchMode::Search => {
3472 self.state_container.set_search_pattern(pattern);
3473 }
3474 SearchMode::Filter => {
3475 self.state_container.set_filter_pattern(pattern.clone());
3476 let mut filter = self.state_container.filter_mut();
3477 filter.pattern = pattern.clone();
3478 filter.is_active = true;
3479 }
3480 SearchMode::FuzzyFilter => {
3481 self.state_container.set_fuzzy_filter_pattern(pattern);
3482 }
3483 SearchMode::ColumnSearch => {
3484 }
3486 }
3487 }
3488 SearchModesAction::ExecuteDebounced(mode, pattern) => {
3489 self.execute_search_action(mode, pattern);
3492 }
3494 SearchModesAction::Apply(mode, pattern) => {
3495 debug!(target: "search", "Apply action triggered for {:?} with pattern '{}'", mode, pattern);
3496 match mode {
3498 SearchMode::Search => {
3499 debug!(target: "search", "Search Apply: Applying search with pattern '{}'", pattern);
3500 self.execute_search_action(SearchMode::Search, pattern);
3502 debug!(target: "search", "Search Apply: last_query='{}', will restore saved SQL from widget", self.state_container.get_last_query());
3503 }
3505 SearchMode::Filter => {
3506 debug!(target: "search", "Filter Apply: Applying filter with pattern '{}'", pattern);
3507 self.state_container.set_filter_pattern(pattern.clone());
3508 {
3509 let mut filter = self.state_container.filter_mut();
3510 filter.pattern = pattern.clone();
3511 filter.is_active = true;
3512 } self.apply_filter(&pattern); debug!(target: "search", "Filter Apply: last_query='{}', will restore saved SQL from widget", self.state_container.get_last_query());
3515 }
3516 SearchMode::FuzzyFilter => {
3517 debug!(target: "search", "FuzzyFilter Apply: Applying filter with pattern '{}'", pattern);
3518 self.state_container.set_fuzzy_filter_pattern(pattern);
3519 self.apply_fuzzy_filter();
3520 debug!(target: "search", "FuzzyFilter Apply: last_query='{}', will restore saved SQL from widget", self.state_container.get_last_query());
3521 }
3522 SearchMode::ColumnSearch => {
3523 let column_info = {
3526 let column_search = self.state_container.column_search();
3527 if !column_search.matching_columns.is_empty() {
3528 let current_match = column_search.current_match;
3529 Some(column_search.matching_columns[current_match].clone())
3530 } else {
3531 None
3532 }
3533 };
3534
3535 if let Some((col_idx, col_name)) = column_info {
3536 self.state_container.set_current_column(col_idx);
3537 self.state_container.set_current_column_buffer(col_idx);
3538
3539 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
3541 if let Some(viewport_manager) = viewport_manager_borrow.as_mut() {
3542 viewport_manager.set_current_column(col_idx);
3543 }
3544 drop(viewport_manager_borrow);
3545
3546 debug!(target: "column_search_sync", "ColumnSearch Apply: About to call sync_navigation_with_viewport() for column: {}", col_name);
3549 debug!(target: "column_search_sync", "ColumnSearch Apply: Pre-sync - viewport current_column: {}",
3550 if let Some(vm) = self.viewport_manager.try_borrow().ok() {
3551 vm.as_ref().map(|v| v.get_crosshair_col()).unwrap_or(0)
3552 } else { 0 });
3553 self.sync_navigation_with_viewport();
3554 debug!(target: "column_search_sync", "ColumnSearch Apply: Post-sync - navigation current_column: {}",
3555 self.state_container.navigation().selected_column);
3556 debug!(target: "column_search_sync", "ColumnSearch Apply: sync_navigation_with_viewport() completed for column: {}", col_name);
3557
3558 self.state_container
3559 .set_status_message(format!("Jumped to column: {}", col_name));
3560 }
3561
3562 debug!(target: "search", "ColumnSearch Apply: Exiting without modifying input_text");
3565 debug!(target: "search", "ColumnSearch Apply: last_query='{}', will restore saved SQL from widget", self.state_container.get_last_query());
3566 }
3568 }
3569
3570 let saved_state = self.search_modes_widget.exit_mode();
3573
3574 if let Some((sql, cursor)) = saved_state {
3575 debug!(target: "search", "Exiting search mode. Original SQL was: '{}', cursor: {}", sql, cursor);
3576 debug!(target: "buffer", "Returning to Results mode, preserving last_query: '{}'",
3577 self.state_container.get_last_query());
3578
3579 debug!(target: "search", "Restoring saved SQL to input_text: '{}'", sql);
3582 self.set_input_text_with_cursor(sql, cursor);
3584 } else {
3585 if mode == SearchMode::ColumnSearch {
3587 let last_query = self.state_container.get_last_query();
3589 if !last_query.is_empty() {
3590 debug!(target: "search", "Column search: No saved state, restoring last_query: '{}'", last_query);
3591 self.set_input_text(last_query);
3592 } else {
3593 debug!(target: "search", "Column search: No saved state or last_query, clearing input");
3594 self.set_input_text(String::new());
3595 }
3596 } else {
3597 debug!(target: "search", "No saved state from widget, keeping current SQL");
3598 }
3599 }
3600
3601 use crate::ui::state::state_coordinator::StateCoordinator;
3603
3604 if mode == SearchMode::ColumnSearch {
3607 debug!(target: "column_search_sync", "ColumnSearch Apply: Canceling column search completely with cancel_search_with_refs()");
3608
3609 if let Some(dataview) = self.state_container.get_buffer_dataview_mut() {
3611 dataview.clear_column_search();
3612 debug!(target: "column_search_sync", "ColumnSearch Apply: Cleared column search in DataView");
3613 }
3614
3615 StateCoordinator::cancel_search_with_refs(
3616 &mut self.state_container,
3617 &self.shadow_state,
3618 Some(&self.vim_search_adapter),
3619 );
3620 debug!(target: "column_search_sync", "ColumnSearch Apply: Column search canceled and mode switched to Results");
3622 } else {
3623 debug!(target: "column_search_sync", "Search Apply: About to call StateCoordinator::complete_search_with_refs() for mode: {:?}", mode);
3625 StateCoordinator::complete_search_with_refs(
3626 &mut self.state_container,
3627 &self.shadow_state,
3628 Some(&self.vim_search_adapter),
3629 AppMode::Results,
3630 "search_applied",
3631 );
3632 debug!(target: "column_search_sync", "Search Apply: StateCoordinator::complete_search_with_refs() completed - should now be in Results mode");
3633 }
3634
3635 let filter_msg = match mode {
3637 SearchMode::FuzzyFilter => {
3638 let query = self.state_container.get_last_query();
3639 format!(
3640 "Fuzzy filter applied. Query: '{}'. Press 'f' again to modify.",
3641 if query.len() > 30 {
3642 format!("{}...", &query[..30])
3643 } else {
3644 query
3645 }
3646 )
3647 }
3648 SearchMode::Filter => "Filter applied. Press 'F' again to modify.".to_string(),
3649 SearchMode::Search => {
3650 let matches = self.state_container.search().matches.len();
3651 if matches > 0 {
3652 format!("Found {} matches. Use n/N to navigate.", matches)
3653 } else {
3654 "No matches found.".to_string()
3655 }
3656 }
3657 SearchMode::ColumnSearch => "Column search complete.".to_string(),
3658 };
3659 self.state_container.set_status_message(filter_msg);
3660 }
3661 SearchModesAction::Cancel => {
3662 let mode = self.shadow_state.borrow().get_mode();
3664 match mode {
3665 AppMode::FuzzyFilter => {
3666 debug!(target: "search", "FuzzyFilter Cancel: Clearing fuzzy filter");
3668 self.state_container.set_fuzzy_filter_pattern(String::new());
3669 self.apply_fuzzy_filter(); self.state_container.set_fuzzy_filter_indices(Vec::new());
3671 self.state_container.set_fuzzy_filter_active(false);
3672 }
3673 AppMode::Filter => {
3674 debug!(target: "search", "Filter Cancel: Clearing filter pattern and state");
3676 self.state_container.filter_mut().clear();
3677 self.state_container.set_filter_pattern(String::new());
3678 self.state_container.set_filter_active(false);
3679 self.apply_filter("");
3681 }
3682 AppMode::ColumnSearch => {
3683 self.state_container.clear_column_search();
3685 debug!(target: "search", "ColumnSearch Cancel: Exiting without modifying input_text");
3687 debug!(target: "search", "ColumnSearch Cancel: last_query='{}', will restore saved SQL from widget", self.state_container.get_last_query());
3688 }
3689 _ => {}
3690 }
3691
3692 if let Some((sql, cursor)) = self.search_modes_widget.exit_mode() {
3694 debug!(target: "search", "Cancel: Restoring saved SQL: '{}', cursor: {}", sql, cursor);
3695 if !sql.is_empty() {
3696 self.set_input_text_with_cursor(sql, cursor);
3698 }
3699 } else {
3700 debug!(target: "search", "Cancel: No saved SQL from widget");
3701 }
3702
3703 use crate::ui::state::state_coordinator::StateCoordinator;
3706 StateCoordinator::cancel_search_with_refs(
3707 &mut self.state_container,
3708 &self.shadow_state,
3709 Some(&self.vim_search_adapter),
3710 );
3711 }
3712 SearchModesAction::NextMatch => {
3713 debug!(target: "search", "NextMatch action, current_mode={:?}, widget_mode={:?}",
3714 self.shadow_state.borrow().get_mode(), self.search_modes_widget.current_mode());
3715
3716 if self.shadow_state.borrow().is_in_column_search()
3718 || self.search_modes_widget.current_mode() == Some(SearchMode::ColumnSearch)
3719 {
3720 debug!(target: "search", "Calling next_column_match");
3721 if !self.shadow_state.borrow().is_in_column_search() {
3723 debug!(target: "search", "WARNING: Mode mismatch - fixing");
3724 self.state_container.set_mode(AppMode::ColumnSearch);
3725 self.shadow_state.borrow_mut().observe_search_start(
3726 crate::ui::state::shadow_state::SearchType::Column,
3727 "column_search_mode_fix_next",
3728 );
3729 }
3730 self.next_column_match();
3731 } else {
3732 debug!(target: "search", "Not in ColumnSearch mode, skipping next_column_match");
3733 }
3734 }
3735 SearchModesAction::PreviousMatch => {
3736 debug!(target: "search", "PreviousMatch action, current_mode={:?}, widget_mode={:?}",
3737 self.shadow_state.borrow().get_mode(), self.search_modes_widget.current_mode());
3738
3739 if self.shadow_state.borrow().get_mode() == AppMode::ColumnSearch
3741 || self.search_modes_widget.current_mode() == Some(SearchMode::ColumnSearch)
3742 {
3743 debug!(target: "search", "Calling previous_column_match");
3744 if self.shadow_state.borrow().get_mode() != AppMode::ColumnSearch {
3746 debug!(target: "search", "WARNING: Mode mismatch - fixing");
3747 self.state_container.set_mode(AppMode::ColumnSearch);
3748 self.shadow_state.borrow_mut().observe_search_start(
3749 crate::ui::state::shadow_state::SearchType::Column,
3750 "column_search_mode_fix_prev",
3751 );
3752 }
3753 self.previous_column_match();
3754 } else {
3755 debug!(target: "search", "Not in ColumnSearch mode, skipping previous_column_match");
3756 }
3757 }
3758 SearchModesAction::PassThrough => {}
3759 }
3760
3761 Ok(false)
3764 }
3765
3766 fn handle_help_input(&mut self, key: crossterm::event::KeyEvent) -> Result<bool> {
3767 let result = match key.code {
3769 crossterm::event::KeyCode::Esc | crossterm::event::KeyCode::Char('q') => {
3770 self.help_widget.on_exit();
3771 self.state_container.set_help_visible(false);
3772
3773 let target_mode = if self.state_container.has_dataview() {
3775 AppMode::Results
3776 } else {
3777 AppMode::Command
3778 };
3779
3780 self.set_mode_via_shadow_state(target_mode, "escape_from_help");
3782
3783 Ok(false)
3785 }
3786 _ => {
3787 self.help_widget.handle_key(key);
3789 Ok(false)
3790 }
3791 };
3792
3793 result
3794 }
3795
3796 fn handle_history_input(&mut self, key: crossterm::event::KeyEvent) -> Result<bool> {
3799 use crossterm::event::{KeyCode, KeyModifiers};
3801
3802 let result = match key.code {
3803 KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => {
3804 crate::ui::input::history_input_handler::HistoryInputResult::Exit
3805 }
3806 KeyCode::Esc => {
3807 let original_input = self.state_container.cancel_history_search();
3809 if let Some(buffer) = self.state_container.current_buffer_mut() {
3810 self.shadow_state.borrow_mut().set_mode(
3811 crate::buffer::AppMode::Command,
3812 buffer,
3813 "history_cancelled",
3814 );
3815 buffer.set_status_message("History search cancelled".to_string());
3816 }
3817 crate::ui::input::history_input_handler::HistoryInputResult::SwitchToCommand(Some(
3818 (original_input, 0),
3819 ))
3820 }
3821 KeyCode::Enter => {
3822 if let Some(command) = self.state_container.accept_history_search() {
3824 if let Some(buffer) = self.state_container.current_buffer_mut() {
3825 self.shadow_state.borrow_mut().set_mode(
3826 crate::buffer::AppMode::Command,
3827 buffer,
3828 "history_accepted",
3829 );
3830 buffer.set_status_message(
3831 "Command loaded from history (cursor at start)".to_string(),
3832 );
3833 }
3834 crate::ui::input::history_input_handler::HistoryInputResult::SwitchToCommand(
3836 Some((command, 0)),
3837 )
3838 } else {
3839 crate::ui::input::history_input_handler::HistoryInputResult::Continue
3840 }
3841 }
3842 KeyCode::Up => {
3843 self.state_container.history_search_previous();
3844 crate::ui::input::history_input_handler::HistoryInputResult::Continue
3845 }
3846 KeyCode::Down => {
3847 self.state_container.history_search_next();
3848 crate::ui::input::history_input_handler::HistoryInputResult::Continue
3849 }
3850 KeyCode::Char('r') if key.modifiers.contains(KeyModifiers::CONTROL) => {
3851 self.state_container.history_search_next();
3853 crate::ui::input::history_input_handler::HistoryInputResult::Continue
3854 }
3855 KeyCode::Backspace => {
3856 self.state_container.history_search_backspace();
3857 crate::ui::input::history_input_handler::HistoryInputResult::Continue
3858 }
3859 KeyCode::Char(c) => {
3860 self.state_container.history_search_add_char(c);
3861 crate::ui::input::history_input_handler::HistoryInputResult::Continue
3862 }
3863 _ => crate::ui::input::history_input_handler::HistoryInputResult::Continue,
3864 };
3865
3866 match result {
3868 crate::ui::input::history_input_handler::HistoryInputResult::Exit => return Ok(true),
3869 crate::ui::input::history_input_handler::HistoryInputResult::SwitchToCommand(
3870 input_data,
3871 ) => {
3872 if let Some((text, cursor_pos)) = input_data {
3873 self.set_input_text_with_cursor(text, cursor_pos);
3874 self.sync_all_input_states();
3876 }
3877 }
3878 crate::ui::input::history_input_handler::HistoryInputResult::Continue => {
3879 if crate::ui::input::history_input_handler::key_updates_search(key) {
3881 self.update_history_matches_in_container();
3882 }
3883 }
3884 }
3885
3886 Ok(false)
3887 }
3888
3889 fn update_history_matches_in_container(&mut self) {
3891 let (current_columns, current_source_str) =
3893 if let Some(dataview) = self.state_container.get_buffer_dataview() {
3894 (
3895 dataview.column_names(), Some(dataview.source().name.clone()), )
3898 } else {
3899 (vec![], None)
3900 };
3901
3902 let current_source = current_source_str.as_deref();
3903 let query = self.state_container.history_search().query.clone();
3904
3905 self.state_container.update_history_search_with_schema(
3906 query,
3907 ¤t_columns,
3908 current_source,
3909 );
3910 }
3911
3912 fn handle_debug_input(&mut self, key: crossterm::event::KeyEvent) -> Result<bool> {
3913 let mut ctx = crate::ui::input::input_handlers::DebugInputContext {
3915 buffer_manager: self.state_container.buffers_mut(),
3916 debug_widget: &mut self.debug_widget,
3917 shadow_state: &self.shadow_state,
3918 };
3919
3920 let should_quit = crate::ui::input::input_handlers::handle_debug_input(&mut ctx, key)?;
3921
3922 if !should_quit {
3925 match key.code {
3926 KeyCode::Char('t') if key.modifiers.contains(KeyModifiers::CONTROL) => {
3927 self.yank_as_test_case();
3929 }
3930 KeyCode::Char('y') if key.modifiers.contains(KeyModifiers::SHIFT) => {
3931 self.yank_debug_with_context();
3933 }
3934 _ => {}
3935 }
3936 }
3937
3938 Ok(should_quit)
3939 }
3941
3942 fn handle_pretty_query_input(&mut self, key: crossterm::event::KeyEvent) -> Result<bool> {
3943 let mut ctx = crate::ui::input::input_handlers::DebugInputContext {
3945 buffer_manager: self.state_container.buffers_mut(),
3946 debug_widget: &mut self.debug_widget,
3947 shadow_state: &self.shadow_state,
3948 };
3949
3950 crate::ui::input::input_handlers::handle_pretty_query_input(&mut ctx, key)
3951 }
3952
3953 pub fn execute_query_v2(&mut self, query: &str) -> Result<()> {
3954 let context = self.query_orchestrator.execute_query(
3956 query,
3957 &mut self.state_container,
3958 &self.vim_search_adapter,
3959 );
3960
3961 match context {
3962 Ok(ctx) => {
3963 self.state_container
3965 .set_dataview(Some(ctx.result.dataview.clone()));
3966
3967 self.update_viewport_manager(Some(ctx.result.dataview.clone()));
3969
3970 self.state_container
3972 .update_data_size(ctx.result.stats.row_count, ctx.result.stats.column_count);
3973
3974 self.calculate_optimal_column_widths();
3976
3977 self.state_container
3979 .set_status_message(ctx.result.status_message());
3980
3981 self.state_container
3983 .command_history_mut()
3984 .add_entry_with_schema(
3985 ctx.query.clone(),
3986 true,
3987 Some(ctx.result.stats.execution_time.as_millis() as u64),
3988 ctx.result.column_names(),
3989 Some(ctx.result.table_name()),
3990 )?;
3991
3992 self.sync_mode(AppMode::Results, "execute_query_success");
3994
3995 self.reset_table_state();
3997
3998 Ok(())
3999 }
4000 Err(e) => {
4001 let error_msg = format!("Query error: {}", e);
4002 self.state_container.set_status_message(error_msg.clone());
4003
4004 self.state_container
4006 .command_history_mut()
4007 .add_entry_with_schema(query.to_string(), false, None, vec![], None)?;
4008
4009 Err(e)
4010 }
4011 }
4012 }
4013
4014 fn handle_completion(&mut self) {
4015 let cursor_pos = self.get_input_cursor();
4016 let query_str = self.get_input_text();
4017 let query = query_str.as_str();
4018
4019 let hybrid_result = self.hybrid_parser.get_completions(query, cursor_pos);
4020 if !hybrid_result.suggestions.is_empty() {
4021 self.state_container.set_status_message(format!(
4022 "Suggestions: {}",
4023 hybrid_result.suggestions.join(", ")
4024 ));
4025 }
4026 }
4027
4028 fn apply_completion(&mut self) {
4029 let cursor_pos = self.get_input_cursor();
4030 let query = self.get_input_text();
4031
4032 let suggestion = match self.get_or_refresh_completion(&query, cursor_pos) {
4034 Some(s) => s,
4035 None => return,
4036 };
4037
4038 self.apply_completion_to_input(&query, cursor_pos, &suggestion);
4040 }
4041
4042 fn get_or_refresh_completion(&mut self, query: &str, cursor_pos: usize) -> Option<String> {
4045 let is_same_context = self
4046 .state_container
4047 .is_same_completion_context(query, cursor_pos);
4048
4049 if !is_same_context {
4050 let hybrid_result = self.hybrid_parser.get_completions(query, cursor_pos);
4052 if hybrid_result.suggestions.is_empty() {
4053 self.state_container
4054 .set_status_message("No completions available".to_string());
4055 return None;
4056 }
4057
4058 self.state_container
4059 .set_completion_suggestions(hybrid_result.suggestions);
4060 } else if self.state_container.is_completion_active() {
4061 self.state_container.next_completion();
4063 } else {
4064 self.state_container
4065 .set_status_message("No completions available".to_string());
4066 return None;
4067 }
4068
4069 match self.state_container.get_current_completion() {
4071 Some(sugg) => Some(sugg),
4072 None => {
4073 self.state_container
4074 .set_status_message("No completion selected".to_string());
4075 None
4076 }
4077 }
4078 }
4079
4080 fn apply_completion_to_input(&mut self, query: &str, cursor_pos: usize, suggestion: &str) {
4082 let partial_word =
4083 crate::ui::utils::text_operations::extract_partial_word_at_cursor(query, cursor_pos);
4084
4085 if let Some(partial) = partial_word {
4086 self.apply_partial_completion(query, cursor_pos, &partial, suggestion);
4087 } else {
4088 self.apply_full_insertion(query, cursor_pos, suggestion);
4089 }
4090 }
4091
4092 fn apply_partial_completion(
4094 &mut self,
4095 query: &str,
4096 cursor_pos: usize,
4097 partial: &str,
4098 suggestion: &str,
4099 ) {
4100 let result = crate::ui::utils::text_operations::apply_completion_to_text(
4102 query, cursor_pos, partial, suggestion,
4103 );
4104
4105 self.set_input_text_with_cursor(result.new_text.clone(), result.new_cursor_position);
4107
4108 self.state_container
4110 .update_completion_context(result.new_text.clone(), result.new_cursor_position);
4111
4112 let completion = self.state_container.completion();
4114 let suggestion_info = if completion.suggestions.len() > 1 {
4115 format!(
4116 "Completed: {} ({}/{} - Tab for next)",
4117 suggestion,
4118 completion.current_index + 1,
4119 completion.suggestions.len()
4120 )
4121 } else {
4122 format!("Completed: {}", suggestion)
4123 };
4124 drop(completion);
4125 self.state_container.set_status_message(suggestion_info);
4126 }
4127
4128 fn apply_full_insertion(&mut self, query: &str, cursor_pos: usize, suggestion: &str) {
4130 let before_cursor = &query[..cursor_pos];
4132 let after_cursor = &query[cursor_pos..];
4133 let new_query = format!("{}{}{}", before_cursor, suggestion, after_cursor);
4134
4135 let cursor_pos_new = if suggestion.ends_with("('')") {
4137 cursor_pos + suggestion.len() - 2
4139 } else {
4140 cursor_pos + suggestion.len()
4141 };
4142
4143 self.set_input_text(new_query.clone());
4145
4146 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
4148 buffer.set_input_cursor_position(cursor_pos_new);
4149 self.sync_all_input_states();
4151 }
4152
4153 self.state_container
4155 .update_completion_context(new_query, cursor_pos_new);
4156
4157 self.state_container
4158 .set_status_message(format!("Inserted: {}", suggestion));
4159 }
4160
4161 fn get_column_count(&self) -> usize {
4168 if let Some(provider) = self.get_data_provider() {
4170 provider.get_column_count()
4171 } else {
4172 0
4173 }
4174 }
4175
4176 fn get_column_names_via_provider(&self) -> Vec<String> {
4181 if let Some(provider) = self.get_data_provider() {
4182 provider.get_column_names()
4183 } else {
4184 Vec::new()
4185 }
4186 }
4187
4188 fn toggle_column_pin_impl(&mut self) {
4193 let visual_col_idx = if let Some(ref viewport_manager) = *self.viewport_manager.borrow() {
4195 viewport_manager.get_crosshair_col()
4196 } else {
4197 0
4198 };
4199
4200 let column_name = if let Some(dataview) = self.state_container.get_buffer_dataview() {
4202 let all_columns = dataview.column_names();
4203 all_columns.get(visual_col_idx).cloned()
4204 } else {
4205 None
4206 };
4207
4208 if let Some(col_name) = column_name {
4209 if let Some(dataview) = self.state_container.get_buffer_dataview_mut() {
4210 let pinned_names = dataview.get_pinned_column_names();
4212 if pinned_names.contains(&col_name) {
4213 dataview.unpin_column_by_name(&col_name);
4215 self.state_container
4216 .set_status_message(format!("Column '{}' unpinned", col_name));
4217 } else {
4218 match dataview.pin_column_by_name(&col_name) {
4220 Ok(_) => {
4221 self.state_container
4222 .set_status_message(format!("Column '{}' pinned [P]", col_name));
4223 }
4224 Err(e) => {
4225 self.state_container.set_status_message(e.to_string());
4226 }
4227 }
4228 }
4229
4230 if let Some(updated_dataview) = self.state_container.get_buffer_dataview() {
4232 self.update_viewport_manager(Some(updated_dataview.clone()));
4233 }
4234 }
4235 } else {
4236 self.state_container
4237 .set_status_message("No column to pin at current position".to_string());
4238 }
4239 }
4240
4241 fn clear_all_pinned_columns_impl(&mut self) {
4242 if let Some(dataview) = self.state_container.get_buffer_dataview_mut() {
4243 dataview.clear_pinned_columns();
4244 }
4245 self.state_container
4246 .set_status_message("All columns unpinned".to_string());
4247
4248 if let Some(updated_dataview) = self.state_container.get_buffer_dataview() {
4250 self.update_viewport_manager(Some(updated_dataview.clone()));
4251 }
4252 }
4253
4254 fn calculate_column_statistics(&mut self) {
4255 use std::time::Instant;
4256
4257 let start_total = Instant::now();
4258
4259 let (column_name, data_to_analyze) = {
4261 let headers = self.get_column_names_via_provider();
4263 if headers.is_empty() {
4264 return;
4265 }
4266
4267 let current_column = self.state_container.get_current_column();
4268 if current_column >= headers.len() {
4269 return;
4270 }
4271
4272 let column_name = headers[current_column].clone();
4273
4274 let data_to_analyze: Vec<String> = if let Some(provider) = self.get_data_provider() {
4276 let row_count = provider.get_row_count();
4277 let mut column_data = Vec::with_capacity(row_count);
4278
4279 for row_idx in 0..row_count {
4280 if let Some(row) = provider.get_row(row_idx) {
4281 if current_column < row.len() {
4282 column_data.push(row[current_column].clone());
4283 } else {
4284 column_data.push(String::new());
4286 }
4287 }
4288 }
4289
4290 column_data
4291 } else {
4292 return;
4294 };
4295
4296 (column_name, data_to_analyze)
4297 };
4298
4299 let data_refs: Vec<&str> = data_to_analyze.iter().map(|s| s.as_str()).collect();
4301
4302 let analyzer_stats = self
4304 .data_analyzer
4305 .calculate_column_statistics(&column_name, &data_refs);
4306
4307 let stats = ColumnStatistics {
4309 column_name: analyzer_stats.column_name,
4310 column_type: match analyzer_stats.data_type {
4311 data_analyzer::ColumnType::Integer | data_analyzer::ColumnType::Float => {
4312 ColumnType::Numeric
4313 }
4314 data_analyzer::ColumnType::String
4315 | data_analyzer::ColumnType::Boolean
4316 | data_analyzer::ColumnType::Date => ColumnType::String,
4317 data_analyzer::ColumnType::Mixed => ColumnType::Mixed,
4318 data_analyzer::ColumnType::Unknown => ColumnType::Mixed,
4319 },
4320 total_count: analyzer_stats.total_values,
4321 null_count: analyzer_stats.null_values,
4322 unique_count: analyzer_stats.unique_values,
4323 frequency_map: analyzer_stats.frequency_map.clone(),
4324 min: analyzer_stats
4326 .min_value
4327 .as_ref()
4328 .and_then(|s| s.parse::<f64>().ok()),
4329 max: analyzer_stats
4330 .max_value
4331 .as_ref()
4332 .and_then(|s| s.parse::<f64>().ok()),
4333 sum: analyzer_stats.sum_value,
4334 mean: analyzer_stats.avg_value,
4335 median: analyzer_stats.median_value,
4336 };
4337
4338 let elapsed = start_total.elapsed();
4340
4341 self.state_container.set_column_stats(Some(stats));
4342
4343 self.state_container.set_status_message(format!(
4345 "Column stats: {:.1}ms for {} values ({} unique)",
4346 elapsed.as_secs_f64() * 1000.0,
4347 data_to_analyze.len(),
4348 analyzer_stats.unique_values
4349 ));
4350
4351 self.state_container.set_mode(AppMode::ColumnStats);
4352 self.shadow_state
4353 .borrow_mut()
4354 .observe_mode_change(AppMode::ColumnStats, "column_stats_requested");
4355 }
4356
4357 fn check_parser_error(&self, query: &str) -> Option<String> {
4358 crate::ui::operations::simple_operations::check_parser_error(query)
4359 }
4360
4361 fn update_viewport_size(&mut self) {
4362 if let Ok((width, height)) = crossterm::terminal::size() {
4364 let data_rows_available = Self::calculate_available_data_rows(height);
4366
4367 let visible_rows = {
4369 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
4370 let viewport_manager = viewport_manager_borrow
4371 .as_mut()
4372 .expect("ViewportManager must exist for viewport size update");
4373 viewport_manager.update_terminal_size(width, data_rows_available)
4374 };
4375
4376 self.state_container.set_last_visible_rows(visible_rows);
4378
4379 self.state_container
4381 .navigation_mut()
4382 .set_viewport_size(visible_rows, width as usize);
4383
4384 info!(target: "navigation", "update_viewport_size - viewport set to: {}x{} rows", visible_rows, width);
4385 }
4386 }
4387
4388 fn perform_search(&mut self) {
4392 if let Some(dataview) = self.get_current_data() {
4393 let data: Vec<Vec<String>> = (0..dataview.row_count())
4395 .filter_map(|i| dataview.get_row(i))
4396 .map(|row| row.values.iter().map(|v| v.to_string()).collect())
4397 .collect();
4398
4399 let pattern = self.state_container.get_search_pattern().to_string();
4401
4402 info!(target: "search", "=== SEARCH START ===");
4403 info!(target: "search", "Pattern: '{}', case_insensitive: {}",
4404 pattern, self.state_container.is_case_insensitive());
4405 info!(target: "search", "Data dimensions: {} rows x {} columns",
4406 data.len(), data.first().map(|r| r.len()).unwrap_or(0));
4407
4408 let column_names = dataview.column_names();
4410 info!(target: "search", "Column names (first 5): {:?}",
4411 column_names.iter().take(5).collect::<Vec<_>>());
4412
4413 for (i, row) in data.iter().take(10).enumerate() {
4415 info!(target: "search", " Data row {}: [{}]", i,
4416 row.iter().take(5).map(|s| format!("'{}'", s)).collect::<Vec<_>>().join(", "));
4417 }
4418
4419 let visible_columns = None;
4421
4422 let match_count = {
4424 let mut search_manager = self.search_manager.borrow_mut();
4425
4426 search_manager.clear();
4428 info!(target: "search", "Cleared previous search results");
4429
4430 search_manager.set_case_sensitive(!self.state_container.is_case_insensitive());
4432 info!(target: "search", "Set case_sensitive to {}", !self.state_container.is_case_insensitive());
4433
4434 let count = search_manager.search(&pattern, &data, visible_columns);
4436 info!(target: "search", "SearchManager.search() returned {} matches", count);
4437 count
4438 };
4439
4440 info!(target: "search", "SearchManager found {} matches", match_count);
4441
4442 if match_count > 0 {
4444 let (first_row, first_col) = {
4446 let search_manager = self.search_manager.borrow();
4447 if let Some(first_match) = search_manager.first_match() {
4448 info!(target: "search", "FIRST MATCH DETAILS:");
4449 info!(target: "search", " Data coordinates: row={}, col={}",
4450 first_match.row, first_match.column);
4451 info!(target: "search", " Matched value: '{}'", first_match.value);
4452 info!(target: "search", " Highlight range: {:?}", first_match.highlight_range);
4453
4454 for (i, m) in search_manager.all_matches().iter().take(5).enumerate() {
4456 info!(target: "search", " Match #{}: row={}, col={}, value='{}'",
4457 i + 1, m.row, m.column, m.value);
4458 }
4459
4460 (first_match.row, first_match.column)
4461 } else {
4462 warn!(target: "search", "SearchManager reported matches but first_match() is None!");
4463 (0, 0)
4464 }
4465 };
4466
4467 self.state_container.set_table_selected_row(Some(first_row));
4469
4470 info!(target: "search", "Updating TableWidgetManager to navigate to ({}, {})", first_row, first_col);
4472 self.table_widget_manager
4473 .borrow_mut()
4474 .navigate_to_search_match(first_row, first_col);
4475
4476 if let Some(dataview) = self.get_current_data() {
4478 if let Some(row_data) = dataview.get_row(first_row) {
4479 if first_col < row_data.values.len() {
4480 info!(target: "search", "VALUE AT NAVIGATION TARGET ({}, {}): '{}'",
4481 first_row, first_col, row_data.values[first_col]);
4482 }
4483 }
4484 }
4485
4486 let buffer_matches: Vec<(usize, usize)> = {
4488 let search_manager = self.search_manager.borrow();
4489 search_manager
4490 .all_matches()
4491 .iter()
4492 .map(|m| (m.row, m.column))
4493 .collect()
4494 };
4495
4496 let state_matches: Vec<(usize, usize, usize, usize)> = {
4499 let search_manager = self.search_manager.borrow();
4500 search_manager
4501 .all_matches()
4502 .iter()
4503 .map(|m| {
4504 (m.row, m.column, m.row, m.column)
4506 })
4507 .collect()
4508 };
4509 self.state_container.search_mut().matches = state_matches;
4510
4511 self.state_container
4512 .set_search_matches_with_index(buffer_matches.clone(), 0);
4513 self.state_container
4514 .set_current_match(Some((first_row, first_col)));
4515 self.state_container
4516 .set_status_message(format!("Found {} matches", match_count));
4517
4518 info!(target: "search", "Search found {} matches for pattern '{}'", match_count, pattern);
4519 } else {
4520 self.state_container.search_mut().matches.clear();
4522 self.state_container.clear_search_state();
4523 self.state_container.set_current_match(None);
4524
4525 info!(target: "search", "No matches found for pattern '{}'", pattern);
4526 }
4527 }
4528 }
4529
4530 fn start_vim_search(&mut self) {
4534 info!(target: "vim_search", "Starting vim search mode");
4535
4536 self.vim_search_adapter.borrow_mut().start_search();
4538
4539 self.shadow_state.borrow_mut().observe_search_start(
4541 crate::ui::state::shadow_state::SearchType::Vim,
4542 "slash_key_pressed",
4543 );
4544
4545 self.enter_search_mode(SearchMode::Search);
4547 }
4548
4549 fn vim_search_next(&mut self) {
4551 if !self.vim_search_adapter.borrow().is_navigating() {
4552 let resumed = {
4554 let mut viewport_borrow = self.viewport_manager.borrow_mut();
4555 if let Some(ref mut viewport) = *viewport_borrow {
4556 if let Some(dataview) = self.state_container.get_buffer_dataview() {
4557 self.vim_search_adapter
4558 .borrow_mut()
4559 .resume_last_search(dataview, viewport)
4560 } else {
4561 false
4562 }
4563 } else {
4564 false
4565 }
4566 };
4567
4568 if !resumed {
4569 self.state_container
4570 .set_status_message("No previous search pattern".to_string());
4571 return;
4572 }
4573 }
4574
4575 let result = {
4577 let mut viewport_borrow = self.viewport_manager.borrow_mut();
4578 if let Some(ref mut viewport) = *viewport_borrow {
4579 let search_match = self.vim_search_adapter.borrow_mut().next_match(viewport);
4580 if search_match.is_some() {
4581 let match_info = self.vim_search_adapter.borrow().get_match_info();
4582 search_match.map(|m| (m, match_info))
4583 } else {
4584 None
4585 }
4586 } else {
4587 None
4588 }
4589 }; if let Some((ref search_match, _)) = result {
4593 info!(target: "search",
4595 "=== UPDATING TUI STATE FOR VIM SEARCH ===");
4596 info!(target: "search",
4597 "Setting selected row to: {}", search_match.row);
4598 info!(target: "search",
4599 "Setting selected column to: {} (visual col)", search_match.col);
4600
4601 if let Some(dataview) = self.state_container.get_buffer_dataview() {
4603 info!(target: "search",
4605 "DEBUG: Fetching row {} from dataview with {} total rows",
4606 search_match.row, dataview.row_count());
4607
4608 if let Some(row_data) = dataview.get_row(search_match.row) {
4609 info!(target: "search",
4611 "Row {} has {} values, first 5: {:?}",
4612 search_match.row, row_data.values.len(),
4613 row_data.values.iter().take(5).map(|v| v.to_string()).collect::<Vec<_>>());
4614
4615 if search_match.col < row_data.values.len() {
4616 let actual_value = &row_data.values[search_match.col];
4617 info!(target: "search",
4618 "Actual value at row {} col {}: '{}'",
4619 search_match.row, search_match.col, actual_value);
4620
4621 let pattern = self
4623 .vim_search_adapter
4624 .borrow()
4625 .get_pattern()
4626 .unwrap_or_default();
4627 let contains_pattern = actual_value
4628 .to_string()
4629 .to_lowercase()
4630 .contains(&pattern.to_lowercase());
4631 if !contains_pattern {
4632 warn!(target: "search",
4633 "WARNING: Cell at ({}, {}) = '{}' does NOT contain pattern '{}'!",
4634 search_match.row, search_match.col, actual_value, pattern);
4635 } else {
4636 info!(target: "search",
4637 "✓ Confirmed: Cell contains pattern '{}'", pattern);
4638 }
4639 } else {
4640 warn!(target: "search",
4641 "Column {} is out of bounds for row {} (row has {} values)",
4642 search_match.col, search_match.row, row_data.values.len());
4643 }
4644 } else {
4645 warn!(target: "search",
4646 "Could not get row data for row {}", search_match.row);
4647 }
4648
4649 let display_columns = dataview.get_display_columns();
4651 info!(target: "search",
4652 "Display columns mapping (first 10): {:?}",
4653 display_columns.iter().take(10).collect::<Vec<_>>());
4654 }
4655
4656 self.state_container
4657 .set_table_selected_row(Some(search_match.row));
4658 self.state_container
4659 .set_selected_row(Some(search_match.row));
4660
4661 info!(target: "search",
4664 "Setting column to visual index {}",
4665 search_match.col);
4666
4667 self.state_container
4669 .set_current_column_buffer(search_match.col);
4670 self.state_container.navigation_mut().selected_column = search_match.col;
4671
4672 self.state_container.select_column(search_match.col);
4674 info!(target: "search",
4675 "Updated SelectionState column to: {}", search_match.col);
4676
4677 info!(target: "search",
4679 "Column state after update: nav.selected_column={}, buffer.current_column={}, selection.selected_column={}",
4680 self.state_container.navigation().selected_column,
4681 self.state_container.get_current_column(),
4682 self.state_container.selection().selected_column);
4683
4684 self.sync_navigation_with_viewport();
4687
4688 let scroll_offset = self.state_container.navigation().scroll_offset;
4690 self.state_container.set_scroll_offset(scroll_offset);
4691
4692 info!(target: "search", "Updating TableWidgetManager for vim search navigation to ({}, {})",
4694 search_match.row, search_match.col);
4695 self.table_widget_manager
4696 .borrow_mut()
4697 .navigate_to(search_match.row, search_match.col);
4698
4699 info!(target: "search",
4701 "After TableWidgetManager update: nav.selected_column={}, buffer.current_column={}, selection.selected_column={}",
4702 self.state_container.navigation().selected_column,
4703 self.state_container.get_current_column(),
4704 self.state_container.selection().selected_column);
4705
4706 }
4710
4711 if let Some((search_match, match_info)) = result {
4713 if let Some((current, total)) = match_info {
4714 self.state_container.set_status_message(format!(
4715 "Match {}/{} at ({}, {})",
4716 current,
4717 total,
4718 search_match.row + 1,
4719 search_match.col + 1
4720 ));
4721 }
4722
4723 info!(target: "search",
4725 "FINAL vim_search_next state: nav.selected_column={}, buffer.current_column={}, selection.selected_column={}",
4726 self.state_container.navigation().selected_column,
4727 self.state_container.get_current_column(),
4728 self.state_container.selection().selected_column);
4729
4730 if let Some(dataview) = self.state_container.get_buffer_dataview() {
4732 let final_row = self.state_container.navigation().selected_row;
4733 let final_col = self.state_container.navigation().selected_column;
4734
4735 if let Some(row_data) = dataview.get_row(final_row) {
4736 if final_col < row_data.values.len() {
4737 let actual_value = &row_data.values[final_col];
4738 info!(target: "search",
4739 "VERIFICATION: Cell at final position ({}, {}) contains: '{}'",
4740 final_row, final_col, actual_value);
4741
4742 let pattern = self
4743 .vim_search_adapter
4744 .borrow()
4745 .get_pattern()
4746 .unwrap_or_default();
4747 if !actual_value
4748 .to_string()
4749 .to_lowercase()
4750 .contains(&pattern.to_lowercase())
4751 {
4752 error!(target: "search",
4753 "ERROR: Final cell '{}' does NOT contain search pattern '{}'!",
4754 actual_value, pattern);
4755 }
4756 }
4757 }
4758 }
4759 }
4760 }
4761
4762 fn vim_search_previous(&mut self) {
4764 if !self.vim_search_adapter.borrow().is_navigating() {
4765 let resumed = {
4767 let mut viewport_borrow = self.viewport_manager.borrow_mut();
4768 if let Some(ref mut viewport) = *viewport_borrow {
4769 if let Some(dataview) = self.state_container.get_buffer_dataview() {
4770 self.vim_search_adapter
4771 .borrow_mut()
4772 .resume_last_search(dataview, viewport)
4773 } else {
4774 false
4775 }
4776 } else {
4777 false
4778 }
4779 };
4780
4781 if !resumed {
4782 self.state_container
4783 .set_status_message("No previous search pattern".to_string());
4784 return;
4785 }
4786 }
4787
4788 let result = {
4790 let mut viewport_borrow = self.viewport_manager.borrow_mut();
4791 if let Some(ref mut viewport) = *viewport_borrow {
4792 let search_match = self
4793 .vim_search_adapter
4794 .borrow_mut()
4795 .previous_match(viewport);
4796 if search_match.is_some() {
4797 let match_info = self.vim_search_adapter.borrow().get_match_info();
4798 search_match.map(|m| (m, match_info))
4799 } else {
4800 None
4801 }
4802 } else {
4803 None
4804 }
4805 }; if let Some((ref search_match, _)) = result {
4809 self.state_container
4810 .set_table_selected_row(Some(search_match.row));
4811 self.state_container
4812 .set_selected_row(Some(search_match.row));
4813
4814 info!(target: "search",
4817 "Setting column to visual index {}",
4818 search_match.col);
4819
4820 self.state_container
4822 .set_current_column_buffer(search_match.col);
4823 self.state_container.navigation_mut().selected_column = search_match.col;
4824
4825 self.state_container.select_column(search_match.col);
4827 info!(target: "search",
4828 "Updated SelectionState column to: {}", search_match.col);
4829
4830 info!(target: "search",
4832 "Column state after update: nav.selected_column={}, buffer.current_column={}, selection.selected_column={}",
4833 self.state_container.navigation().selected_column,
4834 self.state_container.get_current_column(),
4835 self.state_container.selection().selected_column);
4836
4837 self.sync_navigation_with_viewport();
4840
4841 let scroll_offset = self.state_container.navigation().scroll_offset;
4843 self.state_container.set_scroll_offset(scroll_offset);
4844
4845 info!(target: "search", "Updating TableWidgetManager for vim search navigation to ({}, {})",
4847 search_match.row, search_match.col);
4848 self.table_widget_manager
4849 .borrow_mut()
4850 .navigate_to(search_match.row, search_match.col);
4851
4852 info!(target: "search",
4854 "After TableWidgetManager update: nav.selected_column={}, buffer.current_column={}, selection.selected_column={}",
4855 self.state_container.navigation().selected_column,
4856 self.state_container.get_current_column(),
4857 self.state_container.selection().selected_column);
4858
4859 }
4863
4864 if let Some((search_match, match_info)) = result {
4866 if let Some((current, total)) = match_info {
4867 self.state_container.set_status_message(format!(
4868 "Match {}/{} at ({}, {})",
4869 current,
4870 total,
4871 search_match.row + 1,
4872 search_match.col + 1
4873 ));
4874 }
4875 }
4876 }
4878
4879 fn apply_filter(&mut self, pattern: &str) {
4880 use std::sync::atomic::{AtomicUsize, Ordering};
4881
4882 static FILTER_DEPTH: AtomicUsize = AtomicUsize::new(0);
4884 let depth = FILTER_DEPTH.fetch_add(1, Ordering::SeqCst);
4885 if depth > 0 {
4886 eprintln!(
4887 "WARNING: apply_filter re-entrancy detected! depth={}, pattern='{}', thread={:?}",
4888 depth,
4889 pattern,
4890 std::thread::current().id()
4891 );
4892 }
4893
4894 info!(
4895 "Applying filter: '{}' on thread {:?}",
4896 pattern,
4897 std::thread::current().id()
4898 );
4899
4900 use crate::ui::state::state_coordinator::StateCoordinator;
4902 let _rows_after =
4903 StateCoordinator::apply_text_filter_with_refs(&mut self.state_container, pattern);
4904
4905 self.sync_dataview_to_managers();
4908
4909 FILTER_DEPTH.fetch_sub(1, Ordering::SeqCst);
4911 }
4912 fn search_columns(&mut self) {
4913 static SEARCH_DEPTH: std::sync::atomic::AtomicUsize =
4915 std::sync::atomic::AtomicUsize::new(0);
4916 let depth = SEARCH_DEPTH.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
4917
4918 if depth > 10 {
4920 error!(target: "search", "Column search depth exceeded limit, aborting to prevent infinite loop");
4921 SEARCH_DEPTH.store(0, std::sync::atomic::Ordering::SeqCst);
4922 return;
4923 }
4924
4925 struct DepthGuard;
4927 impl Drop for DepthGuard {
4928 fn drop(&mut self) {
4929 SEARCH_DEPTH.fetch_sub(1, std::sync::atomic::Ordering::SeqCst);
4930 }
4931 }
4932 let _guard = DepthGuard;
4933
4934 let pattern = self.state_container.column_search().pattern.clone();
4935 debug!(target: "search", "search_columns called with pattern: '{}', depth: {}", pattern, depth);
4936
4937 if pattern.is_empty() {
4938 debug!(target: "search", "Pattern is empty, skipping column search");
4939 return;
4940 }
4941
4942 let matching_columns = if let Some(dataview) =
4944 self.state_container.get_buffer_dataview_mut()
4945 {
4946 dataview.search_columns(&pattern);
4947
4948 let matches = dataview.get_matching_columns().to_vec();
4950 debug!(target: "search", "DataView found {} matching columns", matches.len());
4951 if !matches.is_empty() {
4952 for (idx, (col_idx, col_name)) in matches.iter().enumerate() {
4953 debug!(target: "search", " Match {}: '{}' at visual index {}", idx + 1, col_name, col_idx);
4954 }
4955 }
4956
4957 let columns: Vec<(String, usize)> = matches
4959 .iter()
4960 .map(|(idx, name)| (name.clone(), *idx))
4961 .collect();
4962 self.state_container
4963 .update_column_search_matches(&columns, &pattern);
4964
4965 matches
4966 } else {
4967 debug!(target: "search", "No DataView available for column search");
4968 Vec::new()
4969 };
4970
4971 if !matching_columns.is_empty() {
4972 let first_match_visual_idx = matching_columns[0].0;
4974 let first_match_name = &matching_columns[0].1;
4975
4976 let datatable_idx = if let Some(dataview) = self.state_container.get_buffer_dataview() {
4978 let display_columns = dataview.get_display_columns();
4979 if first_match_visual_idx < display_columns.len() {
4980 display_columns[first_match_visual_idx]
4981 } else {
4982 first_match_visual_idx }
4984 } else {
4985 first_match_visual_idx
4986 };
4987
4988 self.state_container.set_current_column(datatable_idx);
4989 self.state_container
4990 .set_current_column_buffer(datatable_idx);
4991
4992 {
4995 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
4996 if let Some(viewport_manager) = viewport_manager_borrow.as_mut() {
4997 let viewport_changed =
4998 viewport_manager.set_current_column(first_match_visual_idx);
4999
5000 if viewport_changed {
5002 let new_viewport = viewport_manager.viewport_cols().clone();
5003 let pinned_count =
5004 if let Some(dv) = self.state_container.get_buffer_dataview() {
5005 dv.get_pinned_columns().len()
5006 } else {
5007 0
5008 };
5009 let scrollable_offset = new_viewport.start.saturating_sub(pinned_count);
5010 self.state_container.navigation_mut().scroll_offset.1 = scrollable_offset;
5011
5012 debug!(target: "navigation",
5013 "Column search initial: Jumped to column {} '{}', viewport adjusted to {:?}",
5014 first_match_visual_idx, first_match_name, new_viewport);
5015 }
5016 }
5017 }
5018
5019 debug!(target: "search", "Setting current column to visual index {} ('{}')",
5020 first_match_visual_idx, first_match_name);
5021 let status_msg = format!(
5022 "Found {} columns matching '{}'. Tab/Shift-Tab to navigate.",
5023 matching_columns.len(),
5024 pattern
5025 );
5026 debug!(target: "search", "Setting status: {}", status_msg);
5027 self.state_container.set_status_message(status_msg);
5028
5029 } else {
5031 let status_msg = format!("No columns matching '{}'", pattern);
5032 debug!(target: "search", "Setting status: {}", status_msg);
5033 self.state_container.set_status_message(status_msg);
5034 }
5035
5036 }
5038
5039 fn next_column_match(&mut self) {
5040 let column_match_data =
5043 if let Some(dataview) = self.state_container.get_buffer_dataview_mut() {
5044 if let Some(visual_idx) = dataview.next_column_match() {
5045 let matching_columns = dataview.get_matching_columns();
5047 let current_match_index = dataview.current_column_match_index();
5048 let current_match = current_match_index + 1;
5049 let total_matches = matching_columns.len();
5050 let col_name = matching_columns
5051 .get(current_match_index)
5052 .map(|(_, name)| name.clone())
5053 .unwrap_or_default();
5054
5055 let display_columns = dataview.get_display_columns();
5058 let datatable_idx = if visual_idx < display_columns.len() {
5059 display_columns[visual_idx]
5060 } else {
5061 visual_idx };
5063
5064 Some((
5065 visual_idx,
5066 datatable_idx,
5067 col_name,
5068 current_match,
5069 total_matches,
5070 current_match_index,
5071 ))
5072 } else {
5073 None
5074 }
5075 } else {
5076 None
5077 };
5078
5079 if let Some((
5081 visual_idx,
5082 datatable_idx,
5083 col_name,
5084 current_match,
5085 total_matches,
5086 current_match_index,
5087 )) = column_match_data
5088 {
5089 self.state_container.set_current_column(datatable_idx);
5091 self.state_container
5092 .set_current_column_buffer(datatable_idx);
5093
5094 {
5097 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
5098 if let Some(viewport_manager) = viewport_manager_borrow.as_mut() {
5099 viewport_manager.set_current_column(visual_idx);
5100 }
5101 }
5102
5103 debug!(target: "column_search_sync", "next_column_match: About to call sync_navigation_with_viewport() - visual_idx: {}, datatable_idx: {}", visual_idx, datatable_idx);
5105 debug!(target: "column_search_sync", "next_column_match: Pre-sync - viewport current_column: {}",
5106 if let Some(vm) = self.viewport_manager.try_borrow().ok() {
5107 vm.as_ref().map(|v| v.get_crosshair_col()).unwrap_or(0)
5108 } else { 0 });
5109 self.sync_navigation_with_viewport();
5110 debug!(target: "column_search_sync", "next_column_match: Post-sync - navigation current_column: {}",
5111 self.state_container.navigation().selected_column);
5112 debug!(target: "column_search_sync", "next_column_match: sync_navigation_with_viewport() completed");
5113
5114 debug!(target: "navigation",
5115 "Column search: Jumped to visual column {} (datatable: {}) '{}', synced with viewport",
5116 visual_idx, datatable_idx, col_name);
5117
5118 {
5121 let mut column_search = self.state_container.column_search_mut();
5122 column_search.current_match = current_match_index;
5123 debug!(target: "column_search_sync", "next_column_match: Updated AppStateContainer column_search.current_match to {}", column_search.current_match);
5124 }
5125
5126 self.state_container.set_status_message(format!(
5127 "Column {}/{}: {} - Tab/Shift-Tab to navigate",
5128 current_match, total_matches, col_name
5129 ));
5130 }
5131 }
5132
5133 fn previous_column_match(&mut self) {
5134 let column_match_data =
5137 if let Some(dataview) = self.state_container.get_buffer_dataview_mut() {
5138 if let Some(visual_idx) = dataview.prev_column_match() {
5139 let matching_columns = dataview.get_matching_columns();
5141 let current_match_index = dataview.current_column_match_index();
5142 let current_match = current_match_index + 1;
5143 let total_matches = matching_columns.len();
5144 let col_name = matching_columns
5145 .get(current_match_index)
5146 .map(|(_, name)| name.clone())
5147 .unwrap_or_default();
5148
5149 let display_columns = dataview.get_display_columns();
5152 let datatable_idx = if visual_idx < display_columns.len() {
5153 display_columns[visual_idx]
5154 } else {
5155 visual_idx };
5157
5158 Some((
5159 visual_idx,
5160 datatable_idx,
5161 col_name,
5162 current_match,
5163 total_matches,
5164 current_match_index,
5165 ))
5166 } else {
5167 None
5168 }
5169 } else {
5170 None
5171 };
5172
5173 if let Some((
5175 visual_idx,
5176 datatable_idx,
5177 col_name,
5178 current_match,
5179 total_matches,
5180 current_match_index,
5181 )) = column_match_data
5182 {
5183 self.state_container.set_current_column(datatable_idx);
5185 self.state_container
5186 .set_current_column_buffer(datatable_idx);
5187
5188 {
5191 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
5192 if let Some(viewport_manager) = viewport_manager_borrow.as_mut() {
5193 viewport_manager.set_current_column(visual_idx);
5194 }
5195 }
5196
5197 debug!(target: "column_search_sync", "previous_column_match: About to call sync_navigation_with_viewport() - visual_idx: {}, datatable_idx: {}", visual_idx, datatable_idx);
5199 debug!(target: "column_search_sync", "previous_column_match: Pre-sync - viewport current_column: {}",
5200 if let Some(vm) = self.viewport_manager.try_borrow().ok() {
5201 vm.as_ref().map(|v| v.get_crosshair_col()).unwrap_or(0)
5202 } else { 0 });
5203 self.sync_navigation_with_viewport();
5204 debug!(target: "column_search_sync", "previous_column_match: Post-sync - navigation current_column: {}",
5205 self.state_container.navigation().selected_column);
5206 debug!(target: "column_search_sync", "previous_column_match: sync_navigation_with_viewport() completed");
5207
5208 debug!(target: "navigation",
5209 "Column search (prev): Jumped to visual column {} (datatable: {}) '{}', synced with viewport",
5210 visual_idx, datatable_idx, col_name);
5211
5212 {
5215 let mut column_search = self.state_container.column_search_mut();
5216 column_search.current_match = current_match_index;
5217 debug!(target: "column_search_sync", "previous_column_match: Updated AppStateContainer column_search.current_match to {}", column_search.current_match);
5218 }
5219
5220 self.state_container.set_status_message(format!(
5221 "Column {}/{}: {} - Tab/Shift-Tab to navigate",
5222 current_match, total_matches, col_name
5223 ));
5224 }
5225 }
5226
5227 fn apply_fuzzy_filter(&mut self) {
5228 info!(
5229 "apply_fuzzy_filter called on thread {:?}",
5230 std::thread::current().id()
5231 );
5232
5233 use crate::ui::state::state_coordinator::StateCoordinator;
5235 let (_match_count, indices) = StateCoordinator::apply_fuzzy_filter_with_refs(
5236 &mut self.state_container,
5237 &self.viewport_manager,
5238 );
5239
5240 self.state_container.set_fuzzy_filter_indices(indices);
5242
5243 self.sync_dataview_to_managers();
5246 }
5247
5248 fn toggle_sort_current_column(&mut self) {
5249 let visual_col_idx = if let Some(ref viewport_manager) = *self.viewport_manager.borrow() {
5251 viewport_manager.get_crosshair_col()
5252 } else {
5253 0
5254 };
5255
5256 if let Some(dataview) = self.state_container.get_buffer_dataview_mut() {
5257 let column_names = dataview.column_names();
5260 let col_name = column_names
5261 .get(visual_col_idx)
5262 .map(|s| s.clone())
5263 .unwrap_or_else(|| format!("Column {}", visual_col_idx));
5264
5265 debug!(
5266 "toggle_sort_current_column: visual_idx={}, column_name={}",
5267 visual_col_idx, col_name
5268 );
5269
5270 if let Err(e) = dataview.toggle_sort(visual_col_idx) {
5271 self.state_container
5272 .set_status_message(format!("Sort error: {}", e));
5273 } else {
5274 let sort_state = dataview.get_sort_state();
5276 let message = match sort_state.order {
5277 crate::data::data_view::SortOrder::Ascending => {
5278 format!("Sorted '{}' ascending ↑", col_name)
5279 }
5280 crate::data::data_view::SortOrder::Descending => {
5281 format!("Sorted '{}' descending ↓", col_name)
5282 }
5283 crate::data::data_view::SortOrder::None => {
5284 format!("Cleared sort on '{}'", col_name)
5285 }
5286 };
5287 self.state_container.set_status_message(message);
5288
5289 if let Some(updated_dataview) = self.state_container.get_buffer_dataview() {
5291 self.table_widget_manager
5293 .borrow_mut()
5294 .set_dataview(Arc::new(updated_dataview.clone()));
5295
5296 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
5297 if let Some(ref mut viewport_manager) = *viewport_manager_borrow {
5298 viewport_manager.set_dataview(Arc::new(updated_dataview.clone()));
5299 debug!("Updated ViewportManager with sorted DataView");
5300 }
5301 }
5302 }
5303 } else {
5304 self.state_container
5306 .set_status_message("Error: Invalid column position".to_string());
5307 }
5308 }
5309
5310 fn get_current_data(&self) -> Option<&DataView> {
5311 self.state_container.get_buffer_dataview()
5312 }
5313
5314 fn get_row_count(&self) -> usize {
5315 if self.state_container.is_fuzzy_filter_active() {
5317 self.state_container.get_fuzzy_filter_indices().len()
5319 } else if let Some(dataview) = self.state_container.get_buffer_dataview() {
5320 dataview.row_count()
5322 } else if let Some(provider) = self.get_data_provider() {
5323 provider.get_row_count()
5325 } else {
5326 0
5327 }
5328 }
5329
5330 fn sync_dataview_to_managers(&self) {
5332 if let Some(dataview) = self.state_container.get_buffer_dataview() {
5333 let arc_dataview = Arc::new(dataview.clone());
5334
5335 if let Some(ref mut viewport_manager) = *self.viewport_manager.borrow_mut() {
5337 viewport_manager.set_dataview(arc_dataview.clone());
5338 debug!(
5339 "Updated ViewportManager with DataView (row_count={})",
5340 arc_dataview.row_count()
5341 );
5342 }
5343
5344 self.table_widget_manager
5346 .borrow_mut()
5347 .set_dataview(arc_dataview);
5348 debug!("Updated TableWidgetManager with DataView");
5349 }
5350 }
5351
5352 pub fn reset_table_state(&mut self) {
5353 use crate::ui::state::state_coordinator::StateCoordinator;
5355 StateCoordinator::reset_table_state_with_refs(
5356 &mut self.state_container,
5357 &self.viewport_manager,
5358 );
5359 }
5360
5361 fn update_parser_for_current_buffer(&mut self) {
5362 self.sync_all_input_states();
5364
5365 use crate::ui::state::state_coordinator::StateCoordinator;
5367 StateCoordinator::update_parser_with_refs(&self.state_container, &mut self.hybrid_parser);
5368 }
5369
5370 fn sync_after_buffer_switch(&mut self) {
5375 self.restore_viewport_from_current_buffer();
5377
5378 self.update_parser_for_current_buffer();
5380 }
5381
5382 fn update_viewport_manager(&mut self, dataview: Option<DataView>) {
5384 if let Some(dv) = dataview {
5385 let current_column = self.state_container.get_current_column();
5387
5388 let mut new_viewport_manager = ViewportManager::new(Arc::new(dv));
5390
5391 if let Ok((width, height)) = crossterm::terminal::size() {
5393 let data_rows_available = Self::calculate_available_data_rows(height);
5395 new_viewport_manager.update_terminal_size(width, data_rows_available);
5396 debug!(
5397 "Updated new ViewportManager terminal size: {}x{} (data rows)",
5398 width, data_rows_available
5399 );
5400 }
5401
5402 if current_column < new_viewport_manager.dataview().column_count() {
5405 new_viewport_manager.set_current_column(current_column);
5406 } else {
5407 new_viewport_manager.set_current_column(0);
5409 self.state_container.set_current_column_buffer(0);
5410 }
5411
5412 *self.viewport_manager.borrow_mut() = Some(new_viewport_manager);
5413 debug!(
5414 "ViewportManager updated with new DataView, current_column={}",
5415 current_column
5416 );
5417 } else {
5418 *self.viewport_manager.borrow_mut() = None;
5420 debug!("ViewportManager cleared (no DataView)");
5421 }
5422 }
5423
5424 pub fn calculate_optimal_column_widths(&mut self) {
5425 let widths_from_viewport = {
5427 let mut viewport_opt = self.viewport_manager.borrow_mut();
5428 if let Some(ref mut viewport_manager) = *viewport_opt {
5429 Some(viewport_manager.calculate_optimal_column_widths())
5430 } else {
5431 None
5432 }
5433 };
5434
5435 if let Some(widths) = widths_from_viewport {
5436 self.state_container.set_column_widths(widths);
5437 }
5438 }
5439
5440 pub fn set_status_message(&mut self, message: impl Into<String>) {
5443 let msg = message.into();
5444 debug!("Status: {}", msg);
5445 self.state_container.set_status_message(msg.clone());
5446 }
5449
5450 fn set_error_status(&mut self, context: &str, error: impl std::fmt::Display) {
5452 let msg = format!("{}: {}", context, error);
5453 debug!("Error status: {}", msg);
5454 self.set_status_message(msg);
5455 }
5456
5457 fn export_to_csv(&mut self) {
5458 let result = {
5459 let ctx = crate::ui::operations::data_export_operations::DataExportContext {
5460 data_provider: self.get_data_provider(),
5461 };
5462 crate::ui::operations::data_export_operations::export_to_csv(&ctx)
5463 };
5464
5465 match result {
5466 crate::ui::operations::data_export_operations::ExportResult::Success(message) => {
5467 self.set_status_message(message);
5468 }
5469 crate::ui::operations::data_export_operations::ExportResult::Error(error) => {
5470 self.set_error_status("Export failed", error);
5471 }
5472 }
5473 }
5474
5475 fn paste_from_clipboard(&mut self) {
5482 match self.state_container.read_from_clipboard() {
5484 Ok(text) => {
5485 let mode = self.shadow_state.borrow().get_mode();
5486 match mode {
5487 AppMode::Command => {
5488 let cursor_pos = self.get_input_cursor();
5491 let current_value = self.get_input_text();
5492
5493 let mut new_value = String::new();
5495 new_value.push_str(¤t_value[..cursor_pos]);
5496 new_value.push_str(&text);
5497 new_value.push_str(¤t_value[cursor_pos..]);
5498
5499 self.set_input_text_with_cursor(new_value, cursor_pos + text.len());
5500
5501 self.state_container
5502 .set_status_message(format!("Pasted {} characters", text.len()));
5503 }
5504 AppMode::Filter
5505 | AppMode::FuzzyFilter
5506 | AppMode::Search
5507 | AppMode::ColumnSearch => {
5508 let cursor_pos = self.get_input_cursor();
5510 let current_value = self.get_input_text();
5511
5512 let mut new_value = String::new();
5513 new_value.push_str(¤t_value[..cursor_pos]);
5514 new_value.push_str(&text);
5515 new_value.push_str(¤t_value[cursor_pos..]);
5516
5517 self.set_input_text_with_cursor(new_value, cursor_pos + text.len());
5518
5519 match mode {
5521 AppMode::Filter => {
5522 let pattern = self.get_input_text();
5523 self.state_container.filter_mut().pattern = pattern.clone();
5524 self.apply_filter(&pattern);
5525 }
5526 AppMode::FuzzyFilter => {
5527 let input_text = self.get_input_text();
5528 self.state_container.set_fuzzy_filter_pattern(input_text);
5529 self.apply_fuzzy_filter();
5530 }
5531 AppMode::Search => {
5532 let search_text = self.get_input_text();
5533 self.state_container.set_search_pattern(search_text);
5534 }
5536 AppMode::ColumnSearch => {
5537 let input_text = self.get_input_text();
5538 self.state_container.start_column_search(input_text);
5539 }
5541 _ => {}
5542 }
5543 }
5544 _ => {
5545 self.state_container
5546 .set_status_message("Paste not available in this mode".to_string());
5547 }
5548 }
5549 }
5550 Err(e) => {
5551 self.state_container
5552 .set_status_message(format!("Failed to paste: {}", e));
5553 }
5554 }
5555 }
5556
5557 fn export_to_json(&mut self) {
5558 let result = {
5560 let ctx = crate::ui::operations::data_export_operations::DataExportContext {
5561 data_provider: self.get_data_provider(),
5562 };
5563 crate::ui::operations::data_export_operations::export_to_json(&ctx)
5564 };
5565
5566 match result {
5567 crate::ui::operations::data_export_operations::ExportResult::Success(message) => {
5568 self.set_status_message(message);
5569 }
5570 crate::ui::operations::data_export_operations::ExportResult::Error(error) => {
5571 self.set_error_status("Export failed", error);
5572 }
5573 }
5574 }
5575
5576 fn get_horizontal_scroll_offset(&self) -> u16 {
5577 let (horizontal, _vertical) = self.cursor_manager.scroll_offsets();
5579 horizontal
5580 }
5581
5582 fn update_horizontal_scroll(&mut self, terminal_width: u16) {
5583 let inner_width = terminal_width.saturating_sub(3) as usize; let cursor_pos = self.get_input_cursor();
5585
5586 self.cursor_manager
5588 .update_horizontal_scroll(cursor_pos, terminal_width.saturating_sub(3));
5589
5590 let mut scroll = self.state_container.scroll_mut();
5592 if cursor_pos < scroll.input_scroll_offset as usize {
5593 scroll.input_scroll_offset = cursor_pos as u16;
5594 }
5595 else if cursor_pos >= scroll.input_scroll_offset as usize + inner_width {
5597 scroll.input_scroll_offset = (cursor_pos + 1).saturating_sub(inner_width) as u16;
5598 }
5599 }
5600
5601 fn get_cursor_token_position(&self) -> (usize, usize) {
5602 let ctx = crate::ui::operations::simple_operations::TextNavigationContext {
5603 query: &self.get_input_text(),
5604 cursor_pos: self.get_input_cursor(),
5605 };
5606 crate::ui::operations::simple_operations::get_cursor_token_position(&ctx)
5607 }
5608
5609 fn get_token_at_cursor(&self) -> Option<String> {
5610 let ctx = crate::ui::operations::simple_operations::TextNavigationContext {
5611 query: &self.get_input_text(),
5612 cursor_pos: self.get_input_cursor(),
5613 };
5614 crate::ui::operations::simple_operations::get_token_at_cursor(&ctx)
5615 }
5616
5617 #[allow(dead_code)]
5619
5620 fn ui(&mut self, f: &mut Frame) {
5621 let input_height = INPUT_AREA_HEIGHT;
5623
5624 let buffer_count = self.state_container.buffers().all_buffers().len();
5626 let tab_bar_height = 2; let chunks = Layout::default()
5629 .direction(Direction::Vertical)
5630 .constraints(
5631 [
5632 Constraint::Length(tab_bar_height), Constraint::Length(input_height), Constraint::Min(0), Constraint::Length(STATUS_BAR_HEIGHT), ]
5637 .as_ref(),
5638 )
5639 .split(f.area());
5640
5641 if buffer_count > 0 {
5643 let buffer_names: Vec<String> = self
5644 .state_container
5645 .buffers()
5646 .all_buffers()
5647 .iter()
5648 .map(|b| b.get_name())
5649 .collect();
5650 let current_index = self.state_container.buffers().current_index();
5651
5652 let tab_widget = TabBarWidget::new(current_index, buffer_names);
5653 tab_widget.render(f, chunks[0]);
5654 }
5655
5656 let input_chunk_idx = 1;
5658 let results_chunk_idx = 2;
5659 let status_chunk_idx = 3;
5660
5661 self.update_horizontal_scroll(chunks[input_chunk_idx].width);
5663
5664 let input_text_for_count = self.get_input_text();
5667 let char_count = input_text_for_count.len();
5668 let cursor_pos = self.get_input_cursor();
5669 let char_count_display = if char_count > 0 {
5670 format!(" [{}/{} chars]", cursor_pos, char_count)
5671 } else {
5672 String::new()
5673 };
5674
5675 let scroll_offset = self.get_horizontal_scroll_offset();
5676 let scroll_indicator = if scroll_offset > 0 {
5677 " ◀ " } else {
5679 ""
5680 };
5681
5682 let input_title = match self.shadow_state.borrow().get_mode() {
5683 AppMode::Command => format!("SQL Query{}{}", char_count_display, scroll_indicator),
5684 AppMode::Results => format!(
5685 "SQL Query (Results Mode - Press ↑ to edit){}{}",
5686 char_count_display, scroll_indicator
5687 ),
5688 AppMode::Search => format!("Search Pattern{}{}", char_count_display, scroll_indicator),
5689 AppMode::Filter => format!("Filter Pattern{}{}", char_count_display, scroll_indicator),
5690 AppMode::FuzzyFilter => {
5691 format!("Fuzzy Filter{}{}", char_count_display, scroll_indicator)
5692 }
5693 AppMode::ColumnSearch => {
5694 format!("Column Search{}{}", char_count_display, scroll_indicator)
5695 }
5696 AppMode::Help => "Help".to_string(),
5697 AppMode::History => {
5698 let query = self.state_container.history_search().query.clone();
5699 format!("History Search: '{}' (Esc to cancel)", query)
5700 }
5701 AppMode::Debug => "Parser Debug (F5)".to_string(),
5702 AppMode::PrettyQuery => "Pretty Query View (F6)".to_string(),
5703 AppMode::JumpToRow => format!("Jump to row: {}", self.get_jump_to_row_input()),
5704 AppMode::ColumnStats => "Column Statistics (S to close)".to_string(),
5705 };
5706
5707 let input_block = Block::default().borders(Borders::ALL).title(input_title);
5708
5709 let use_search_widget = matches!(
5711 self.shadow_state.borrow().get_mode(),
5712 AppMode::Search | AppMode::Filter | AppMode::FuzzyFilter | AppMode::ColumnSearch
5713 ) && self.search_modes_widget.is_active();
5714
5715 if use_search_widget {
5716 self.search_modes_widget.render(f, chunks[input_chunk_idx]);
5718 } else {
5719 let input_text_string = self.get_input_text();
5721
5722 trace!(target: "render", "Rendering input: text='{}', mode={:?}, cursor={}",
5724 if input_text_string.len() > 50 {
5725 format!("{}...", &input_text_string[..50])
5726 } else {
5727 input_text_string.clone()
5728 },
5729 self.shadow_state.borrow().get_mode(),
5730 self.get_input_cursor());
5731
5732 let history_query_string = if self.shadow_state.borrow().is_in_history_mode() {
5734 self.state_container.history_search().query.clone()
5735 } else {
5736 String::new()
5737 };
5738
5739 let input_text = match self.shadow_state.borrow().get_mode() {
5740 AppMode::History => &history_query_string,
5741 _ => &input_text_string,
5742 };
5743
5744 let input_paragraph = match self.shadow_state.borrow().get_mode() {
5745 AppMode::Command => {
5746 match self.state_container.get_edit_mode() {
5747 Some(EditMode::SingleLine) => {
5748 let highlighted_line =
5750 self.sql_highlighter.simple_sql_highlight(input_text);
5751 Paragraph::new(Text::from(vec![highlighted_line]))
5752 .block(input_block)
5753 .scroll((0, self.get_horizontal_scroll_offset()))
5754 }
5755 Some(EditMode::MultiLine) => {
5756 let highlighted_line =
5758 self.sql_highlighter.simple_sql_highlight(input_text);
5759 Paragraph::new(Text::from(vec![highlighted_line]))
5760 .block(input_block)
5761 .scroll((0, self.get_horizontal_scroll_offset()))
5762 }
5763 None => {
5764 let highlighted_line =
5766 self.sql_highlighter.simple_sql_highlight(input_text);
5767 Paragraph::new(Text::from(vec![highlighted_line]))
5768 .block(input_block)
5769 .scroll((0, self.get_horizontal_scroll_offset()))
5770 }
5771 }
5772 }
5773 _ => {
5774 Paragraph::new(input_text.as_str())
5776 .block(input_block)
5777 .style(match self.shadow_state.borrow().get_mode() {
5778 AppMode::Results => Style::default().fg(Color::DarkGray),
5779 AppMode::Search => Style::default().fg(Color::Yellow),
5780 AppMode::Filter => Style::default().fg(Color::Cyan),
5781 AppMode::FuzzyFilter => Style::default().fg(Color::Magenta),
5782 AppMode::ColumnSearch => Style::default().fg(Color::Green),
5783 AppMode::Help => Style::default().fg(Color::DarkGray),
5784 AppMode::History => Style::default().fg(Color::Magenta),
5785 AppMode::Debug => Style::default().fg(Color::Yellow),
5786 AppMode::PrettyQuery => Style::default().fg(Color::Green),
5787 AppMode::JumpToRow => Style::default().fg(Color::Magenta),
5788 AppMode::ColumnStats => Style::default().fg(Color::Cyan),
5789 _ => Style::default(),
5790 })
5791 .scroll((0, self.get_horizontal_scroll_offset()))
5792 }
5793 };
5794
5795 f.render_widget(input_paragraph, chunks[input_chunk_idx]);
5797 }
5798 let results_area = chunks[results_chunk_idx];
5799
5800 if !use_search_widget {
5802 match self.shadow_state.borrow().get_mode() {
5803 AppMode::Command => {
5804 let inner_width = chunks[input_chunk_idx].width.saturating_sub(2) as usize;
5807 let cursor_pos = self.get_visual_cursor().1; let scroll_offset = self.get_horizontal_scroll_offset() as usize;
5809
5810 if cursor_pos >= scroll_offset && cursor_pos < scroll_offset + inner_width {
5812 let visible_pos = cursor_pos - scroll_offset;
5813 f.set_cursor_position((
5814 chunks[input_chunk_idx].x + visible_pos as u16 + 1,
5815 chunks[input_chunk_idx].y + 1,
5816 ));
5817 }
5818 }
5819 AppMode::Search => {
5820 f.set_cursor_position((
5821 chunks[input_chunk_idx].x + self.get_input_cursor() as u16 + 1,
5822 chunks[input_chunk_idx].y + 1,
5823 ));
5824 }
5825 AppMode::Filter => {
5826 f.set_cursor_position((
5827 chunks[input_chunk_idx].x + self.get_input_cursor() as u16 + 1,
5828 chunks[input_chunk_idx].y + 1,
5829 ));
5830 }
5831 AppMode::FuzzyFilter => {
5832 f.set_cursor_position((
5833 chunks[input_chunk_idx].x + self.get_input_cursor() as u16 + 1,
5834 chunks[input_chunk_idx].y + 1,
5835 ));
5836 }
5837 AppMode::ColumnSearch => {
5838 f.set_cursor_position((
5839 chunks[input_chunk_idx].x + self.get_input_cursor() as u16 + 1,
5840 chunks[input_chunk_idx].y + 1,
5841 ));
5842 }
5843 AppMode::JumpToRow => {
5844 f.set_cursor_position((
5845 chunks[input_chunk_idx].x + self.get_jump_to_row_input().len() as u16 + 1,
5846 chunks[input_chunk_idx].y + 1,
5847 ));
5848 }
5849 AppMode::History => {
5850 let query_len = self.state_container.history_search().query.len();
5851 f.set_cursor_position((
5852 chunks[input_chunk_idx].x + query_len as u16 + 1,
5853 chunks[input_chunk_idx].y + 1,
5854 ));
5855 }
5856 _ => {}
5857 }
5858 }
5859
5860 let mode = self.shadow_state.borrow().get_mode();
5862 match mode {
5863 AppMode::Help => self.render_help(f, results_area),
5864 AppMode::History => self.render_history(f, results_area),
5865 AppMode::Debug => self.render_debug(f, results_area),
5866 AppMode::PrettyQuery => self.render_pretty_query(f, results_area),
5867 AppMode::ColumnStats => self.render_column_stats(f, results_area),
5868 _ if self.state_container.has_dataview() => {
5869 if let Some(provider) = self.get_data_provider() {
5872 self.render_table_with_provider(f, results_area, provider.as_ref());
5873 }
5874 }
5875 _ => {
5876 let placeholder = Paragraph::new("Enter SQL query and press Enter\n\nTip: Use Tab for completion, Ctrl+R for history")
5878 .block(Block::default().borders(Borders::ALL).title("Results"))
5879 .style(Style::default().fg(Color::DarkGray));
5880 f.render_widget(placeholder, results_area);
5881 }
5882 }
5883
5884 self.render_status_line(f, chunks[status_chunk_idx]);
5886 }
5888
5889 fn add_mode_styling(&self, spans: &mut Vec<Span>) -> (Style, Color) {
5891 let (status_style, mode_color) = match self.shadow_state.borrow().get_mode() {
5893 AppMode::Command => (Style::default().fg(Color::Green), Color::Green),
5894 AppMode::Results => (Style::default().fg(Color::Blue), Color::Blue),
5895 AppMode::Search => (Style::default().fg(Color::Yellow), Color::Yellow),
5896 AppMode::Filter => (Style::default().fg(Color::Cyan), Color::Cyan),
5897 AppMode::FuzzyFilter => (Style::default().fg(Color::Magenta), Color::Magenta),
5898 AppMode::ColumnSearch => (Style::default().fg(Color::Green), Color::Green),
5899 AppMode::Help => (Style::default().fg(Color::Magenta), Color::Magenta),
5900 AppMode::History => (Style::default().fg(Color::Magenta), Color::Magenta),
5901 AppMode::Debug => (Style::default().fg(Color::Yellow), Color::Yellow),
5902 AppMode::PrettyQuery => (Style::default().fg(Color::Green), Color::Green),
5903 AppMode::JumpToRow => (Style::default().fg(Color::Magenta), Color::Magenta),
5904 AppMode::ColumnStats => (Style::default().fg(Color::Cyan), Color::Cyan),
5905 };
5906
5907 let mode_indicator = match self.shadow_state.borrow().get_mode() {
5908 AppMode::Command => "CMD",
5909 AppMode::Results => "NAV",
5910 AppMode::Search => "SEARCH",
5911 AppMode::Filter => "FILTER",
5912 AppMode::FuzzyFilter => "FUZZY",
5913 AppMode::ColumnSearch => "COL",
5914 AppMode::Help => "HELP",
5915 AppMode::History => "HISTORY",
5916 AppMode::Debug => "DEBUG",
5917 AppMode::PrettyQuery => "PRETTY",
5918 AppMode::JumpToRow => "JUMP",
5919 AppMode::ColumnStats => "STATS",
5920 };
5921
5922 spans.push(Span::styled(
5924 format!("[{}]", mode_indicator),
5925 Style::default().fg(mode_color).add_modifier(Modifier::BOLD),
5926 ));
5927
5928 (status_style, mode_color)
5929 }
5930
5931 fn add_data_source_display(&self, spans: &mut Vec<Span>) {
5933 }
5936
5937 fn add_buffer_information(&self, spans: &mut Vec<Span>) {
5939 let index = self.state_container.buffers().current_index();
5940 let total = self.state_container.buffers().all_buffers().len();
5941
5942 if total > 1 {
5944 spans.push(Span::raw(" "));
5945 spans.push(Span::styled(
5946 format!("[{}/{}]", index + 1, total),
5947 Style::default().fg(Color::Yellow),
5948 ));
5949 }
5950
5951 if let Some(buffer) = self.state_container.buffers().current() {
5954 let query = buffer.get_input_text();
5955 if let Some(from_pos) = query.to_uppercase().find(" FROM ") {
5957 let after_from = &query[from_pos + 6..];
5958 if let Some(table_name) = after_from.split_whitespace().next() {
5960 let clean_name = table_name
5962 .trim_matches('"')
5963 .trim_matches('\'')
5964 .trim_matches('`');
5965
5966 spans.push(Span::raw(" "));
5967 spans.push(Span::styled(
5968 clean_name.to_string(),
5969 Style::default().fg(Color::Cyan),
5970 ));
5971 }
5972 }
5973 }
5974 }
5975
5976 fn add_mode_specific_info(&self, spans: &mut Vec<Span>, mode_color: Color, area: Rect) {
5978 match self.shadow_state.borrow().get_mode() {
5979 AppMode::Command => {
5980 if !self.get_input_text().trim().is_empty() {
5982 let (token_pos, total_tokens) = self.get_cursor_token_position();
5983 spans.push(Span::raw(" | "));
5984 spans.push(Span::styled(
5985 format!("Token {}/{}", token_pos, total_tokens),
5986 Style::default().fg(Color::DarkGray),
5987 ));
5988
5989 if let Some(token) = self.get_token_at_cursor() {
5991 spans.push(Span::raw(" "));
5992 spans.push(Span::styled(
5993 format!("[{}]", token),
5994 Style::default().fg(Color::Cyan),
5995 ));
5996 }
5997
5998 if let Some(error_msg) = self.check_parser_error(&self.get_input_text()) {
6000 spans.push(Span::raw(" | "));
6001 spans.push(Span::styled(
6002 format!("{} {}", self.config.display.icons.warning, error_msg),
6003 Style::default().fg(Color::Red).add_modifier(Modifier::BOLD),
6004 ));
6005 }
6006 }
6007 }
6008 AppMode::Results => {
6009 self.add_results_mode_info(spans, area);
6011 }
6012 AppMode::Search | AppMode::Filter | AppMode::FuzzyFilter | AppMode::ColumnSearch => {
6013 let pattern = self.get_input_text();
6015 if !pattern.is_empty() {
6016 spans.push(Span::raw(" | Pattern: "));
6017 spans.push(Span::styled(pattern, Style::default().fg(mode_color)));
6018 }
6019 }
6020 _ => {}
6021 }
6022 }
6023
6024 fn add_results_mode_info(&self, spans: &mut Vec<Span>, area: Rect) {
6026 let total_rows = self.get_row_count();
6027 if total_rows > 0 {
6028 let selected = self.state_container.navigation().selected_row + 1;
6030 spans.push(Span::raw(" | "));
6031
6032 let selection_mode = self.get_selection_mode();
6034 let mode_text = match selection_mode {
6035 SelectionMode::Cell => "CELL",
6036 SelectionMode::Row => "ROW",
6037 SelectionMode::Column => "COL",
6038 };
6039 spans.push(Span::styled(
6040 format!("[{}]", mode_text),
6041 Style::default()
6042 .fg(Color::Cyan)
6043 .add_modifier(Modifier::BOLD),
6044 ));
6045
6046 spans.push(Span::raw(" "));
6047 spans.push(Span::styled(
6048 format!("Row {}/{}", selected, total_rows),
6049 Style::default().fg(Color::White),
6050 ));
6051
6052 let visual_col_display =
6055 if let Some(ref viewport_manager) = *self.viewport_manager.borrow() {
6056 viewport_manager.get_crosshair_col() + 1
6057 } else {
6058 1
6059 };
6060 spans.push(Span::raw(" "));
6061 spans.push(Span::styled(
6062 format!("({},{})", visual_col_display, selected),
6063 Style::default().fg(Color::DarkGray),
6064 ));
6065
6066 if let Some(ref mut viewport_manager) = *self.viewport_manager.borrow_mut() {
6068 let available_width = area.width.saturating_sub(TABLE_BORDER_WIDTH) as u16;
6069 let visual_col = viewport_manager.get_crosshair_col();
6071 if let Some(x_pos) =
6072 viewport_manager.get_column_x_position(visual_col, available_width)
6073 {
6074 let terminal_x = x_pos + 2;
6076 let terminal_y = (selected as u16)
6077 .saturating_sub(self.state_container.get_scroll_offset().0 as u16)
6078 + 3;
6079 spans.push(Span::raw(" "));
6080 spans.push(Span::styled(
6081 format!("[{}x{}]", terminal_x, terminal_y),
6082 Style::default().fg(Color::DarkGray),
6083 ));
6084 }
6085 }
6086
6087 if let Some(dataview) = self.state_container.get_buffer_dataview() {
6089 let headers = dataview.column_names();
6090
6091 let (visual_row, visual_col) =
6094 if let Some(ref viewport_manager) = *self.viewport_manager.borrow() {
6095 (
6096 viewport_manager.get_crosshair_row(),
6097 viewport_manager.get_crosshair_col(),
6098 )
6099 } else {
6100 (0, 0)
6101 };
6102
6103 if visual_col < headers.len() {
6105 spans.push(Span::raw(" | Col: "));
6106 spans.push(Span::styled(
6107 headers[visual_col].clone(),
6108 Style::default().fg(Color::Cyan),
6109 ));
6110
6111 let viewport_info =
6113 if let Some(ref viewport_manager) = *self.viewport_manager.borrow() {
6114 let viewport_rows = viewport_manager.get_viewport_rows();
6115 let viewport_height = viewport_rows.end - viewport_rows.start;
6116 format!("[V:{},{} @ {}r]", visual_row, visual_col, viewport_height)
6117 } else {
6118 format!("[V:{},{}]", visual_row, visual_col)
6119 };
6120 spans.push(Span::raw(" "));
6121 spans.push(Span::styled(
6122 viewport_info,
6123 Style::default().fg(Color::Magenta),
6124 ));
6125 }
6126 }
6127 }
6128 }
6129
6130 fn render_status_line(&self, f: &mut Frame, area: Rect) {
6131 let mut spans = Vec::new();
6132
6133 let (status_style, mode_color) = self.add_mode_styling(&mut spans);
6135
6136 self.add_data_source_display(&mut spans);
6138
6139 self.add_buffer_information(&mut spans);
6141
6142 self.add_mode_specific_info(&mut spans, mode_color, area);
6144
6145 self.add_query_source_indicator(&mut spans);
6147
6148 self.add_case_sensitivity_indicator(&mut spans);
6150
6151 self.add_column_packing_indicator(&mut spans);
6153
6154 self.add_status_message(&mut spans);
6156
6157 let help_text = self.get_help_text_for_mode();
6159
6160 self.add_global_indicators(&mut spans);
6161
6162 self.add_shadow_state_display(&mut spans);
6164
6165 self.add_help_text_display(&mut spans, help_text, area);
6166
6167 let status_line = Line::from(spans);
6168 let status = Paragraph::new(status_line)
6169 .block(Block::default().borders(Borders::ALL))
6170 .style(status_style);
6171 f.render_widget(status, area);
6172 }
6173
6174 fn build_table_context(
6177 &self,
6178 area: Rect,
6179 provider: &dyn DataProvider,
6180 ) -> crate::ui::rendering::table_render_context::TableRenderContext {
6181 use crate::ui::rendering::table_render_context::TableRenderContextBuilder;
6182
6183 let row_count = provider.get_row_count();
6184 let available_width = area.width.saturating_sub(TABLE_BORDER_WIDTH) as u16;
6185 let available_height = area.height as u16;
6188
6189 let headers = {
6191 let viewport_manager = self.viewport_manager.borrow();
6192 let viewport_manager = viewport_manager
6193 .as_ref()
6194 .expect("ViewportManager must exist");
6195 viewport_manager.get_column_names_ordered()
6196 };
6197
6198 {
6200 let mut viewport_opt = self.viewport_manager.borrow_mut();
6201 if let Some(ref mut viewport_manager) = *viewport_opt {
6202 let data_rows = Self::calculate_table_data_rows(available_height);
6204 viewport_manager.update_terminal_size(available_width, data_rows);
6205 let _ = viewport_manager.get_column_widths(); }
6207 }
6208
6209 let (pinned_visual_positions, crosshair_column_position, _) = {
6211 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
6212 let viewport_manager = viewport_manager_borrow
6213 .as_mut()
6214 .expect("ViewportManager must exist for rendering");
6215 let info = viewport_manager.get_visible_columns_info(available_width);
6216
6217 let visible_indices = &info.0;
6221 let pinned_source_indices = &info.1;
6222
6223 let mut pinned_visual_positions = Vec::new();
6226 for &source_idx in pinned_source_indices {
6227 if let Some(visual_pos) = visible_indices.iter().position(|&x| x == source_idx) {
6228 pinned_visual_positions.push(visual_pos);
6229 }
6230 }
6231
6232 let crosshair_column_position =
6236 if let Some((_, col_pos)) = viewport_manager.get_crosshair_viewport_position() {
6237 col_pos
6238 } else {
6239 0
6241 };
6242
6243 let crosshair_visual = viewport_manager.get_crosshair_col();
6244
6245 (
6246 pinned_visual_positions,
6247 crosshair_column_position,
6248 crosshair_visual,
6249 )
6250 };
6251
6252 let row_viewport_start = self
6254 .state_container
6255 .navigation()
6256 .scroll_offset
6257 .0
6258 .min(row_count.saturating_sub(1));
6259 let row_viewport_end = (row_viewport_start + available_height as usize).min(row_count);
6260 let visible_row_indices: Vec<usize> = (row_viewport_start..row_viewport_end).collect();
6261
6262 let (column_headers, data_to_display, column_widths_visual) = {
6264 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
6265 if let Some(ref mut viewport_manager) = *viewport_manager_borrow {
6266 viewport_manager.get_visual_display(available_width, &visible_row_indices)
6267 } else {
6268 let visible_rows = provider
6270 .get_visible_rows(row_viewport_start, row_viewport_end - row_viewport_start);
6271 let widths = vec![15u16; headers.len()];
6272 (headers.clone(), visible_rows, widths)
6273 }
6274 };
6275
6276 let sort_state = self
6278 .buffer()
6279 .get_dataview()
6280 .map(|dv| dv.get_sort_state().clone());
6281
6282 let fuzzy_filter_pattern = if self.state_container.is_fuzzy_filter_active() {
6284 let pattern = self.state_container.get_fuzzy_filter_pattern();
6285 if !pattern.is_empty() {
6286 Some(pattern)
6287 } else {
6288 None
6289 }
6290 } else {
6291 None
6292 };
6293
6294 let selected_row = self.state_container.navigation().selected_row;
6296 let selected_col = crosshair_column_position;
6297
6298 trace!(target: "search", "Building TableRenderContext: selected_row={}, selected_col={}, mode={:?}",
6300 selected_row, selected_col, self.state_container.get_selection_mode());
6301
6302 TableRenderContextBuilder::new()
6303 .row_count(row_count)
6304 .visible_rows(visible_row_indices.clone(), data_to_display)
6305 .columns(column_headers, column_widths_visual)
6306 .pinned_columns(pinned_visual_positions)
6307 .selection(
6308 selected_row,
6309 selected_col,
6310 self.state_container.get_selection_mode(),
6311 )
6312 .row_viewport(row_viewport_start..row_viewport_end)
6313 .sort_state(sort_state)
6314 .display_options(
6315 self.state_container.is_show_row_numbers(),
6316 self.shadow_state.borrow().get_mode(),
6317 )
6318 .filter(
6319 fuzzy_filter_pattern,
6320 self.state_container.is_case_insensitive(),
6321 )
6322 .dimensions(available_width, available_height)
6323 .build()
6324 }
6325
6326 fn render_table_with_provider(&self, f: &mut Frame, area: Rect, provider: &dyn DataProvider) {
6329 let context = self.build_table_context(area, provider);
6331
6332 crate::ui::rendering::table_renderer::render_table(f, area, &context);
6334 }
6335
6336 fn render_help(&mut self, f: &mut Frame, area: Rect) {
6337 self.render_help_two_column(f, area);
6339 }
6340
6341 fn render_help_two_column(&self, f: &mut Frame, area: Rect) {
6342 let chunks = Layout::default()
6344 .direction(Direction::Horizontal)
6345 .constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
6346 .split(area);
6347
6348 let left_content = HelpText::left_column();
6350 let right_content = HelpText::right_column();
6351
6352 let visible_height = area.height.saturating_sub(2) as usize; let left_total_lines = left_content.len();
6355 let right_total_lines = right_content.len();
6356 let max_lines = left_total_lines.max(right_total_lines);
6357
6358 let scroll_offset = { self.state_container.help_scroll_offset() as usize };
6360
6361 let left_visible: Vec<Line> = left_content
6363 .into_iter()
6364 .skip(scroll_offset)
6365 .take(visible_height)
6366 .collect();
6367
6368 let right_visible: Vec<Line> = right_content
6369 .into_iter()
6370 .skip(scroll_offset)
6371 .take(visible_height)
6372 .collect();
6373
6374 let scroll_indicator = if max_lines > visible_height {
6376 format!(
6377 " (↓/↑ to scroll, {}/{})",
6378 scroll_offset + 1,
6379 max_lines.saturating_sub(visible_height) + 1
6380 )
6381 } else {
6382 String::new()
6383 };
6384
6385 let left_paragraph = Paragraph::new(Text::from(left_visible))
6387 .block(
6388 Block::default()
6389 .borders(Borders::ALL)
6390 .title(format!("Help - Commands{}", scroll_indicator)),
6391 )
6392 .style(Style::default());
6393
6394 let right_paragraph = Paragraph::new(Text::from(right_visible))
6396 .block(
6397 Block::default()
6398 .borders(Borders::ALL)
6399 .title("Help - Navigation & Features"),
6400 )
6401 .style(Style::default());
6402
6403 f.render_widget(left_paragraph, chunks[0]);
6404 f.render_widget(right_paragraph, chunks[1]);
6405 }
6406
6407 fn render_debug(&self, f: &mut Frame, area: Rect) {
6408 <Self as DebugContext>::render_debug(self, f, area);
6409 }
6410
6411 fn render_pretty_query(&self, f: &mut Frame, area: Rect) {
6412 <Self as DebugContext>::render_pretty_query(self, f, area);
6413 }
6414
6415 fn render_history(&self, f: &mut Frame, area: Rect) {
6416 let history_search = self.state_container.history_search();
6418 let matches_empty = history_search.matches.is_empty();
6419 let search_query_empty = history_search.query.is_empty();
6420
6421 if matches_empty {
6422 let no_history = if search_query_empty {
6423 "No command history found.\nExecute some queries to build history."
6424 } else {
6425 "No matches found for your search.\nTry a different search term."
6426 };
6427
6428 let placeholder = Paragraph::new(no_history)
6429 .block(
6430 Block::default()
6431 .borders(Borders::ALL)
6432 .title("Command History"),
6433 )
6434 .style(Style::default().fg(Color::DarkGray));
6435 f.render_widget(placeholder, area);
6436 return;
6437 }
6438
6439 let chunks = Layout::default()
6441 .direction(Direction::Vertical)
6442 .constraints([
6443 Constraint::Percentage(50), Constraint::Percentage(50), ])
6446 .split(area);
6447
6448 self.render_history_list(f, chunks[0]);
6449 self.render_selected_command_preview(f, chunks[1]);
6450 }
6451
6452 fn render_history_list(&self, f: &mut Frame, area: Rect) {
6453 let history_search = self.state_container.history_search();
6455 let matches = history_search.matches.clone();
6456 let selected_index = history_search.selected_index;
6457 let match_count = matches.len();
6458
6459 let history_items: Vec<Line> = matches
6461 .iter()
6462 .enumerate()
6463 .map(|(i, history_match)| {
6464 let entry = &history_match.entry;
6465 let is_selected = i == selected_index;
6466
6467 let success_indicator = if entry.success { "✓" } else { "✗" };
6468 let time_ago = {
6469 let elapsed = chrono::Utc::now() - entry.timestamp;
6470 if elapsed.num_days() > 0 {
6471 format!("{}d", elapsed.num_days())
6472 } else if elapsed.num_hours() > 0 {
6473 format!("{}h", elapsed.num_hours())
6474 } else if elapsed.num_minutes() > 0 {
6475 format!("{}m", elapsed.num_minutes())
6476 } else {
6477 "now".to_string()
6478 }
6479 };
6480
6481 let terminal_width = area.width as usize;
6483 let metadata_space = 15; let available_for_command = terminal_width.saturating_sub(metadata_space).max(50);
6485
6486 let command_text = if entry.command.len() > available_for_command {
6487 format!(
6488 "{}…",
6489 &entry.command[..available_for_command.saturating_sub(1)]
6490 )
6491 } else {
6492 entry.command.clone()
6493 };
6494
6495 let line_text = format!(
6496 "{} {} {} {}x {}",
6497 if is_selected { "►" } else { " " },
6498 command_text,
6499 success_indicator,
6500 entry.execution_count,
6501 time_ago
6502 );
6503
6504 let mut style = Style::default();
6505 if is_selected {
6506 style = style.bg(Color::DarkGray).add_modifier(Modifier::BOLD);
6507 }
6508 if !entry.success {
6509 style = style.fg(Color::Red);
6510 }
6511
6512 if !history_match.indices.is_empty() && is_selected {
6514 style = style.fg(Color::Yellow);
6515 }
6516
6517 Line::from(line_text).style(style)
6518 })
6519 .collect();
6520
6521 let history_paragraph = Paragraph::new(history_items)
6522 .block(Block::default().borders(Borders::ALL).title(format!(
6523 "History ({} matches) - j/k to navigate, Enter to select",
6524 match_count
6525 )))
6526 .wrap(ratatui::widgets::Wrap { trim: false });
6527
6528 f.render_widget(history_paragraph, area);
6529 }
6530
6531 fn render_selected_command_preview(&self, f: &mut Frame, area: Rect) {
6532 let history_search = self.state_container.history_search();
6534 let selected_match = history_search
6535 .matches
6536 .get(history_search.selected_index)
6537 .cloned();
6538
6539 if let Some(selected_match) = selected_match {
6540 let entry = &selected_match.entry;
6541
6542 use crate::recursive_parser::format_sql_pretty_compact;
6544
6545 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);
6551
6552 let max_lines = area.height.saturating_sub(2) as usize; if pretty_lines.len() > max_lines && cols_per_line < 12 {
6555 pretty_lines = format_sql_pretty_compact(&entry.command, 15);
6557 }
6558
6559 let mut highlighted_lines = Vec::new();
6561 for line in pretty_lines {
6562 highlighted_lines.push(self.sql_highlighter.simple_sql_highlight(&line));
6563 }
6564
6565 let preview_text = Text::from(highlighted_lines);
6566
6567 let duration_text = entry
6568 .duration_ms
6569 .map(|d| format!("{}ms", d))
6570 .unwrap_or_else(|| "?ms".to_string());
6571
6572 let success_text = if entry.success {
6573 "✓ Success"
6574 } else {
6575 "✗ Failed"
6576 };
6577
6578 let preview = Paragraph::new(preview_text)
6579 .block(Block::default().borders(Borders::ALL).title(format!(
6580 "Pretty SQL Preview: {} | {} | Used {}x",
6581 success_text, duration_text, entry.execution_count
6582 )))
6583 .scroll((0, 0)); f.render_widget(preview, area);
6586 } else {
6587 let empty_preview = Paragraph::new("No command selected")
6588 .block(Block::default().borders(Borders::ALL).title("Preview"))
6589 .style(Style::default().fg(Color::DarkGray));
6590 f.render_widget(empty_preview, area);
6591 }
6592 }
6593
6594 fn handle_column_stats_input(&mut self, key: crossterm::event::KeyEvent) -> Result<bool> {
6595 let mut ctx = crate::ui::input::input_handlers::StatsInputContext {
6597 buffer_manager: self.state_container.buffers_mut(),
6598 stats_widget: &mut self.stats_widget,
6599 shadow_state: &self.shadow_state,
6600 };
6601
6602 let result = crate::ui::input::input_handlers::handle_column_stats_input(&mut ctx, key)?;
6603
6604 if self.shadow_state.borrow().get_mode() == AppMode::Results {
6606 self.shadow_state
6607 .borrow_mut()
6608 .observe_mode_change(AppMode::Results, "column_stats_closed");
6609 }
6610
6611 Ok(result)
6612 }
6613
6614 fn handle_jump_to_row_input(&mut self, key: crossterm::event::KeyEvent) -> Result<bool> {
6615 match key.code {
6616 KeyCode::Enter => {
6617 let input = self.get_jump_to_row_input();
6619 self.complete_jump_to_row(&input);
6620 }
6621 _ => {
6622 self.process_jump_to_row_key(key);
6624 }
6625 }
6626 Ok(false)
6627 }
6628
6629 fn render_column_stats(&self, f: &mut Frame, area: Rect) {
6630 self.stats_widget.render(
6632 f,
6633 area,
6634 self.state_container
6635 .current_buffer()
6636 .expect("Buffer should exist"),
6637 );
6638 }
6639
6640 fn handle_execute_query(&mut self) -> Result<bool> {
6646 use crate::ui::state::state_coordinator::StateCoordinator;
6647
6648 let query = self.get_input_text().trim().to_string();
6649 debug!(target: "action", "Executing query: {}", query);
6650
6651 let should_exit = StateCoordinator::handle_execute_query_with_refs(
6653 &mut self.state_container,
6654 &self.shadow_state,
6655 &query,
6656 )?;
6657
6658 if should_exit {
6659 return Ok(true);
6660 }
6661
6662 if !query.is_empty() && !query.starts_with(':') {
6664 if let Err(e) = self.execute_query_v2(&query) {
6665 self.state_container
6666 .set_status_message(format!("Error executing query: {}", e));
6667 }
6668 }
6670
6671 Ok(false) }
6673
6674 fn handle_buffer_action(&mut self, action: BufferAction) -> Result<bool> {
6675 match action {
6676 BufferAction::NextBuffer => {
6677 let message = self
6678 .buffer_handler
6679 .next_buffer(self.state_container.buffers_mut());
6680 debug!("{}", message);
6681 self.sync_after_buffer_switch();
6683 Ok(false)
6684 }
6685 BufferAction::PreviousBuffer => {
6686 let message = self
6687 .buffer_handler
6688 .previous_buffer(self.state_container.buffers_mut());
6689 debug!("{}", message);
6690 self.sync_after_buffer_switch();
6692 Ok(false)
6693 }
6694 BufferAction::QuickSwitch => {
6695 let message = self
6696 .buffer_handler
6697 .quick_switch(self.state_container.buffers_mut());
6698 debug!("{}", message);
6699 self.sync_after_buffer_switch();
6701 Ok(false)
6702 }
6703 BufferAction::NewBuffer => {
6704 let message = self
6705 .buffer_handler
6706 .new_buffer(self.state_container.buffers_mut(), &self.config);
6707 debug!("{}", message);
6708 Ok(false)
6709 }
6710 BufferAction::CloseBuffer => {
6711 let (success, message) = self
6712 .buffer_handler
6713 .close_buffer(self.state_container.buffers_mut());
6714 debug!("{}", message);
6715 Ok(!success) }
6717 BufferAction::ListBuffers => {
6718 let buffer_list = self
6719 .buffer_handler
6720 .list_buffers(self.state_container.buffers());
6721 for line in &buffer_list {
6723 debug!("{}", line);
6724 }
6725 Ok(false)
6726 }
6727 BufferAction::SwitchToBuffer(buffer_index) => {
6728 let message = self
6729 .buffer_handler
6730 .switch_to_buffer(self.state_container.buffers_mut(), buffer_index);
6731 debug!("{}", message);
6732
6733 self.sync_after_buffer_switch();
6735
6736 Ok(false)
6737 }
6738 }
6739 }
6740
6741 fn handle_expand_asterisk(&mut self) -> Result<bool> {
6742 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
6743 if buffer.expand_asterisk(&self.hybrid_parser) {
6744 if buffer.get_edit_mode() == EditMode::SingleLine {
6746 let text = buffer.get_input_text();
6747 let cursor = buffer.get_input_cursor_position();
6748 self.set_input_text_with_cursor(text, cursor);
6749 }
6750 }
6751 }
6752 Ok(false)
6753 }
6754
6755 pub(crate) fn toggle_debug_mode(&mut self) {
6756 DebugContext::toggle_debug_mode(self);
6758 }
6759
6760 pub(crate) fn debug_generate_parser_info(&self, query: &str) -> String {
6765 self.hybrid_parser
6766 .get_detailed_debug_info(query, query.len())
6767 }
6768
6769 fn debug_generate_navigation_state(&self) -> String {
6770 let mut debug_info = String::new();
6771 debug_info.push_str("\n========== NAVIGATION DEBUG ==========\n");
6772 let current_column = self.state_container.get_current_column();
6773 let scroll_offset = self.state_container.get_scroll_offset();
6774 let nav_state = self.state_container.navigation();
6775
6776 debug_info.push_str(&format!("Buffer Column Position: {}\n", current_column));
6777 debug_info.push_str(&format!(
6778 "Buffer Scroll Offset: row={}, col={}\n",
6779 scroll_offset.0, scroll_offset.1
6780 ));
6781 debug_info.push_str(&format!(
6782 "NavigationState Column: {}\n",
6783 nav_state.selected_column
6784 ));
6785 debug_info.push_str(&format!(
6786 "NavigationState Row: {:?}\n",
6787 nav_state.selected_row
6788 ));
6789 debug_info.push_str(&format!(
6790 "NavigationState Scroll Offset: row={}, col={}\n",
6791 nav_state.scroll_offset.0, nav_state.scroll_offset.1
6792 ));
6793
6794 if current_column != nav_state.selected_column {
6796 debug_info.push_str(&format!(
6797 "⚠️ WARNING: Column mismatch! Buffer={}, Nav={}\n",
6798 current_column, nav_state.selected_column
6799 ));
6800 }
6801 if scroll_offset.1 != nav_state.scroll_offset.1 {
6802 debug_info.push_str(&format!(
6803 "⚠️ WARNING: Scroll column mismatch! Buffer={}, Nav={}\n",
6804 scroll_offset.1, nav_state.scroll_offset.1
6805 ));
6806 }
6807
6808 debug_info.push_str("\n--- Navigation Flow ---\n");
6809 debug_info.push_str(
6810 "(Enable RUST_LOG=sql_cli::ui::viewport_manager=debug,navigation=debug to see flow)\n",
6811 );
6812
6813 if let Some(dataview) = self.state_container.get_buffer_dataview() {
6815 let pinned_count = dataview.get_pinned_columns().len();
6816 let pinned_names = dataview.get_pinned_column_names();
6817 debug_info.push_str(&format!("Pinned Column Count: {}\n", pinned_count));
6818 if !pinned_names.is_empty() {
6819 debug_info.push_str(&format!("Pinned Column Names: {:?}\n", pinned_names));
6820 }
6821 debug_info.push_str(&format!("First Scrollable Column: {}\n", pinned_count));
6822
6823 if current_column < pinned_count {
6825 debug_info.push_str(&format!(
6826 "Current Position: PINNED area (column {})\n",
6827 current_column
6828 ));
6829 } else {
6830 debug_info.push_str(&format!(
6831 "Current Position: SCROLLABLE area (column {}, scrollable index {})\n",
6832 current_column,
6833 current_column - pinned_count
6834 ));
6835 }
6836
6837 let display_columns = dataview.get_display_columns();
6839 debug_info.push_str(&format!("\n--- COLUMN ORDERING ---\n"));
6840 debug_info.push_str(&format!(
6841 "Display column order (first 10): {:?}\n",
6842 &display_columns[..display_columns.len().min(10)]
6843 ));
6844 if display_columns.len() > 10 {
6845 debug_info.push_str(&format!(
6846 "... and {} more columns\n",
6847 display_columns.len() - 10
6848 ));
6849 }
6850
6851 if let Some(display_idx) = display_columns
6853 .iter()
6854 .position(|&idx| idx == current_column)
6855 {
6856 debug_info.push_str(&format!(
6857 "Current column {} is at display index {}/{}\n",
6858 current_column,
6859 display_idx,
6860 display_columns.len()
6861 ));
6862
6863 if display_idx + 1 < display_columns.len() {
6865 let next_col = display_columns[display_idx + 1];
6866 debug_info.push_str(&format!(
6867 "Next 'l' press should move to column {} (display index {})\n",
6868 next_col,
6869 display_idx + 1
6870 ));
6871 } else {
6872 debug_info.push_str("Next 'l' press should wrap to first column\n");
6873 }
6874 } else {
6875 debug_info.push_str(&format!(
6876 "WARNING: Current column {} not found in display order!\n",
6877 current_column
6878 ));
6879 }
6880 }
6881 debug_info.push_str("==========================================\n");
6882 debug_info
6883 }
6884
6885 fn debug_generate_column_search_state(&self) -> String {
6886 let mut debug_info = String::new();
6887 let show_column_search = self.shadow_state.borrow().get_mode() == AppMode::ColumnSearch
6888 || !self.state_container.column_search().pattern.is_empty();
6889 if show_column_search {
6890 let column_search = self.state_container.column_search();
6891 debug_info.push_str("\n========== COLUMN SEARCH STATE ==========\n");
6892 debug_info.push_str(&format!("Pattern: '{}'\n", column_search.pattern));
6893 debug_info.push_str(&format!(
6894 "Matching Columns: {} found\n",
6895 column_search.matching_columns.len()
6896 ));
6897 if !column_search.matching_columns.is_empty() {
6898 debug_info.push_str("Matches:\n");
6899 for (idx, (col_idx, col_name)) in column_search.matching_columns.iter().enumerate()
6900 {
6901 let marker = if idx == column_search.current_match {
6902 " <--"
6903 } else {
6904 ""
6905 };
6906 debug_info.push_str(&format!(
6907 " [{}] {} (index {}){}\n",
6908 idx, col_name, col_idx, marker
6909 ));
6910 }
6911 }
6912 debug_info.push_str(&format!(
6913 "Current Match Index: {}\n",
6914 column_search.current_match
6915 ));
6916 debug_info.push_str(&format!(
6917 "Current Column: {}\n",
6918 self.state_container.get_current_column()
6919 ));
6920 debug_info.push_str("==========================================\n");
6921 }
6922 debug_info
6923 }
6924
6925 pub(crate) fn debug_generate_trace_logs(&self) -> String {
6926 let mut debug_info = String::from("\n========== TRACE LOGS ==========\n");
6927 debug_info.push_str("(Most recent at bottom, last 100 entries)\n");
6928
6929 if let Some(ref log_buffer) = self.log_buffer {
6930 let recent_logs = log_buffer.get_recent(100);
6931 for entry in recent_logs {
6932 debug_info.push_str(&entry.format_for_display());
6933 debug_info.push('\n');
6934 }
6935 debug_info.push_str(&format!("Total log entries: {}\n", log_buffer.len()));
6936 } else {
6937 debug_info.push_str("Log buffer not initialized\n");
6938 }
6939 debug_info.push_str("================================\n");
6940
6941 debug_info
6942 }
6943
6944 pub(crate) fn debug_generate_state_logs(&self) -> String {
6946 let mut debug_info = String::new();
6947
6948 if let Some(ref debug_service) = self.debug_service {
6949 debug_info.push_str("\n========== STATE CHANGE LOGS ==========\n");
6950 debug_info.push_str("(Most recent at bottom, from DebugService)\n");
6951 let debug_entries = debug_service.get_entries();
6952 let recent = debug_entries.iter().rev().take(50).rev();
6953 for entry in recent {
6954 debug_info.push_str(&format!(
6955 "[{}] {:?} [{}]: {}\n",
6956 entry.timestamp, entry.level, entry.component, entry.message
6957 ));
6958 }
6959 debug_info.push_str(&format!(
6960 "Total state change entries: {}\n",
6961 debug_entries.len()
6962 ));
6963 debug_info.push_str("================================\n");
6964 } else {
6965 debug_info.push_str("\n========== STATE CHANGE LOGS ==========\n");
6966 debug_info.push_str("DebugService not available (service_container is None)\n");
6967 debug_info.push_str("================================\n");
6968 }
6969
6970 debug_info
6971 }
6972
6973 pub(crate) fn debug_extract_timing(&self, s: &str) -> Option<f64> {
6975 crate::ui::rendering::ui_layout_utils::extract_timing_from_debug_string(s)
6976 }
6977
6978 fn show_pretty_query(&mut self) {
6979 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
6980 self.shadow_state.borrow_mut().set_mode(
6981 AppMode::PrettyQuery,
6982 buffer,
6983 "pretty_query_show",
6984 );
6985 let query = buffer.get_input_text();
6986 self.debug_widget.generate_pretty_sql(&query);
6987 }
6988 }
6989
6990 fn add_global_indicators(&self, spans: &mut Vec<Span>) {
6992 if self.key_sequence_renderer.has_content() {
6993 let key_display = self.key_sequence_renderer.get_display();
6994 if !key_display.is_empty() {
6995 spans.push(Span::raw(" | Keys: "));
6996 spans.push(Span::styled(
6997 key_display,
6998 Style::default()
6999 .fg(Color::Cyan)
7000 .add_modifier(Modifier::ITALIC),
7001 ));
7002 }
7003 }
7004 }
7005
7006 fn add_query_source_indicator(&self, spans: &mut Vec<Span>) {
7007 if let Some(source) = self.state_container.get_last_query_source() {
7008 spans.push(Span::raw(" | "));
7009 let (icon, label, color) = match source.as_str() {
7010 "cache" => (
7011 &self.config.display.icons.cache,
7012 "CACHE".to_string(),
7013 Color::Cyan,
7014 ),
7015 "file" | "FileDataSource" => (
7016 &self.config.display.icons.file,
7017 "FILE".to_string(),
7018 Color::Green,
7019 ),
7020 "SqlServerDataSource" => (
7021 &self.config.display.icons.database,
7022 "SQL".to_string(),
7023 Color::Blue,
7024 ),
7025 "PublicApiDataSource" => (
7026 &self.config.display.icons.api,
7027 "API".to_string(),
7028 Color::Yellow,
7029 ),
7030 _ => (
7031 &self.config.display.icons.api,
7032 source.clone(),
7033 Color::Magenta,
7034 ),
7035 };
7036 spans.push(Span::raw(format!("{} ", icon)));
7037 spans.push(Span::styled(label, Style::default().fg(color)));
7038 }
7039 }
7040
7041 fn add_case_sensitivity_indicator(&self, spans: &mut Vec<Span>) {
7042 let case_insensitive = self.state_container.is_case_insensitive();
7043 if case_insensitive {
7044 spans.push(Span::raw(" | "));
7045 let icon = self.config.display.icons.case_insensitive.clone();
7046 spans.push(Span::styled(
7047 format!("{} CASE", icon),
7048 Style::default().fg(Color::Cyan),
7049 ));
7050 }
7051 }
7052
7053 fn add_column_packing_indicator(&self, spans: &mut Vec<Span>) {
7054 if let Some(ref viewport_manager) = *self.viewport_manager.borrow() {
7055 let packing_mode = viewport_manager.get_packing_mode();
7056 spans.push(Span::raw(" | "));
7057 let (text, color) = match packing_mode {
7058 ColumnPackingMode::DataFocus => ("DATA", Color::Cyan),
7059 ColumnPackingMode::HeaderFocus => ("HEADER", Color::Yellow),
7060 ColumnPackingMode::Balanced => ("BALANCED", Color::Green),
7061 };
7062 spans.push(Span::styled(text, Style::default().fg(color)));
7063 }
7064 }
7065
7066 fn add_status_message(&self, spans: &mut Vec<Span>) {
7067 let status_msg = self.state_container.get_buffer_status_message();
7068 if !status_msg.is_empty() {
7069 spans.push(Span::raw(" | "));
7070 spans.push(Span::styled(
7071 status_msg,
7072 Style::default()
7073 .fg(Color::Yellow)
7074 .add_modifier(Modifier::BOLD),
7075 ));
7076 }
7077 }
7078
7079 fn get_help_text_for_mode(&self) -> &str {
7080 match self.shadow_state.borrow().get_mode() {
7081 AppMode::Command => "Enter:Run | Tab:Complete | ↓:Results | F1:Help",
7082 AppMode::Results => match self.get_selection_mode() {
7083 SelectionMode::Cell => "v:Row mode | y:Yank cell | ↑:Edit | F1:Help",
7084 SelectionMode::Row => "v:Cell mode | y:Yank | f:Filter | ↑:Edit | F1:Help",
7085 SelectionMode::Column => "v:Cell mode | y:Yank col | ↑:Edit | F1:Help",
7086 },
7087 AppMode::Search | AppMode::Filter | AppMode::FuzzyFilter | AppMode::ColumnSearch => {
7088 "Enter:Apply | Esc:Cancel"
7089 }
7090 AppMode::Help | AppMode::Debug | AppMode::PrettyQuery | AppMode::ColumnStats => {
7091 "Esc:Close"
7092 }
7093 AppMode::History => "Enter:Select | Esc:Cancel",
7094 AppMode::JumpToRow => "Enter:Jump | Esc:Cancel",
7095 }
7096 }
7097
7098 fn add_shadow_state_display(&self, spans: &mut Vec<Span>) {
7099 let shadow_display = self.shadow_state.borrow().status_display();
7100 spans.push(Span::raw(" "));
7101 spans.push(Span::styled(
7102 shadow_display,
7103 Style::default().fg(Color::Cyan),
7104 ));
7105 }
7106
7107 fn add_help_text_display<'a>(&self, spans: &mut Vec<Span<'a>>, help_text: &'a str, area: Rect) {
7109 let current_length: usize = spans.iter().map(|s| s.content.len()).sum();
7110 let available_width = area.width.saturating_sub(TABLE_BORDER_WIDTH) as usize;
7111 let help_length = help_text.len();
7112
7113 if current_length + help_length + 3 < available_width {
7114 let padding = available_width - current_length - help_length - 3;
7115 spans.push(Span::raw(" ".repeat(padding)));
7116 spans.push(Span::raw(" | "));
7117 spans.push(Span::styled(
7118 help_text,
7119 Style::default().fg(Color::DarkGray),
7120 ));
7121 }
7122 }
7123}
7124
7125impl ActionHandlerContext for EnhancedTuiApp {
7127 fn previous_row(&mut self) {
7129 <Self as NavigationBehavior>::previous_row(self);
7130
7131 let current_row = self.state_container.navigation().selected_row;
7133 let current_col = self.state_container.navigation().selected_column;
7134 self.table_widget_manager
7135 .borrow_mut()
7136 .navigate_to(current_row, current_col);
7137 info!(target: "navigation", "previous_row: Updated TableWidgetManager to ({}, {})", current_row, current_col);
7138 }
7139
7140 fn next_row(&mut self) {
7141 info!(target: "navigation", "next_row called - calling NavigationBehavior::next_row");
7142 <Self as NavigationBehavior>::next_row(self);
7143
7144 let current_row = self.state_container.navigation().selected_row;
7146 let current_col = self.state_container.navigation().selected_column;
7147 self.table_widget_manager
7148 .borrow_mut()
7149 .navigate_to(current_row, current_col);
7150 info!(target: "navigation", "next_row: Updated TableWidgetManager to ({}, {})", current_row, current_col);
7151 }
7152
7153 fn move_column_left(&mut self) {
7154 <Self as ColumnBehavior>::move_column_left(self);
7155
7156 let current_row = self.state_container.navigation().selected_row;
7158 let current_col = self.state_container.navigation().selected_column;
7159 self.table_widget_manager
7160 .borrow_mut()
7161 .navigate_to(current_row, current_col);
7162 info!(target: "navigation", "move_column_left: Updated TableWidgetManager to ({}, {})", current_row, current_col);
7163 }
7164
7165 fn move_column_right(&mut self) {
7166 <Self as ColumnBehavior>::move_column_right(self);
7167
7168 let current_row = self.state_container.navigation().selected_row;
7170 let current_col = self.state_container.navigation().selected_column;
7171 self.table_widget_manager
7172 .borrow_mut()
7173 .navigate_to(current_row, current_col);
7174 info!(target: "navigation", "move_column_right: Updated TableWidgetManager to ({}, {})", current_row, current_col);
7175 }
7176
7177 fn page_up(&mut self) {
7178 <Self as NavigationBehavior>::page_up(self);
7179 }
7180
7181 fn page_down(&mut self) {
7182 <Self as NavigationBehavior>::page_down(self);
7183 }
7184
7185 fn goto_first_row(&mut self) {
7186 use crate::ui::state::state_coordinator::StateCoordinator;
7187
7188 <Self as NavigationBehavior>::goto_first_row(self);
7190
7191 StateCoordinator::goto_first_row_with_refs(
7193 &mut self.state_container,
7194 Some(&self.vim_search_adapter),
7195 Some(&self.viewport_manager),
7196 );
7197 }
7198
7199 fn goto_last_row(&mut self) {
7200 use crate::ui::state::state_coordinator::StateCoordinator;
7201
7202 <Self as NavigationBehavior>::goto_last_row(self);
7204
7205 StateCoordinator::goto_last_row_with_refs(&mut self.state_container);
7207 }
7208
7209 fn goto_first_column(&mut self) {
7210 <Self as ColumnBehavior>::goto_first_column(self);
7211 }
7212
7213 fn goto_last_column(&mut self) {
7214 <Self as ColumnBehavior>::goto_last_column(self);
7215 }
7216
7217 fn goto_row(&mut self, row: usize) {
7218 use crate::ui::state::state_coordinator::StateCoordinator;
7219
7220 <Self as NavigationBehavior>::goto_line(self, row + 1); StateCoordinator::goto_row_with_refs(&mut self.state_container, row);
7225 }
7226
7227 fn goto_column(&mut self, col: usize) {
7228 let current_col = self.state_container.get_current_column();
7231 if col < current_col {
7232 for _ in 0..(current_col - col) {
7233 <Self as ColumnBehavior>::move_column_left(self);
7234 }
7235 } else if col > current_col {
7236 for _ in 0..(col - current_col) {
7237 <Self as ColumnBehavior>::move_column_right(self);
7238 }
7239 }
7240 }
7241
7242 fn set_mode(&mut self, mode: AppMode) {
7244 self.set_mode_via_shadow_state(mode, "action_handler");
7246 }
7247
7248 fn get_mode(&self) -> AppMode {
7249 self.shadow_state.borrow().get_mode()
7250 }
7251
7252 fn set_status_message(&mut self, message: String) {
7253 self.state_container.set_status_message(message);
7254 }
7255
7256 fn toggle_column_pin(&mut self) {
7258 self.toggle_column_pin_impl();
7260 }
7261
7262 fn hide_current_column(&mut self) {
7263 <Self as ColumnBehavior>::hide_current_column(self);
7264 }
7265
7266 fn unhide_all_columns(&mut self) {
7267 <Self as ColumnBehavior>::unhide_all_columns(self);
7268 }
7269
7270 fn clear_all_pinned_columns(&mut self) {
7271 self.clear_all_pinned_columns_impl();
7273 }
7274
7275 fn export_to_csv(&mut self) {
7277 self.state_container
7279 .set_status_message("CSV export not yet implemented".to_string());
7280 }
7281
7282 fn export_to_json(&mut self) {
7283 self.state_container
7285 .set_status_message("JSON export not yet implemented".to_string());
7286 }
7287
7288 fn yank_cell(&mut self) {
7290 YankBehavior::yank_cell(self);
7291 }
7292
7293 fn yank_row(&mut self) {
7294 YankBehavior::yank_row(self);
7295 }
7296
7297 fn yank_column(&mut self) {
7298 YankBehavior::yank_column(self);
7299 }
7300
7301 fn yank_all(&mut self) {
7302 YankBehavior::yank_all(self);
7303 }
7304
7305 fn yank_query(&mut self) {
7306 YankBehavior::yank_query(self);
7307 }
7308
7309 fn toggle_selection_mode(&mut self) {
7311 self.state_container.toggle_selection_mode();
7312 let new_mode = self.state_container.get_selection_mode();
7313 let msg = match new_mode {
7314 SelectionMode::Cell => "Cell mode - Navigate to select individual cells",
7315 SelectionMode::Row => "Row mode - Navigate to select rows",
7316 SelectionMode::Column => "Column mode - Navigate to select columns",
7317 };
7318 self.state_container.set_status_message(msg.to_string());
7319 }
7320
7321 fn toggle_row_numbers(&mut self) {
7322 let current = self.state_container.is_show_row_numbers();
7323 self.state_container.set_show_row_numbers(!current);
7324 let message = if !current {
7325 "Row numbers: ON (showing line numbers)".to_string()
7326 } else {
7327 "Row numbers: OFF".to_string()
7328 };
7329 self.state_container.set_status_message(message);
7330 self.calculate_optimal_column_widths();
7332 }
7333
7334 fn toggle_compact_mode(&mut self) {
7335 let current_mode = self.state_container.is_compact_mode();
7336 self.state_container.set_compact_mode(!current_mode);
7337 let message = if !current_mode {
7338 "Compact mode enabled"
7339 } else {
7340 "Compact mode disabled"
7341 };
7342 self.state_container.set_status_message(message.to_string());
7343 }
7344
7345 fn toggle_case_insensitive(&mut self) {
7346 let current = self.state_container.is_case_insensitive();
7347 self.state_container.set_case_insensitive(!current);
7348 self.state_container.set_status_message(format!(
7349 "Case-insensitive string comparisons: {}",
7350 if !current { "ON" } else { "OFF" }
7351 ));
7352 }
7353
7354 fn toggle_key_indicator(&mut self) {
7355 let enabled = !self.key_indicator.enabled;
7356 self.key_indicator.set_enabled(enabled);
7357 self.key_sequence_renderer.set_enabled(enabled);
7358 self.state_container.set_status_message(format!(
7359 "Key press indicator {}",
7360 if enabled { "enabled" } else { "disabled" }
7361 ));
7362 }
7363
7364 fn clear_filter(&mut self) {
7366 if let Some(dataview) = self.state_container.get_buffer_dataview() {
7368 if dataview.has_filter() {
7369 if let Some(dataview_mut) = self.state_container.get_buffer_dataview_mut() {
7371 dataview_mut.clear_filter();
7372 self.state_container
7373 .set_status_message("Filter cleared".to_string());
7374 }
7375
7376 self.sync_dataview_to_managers();
7379 } else {
7380 self.state_container
7381 .set_status_message("No active filter to clear".to_string());
7382 }
7383 } else {
7384 self.state_container
7385 .set_status_message("No data loaded".to_string());
7386 }
7387 }
7388
7389 fn clear_line(&mut self) {
7390 self.state_container.clear_line();
7391 }
7392
7393 fn start_search(&mut self) {
7395 self.start_vim_search();
7396 }
7397
7398 fn start_column_search(&mut self) {
7399 self.enter_search_mode(SearchMode::ColumnSearch);
7400 }
7401
7402 fn start_filter(&mut self) {
7403 self.enter_search_mode(SearchMode::Filter);
7404 }
7405
7406 fn start_fuzzy_filter(&mut self) {
7407 self.enter_search_mode(SearchMode::FuzzyFilter);
7408 }
7409
7410 fn exit_current_mode(&mut self) {
7411 let mode = self.shadow_state.borrow().get_mode();
7413 match mode {
7414 AppMode::Results => {
7415 self.state_container.set_mode(AppMode::Command);
7418 }
7419 AppMode::Command => {
7420 self.state_container.set_mode(AppMode::Results);
7421 }
7422 AppMode::Help => {
7423 self.state_container.set_mode(AppMode::Results);
7424 }
7425 AppMode::JumpToRow => {
7426 self.state_container.set_mode(AppMode::Results);
7427 <Self as InputBehavior>::clear_jump_to_row_input(self);
7428 self.state_container.jump_to_row_mut().is_active = false;
7430 self.state_container
7431 .set_status_message("Jump to row cancelled".to_string());
7432 }
7433 _ => {
7434 self.state_container.set_mode(AppMode::Results);
7436 }
7437 }
7438 }
7439
7440 fn toggle_debug_mode(&mut self) {
7441 <Self as DebugContext>::toggle_debug_mode(self);
7443 }
7444
7445 fn move_current_column_left(&mut self) {
7447 <Self as ColumnBehavior>::move_current_column_left(self);
7448 }
7449
7450 fn move_current_column_right(&mut self) {
7451 <Self as ColumnBehavior>::move_current_column_right(self);
7452 }
7453
7454 fn next_search_match(&mut self) {
7456 self.vim_search_next();
7457 }
7458
7459 fn previous_search_match(&mut self) {
7460 if self.vim_search_adapter.borrow().is_active()
7461 || self.vim_search_adapter.borrow().get_pattern().is_some()
7462 {
7463 self.vim_search_previous();
7464 }
7465 }
7466
7467 fn show_column_statistics(&mut self) {
7469 self.calculate_column_statistics();
7470 }
7471
7472 fn cycle_column_packing(&mut self) {
7473 let message = {
7474 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
7475 let viewport_manager = viewport_manager_borrow
7476 .as_mut()
7477 .expect("ViewportManager must exist");
7478 let new_mode = viewport_manager.cycle_packing_mode();
7479 format!("Column packing: {}", new_mode.display_name())
7480 };
7481 self.state_container.set_status_message(message);
7482 }
7483
7484 fn navigate_to_viewport_top(&mut self) {
7486 let result = {
7487 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
7488 if let Some(ref mut viewport_manager) = *viewport_manager_borrow {
7489 Some(viewport_manager.navigate_to_viewport_top())
7490 } else {
7491 None
7492 }
7493 };
7494
7495 if let Some(result) = result {
7496 self.sync_navigation_with_viewport();
7498
7499 self.state_container
7501 .set_selected_row(Some(result.row_position));
7502
7503 if result.viewport_changed {
7505 let scroll_offset = self.state_container.navigation().scroll_offset;
7506 self.state_container.set_scroll_offset(scroll_offset);
7507 }
7508 }
7509 }
7510
7511 fn navigate_to_viewport_middle(&mut self) {
7512 let result = {
7513 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
7514 if let Some(ref mut viewport_manager) = *viewport_manager_borrow {
7515 Some(viewport_manager.navigate_to_viewport_middle())
7516 } else {
7517 None
7518 }
7519 };
7520
7521 if let Some(result) = result {
7522 self.sync_navigation_with_viewport();
7524
7525 self.state_container
7527 .set_selected_row(Some(result.row_position));
7528
7529 if result.viewport_changed {
7531 let scroll_offset = self.state_container.navigation().scroll_offset;
7532 self.state_container.set_scroll_offset(scroll_offset);
7533 }
7534 }
7535 }
7536
7537 fn navigate_to_viewport_bottom(&mut self) {
7538 let result = {
7539 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
7540 if let Some(ref mut viewport_manager) = *viewport_manager_borrow {
7541 Some(viewport_manager.navigate_to_viewport_bottom())
7542 } else {
7543 None
7544 }
7545 };
7546
7547 if let Some(result) = result {
7548 self.sync_navigation_with_viewport();
7550
7551 self.state_container
7553 .set_selected_row(Some(result.row_position));
7554
7555 if result.viewport_changed {
7557 let scroll_offset = self.state_container.navigation().scroll_offset;
7558 self.state_container.set_scroll_offset(scroll_offset);
7559 }
7560 }
7561 }
7562
7563 fn move_input_cursor_left(&mut self) {
7565 self.state_container.move_input_cursor_left();
7566 }
7567
7568 fn move_input_cursor_right(&mut self) {
7569 self.state_container.move_input_cursor_right();
7570 }
7571
7572 fn move_input_cursor_home(&mut self) {
7573 self.state_container.set_input_cursor_position(0);
7574 }
7575
7576 fn move_input_cursor_end(&mut self) {
7577 let text_len = self.state_container.get_input_text().chars().count();
7578 self.state_container.set_input_cursor_position(text_len);
7579 }
7580
7581 fn backspace(&mut self) {
7582 self.state_container.backspace();
7583 }
7584
7585 fn delete(&mut self) {
7586 self.state_container.delete();
7587 }
7588
7589 fn undo(&mut self) {
7590 self.state_container.perform_undo();
7591 }
7592
7593 fn redo(&mut self) {
7594 self.state_container.perform_redo();
7595 }
7596
7597 fn start_jump_to_row(&mut self) {
7598 self.state_container.set_mode(AppMode::JumpToRow);
7599 self.shadow_state
7600 .borrow_mut()
7601 .observe_mode_change(AppMode::JumpToRow, "jump_to_row_requested");
7602 <Self as InputBehavior>::clear_jump_to_row_input(self);
7603
7604 self.state_container.jump_to_row_mut().is_active = true;
7606
7607 self.state_container
7608 .set_status_message("Enter row number (1-based):".to_string());
7609 }
7610
7611 fn clear_jump_to_row_input(&mut self) {
7612 <Self as InputBehavior>::clear_jump_to_row_input(self);
7613 }
7614
7615 fn toggle_cursor_lock(&mut self) {
7616 let is_locked = {
7618 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
7619 if let Some(ref mut viewport_manager) = *viewport_manager_borrow {
7620 viewport_manager.toggle_cursor_lock();
7621 Some(viewport_manager.is_cursor_locked())
7622 } else {
7623 None
7624 }
7625 };
7626
7627 if let Some(is_locked) = is_locked {
7628 let msg = if is_locked {
7629 "Cursor lock ON - cursor stays in viewport position while scrolling"
7630 } else {
7631 "Cursor lock OFF"
7632 };
7633 self.state_container.set_status_message(msg.to_string());
7634
7635 info!(target: "shadow_state",
7637 "Cursor lock toggled: {} (in {:?} mode)",
7638 if is_locked { "ON" } else { "OFF" },
7639 self.shadow_state.borrow().get_mode()
7640 );
7641 }
7642 }
7643
7644 fn toggle_viewport_lock(&mut self) {
7645 let is_locked = {
7647 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
7648 if let Some(ref mut viewport_manager) = *viewport_manager_borrow {
7649 viewport_manager.toggle_viewport_lock();
7650 Some(viewport_manager.is_viewport_locked())
7651 } else {
7652 None
7653 }
7654 };
7655
7656 if let Some(is_locked) = is_locked {
7657 let msg = if is_locked {
7658 "Viewport lock ON - navigation constrained to current viewport"
7659 } else {
7660 "Viewport lock OFF"
7661 };
7662 self.state_container.set_status_message(msg.to_string());
7663
7664 info!(target: "shadow_state",
7666 "Viewport lock toggled: {} (in {:?} mode)",
7667 if is_locked { "ON" } else { "OFF" },
7668 self.shadow_state.borrow().get_mode()
7669 );
7670 }
7671 }
7672
7673 fn show_debug_info(&mut self) {
7675 <Self as DebugContext>::toggle_debug_mode(self);
7676 }
7677
7678 fn show_pretty_query(&mut self) {
7679 self.show_pretty_query();
7680 }
7681
7682 fn show_help(&mut self) {
7683 self.state_container.set_help_visible(true);
7684 self.set_mode_via_shadow_state(AppMode::Help, "help_requested");
7685 self.help_widget.on_enter();
7686 }
7687
7688 fn kill_line(&mut self) {
7690 use crate::ui::traits::input_ops::InputBehavior;
7691 InputBehavior::kill_line(self);
7692 let message = if !self.state_container.is_kill_ring_empty() {
7693 let kill_ring = self.state_container.get_kill_ring();
7694 format!(
7695 "Killed to end of line - {} chars in kill ring",
7696 kill_ring.len()
7697 )
7698 } else {
7699 "Kill line - nothing to kill".to_string()
7700 };
7701 self.state_container.set_status_message(message);
7702 }
7703
7704 fn kill_line_backward(&mut self) {
7705 use crate::ui::traits::input_ops::InputBehavior;
7706 InputBehavior::kill_line_backward(self);
7707 let message = if !self.state_container.is_kill_ring_empty() {
7708 let kill_ring = self.state_container.get_kill_ring();
7709 format!(
7710 "Killed to beginning of line - {} chars in kill ring",
7711 kill_ring.len()
7712 )
7713 } else {
7714 "Kill line backward - nothing to kill".to_string()
7715 };
7716 self.state_container.set_status_message(message);
7717 }
7718
7719 fn delete_word_backward(&mut self) {
7720 use crate::ui::traits::input_ops::InputBehavior;
7721 InputBehavior::delete_word_backward(self);
7722 }
7723
7724 fn delete_word_forward(&mut self) {
7725 use crate::ui::traits::input_ops::InputBehavior;
7726 InputBehavior::delete_word_forward(self);
7727 }
7728
7729 fn expand_asterisk(&mut self) {
7730 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
7731 if buffer.expand_asterisk(&self.hybrid_parser) {
7732 if buffer.get_edit_mode() == EditMode::SingleLine {
7734 let text = buffer.get_input_text();
7735 let cursor = buffer.get_input_cursor_position();
7736 self.set_input_text_with_cursor(text, cursor);
7737 }
7738 }
7739 }
7740 }
7741
7742 fn expand_asterisk_visible(&mut self) {
7743 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
7744 if buffer.expand_asterisk_visible() {
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 previous_history_command(&mut self) {
7756 let history_entries = self
7757 .state_container
7758 .command_history()
7759 .get_navigation_entries();
7760 let history_commands: Vec<String> =
7761 history_entries.iter().map(|e| e.command.clone()).collect();
7762
7763 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
7764 if buffer.navigate_history_up(&history_commands) {
7765 self.sync_all_input_states();
7766 self.state_container
7767 .set_status_message("Previous command from history".to_string());
7768 }
7769 }
7770 }
7771
7772 fn next_history_command(&mut self) {
7773 let history_entries = self
7774 .state_container
7775 .command_history()
7776 .get_navigation_entries();
7777 let history_commands: Vec<String> =
7778 history_entries.iter().map(|e| e.command.clone()).collect();
7779
7780 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
7781 if buffer.navigate_history_down(&history_commands) {
7782 self.sync_all_input_states();
7783 self.state_container
7784 .set_status_message("Next command from history".to_string());
7785 }
7786 }
7787 }
7788}
7789
7790impl NavigationBehavior for EnhancedTuiApp {
7792 fn viewport_manager(&self) -> &RefCell<Option<ViewportManager>> {
7793 &self.viewport_manager
7794 }
7795
7796 fn buffer_mut(&mut self) -> &mut dyn BufferAPI {
7797 self.state_container
7798 .current_buffer_mut()
7799 .expect("Buffer should exist")
7800 }
7801
7802 fn buffer(&self) -> &dyn BufferAPI {
7803 self.state_container
7804 .current_buffer()
7805 .expect("Buffer should exist")
7806 }
7807
7808 fn state_container(&self) -> &AppStateContainer {
7809 &self.state_container
7810 }
7811
7812 fn state_container_mut(&mut self) -> &mut AppStateContainer {
7813 &mut self.state_container
7814 }
7815
7816 fn get_row_count(&self) -> usize {
7817 self.get_row_count()
7818 }
7819
7820 fn set_mode_with_sync(&mut self, mode: AppMode, trigger: &str) {
7821 self.set_mode_via_shadow_state(mode, trigger);
7823 }
7824}
7825
7826impl ColumnBehavior for EnhancedTuiApp {
7828 fn viewport_manager(&self) -> &RefCell<Option<ViewportManager>> {
7829 &self.viewport_manager
7830 }
7831
7832 fn buffer_mut(&mut self) -> &mut dyn BufferAPI {
7833 self.state_container
7834 .current_buffer_mut()
7835 .expect("Buffer should exist")
7836 }
7837
7838 fn buffer(&self) -> &dyn BufferAPI {
7839 self.state_container
7840 .current_buffer()
7841 .expect("Buffer should exist")
7842 }
7843
7844 fn state_container(&self) -> &AppStateContainer {
7845 &self.state_container
7846 }
7847
7848 fn is_in_results_mode(&self) -> bool {
7849 self.shadow_state.borrow().is_in_results_mode()
7850 }
7851}
7852
7853impl InputBehavior for EnhancedTuiApp {
7854 fn buffer_manager(&mut self) -> &mut BufferManager {
7855 self.state_container.buffers_mut()
7856 }
7857
7858 fn cursor_manager(&mut self) -> &mut CursorManager {
7859 &mut self.cursor_manager
7860 }
7861
7862 fn set_input_text_with_cursor(&mut self, text: String, cursor: usize) {
7863 self.set_input_text_with_cursor(text, cursor)
7864 }
7865
7866 fn state_container(&self) -> &AppStateContainer {
7867 &self.state_container
7868 }
7869
7870 fn state_container_mut(&mut self) -> &mut AppStateContainer {
7871 &mut self.state_container
7872 }
7873
7874 fn buffer_mut(&mut self) -> &mut dyn BufferAPI {
7875 self.state_container
7876 .current_buffer_mut()
7877 .expect("Buffer should exist")
7878 }
7879
7880 fn set_mode_with_sync(&mut self, mode: AppMode, trigger: &str) {
7881 self.set_mode_via_shadow_state(mode, trigger);
7883 }
7884}
7885
7886impl YankBehavior for EnhancedTuiApp {
7887 fn buffer(&self) -> &dyn BufferAPI {
7888 self.state_container
7889 .current_buffer()
7890 .expect("Buffer should exist")
7891 }
7892
7893 fn buffer_mut(&mut self) -> &mut dyn BufferAPI {
7894 self.state_container
7895 .current_buffer_mut()
7896 .expect("Buffer should exist")
7897 }
7898
7899 fn state_container(&self) -> &AppStateContainer {
7900 &self.state_container
7901 }
7902
7903 fn set_status_message(&mut self, message: String) {
7904 self.state_container.set_status_message(message)
7905 }
7906
7907 fn set_error_status(&mut self, prefix: &str, error: anyhow::Error) {
7908 self.set_error_status(prefix, error)
7909 }
7910}
7911
7912impl BufferManagementBehavior for EnhancedTuiApp {
7913 fn buffer_manager(&mut self) -> &mut BufferManager {
7914 self.state_container.buffers_mut()
7915 }
7916
7917 fn buffer_handler(&mut self) -> &mut BufferHandler {
7918 &mut self.buffer_handler
7919 }
7920
7921 fn buffer(&self) -> &dyn BufferAPI {
7922 self.state_container
7923 .current_buffer()
7924 .expect("Buffer should exist")
7925 }
7926
7927 fn buffer_mut(&mut self) -> &mut dyn BufferAPI {
7928 self.state_container
7929 .current_buffer_mut()
7930 .expect("Buffer should exist")
7931 }
7932
7933 fn config(&self) -> &Config {
7934 &self.config
7935 }
7936
7937 fn cursor_manager(&mut self) -> &mut CursorManager {
7938 &mut self.cursor_manager
7939 }
7940
7941 fn set_input_text_with_cursor(&mut self, text: String, cursor: usize) {
7942 self.set_input_text_with_cursor(text, cursor)
7943 }
7944
7945 fn next_buffer(&mut self) -> String {
7946 self.save_viewport_to_current_buffer();
7948
7949 let result = self
7950 .buffer_handler
7951 .next_buffer(self.state_container.buffers_mut());
7952
7953 self.sync_after_buffer_switch();
7955
7956 result
7957 }
7958
7959 fn previous_buffer(&mut self) -> String {
7960 self.save_viewport_to_current_buffer();
7962
7963 let result = self
7964 .buffer_handler
7965 .previous_buffer(self.state_container.buffers_mut());
7966
7967 self.sync_after_buffer_switch();
7969
7970 result
7971 }
7972
7973 fn quick_switch_buffer(&mut self) -> String {
7974 self.save_viewport_to_current_buffer();
7976
7977 let result = self
7978 .buffer_handler
7979 .quick_switch(self.state_container.buffers_mut());
7980
7981 self.sync_after_buffer_switch();
7983
7984 result
7985 }
7986
7987 fn close_buffer(&mut self) -> (bool, String) {
7988 self.buffer_handler
7989 .close_buffer(self.state_container.buffers_mut())
7990 }
7991
7992 fn switch_to_buffer(&mut self, index: usize) -> String {
7993 self.save_viewport_to_current_buffer();
7995
7996 let result = self
7998 .buffer_handler
7999 .switch_to_buffer(self.state_container.buffers_mut(), index);
8000
8001 self.sync_after_buffer_switch();
8003
8004 result
8005 }
8006
8007 fn buffer_count(&self) -> usize {
8008 self.state_container.buffers().all_buffers().len()
8009 }
8010
8011 fn current_buffer_index(&self) -> usize {
8012 self.state_container.buffers().current_index()
8013 }
8014}
8015
8016pub fn run_enhanced_tui_multi(api_url: &str, data_files: Vec<&str>) -> Result<()> {
8017 let app = if !data_files.is_empty() {
8018 use crate::services::ApplicationOrchestrator;
8020
8021 let config = Config::default();
8023 let orchestrator = ApplicationOrchestrator::new(
8024 config.behavior.case_insensitive_default,
8025 config.behavior.hide_empty_columns,
8026 );
8027
8028 let mut app = orchestrator.create_tui_with_file(data_files[0])?;
8030
8031 for file_path in data_files.iter().skip(1) {
8033 if let Err(e) = orchestrator.load_additional_file(&mut app, file_path) {
8034 app.state_container
8035 .set_status_message(format!("Error loading {}: {}", file_path, e));
8036 continue;
8037 }
8038 }
8039
8040 if data_files.len() > 1 {
8042 app.state_container.buffers_mut().switch_to(0);
8043 app.sync_after_buffer_switch();
8045 app.state_container.set_status_message(format!(
8046 "Loaded {} files into separate buffers. Use Alt+Tab to switch.",
8047 data_files.len()
8048 ));
8049 } else if data_files.len() == 1 {
8050 app.update_parser_for_current_buffer();
8052 }
8053
8054 app
8055 } else {
8056 EnhancedTuiApp::new(api_url)
8057 };
8058
8059 app.run()
8060}
8061
8062pub fn run_enhanced_tui(api_url: &str, data_file: Option<&str>) -> Result<()> {
8063 let files = if let Some(file) = data_file {
8065 vec![file]
8066 } else {
8067 vec![]
8068 };
8069 run_enhanced_tui_multi(api_url, files)
8070}