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::new(
1543 config.behavior.case_insensitive_default,
1544 config.behavior.hide_empty_columns,
1545 ),
1546 debug_registry: DebugRegistry::new(),
1547 memory_tracker: MemoryTracker::new(100),
1548 };
1549
1550 app.setup_state_coordination();
1552
1553 app
1554 }
1555
1556 fn setup_state_coordination(&mut self) {
1558 if let Some(current_buffer) = self.state_container.buffers().current() {
1560 let buffer_rc = std::rc::Rc::new(std::cell::RefCell::new(current_buffer.clone()));
1561 self.state_dispatcher.borrow_mut().set_buffer(buffer_rc);
1562 }
1563
1564 info!("State coordination setup complete");
1569 }
1570
1571 pub fn new_with_dataview(dataview: DataView, source_name: &str) -> Result<Self> {
1574 let mut app = Self::new("");
1576
1577 app.data_source = Some(source_name.to_string());
1579
1580 app.state_container.buffers_mut().clear_all();
1582 let mut buffer = buffer::Buffer::new(1);
1583
1584 let source_table = dataview.source_arc();
1587
1588 use crate::utils::memory_audit;
1590 use crate::utils::memory_tracker;
1591
1592 memory_tracker::track_memory("before_arc_share");
1593
1594 buffer.set_datatable(Some(source_table));
1596
1597 memory_tracker::track_memory("after_arc_share");
1598
1599 let audits = memory_audit::perform_memory_audit(
1601 buffer.get_datatable(),
1602 buffer.get_original_source(),
1603 None,
1604 );
1605 memory_audit::log_memory_audit(&audits);
1606
1607 buffer.set_dataview(Some(dataview.clone()));
1609 let buffer_name = std::path::Path::new(source_name)
1611 .file_name()
1612 .and_then(|s| s.to_str())
1613 .unwrap_or(source_name)
1614 .to_string();
1615 buffer.set_name(buffer_name);
1616
1617 buffer.set_case_insensitive(app.config.behavior.case_insensitive_default);
1619 buffer.set_compact_mode(app.config.display.compact_mode);
1620 buffer.set_show_row_numbers(app.config.display.show_row_numbers);
1621
1622 app.state_container.buffers_mut().add_buffer(buffer);
1624
1625 app.state_container.set_dataview(Some(dataview.clone()));
1627
1628 app.viewport_manager = RefCell::new(Some(ViewportManager::new(Arc::new(dataview.clone()))));
1630
1631 app.calculate_optimal_column_widths();
1633
1634 let row_count = dataview.row_count();
1636 let column_count = dataview.column_count();
1637 app.state_container
1638 .update_data_size(row_count, column_count);
1639
1640 Ok(app)
1641 }
1642
1643 pub fn add_dataview(&mut self, dataview: DataView, source_name: &str) -> Result<()> {
1645 use crate::ui::state::state_coordinator::StateCoordinator;
1647
1648 StateCoordinator::add_dataview_with_refs(
1649 &mut self.state_container,
1650 &self.viewport_manager,
1651 dataview,
1652 source_name,
1653 &self.config,
1654 )?;
1655
1656 self.calculate_optimal_column_widths();
1658
1659 Ok(())
1660 }
1661
1662 pub fn update_viewport_with_dataview(&mut self, dataview: DataView) {
1664 self.viewport_manager = RefCell::new(Some(ViewportManager::new(Arc::new(dataview))));
1665 }
1666
1667 pub fn vim_search_adapter(&self) -> &RefCell<VimSearchAdapter> {
1669 &self.vim_search_adapter
1670 }
1671
1672 pub fn set_sql_query(&mut self, table_name: &str, raw_table_name: &str) {
1674 use crate::ui::state::state_coordinator::StateCoordinator;
1675
1676 let auto_query = StateCoordinator::set_sql_query_with_refs(
1678 &mut self.state_container,
1679 &self.shadow_state,
1680 &mut self.hybrid_parser,
1681 table_name,
1682 raw_table_name,
1683 &self.config,
1684 );
1685
1686 self.set_input_text(auto_query);
1688 }
1689
1690 pub fn run(mut self) -> Result<()> {
1691 if let Err(e) = enable_raw_mode() {
1693 return Err(anyhow::anyhow!(
1694 "Failed to enable raw mode: {}. Try running with --classic flag.",
1695 e
1696 ));
1697 }
1698
1699 let mut stdout = io::stdout();
1700 if let Err(e) = execute!(stdout, EnterAlternateScreen, EnableMouseCapture) {
1701 let _ = disable_raw_mode();
1702 return Err(anyhow::anyhow!(
1703 "Failed to setup terminal: {}. Try running with --classic flag.",
1704 e
1705 ));
1706 }
1707
1708 let backend = CrosstermBackend::new(stdout);
1709 let mut terminal = match Terminal::new(backend) {
1710 Ok(t) => t,
1711 Err(e) => {
1712 let _ = disable_raw_mode();
1713 return Err(anyhow::anyhow!(
1714 "Failed to create terminal: {}. Try running with --classic flag.",
1715 e
1716 ));
1717 }
1718 };
1719
1720 let res = self.run_app(&mut terminal);
1721
1722 let _ = disable_raw_mode();
1724 let _ = execute!(
1725 terminal.backend_mut(),
1726 LeaveAlternateScreen,
1727 DisableMouseCapture
1728 );
1729 let _ = terminal.show_cursor();
1730
1731 match res {
1732 Ok(_) => Ok(()),
1733 Err(e) => Err(anyhow::anyhow!("TUI error: {}", e)),
1734 }
1735 }
1736
1737 fn initialize_viewport<B: Backend>(&mut self, terminal: &mut Terminal<B>) -> Result<()> {
1739 self.update_viewport_size();
1740 info!(target: "navigation", "Initial viewport size update completed");
1741 terminal.draw(|f| self.ui(f))?;
1742 Ok(())
1743 }
1744
1745 fn try_handle_debounced_actions<B: Backend>(
1747 &mut self,
1748 terminal: &mut Terminal<B>,
1749 ) -> Result<bool> {
1750 if !self.search_modes_widget.is_active() {
1751 return Ok(false);
1752 }
1753
1754 if let Some(action) = self.search_modes_widget.check_debounce() {
1755 let mut needs_redraw = false;
1756 match action {
1757 SearchModesAction::ExecuteDebounced(mode, pattern) => {
1758 info!(target: "search", "=== DEBOUNCED SEARCH EXECUTING ===");
1759 info!(target: "search", "Mode: {:?}, Pattern: '{}', AppMode: {:?}",
1760 mode, pattern, self.shadow_state.borrow().get_mode());
1761
1762 {
1764 let nav = self.state_container.navigation();
1765 info!(target: "search", "BEFORE: nav.selected_row={}, nav.selected_column={}",
1766 nav.selected_row, nav.selected_column);
1767 info!(target: "search", "BEFORE: buffer.selected_row={:?}, buffer.current_column={}",
1768 self.state_container.get_buffer_selected_row(), self.state_container.get_current_column());
1769 }
1770
1771 self.execute_search_action(mode, pattern);
1772
1773 {
1775 let nav = self.state_container.navigation();
1776 info!(target: "search", "AFTER: nav.selected_row={}, nav.selected_column={}",
1777 nav.selected_row, nav.selected_column);
1778 info!(target: "search", "AFTER: buffer.selected_row={:?}, buffer.current_column={}",
1779 self.state_container.get_buffer_selected_row(), self.state_container.get_current_column());
1780
1781 let viewport_manager = self.viewport_manager.borrow();
1783 if let Some(ref vm) = *viewport_manager {
1784 info!(target: "search", "AFTER: ViewportManager crosshair=({}, {})",
1785 vm.get_crosshair_row(), vm.get_crosshair_col());
1786 }
1787 }
1788
1789 info!(target: "search", "=== FORCING REDRAW ===");
1790 needs_redraw = true;
1792 }
1793 _ => {}
1794 }
1795
1796 if needs_redraw || self.table_widget_manager.borrow().needs_render() {
1798 info!(target: "search", "Triggering redraw: needs_redraw={}, table_needs_render={}",
1799 needs_redraw, self.table_widget_manager.borrow().needs_render());
1800 terminal.draw(|f| self.ui(f))?;
1801 self.table_widget_manager.borrow_mut().rendered();
1802 }
1803 }
1804 Ok(false)
1805 }
1806
1807 fn try_handle_chord_processing(&mut self, key: crossterm::event::KeyEvent) -> Result<bool> {
1809 if self
1812 .vim_search_adapter
1813 .borrow()
1814 .should_handle_key(&self.state_container)
1815 {
1816 let handled = self
1817 .vim_search_adapter
1818 .borrow_mut()
1819 .handle_key(key.code, &mut self.state_container);
1820 if handled {
1821 debug!("VimSearchAdapter handled key: {:?}", key.code);
1822 return Ok(false); }
1824 }
1825
1826 if let Some(result) = self.try_handle_buffer_operations(&key)? {
1828 return Ok(result);
1829 }
1830
1831 let chord_result = self.key_chord_handler.process_key(key);
1832 debug!("Chord handler returned: {:?}", chord_result);
1833
1834 match chord_result {
1835 ChordResult::CompleteChord(action) => {
1836 debug!("Chord completed: {:?}", action);
1838 self.key_sequence_renderer.clear_chord_mode();
1840
1841 let current_mode = self.shadow_state.borrow().get_mode();
1843
1844 self.try_handle_action(
1846 action,
1847 &ActionContext {
1848 mode: current_mode,
1849 selection_mode: self.state_container.get_selection_mode(),
1850 has_results: self.state_container.get_buffer_dataview().is_some(),
1851 has_filter: false,
1852 has_search: false,
1853 row_count: self.get_row_count(),
1854 column_count: self.get_column_count(),
1855 current_row: self.state_container.get_table_selected_row().unwrap_or(0),
1856 current_column: self.state_container.get_current_column(),
1857 },
1858 )?;
1859 Ok(false)
1860 }
1861 ChordResult::PartialChord(description) => {
1862 self.state_container.set_status_message(description.clone());
1864 if description.contains("y=row") {
1867 self.key_sequence_renderer
1868 .set_chord_mode(Some("y(a,c,q,r,v)".to_string()));
1869 } else {
1870 self.key_sequence_renderer
1871 .set_chord_mode(Some(description.clone()));
1872 }
1873 Ok(false) }
1875 ChordResult::Cancelled => {
1876 self.state_container
1877 .set_status_message("Chord cancelled".to_string());
1878 self.key_sequence_renderer.clear_chord_mode();
1880 Ok(false)
1881 }
1882 ChordResult::SingleKey(single_key) => {
1883 self.handle_results_input(single_key)
1885 }
1886 }
1887 }
1888
1889 fn try_handle_mode_dispatch(&mut self, key: crossterm::event::KeyEvent) -> Result<bool> {
1891 let mode = self.shadow_state.borrow().get_mode();
1892 debug!(
1893 "try_handle_mode_dispatch: mode={:?}, key={:?}",
1894 mode, key.code
1895 );
1896 match mode {
1897 AppMode::Command => self.handle_command_input(key),
1898 AppMode::Results => {
1899 self.try_handle_chord_processing(key)
1901 }
1902 AppMode::Search | AppMode::Filter | AppMode::FuzzyFilter | AppMode::ColumnSearch => {
1903 self.handle_search_modes_input(key)
1904 }
1905 AppMode::Help => self.handle_help_input(key),
1906 AppMode::History => self.handle_history_input(key),
1907 AppMode::Debug => self.handle_debug_input(key),
1908 AppMode::PrettyQuery => self.handle_pretty_query_input(key),
1909 AppMode::JumpToRow => self.handle_jump_to_row_input(key),
1910 AppMode::ColumnStats => self.handle_column_stats_input(key),
1911 }
1912 }
1913
1914 fn try_handle_key_event<B: Backend>(
1916 &mut self,
1917 terminal: &mut Terminal<B>,
1918 key: crossterm::event::KeyEvent,
1919 ) -> Result<bool> {
1920 if key.kind != crossterm::event::KeyEventKind::Press {
1923 return Ok(false);
1924 }
1925
1926 if key.code == KeyCode::Char('c') && key.modifiers.contains(KeyModifiers::CONTROL) {
1929 info!(target: "app", "Ctrl-C detected, forcing exit");
1930 return Ok(true);
1931 }
1932
1933 let key_display = format_key_for_display(&key);
1935 self.key_indicator.record_key(key_display.clone());
1936 self.key_sequence_renderer.record_key(key_display);
1937
1938 let should_exit = self.try_handle_mode_dispatch(key)?;
1940
1941 if should_exit {
1942 return Ok(true);
1943 }
1944
1945 if self.table_widget_manager.borrow().needs_render() {
1947 info!("TableWidgetManager needs render after key event");
1948 }
1949 terminal.draw(|f| self.ui(f))?;
1950 self.table_widget_manager.borrow_mut().rendered();
1951
1952 Ok(false)
1953 }
1954
1955 fn try_handle_events<B: Backend>(&mut self, terminal: &mut Terminal<B>) -> Result<bool> {
1957 if event::poll(std::time::Duration::from_millis(50))? {
1959 match event::read()? {
1960 Event::Key(key) => {
1961 if self.try_handle_key_event(terminal, key)? {
1962 return Ok(true);
1963 }
1964 }
1965 _ => {
1966 }
1968 }
1969 } else {
1970 if self.search_modes_widget.is_active()
1972 || self.table_widget_manager.borrow().needs_render()
1973 {
1974 if self.table_widget_manager.borrow().needs_render() {
1975 info!("TableWidgetManager needs periodic render");
1976 }
1977 terminal.draw(|f| self.ui(f))?;
1978 self.table_widget_manager.borrow_mut().rendered();
1979 }
1980 }
1981 Ok(false)
1982 }
1983
1984 fn run_app<B: Backend>(&mut self, terminal: &mut Terminal<B>) -> Result<()> {
1985 self.initialize_viewport(terminal)?;
1986
1987 loop {
1988 if self.try_handle_debounced_actions(terminal)? {
1990 break;
1991 }
1992
1993 if self.try_handle_events(terminal)? {
1995 break;
1996 }
1997 }
1998 Ok(())
1999 }
2000
2001 fn handle_command_input(&mut self, key: crossterm::event::KeyEvent) -> Result<bool> {
2002 let normalized_key = self.normalize_and_log_key(key);
2004
2005 let is_special_combo = if let KeyCode::Char(c) = normalized_key.code {
2012 (normalized_key.modifiers.contains(KeyModifiers::CONTROL) && matches!(c,
2014 'x' | 'X' | 'p' | 'P' | 'n' | 'N' | 'r' | 'R' | 'j' | 'J' | 'o' | 'O' | 'b' | 'B' | 'l' | 'L' )) ||
2023 (normalized_key.modifiers.contains(KeyModifiers::ALT) && matches!(c,
2025 'x' | 'X' ))
2027 } else {
2028 false
2029 };
2030
2031 let should_try_command_editor = !is_special_combo
2032 && matches!(
2033 normalized_key.code,
2034 KeyCode::Char(_)
2035 | KeyCode::Backspace
2036 | KeyCode::Delete
2037 | KeyCode::Left
2038 | KeyCode::Right
2039 | KeyCode::Home
2040 | KeyCode::End
2041 );
2042
2043 if should_try_command_editor {
2044 let before_text = self.input.value().to_string();
2047 let before_cursor = self.input.cursor();
2048
2049 if self.command_editor.get_text() != before_text {
2050 self.command_editor.set_text(before_text.clone());
2051 }
2052 if self.command_editor.get_cursor() != before_cursor {
2053 self.command_editor.set_cursor(before_cursor);
2054 }
2055
2056 let result = self.command_editor.handle_input(
2058 normalized_key.clone(),
2059 &mut self.state_container,
2060 &self.shadow_state,
2061 )?;
2062
2063 let new_text = self.command_editor.get_text();
2066 let new_cursor = self.command_editor.get_cursor();
2067
2068 if new_text != before_text || new_cursor != before_cursor {
2070 debug!(
2071 "CommandEditor changed input: '{}' -> '{}', cursor: {} -> {}",
2072 before_text, new_text, before_cursor, new_cursor
2073 );
2074 }
2075
2076 self.input = tui_input::Input::from(new_text.clone()).with_cursor(new_cursor);
2078
2079 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
2082 buffer.set_input_text(new_text.clone());
2083 buffer.set_input_cursor_position(new_cursor);
2084 }
2085
2086 self.state_container.set_input_text(new_text);
2088 self.state_container.set_input_cursor_position(new_cursor);
2089
2090 if result {
2091 return Ok(true);
2092 }
2093
2094 return Ok(false);
2096 }
2097 if let Some(result) = self.try_action_system(normalized_key.clone())? {
2101 return Ok(result);
2102 }
2103
2104 if let Some(result) = self.try_editor_widget(normalized_key.clone())? {
2106 return Ok(result);
2107 }
2108
2109 if let Some(result) = self.try_handle_history_navigation(&normalized_key)? {
2113 return Ok(result);
2114 }
2115
2116 let old_cursor = self.get_input_cursor();
2118
2119 trace!(target: "input", "Key: {:?} Modifiers: {:?}", key.code, key.modifiers);
2121
2122 if let Some(result) = self.try_handle_buffer_operations(&key)? {
2127 return Ok(result);
2128 }
2129
2130 if let Some(result) = self.try_handle_function_keys(&key)? {
2132 return Ok(result);
2133 }
2134
2135 if let Some(result) = self.try_handle_text_editing(&key)? {
2137 return Ok(result);
2138 }
2139
2140 if let Some(result) = self.try_handle_mode_transitions(&key, old_cursor)? {
2142 return Ok(result);
2143 }
2144
2145 Ok(false)
2149 }
2150
2151 fn normalize_and_log_key(
2156 &mut self,
2157 key: crossterm::event::KeyEvent,
2158 ) -> crossterm::event::KeyEvent {
2159 let normalized = self.state_container.normalize_key(key);
2160
2161 let action = self
2163 .key_dispatcher
2164 .get_command_action(&normalized)
2165 .map(|s| s.to_string());
2166
2167 if normalized != key {
2169 self.state_container
2170 .log_key_press(key, Some(format!("normalized to {:?}", normalized)));
2171 }
2172 self.state_container.log_key_press(normalized, action);
2173
2174 normalized
2175 }
2176
2177 fn try_action_system(
2179 &mut self,
2180 normalized_key: crossterm::event::KeyEvent,
2181 ) -> Result<Option<bool>> {
2182 let action_context = self.build_action_context();
2183 if let Some(action) = self
2184 .key_mapper
2185 .map_key(normalized_key.clone(), &action_context)
2186 {
2187 info!(
2188 "✓ Action system (Command): key {:?} -> action {:?}",
2189 normalized_key.code, action
2190 );
2191 if let Ok(result) = self.try_handle_action(action, &action_context) {
2192 match result {
2193 ActionResult::Handled => {
2194 debug!("Action handled by new system in Command mode");
2195 return Ok(Some(false));
2196 }
2197 ActionResult::Exit => {
2198 return Ok(Some(true));
2199 }
2200 ActionResult::NotHandled => {
2201 }
2203 _ => {}
2204 }
2205 }
2206 }
2207 Ok(None)
2208 }
2209
2210 fn try_editor_widget(
2212 &mut self,
2213 normalized_key: crossterm::event::KeyEvent,
2214 ) -> Result<Option<bool>> {
2215 let key_dispatcher = self.key_dispatcher.clone();
2216 let editor_result = if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
2217 self.editor_widget
2218 .handle_key(normalized_key.clone(), &key_dispatcher, buffer)?
2219 } else {
2220 EditorAction::PassToMainApp(normalized_key.clone())
2221 };
2222
2223 match editor_result {
2224 EditorAction::Quit => return Ok(Some(true)),
2225 EditorAction::ExecuteQuery => {
2226 return self.handle_execute_query().map(Some);
2227 }
2228 EditorAction::BufferAction(buffer_action) => {
2229 return self.handle_buffer_action(buffer_action).map(Some);
2230 }
2231 EditorAction::ExpandAsterisk => {
2232 return self.handle_expand_asterisk().map(Some);
2233 }
2234 EditorAction::ShowHelp => {
2235 self.state_container.set_help_visible(true);
2236 self.set_mode_via_shadow_state(AppMode::Help, "help_requested");
2238 return Ok(Some(false));
2239 }
2240 EditorAction::ShowDebug => {
2241 return Ok(Some(false));
2243 }
2244 EditorAction::ShowPrettyQuery => {
2245 self.show_pretty_query();
2246 return Ok(Some(false));
2247 }
2248 EditorAction::SwitchMode(mode) => {
2249 self.handle_editor_mode_switch(mode);
2250 return Ok(Some(false));
2251 }
2252 EditorAction::PassToMainApp(_) => {
2253 }
2255 EditorAction::Continue => return Ok(Some(false)),
2256 }
2257
2258 Ok(None)
2259 }
2260
2261 fn handle_editor_mode_switch(&mut self, mode: AppMode) {
2263 debug!(target: "shadow_state", "EditorAction::SwitchMode to {:?}", mode);
2264 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
2265 let trigger = match mode {
2267 AppMode::Results => "enter_results_mode",
2268 AppMode::Command => "enter_command_mode",
2269 AppMode::History => "enter_history_mode",
2270 _ => "switch_mode",
2271 };
2272 debug!(target: "shadow_state", "Setting mode via shadow state to {:?} with trigger {}", mode, trigger);
2273 self.shadow_state
2274 .borrow_mut()
2275 .set_mode(mode.clone(), buffer, trigger);
2276 } else {
2277 debug!(target: "shadow_state", "No buffer available for mode switch!");
2278 }
2279
2280 if mode == AppMode::History {
2282 eprintln!("[DEBUG] Using AppStateContainer for history search");
2283 let current_input = self.get_input_text();
2284
2285 self.state_container.start_history_search(current_input);
2287
2288 self.update_history_matches_in_container();
2290
2291 let match_count = self.state_container.history_search().matches.len();
2293
2294 self.state_container
2295 .set_status_message(format!("History search: {} matches", match_count));
2296 }
2297 }
2298
2299 fn try_handle_function_keys(
2301 &mut self,
2302 key: &crossterm::event::KeyEvent,
2303 ) -> Result<Option<bool>> {
2304 match key.code {
2305 KeyCode::F(1) | KeyCode::Char('?') => {
2306 if self.shadow_state.borrow().is_in_help_mode() {
2308 let mode = if self.state_container.has_dataview() {
2310 AppMode::Results
2311 } else {
2312 AppMode::Command
2313 };
2314 self.set_mode_via_shadow_state(mode, "exit_help");
2316 self.state_container.set_help_visible(false);
2317 self.help_widget.on_exit();
2318 } else {
2319 self.state_container.set_help_visible(true);
2321 self.set_mode_via_shadow_state(AppMode::Help, "help_requested");
2323 self.help_widget.on_enter();
2324 }
2325 Ok(Some(false))
2326 }
2327 KeyCode::F(3) => {
2328 self.show_pretty_query();
2330 Ok(Some(false))
2331 }
2332 KeyCode::F(5) => {
2333 self.toggle_debug_mode();
2335 Ok(Some(false))
2336 }
2337 KeyCode::F(6) => {
2338 let current = self.state_container.is_show_row_numbers();
2340 self.state_container.set_show_row_numbers(!current);
2341 self.state_container.set_status_message(format!(
2342 "Row numbers: {}",
2343 if !current { "ON" } else { "OFF" }
2344 ));
2345 Ok(Some(false))
2346 }
2347 KeyCode::F(7) => {
2348 let current_mode = self.state_container.is_compact_mode();
2350 self.state_container.set_compact_mode(!current_mode);
2351 let message = if !current_mode {
2352 "Compact mode enabled"
2353 } else {
2354 "Compact mode disabled"
2355 };
2356 self.state_container.set_status_message(message.to_string());
2357 Ok(Some(false))
2358 }
2359 KeyCode::F(8) => {
2360 let current = self.state_container.is_case_insensitive();
2362 self.state_container.set_case_insensitive(!current);
2363 self.state_container.set_status_message(format!(
2364 "Case-insensitive string comparisons: {}",
2365 if !current { "ON" } else { "OFF" }
2366 ));
2367 Ok(Some(false))
2368 }
2369 KeyCode::F(9) => {
2370 use crate::ui::traits::input_ops::InputBehavior;
2372 InputBehavior::kill_line(self);
2373 let message = if !self.state_container.is_kill_ring_empty() {
2374 format!(
2375 "Killed to end of line ('{}' saved to kill ring)",
2376 self.state_container.get_kill_ring()
2377 )
2378 } else {
2379 "Killed to end of line".to_string()
2380 };
2381 self.state_container.set_status_message(message);
2382 Ok(Some(false))
2383 }
2384 KeyCode::F(10) => {
2385 use crate::ui::traits::input_ops::InputBehavior;
2387 InputBehavior::kill_line_backward(self);
2388 let message = if !self.state_container.is_kill_ring_empty() {
2389 format!(
2390 "Killed to beginning of line ('{}' saved to kill ring)",
2391 self.state_container.get_kill_ring()
2392 )
2393 } else {
2394 "Killed to beginning of line".to_string()
2395 };
2396 self.state_container.set_status_message(message);
2397 Ok(Some(false))
2398 }
2399 KeyCode::F(12) => {
2400 let enabled = !self.key_indicator.enabled;
2402 self.key_indicator.set_enabled(enabled);
2403 self.key_sequence_renderer.set_enabled(enabled);
2404 self.state_container.set_status_message(format!(
2405 "Key press indicator {}",
2406 if enabled { "enabled" } else { "disabled" }
2407 ));
2408 Ok(Some(false))
2409 }
2410 _ => Ok(None), }
2412 }
2413
2414 fn try_handle_buffer_operations(
2416 &mut self,
2417 key: &crossterm::event::KeyEvent,
2418 ) -> Result<Option<bool>> {
2419 if let Some(action) = self.key_dispatcher.get_command_action(key) {
2420 match action {
2421 "quit" => return Ok(Some(true)),
2422 "next_buffer" => {
2423 self.save_viewport_to_current_buffer();
2425
2426 let message = self
2427 .buffer_handler
2428 .next_buffer(self.state_container.buffers_mut());
2429 debug!("{}", message);
2430
2431 self.sync_after_buffer_switch();
2433 return Ok(Some(false));
2434 }
2435 "previous_buffer" => {
2436 self.save_viewport_to_current_buffer();
2438
2439 let message = self
2440 .buffer_handler
2441 .previous_buffer(self.state_container.buffers_mut());
2442 debug!("{}", message);
2443
2444 self.sync_after_buffer_switch();
2446 return Ok(Some(false));
2447 }
2448 "quick_switch_buffer" => {
2449 self.save_viewport_to_current_buffer();
2451
2452 let message = self
2453 .buffer_handler
2454 .quick_switch(self.state_container.buffers_mut());
2455 debug!("{}", message);
2456
2457 self.sync_after_buffer_switch();
2459
2460 return Ok(Some(false));
2461 }
2462 "new_buffer" => {
2463 let message = self
2464 .buffer_handler
2465 .new_buffer(self.state_container.buffers_mut(), &self.config);
2466 debug!("{}", message);
2467 return Ok(Some(false));
2468 }
2469 "close_buffer" => {
2470 let (success, message) = self
2471 .buffer_handler
2472 .close_buffer(self.state_container.buffers_mut());
2473 debug!("{}", message);
2474 return Ok(Some(!success)); }
2476 "list_buffers" => {
2477 let buffer_list = self
2478 .buffer_handler
2479 .list_buffers(self.state_container.buffers());
2480 for line in &buffer_list {
2481 debug!("{}", line);
2482 }
2483 return Ok(Some(false));
2484 }
2485 action if action.starts_with("switch_to_buffer_") => {
2486 if let Some(buffer_num_str) = action.strip_prefix("switch_to_buffer_") {
2487 if let Ok(buffer_num) = buffer_num_str.parse::<usize>() {
2488 self.save_viewport_to_current_buffer();
2490
2491 let message = self.buffer_handler.switch_to_buffer(
2492 self.state_container.buffers_mut(),
2493 buffer_num - 1,
2494 );
2495 debug!("{}", message);
2496
2497 self.sync_after_buffer_switch();
2499 }
2500 }
2501 return Ok(Some(false));
2502 }
2503 _ => {} }
2505 }
2506 Ok(None)
2507 }
2508
2509 fn try_handle_history_navigation(
2511 &mut self,
2512 key: &crossterm::event::KeyEvent,
2513 ) -> Result<Option<bool>> {
2514 if let KeyCode::Char('r') = key.code {
2516 if key.modifiers.contains(KeyModifiers::CONTROL) {
2517 let current_input = self.get_input_text();
2519
2520 self.state_container.start_history_search(current_input);
2522
2523 self.update_history_matches_in_container();
2525
2526 let match_count = self.state_container.history_search().matches.len();
2528
2529 self.state_container.set_mode(AppMode::History);
2530 self.shadow_state
2531 .borrow_mut()
2532 .observe_mode_change(AppMode::History, "history_search_started");
2533 self.state_container.set_status_message(format!(
2534 "History search started (Ctrl+R) - {} matches",
2535 match_count
2536 ));
2537 return Ok(Some(false));
2538 }
2539 }
2540
2541 if let KeyCode::Char('p') = key.code {
2543 if key.modifiers.contains(KeyModifiers::CONTROL) {
2544 let history_entries = self
2545 .state_container
2546 .command_history()
2547 .get_navigation_entries();
2548 let history_commands: Vec<String> =
2549 history_entries.iter().map(|e| e.command.clone()).collect();
2550
2551 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
2552 if buffer.navigate_history_up(&history_commands) {
2553 self.sync_all_input_states();
2554 self.state_container
2555 .set_status_message("Previous command from history".to_string());
2556 }
2557 }
2558 return Ok(Some(false));
2559 }
2560 }
2561
2562 if let KeyCode::Char('n') = key.code {
2564 if key.modifiers.contains(KeyModifiers::CONTROL) {
2565 let history_entries = self
2566 .state_container
2567 .command_history()
2568 .get_navigation_entries();
2569 let history_commands: Vec<String> =
2570 history_entries.iter().map(|e| e.command.clone()).collect();
2571
2572 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
2573 if buffer.navigate_history_down(&history_commands) {
2574 self.sync_all_input_states();
2575 self.state_container
2576 .set_status_message("Next command from history".to_string());
2577 }
2578 }
2579 return Ok(Some(false));
2580 }
2581 }
2582
2583 match key.code {
2585 KeyCode::Up if key.modifiers.contains(KeyModifiers::ALT) => {
2586 let history_entries = self
2587 .state_container
2588 .command_history()
2589 .get_navigation_entries();
2590 let history_commands: Vec<String> =
2591 history_entries.iter().map(|e| e.command.clone()).collect();
2592
2593 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
2594 if buffer.navigate_history_up(&history_commands) {
2595 self.sync_all_input_states();
2596 self.state_container
2597 .set_status_message("Previous command (Alt+Up)".to_string());
2598 }
2599 }
2600 Ok(Some(false))
2601 }
2602 KeyCode::Down if key.modifiers.contains(KeyModifiers::ALT) => {
2603 let history_entries = self
2604 .state_container
2605 .command_history()
2606 .get_navigation_entries();
2607 let history_commands: Vec<String> =
2608 history_entries.iter().map(|e| e.command.clone()).collect();
2609
2610 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
2611 if buffer.navigate_history_down(&history_commands) {
2612 self.sync_all_input_states();
2613 self.state_container
2614 .set_status_message("Next command (Alt+Down)".to_string());
2615 }
2616 }
2617 Ok(Some(false))
2618 }
2619 _ => Ok(None),
2620 }
2621 }
2622
2623 fn try_handle_text_editing(
2625 &mut self,
2626 key: &crossterm::event::KeyEvent,
2627 ) -> Result<Option<bool>> {
2628 if let Some(action) = self.key_dispatcher.get_command_action(key) {
2630 match action {
2631 "expand_asterisk" => {
2632 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
2633 if buffer.expand_asterisk(&self.hybrid_parser) {
2634 if buffer.get_edit_mode() == EditMode::SingleLine {
2636 let text = buffer.get_input_text();
2637 let cursor = buffer.get_input_cursor_position();
2638 self.set_input_text_with_cursor(text, cursor);
2639 }
2640 }
2641 }
2642 return Ok(Some(false));
2643 }
2644 "expand_asterisk_visible" => {
2645 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
2646 if buffer.expand_asterisk_visible() {
2647 if buffer.get_edit_mode() == EditMode::SingleLine {
2649 let text = buffer.get_input_text();
2650 let cursor = buffer.get_input_cursor_position();
2651 self.set_input_text_with_cursor(text, cursor);
2652 }
2653 }
2654 }
2655 return Ok(Some(false));
2656 }
2657 "delete_word_backward" => {
2659 use crate::ui::traits::input_ops::InputBehavior;
2660 InputBehavior::delete_word_backward(self);
2661 return Ok(Some(false));
2662 }
2663 "delete_word_forward" => {
2665 use crate::ui::traits::input_ops::InputBehavior;
2666 InputBehavior::delete_word_forward(self);
2667 return Ok(Some(false));
2668 }
2669 "kill_line" => {
2671 use crate::ui::traits::input_ops::InputBehavior;
2672 InputBehavior::kill_line(self);
2673 return Ok(Some(false));
2674 }
2675 "kill_line_backward" => {
2677 use crate::ui::traits::input_ops::InputBehavior;
2678 InputBehavior::kill_line_backward(self);
2679 return Ok(Some(false));
2680 }
2681 "move_word_backward" => {
2683 self.move_cursor_word_backward();
2684 return Ok(Some(false));
2685 }
2686 "move_word_forward" => {
2688 self.move_cursor_word_forward();
2689 return Ok(Some(false));
2690 }
2691 _ => {} }
2693 }
2694
2695 match key.code {
2697 KeyCode::Char('k') if key.modifiers.contains(KeyModifiers::CONTROL) => {
2699 self.state_container
2701 .set_status_message("Ctrl+K pressed - killing to end of line".to_string());
2702 use crate::ui::traits::input_ops::InputBehavior;
2703 InputBehavior::kill_line(self);
2704 Ok(Some(false))
2705 }
2706 KeyCode::Char('k') if key.modifiers.contains(KeyModifiers::ALT) => {
2708 self.state_container
2710 .set_status_message("Alt+K - killing to end of line".to_string());
2711 use crate::ui::traits::input_ops::InputBehavior;
2712 InputBehavior::kill_line(self);
2713 Ok(Some(false))
2714 }
2715 KeyCode::Char('u') if key.modifiers.contains(KeyModifiers::CONTROL) => {
2717 use crate::ui::traits::input_ops::InputBehavior;
2719 InputBehavior::kill_line_backward(self);
2720 Ok(Some(false))
2721 }
2722 KeyCode::Char('v') if key.modifiers.contains(KeyModifiers::CONTROL) => {
2723 self.paste_from_clipboard();
2725 Ok(Some(false))
2726 }
2727 KeyCode::Char('y') if key.modifiers.contains(KeyModifiers::CONTROL) => {
2728 self.yank();
2730 Ok(Some(false))
2731 }
2732 KeyCode::Left if key.modifiers.contains(KeyModifiers::CONTROL) => {
2734 self.move_cursor_word_backward();
2736 Ok(Some(false))
2737 }
2738 KeyCode::Right if key.modifiers.contains(KeyModifiers::CONTROL) => {
2740 self.move_cursor_word_forward();
2742 Ok(Some(false))
2743 }
2744 KeyCode::Char('b') if key.modifiers.contains(KeyModifiers::ALT) => {
2746 self.move_cursor_word_backward();
2748 Ok(Some(false))
2749 }
2750 KeyCode::Char('f') if key.modifiers.contains(KeyModifiers::ALT) => {
2752 self.move_cursor_word_forward();
2754 Ok(Some(false))
2755 }
2756 _ => Ok(None), }
2758 }
2759
2760 fn try_handle_mode_transitions(
2762 &mut self,
2763 key: &crossterm::event::KeyEvent,
2764 old_cursor: usize,
2765 ) -> Result<Option<bool>> {
2766 match key.code {
2767 KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => {
2768 Ok(Some(true))
2770 }
2771 KeyCode::Char('d') if key.modifiers.contains(KeyModifiers::CONTROL) => {
2772 Ok(Some(true))
2774 }
2775 KeyCode::Enter => {
2776 let query = self.get_input_text().trim().to_string();
2778 debug!(target: "action", "Executing query: {}", query);
2779
2780 if !query.is_empty() {
2781 if query == ":help" {
2783 self.state_container.set_help_visible(true);
2784 self.set_mode_via_shadow_state(AppMode::Help, "help_requested");
2786 self.state_container
2787 .set_status_message("Help Mode - Press ESC to return".to_string());
2788 } else if query == ":exit" || query == ":quit" || query == ":q" {
2789 return Ok(Some(true));
2790 } else if query == ":tui" {
2791 self.state_container
2793 .set_status_message("Already in TUI mode".to_string());
2794 } else {
2795 self.state_container
2796 .set_status_message(format!("Processing query: '{}'", query));
2797 self.execute_query_v2(&query)?;
2798 }
2799 } else {
2800 self.state_container
2801 .set_status_message("Empty query - please enter a SQL command".to_string());
2802 }
2803 Ok(Some(false))
2804 }
2805 KeyCode::Tab => {
2806 self.apply_completion();
2808 Ok(Some(false))
2809 }
2810 KeyCode::Down => {
2811 debug!(target: "shadow_state", "Down arrow pressed in Command mode. has_dataview={}, edit_mode={:?}",
2812 self.state_container.has_dataview(),
2813 self.state_container.get_edit_mode());
2814
2815 if self.state_container.has_dataview()
2816 && self.state_container.get_edit_mode() == Some(EditMode::SingleLine)
2817 {
2818 debug!(target: "shadow_state", "Down arrow conditions met, switching to Results via set_mode");
2819 self.state_container.set_mode(AppMode::Results);
2821 self.shadow_state
2822 .borrow_mut()
2823 .observe_mode_change(AppMode::Results, "down_arrow_to_results");
2824 let row = self.state_container.get_last_results_row().unwrap_or(0);
2826 self.state_container.set_table_selected_row(Some(row));
2827
2828 let last_offset = self.state_container.get_last_scroll_offset();
2830 self.state_container.set_scroll_offset(last_offset);
2831 Ok(Some(false))
2832 } else {
2833 debug!(target: "shadow_state", "Down arrow conditions not met, falling through");
2834 Ok(None)
2836 }
2837 }
2838 _ => {
2839 self.handle_input_key(*key);
2841
2842 self.state_container.clear_completion();
2844
2845 self.handle_completion();
2847
2848 if self.get_input_cursor() != old_cursor {
2850 self.update_horizontal_scroll(120); }
2852
2853 Ok(Some(false))
2854 }
2855 }
2856 }
2857
2858 fn try_handle_results_navigation(
2860 &mut self,
2861 key: &crossterm::event::KeyEvent,
2862 ) -> Result<Option<bool>> {
2863 match key.code {
2864 KeyCode::PageDown | KeyCode::Char('f')
2865 if key.modifiers.contains(KeyModifiers::CONTROL) =>
2866 {
2867 NavigationBehavior::page_down(self);
2868 Ok(Some(false))
2869 }
2870 KeyCode::PageUp | KeyCode::Char('b')
2871 if key.modifiers.contains(KeyModifiers::CONTROL) =>
2872 {
2873 NavigationBehavior::page_up(self);
2874 Ok(Some(false))
2875 }
2876 _ => Ok(None), }
2878 }
2879
2880 fn try_handle_results_clipboard(
2882 &mut self,
2883 key: &crossterm::event::KeyEvent,
2884 ) -> Result<Option<bool>> {
2885 match key.code {
2886 KeyCode::Char('y') => {
2887 let selection_mode = self.get_selection_mode();
2888 debug!("'y' key pressed - selection_mode={:?}", selection_mode);
2889 match selection_mode {
2890 SelectionMode::Cell => {
2891 debug!("Yanking cell in cell selection mode");
2893 self.state_container
2894 .set_status_message("Yanking cell...".to_string());
2895 YankBehavior::yank_cell(self);
2896 }
2898 SelectionMode::Row => {
2899 debug!("'y' pressed in row mode - waiting for chord completion");
2902 self.state_container.set_status_message(
2903 "Press second key for chord: yy=row, yc=column, ya=all, yv=cell"
2904 .to_string(),
2905 );
2906 }
2907 SelectionMode::Column => {
2908 debug!("Yanking column in column selection mode");
2910 self.state_container
2911 .set_status_message("Yanking column...".to_string());
2912 YankBehavior::yank_column(self);
2913 }
2914 }
2915 Ok(Some(false))
2916 }
2917 _ => Ok(None),
2918 }
2919 }
2920
2921 fn try_handle_results_export(
2923 &mut self,
2924 key: &crossterm::event::KeyEvent,
2925 ) -> Result<Option<bool>> {
2926 match key.code {
2927 KeyCode::Char('e') if key.modifiers.contains(KeyModifiers::CONTROL) => {
2928 self.export_to_csv();
2929 Ok(Some(false))
2930 }
2931 KeyCode::Char('j') if key.modifiers.contains(KeyModifiers::CONTROL) => {
2932 self.export_to_json();
2933 Ok(Some(false))
2934 }
2935 _ => Ok(None),
2936 }
2937 }
2938
2939 fn try_handle_results_help(
2941 &mut self,
2942 key: &crossterm::event::KeyEvent,
2943 ) -> Result<Option<bool>> {
2944 match key.code {
2945 KeyCode::F(1) | KeyCode::Char('?') => {
2946 self.state_container.set_help_visible(true);
2947 self.set_mode_via_shadow_state(AppMode::Help, "help_requested");
2949 self.help_widget.on_enter();
2950 Ok(Some(false))
2951 }
2952 _ => Ok(None),
2953 }
2954 }
2955
2956 fn handle_results_input(&mut self, key: crossterm::event::KeyEvent) -> Result<bool> {
2957 debug!(
2959 "handle_results_input: Processing key {:?} in Results mode",
2960 key
2961 );
2962
2963 let is_vim_navigating = self.vim_search_adapter.borrow().is_navigating();
2965 let vim_is_active = self.vim_search_adapter.borrow().is_active();
2966 let has_search_pattern = !self.state_container.get_search_pattern().is_empty();
2967
2968 debug!(
2969 "Search state check: vim_navigating={}, vim_active={}, has_pattern={}, pattern='{}'",
2970 is_vim_navigating,
2971 vim_is_active,
2972 has_search_pattern,
2973 self.state_container.get_search_pattern()
2974 );
2975
2976 if key.code == KeyCode::Esc {
2978 info!("ESCAPE KEY DETECTED in Results mode!");
2979
2980 if is_vim_navigating || vim_is_active || has_search_pattern {
2981 info!("Escape pressed with active search - clearing via StateCoordinator");
2982 debug!(
2983 "Pre-clear state: vim_navigating={}, vim_active={}, pattern='{}'",
2984 is_vim_navigating,
2985 vim_is_active,
2986 self.state_container.get_search_pattern()
2987 );
2988
2989 use crate::ui::state::state_coordinator::StateCoordinator;
2991 StateCoordinator::cancel_search_with_refs(
2992 &mut self.state_container,
2993 &self.shadow_state,
2994 Some(&self.vim_search_adapter),
2995 );
2996
2997 let post_pattern = self.state_container.get_search_pattern();
2999 let post_vim_active = self.vim_search_adapter.borrow().is_active();
3000 info!(
3001 "Post-clear state: pattern='{}', vim_active={}",
3002 post_pattern, post_vim_active
3003 );
3004
3005 self.state_container
3006 .set_status_message("Search cleared".to_string());
3007 return Ok(false);
3008 } else {
3009 info!("Escape pressed but no active search to clear");
3010 }
3011 }
3012
3013 let selection_mode = self.state_container.get_selection_mode();
3014
3015 debug!(
3016 "handle_results_input: key={:?}, selection_mode={:?}",
3017 key, selection_mode
3018 );
3019
3020 let normalized = self.state_container.normalize_key(key);
3022
3023 let action_context = self.build_action_context();
3025 let mapped_action = self.key_mapper.map_key(normalized, &action_context);
3026 let action = mapped_action.as_ref().map(|a| format!("{:?}", a));
3027
3028 if normalized != key {
3030 self.state_container
3031 .log_key_press(key, Some(format!("normalized to {:?}", normalized)));
3032 }
3033 self.state_container
3034 .log_key_press(normalized, action.clone());
3035
3036 let normalized_key = normalized;
3037
3038 let action_context = self.build_action_context();
3042 debug!(
3043 "Action context for key {:?}: mode={:?}",
3044 normalized_key.code, action_context.mode
3045 );
3046 if let Some(action) = self
3047 .key_mapper
3048 .map_key(normalized_key.clone(), &action_context)
3049 {
3050 info!(
3051 "✓ Action system: key {:?} -> action {:?}",
3052 normalized_key.code, action
3053 );
3054 if let Ok(result) = self.try_handle_action(action, &action_context) {
3055 match result {
3056 ActionResult::Handled => {
3057 debug!("Action handled by new system");
3058 return Ok(false);
3059 }
3060 ActionResult::Exit => {
3061 debug!("Action requested exit");
3062 return Ok(true);
3063 }
3064 ActionResult::SwitchMode(mode) => {
3065 debug!("Action requested mode switch to {:?}", mode);
3066 self.state_container.set_mode(mode);
3067 return Ok(false);
3068 }
3069 ActionResult::Error(err) => {
3070 warn!("Action error: {}", err);
3071 self.state_container
3072 .set_status_message(format!("Error: {}", err));
3073 return Ok(false);
3074 }
3075 ActionResult::NotHandled => {
3076 debug!("Action not handled, falling back to legacy system");
3078 }
3079 }
3080 }
3081 }
3082
3083 if matches!(key.code, KeyCode::Char('G')) {
3085 debug!("Detected uppercase G key press!");
3086 }
3087
3088 if mapped_action.is_none() {
3102 debug!(
3103 "No action mapping for key {:?} in Results mode",
3104 normalized_key
3105 );
3106 }
3107
3108 if let Some(result) = self.try_handle_results_navigation(&normalized_key)? {
3110 return Ok(result);
3111 }
3112
3113 if let Some(result) = self.try_handle_results_clipboard(&normalized_key)? {
3115 return Ok(result);
3116 }
3117
3118 if let Some(result) = self.try_handle_results_export(&normalized_key)? {
3120 return Ok(result);
3121 }
3122
3123 if let Some(result) = self.try_handle_results_help(&normalized_key)? {
3125 return Ok(result);
3126 }
3127
3128 Ok(false)
3133 }
3134 fn execute_search_action(&mut self, mode: SearchMode, pattern: String) {
3137 debug!(target: "search", "execute_search_action called: mode={:?}, pattern='{}', current_app_mode={:?}, thread={:?}",
3138 mode, pattern, self.shadow_state.borrow().get_mode(), std::thread::current().id());
3139 match mode {
3140 SearchMode::Search => {
3141 debug!(target: "search", "Executing search with pattern: '{}', app_mode={:?}", pattern, self.shadow_state.borrow().get_mode());
3142 debug!(target: "search", "Search: current results count={}",
3143 self.state_container.get_buffer_dataview().map(|v| v.source().row_count()).unwrap_or(0));
3144
3145 self.state_container.start_search(pattern.clone());
3147
3148 self.state_container.set_search_pattern(pattern.clone());
3149 self.perform_search();
3150 let matches_count = self.state_container.search().matches.len();
3151 debug!(target: "search", "After perform_search, app_mode={:?}, matches_found={}",
3152 self.shadow_state.borrow().get_mode(),
3153 matches_count);
3154
3155 if matches_count > 0 {
3157 let matches_for_vim: Vec<(usize, usize)> = {
3159 let search_manager = self.search_manager.borrow();
3160 search_manager
3161 .all_matches()
3162 .iter()
3163 .map(|m| (m.row, m.column))
3164 .collect()
3165 };
3166
3167 if let Some(dataview) = self.state_container.get_buffer_dataview() {
3169 info!(target: "search", "Syncing {} matches to VimSearchManager for pattern '{}'",
3170 matches_for_vim.len(), pattern);
3171 self.vim_search_adapter
3172 .borrow_mut()
3173 .set_search_state_from_external(
3174 pattern.clone(),
3175 matches_for_vim,
3176 dataview,
3177 );
3178 }
3179 }
3180
3181 if matches_count > 0 {
3183 let (row, col) = {
3185 let search_manager = self.search_manager.borrow();
3186 if let Some(first_match) = search_manager.first_match() {
3187 (first_match.row, first_match.column)
3188 } else {
3189 let search_state = self.state_container.search();
3191 if let Some((row, col, _, _)) = search_state.matches.first() {
3192 (*row, *col)
3193 } else {
3194 (0, 0)
3195 }
3196 }
3197 };
3198
3199 info!(target: "search", "NAVIGATION START: Moving to first match at data row={}, col={}", row, col);
3200
3201 self.state_container.set_table_selected_row(Some(row));
3204 self.state_container.set_selected_row(Some(row));
3205 info!(target: "search", " Set row position to {}", row);
3206
3207 {
3209 let mut nav = self.state_container.navigation_mut();
3210 nav.selected_column = col;
3211 }
3212 self.state_container.set_current_column_buffer(col);
3213 info!(target: "search", " Set column position to {}", col);
3214
3215 info!(target: "search", "Updating TableWidgetManager for debounced search to ({}, {})", row, col);
3217 self.table_widget_manager
3218 .borrow_mut()
3219 .on_debounced_search(row, col);
3220
3221 {
3223 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
3224 if let Some(ref mut viewport_manager) = *viewport_manager_borrow {
3225 let viewport_height = self.state_container.navigation().viewport_rows;
3227 let viewport_width = self.state_container.navigation().viewport_columns;
3228 let current_scroll = self.state_container.navigation().scroll_offset.0;
3229
3230 info!(target: "search", " Viewport dimensions: {}x{}, current_scroll: {}",
3231 viewport_height, viewport_width, current_scroll);
3232
3233 let new_row_offset = if row < current_scroll {
3235 info!(target: "search", " Match is above viewport, scrolling up to row {}", row);
3236 row } else if row >= current_scroll + viewport_height.saturating_sub(1) {
3238 let centered = row.saturating_sub(viewport_height / 2);
3239 info!(target: "search", " Match is below viewport, centering at row {}", centered);
3240 centered } else {
3242 info!(target: "search", " Match is already visible, keeping scroll at {}", current_scroll);
3243 current_scroll };
3245
3246 let current_col_scroll =
3248 self.state_container.navigation().scroll_offset.1;
3249 let new_col_offset = if col < current_col_scroll {
3250 info!(target: "search", " Match column {} is left of viewport (scroll={}), scrolling left", col, current_col_scroll);
3251 col } else if col >= current_col_scroll + viewport_width.saturating_sub(1) {
3253 let centered = col.saturating_sub(viewport_width / 4);
3254 info!(target: "search", " Match column {} is right of viewport (scroll={}, width={}), scrolling to {}",
3255 col, current_col_scroll, viewport_width, centered);
3256 centered } else {
3258 info!(target: "search", " Match column {} is visible, keeping scroll at {}", col, current_col_scroll);
3259 current_col_scroll };
3261
3262 viewport_manager.set_viewport(
3264 new_row_offset,
3265 new_col_offset,
3266 viewport_width as u16,
3267 viewport_height as u16,
3268 );
3269 info!(target: "search", " Set viewport to row_offset={}, col_offset={}", new_row_offset, new_col_offset);
3270
3271 let crosshair_row = row - new_row_offset;
3273 let crosshair_col = col - new_col_offset;
3274 viewport_manager.set_crosshair(crosshair_row, crosshair_col);
3275 info!(target: "search", " Set crosshair to viewport-relative ({}, {}), absolute was ({}, {})",
3276 crosshair_row, crosshair_col, row, col);
3277
3278 let mut nav = self.state_container.navigation_mut();
3280 nav.scroll_offset.0 = new_row_offset;
3281 nav.scroll_offset.1 = new_col_offset;
3282 }
3283 }
3284
3285 self.state_container.set_current_match(Some((row, col)));
3287
3288 {
3291 let mut nav = self.state_container.navigation_mut();
3292 nav.selected_row = row;
3293 nav.selected_column = col;
3294 }
3295 info!(target: "search", " Forced navigation state to row={}, col={}", row, col);
3296
3297 self.state_container.set_status_message(format!(
3299 "Match 1/{} at row {}, col {}",
3300 matches_count,
3301 row + 1,
3302 col + 1
3303 ));
3304 }
3305 }
3306 SearchMode::Filter => {
3307 use crate::ui::state::state_coordinator::StateCoordinator;
3308
3309 StateCoordinator::apply_filter_search_with_refs(
3311 &mut self.state_container,
3312 &self.shadow_state,
3313 &pattern,
3314 );
3315
3316 self.apply_filter(&pattern);
3318
3319 debug!(target: "search", "After apply_filter, filtered_count={}",
3320 self.state_container.get_buffer_dataview().map(|v| v.row_count()).unwrap_or(0));
3321 }
3322 SearchMode::FuzzyFilter => {
3323 use crate::ui::state::state_coordinator::StateCoordinator;
3324
3325 StateCoordinator::apply_fuzzy_filter_search_with_refs(
3327 &mut self.state_container,
3328 &self.shadow_state,
3329 &pattern,
3330 );
3331
3332 self.apply_fuzzy_filter();
3334
3335 let indices_count = self.state_container.get_fuzzy_filter_indices().len();
3336 debug!(target: "search", "After apply_fuzzy_filter, matched_indices={}", indices_count);
3337 }
3338 SearchMode::ColumnSearch => {
3339 use crate::ui::state::state_coordinator::StateCoordinator;
3340
3341 debug!(target: "search", "Executing column search with pattern: '{}'", pattern);
3342
3343 StateCoordinator::apply_column_search_with_refs(
3345 &mut self.state_container,
3346 &self.shadow_state,
3347 &pattern,
3348 );
3349
3350 self.search_columns();
3352
3353 debug!(target: "search", "After search_columns, app_mode={:?}", self.shadow_state.borrow().get_mode());
3354 }
3355 }
3356 }
3357
3358 fn enter_search_mode(&mut self, mode: SearchMode) {
3359 debug!(target: "search", "enter_search_mode called for {:?}, current_mode={:?}, input_text='{}'",
3360 mode, self.shadow_state.borrow().get_mode(), self.state_container.get_input_text());
3361
3362 let current_sql = if self.shadow_state.borrow().is_in_results_mode() {
3364 let last_query = self.state_container.get_last_query();
3366 let input_text = self.state_container.get_input_text();
3367 debug!(target: "search", "COLUMN_SEARCH_SAVE_DEBUG: last_query='{}', input_text='{}'", last_query, input_text);
3368
3369 if !last_query.is_empty() {
3370 debug!(target: "search", "Using last_query for search mode: '{}'", last_query);
3371 last_query
3372 } else if !input_text.is_empty() {
3373 debug!(target: "search", "No last_query, using input_text as fallback: '{}'", input_text);
3376 input_text
3377 } else {
3378 warn!(target: "search", "No last_query or input_text found when entering search mode from Results!");
3380 String::new()
3381 }
3382 } else {
3383 self.get_input_text()
3385 };
3386
3387 let cursor_pos = current_sql.len();
3388
3389 debug!(
3390 "Entering {} mode, saving SQL: '{}', cursor: {}",
3391 mode.title(),
3392 current_sql,
3393 cursor_pos
3394 );
3395
3396 self.search_modes_widget
3398 .enter_mode(mode.clone(), current_sql, cursor_pos);
3399
3400 debug!(target: "mode", "Setting app mode from {:?} to {:?}", self.shadow_state.borrow().get_mode(), mode.to_app_mode());
3402 let trigger = match mode {
3403 SearchMode::ColumnSearch => "backslash_column_search",
3404 SearchMode::Search => "data_search_started",
3405 SearchMode::FuzzyFilter => "fuzzy_filter_started",
3406 SearchMode::Filter => "filter_started",
3407 };
3408 self.sync_mode(mode.to_app_mode(), trigger);
3409
3410 let search_type = match mode {
3412 SearchMode::ColumnSearch => crate::ui::state::shadow_state::SearchType::Column,
3413 SearchMode::Search => crate::ui::state::shadow_state::SearchType::Data,
3414 SearchMode::FuzzyFilter | SearchMode::Filter => {
3415 crate::ui::state::shadow_state::SearchType::Fuzzy
3416 }
3417 };
3418 self.shadow_state
3419 .borrow_mut()
3420 .observe_search_start(search_type, trigger);
3421
3422 match mode {
3424 SearchMode::Search => {
3425 self.state_container.clear_search();
3427 self.state_container.set_search_pattern(String::new());
3428 }
3429 SearchMode::Filter => {
3430 self.state_container.set_filter_pattern(String::new());
3431 self.state_container.filter_mut().clear();
3432 }
3433 SearchMode::FuzzyFilter => {
3434 self.state_container.set_fuzzy_filter_pattern(String::new());
3435 self.state_container.set_fuzzy_filter_indices(Vec::new());
3436 self.state_container.set_fuzzy_filter_active(false);
3437 }
3438 SearchMode::ColumnSearch => {
3439 self.state_container.clear_column_search();
3441 if let Some(dataview) = self.state_container.get_buffer_dataview_mut() {
3442 dataview.clear_column_search();
3443 }
3444
3445 }
3447 }
3448
3449 self.input = tui_input::Input::default();
3451 }
3453
3454 fn handle_search_modes_input(&mut self, key: crossterm::event::KeyEvent) -> Result<bool> {
3455 if key.code == KeyCode::Char('c') && key.modifiers.contains(KeyModifiers::CONTROL) {
3457 return Ok(true); }
3459
3460 let action = self.search_modes_widget.handle_key(key);
3463
3464 match action {
3465 SearchModesAction::Continue => {
3466 }
3468 SearchModesAction::InputChanged(mode, pattern) => {
3469 self.set_input_text_with_cursor(pattern.clone(), pattern.len());
3471
3472 match mode {
3474 SearchMode::Search => {
3475 self.state_container.set_search_pattern(pattern);
3476 }
3477 SearchMode::Filter => {
3478 self.state_container.set_filter_pattern(pattern.clone());
3479 let mut filter = self.state_container.filter_mut();
3480 filter.pattern = pattern.clone();
3481 filter.is_active = true;
3482 }
3483 SearchMode::FuzzyFilter => {
3484 self.state_container.set_fuzzy_filter_pattern(pattern);
3485 }
3486 SearchMode::ColumnSearch => {
3487 }
3489 }
3490 }
3491 SearchModesAction::ExecuteDebounced(mode, pattern) => {
3492 self.execute_search_action(mode, pattern);
3495 }
3497 SearchModesAction::Apply(mode, pattern) => {
3498 debug!(target: "search", "Apply action triggered for {:?} with pattern '{}'", mode, pattern);
3499 match mode {
3501 SearchMode::Search => {
3502 debug!(target: "search", "Search Apply: Applying search with pattern '{}'", pattern);
3503 self.execute_search_action(SearchMode::Search, pattern);
3505 debug!(target: "search", "Search Apply: last_query='{}', will restore saved SQL from widget", self.state_container.get_last_query());
3506 }
3508 SearchMode::Filter => {
3509 debug!(target: "search", "Filter Apply: Applying filter with pattern '{}'", pattern);
3510 self.state_container.set_filter_pattern(pattern.clone());
3511 {
3512 let mut filter = self.state_container.filter_mut();
3513 filter.pattern = pattern.clone();
3514 filter.is_active = true;
3515 } self.apply_filter(&pattern); debug!(target: "search", "Filter Apply: last_query='{}', will restore saved SQL from widget", self.state_container.get_last_query());
3518 }
3519 SearchMode::FuzzyFilter => {
3520 debug!(target: "search", "FuzzyFilter Apply: Applying filter with pattern '{}'", pattern);
3521 self.state_container.set_fuzzy_filter_pattern(pattern);
3522 self.apply_fuzzy_filter();
3523 debug!(target: "search", "FuzzyFilter Apply: last_query='{}', will restore saved SQL from widget", self.state_container.get_last_query());
3524 }
3525 SearchMode::ColumnSearch => {
3526 let column_info = {
3529 let column_search = self.state_container.column_search();
3530 if !column_search.matching_columns.is_empty() {
3531 let current_match = column_search.current_match;
3532 Some(column_search.matching_columns[current_match].clone())
3533 } else {
3534 None
3535 }
3536 };
3537
3538 if let Some((col_idx, col_name)) = column_info {
3539 self.state_container.set_current_column(col_idx);
3540 self.state_container.set_current_column_buffer(col_idx);
3541
3542 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
3544 if let Some(viewport_manager) = viewport_manager_borrow.as_mut() {
3545 viewport_manager.set_current_column(col_idx);
3546 }
3547 drop(viewport_manager_borrow);
3548
3549 debug!(target: "column_search_sync", "ColumnSearch Apply: About to call sync_navigation_with_viewport() for column: {}", col_name);
3552 debug!(target: "column_search_sync", "ColumnSearch Apply: Pre-sync - viewport current_column: {}",
3553 if let Some(vm) = self.viewport_manager.try_borrow().ok() {
3554 vm.as_ref().map(|v| v.get_crosshair_col()).unwrap_or(0)
3555 } else { 0 });
3556 self.sync_navigation_with_viewport();
3557 debug!(target: "column_search_sync", "ColumnSearch Apply: Post-sync - navigation current_column: {}",
3558 self.state_container.navigation().selected_column);
3559 debug!(target: "column_search_sync", "ColumnSearch Apply: sync_navigation_with_viewport() completed for column: {}", col_name);
3560
3561 self.state_container
3562 .set_status_message(format!("Jumped to column: {}", col_name));
3563 }
3564
3565 debug!(target: "search", "ColumnSearch Apply: Exiting without modifying input_text");
3568 debug!(target: "search", "ColumnSearch Apply: last_query='{}', will restore saved SQL from widget", self.state_container.get_last_query());
3569 }
3571 }
3572
3573 let saved_state = self.search_modes_widget.exit_mode();
3576
3577 if let Some((sql, cursor)) = saved_state {
3578 debug!(target: "search", "Exiting search mode. Original SQL was: '{}', cursor: {}", sql, cursor);
3579 debug!(target: "buffer", "Returning to Results mode, preserving last_query: '{}'",
3580 self.state_container.get_last_query());
3581
3582 debug!(target: "search", "Restoring saved SQL to input_text: '{}'", sql);
3585 self.set_input_text_with_cursor(sql, cursor);
3587 } else {
3588 if mode == SearchMode::ColumnSearch {
3590 let last_query = self.state_container.get_last_query();
3592 if !last_query.is_empty() {
3593 debug!(target: "search", "Column search: No saved state, restoring last_query: '{}'", last_query);
3594 self.set_input_text(last_query);
3595 } else {
3596 debug!(target: "search", "Column search: No saved state or last_query, clearing input");
3597 self.set_input_text(String::new());
3598 }
3599 } else {
3600 debug!(target: "search", "No saved state from widget, keeping current SQL");
3601 }
3602 }
3603
3604 use crate::ui::state::state_coordinator::StateCoordinator;
3606
3607 if mode == SearchMode::ColumnSearch {
3610 debug!(target: "column_search_sync", "ColumnSearch Apply: Canceling column search completely with cancel_search_with_refs()");
3611
3612 if let Some(dataview) = self.state_container.get_buffer_dataview_mut() {
3614 dataview.clear_column_search();
3615 debug!(target: "column_search_sync", "ColumnSearch Apply: Cleared column search in DataView");
3616 }
3617
3618 StateCoordinator::cancel_search_with_refs(
3619 &mut self.state_container,
3620 &self.shadow_state,
3621 Some(&self.vim_search_adapter),
3622 );
3623 debug!(target: "column_search_sync", "ColumnSearch Apply: Column search canceled and mode switched to Results");
3625 } else {
3626 debug!(target: "column_search_sync", "Search Apply: About to call StateCoordinator::complete_search_with_refs() for mode: {:?}", mode);
3628 StateCoordinator::complete_search_with_refs(
3629 &mut self.state_container,
3630 &self.shadow_state,
3631 Some(&self.vim_search_adapter),
3632 AppMode::Results,
3633 "search_applied",
3634 );
3635 debug!(target: "column_search_sync", "Search Apply: StateCoordinator::complete_search_with_refs() completed - should now be in Results mode");
3636 }
3637
3638 let filter_msg = match mode {
3640 SearchMode::FuzzyFilter => {
3641 let query = self.state_container.get_last_query();
3642 format!(
3643 "Fuzzy filter applied. Query: '{}'. Press 'f' again to modify.",
3644 if query.len() > 30 {
3645 format!("{}...", &query[..30])
3646 } else {
3647 query
3648 }
3649 )
3650 }
3651 SearchMode::Filter => "Filter applied. Press 'F' again to modify.".to_string(),
3652 SearchMode::Search => {
3653 let matches = self.state_container.search().matches.len();
3654 if matches > 0 {
3655 format!("Found {} matches. Use n/N to navigate.", matches)
3656 } else {
3657 "No matches found.".to_string()
3658 }
3659 }
3660 SearchMode::ColumnSearch => "Column search complete.".to_string(),
3661 };
3662 self.state_container.set_status_message(filter_msg);
3663 }
3664 SearchModesAction::Cancel => {
3665 let mode = self.shadow_state.borrow().get_mode();
3667 match mode {
3668 AppMode::FuzzyFilter => {
3669 debug!(target: "search", "FuzzyFilter Cancel: Clearing fuzzy filter");
3671 self.state_container.set_fuzzy_filter_pattern(String::new());
3672 self.apply_fuzzy_filter(); self.state_container.set_fuzzy_filter_indices(Vec::new());
3674 self.state_container.set_fuzzy_filter_active(false);
3675 }
3676 AppMode::Filter => {
3677 debug!(target: "search", "Filter Cancel: Clearing filter pattern and state");
3679 self.state_container.filter_mut().clear();
3680 self.state_container.set_filter_pattern(String::new());
3681 self.state_container.set_filter_active(false);
3682 self.apply_filter("");
3684 }
3685 AppMode::ColumnSearch => {
3686 self.state_container.clear_column_search();
3688 debug!(target: "search", "ColumnSearch Cancel: Exiting without modifying input_text");
3690 debug!(target: "search", "ColumnSearch Cancel: last_query='{}', will restore saved SQL from widget", self.state_container.get_last_query());
3691 }
3692 _ => {}
3693 }
3694
3695 if let Some((sql, cursor)) = self.search_modes_widget.exit_mode() {
3697 debug!(target: "search", "Cancel: Restoring saved SQL: '{}', cursor: {}", sql, cursor);
3698 if !sql.is_empty() {
3699 self.set_input_text_with_cursor(sql, cursor);
3701 }
3702 } else {
3703 debug!(target: "search", "Cancel: No saved SQL from widget");
3704 }
3705
3706 use crate::ui::state::state_coordinator::StateCoordinator;
3709 StateCoordinator::cancel_search_with_refs(
3710 &mut self.state_container,
3711 &self.shadow_state,
3712 Some(&self.vim_search_adapter),
3713 );
3714 }
3715 SearchModesAction::NextMatch => {
3716 debug!(target: "search", "NextMatch action, current_mode={:?}, widget_mode={:?}",
3717 self.shadow_state.borrow().get_mode(), self.search_modes_widget.current_mode());
3718
3719 if self.shadow_state.borrow().is_in_column_search()
3721 || self.search_modes_widget.current_mode() == Some(SearchMode::ColumnSearch)
3722 {
3723 debug!(target: "search", "Calling next_column_match");
3724 if !self.shadow_state.borrow().is_in_column_search() {
3726 debug!(target: "search", "WARNING: Mode mismatch - fixing");
3727 self.state_container.set_mode(AppMode::ColumnSearch);
3728 self.shadow_state.borrow_mut().observe_search_start(
3729 crate::ui::state::shadow_state::SearchType::Column,
3730 "column_search_mode_fix_next",
3731 );
3732 }
3733 self.next_column_match();
3734 } else {
3735 debug!(target: "search", "Not in ColumnSearch mode, skipping next_column_match");
3736 }
3737 }
3738 SearchModesAction::PreviousMatch => {
3739 debug!(target: "search", "PreviousMatch action, current_mode={:?}, widget_mode={:?}",
3740 self.shadow_state.borrow().get_mode(), self.search_modes_widget.current_mode());
3741
3742 if self.shadow_state.borrow().get_mode() == AppMode::ColumnSearch
3744 || self.search_modes_widget.current_mode() == Some(SearchMode::ColumnSearch)
3745 {
3746 debug!(target: "search", "Calling previous_column_match");
3747 if self.shadow_state.borrow().get_mode() != AppMode::ColumnSearch {
3749 debug!(target: "search", "WARNING: Mode mismatch - fixing");
3750 self.state_container.set_mode(AppMode::ColumnSearch);
3751 self.shadow_state.borrow_mut().observe_search_start(
3752 crate::ui::state::shadow_state::SearchType::Column,
3753 "column_search_mode_fix_prev",
3754 );
3755 }
3756 self.previous_column_match();
3757 } else {
3758 debug!(target: "search", "Not in ColumnSearch mode, skipping previous_column_match");
3759 }
3760 }
3761 SearchModesAction::PassThrough => {}
3762 }
3763
3764 Ok(false)
3767 }
3768
3769 fn handle_help_input(&mut self, key: crossterm::event::KeyEvent) -> Result<bool> {
3770 let result = match key.code {
3772 crossterm::event::KeyCode::Esc | crossterm::event::KeyCode::Char('q') => {
3773 self.help_widget.on_exit();
3774 self.state_container.set_help_visible(false);
3775
3776 let target_mode = if self.state_container.has_dataview() {
3778 AppMode::Results
3779 } else {
3780 AppMode::Command
3781 };
3782
3783 self.set_mode_via_shadow_state(target_mode, "escape_from_help");
3785
3786 Ok(false)
3788 }
3789 _ => {
3790 self.help_widget.handle_key(key);
3792 Ok(false)
3793 }
3794 };
3795
3796 result
3797 }
3798
3799 fn handle_history_input(&mut self, key: crossterm::event::KeyEvent) -> Result<bool> {
3802 use crossterm::event::{KeyCode, KeyModifiers};
3804
3805 let result = match key.code {
3806 KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => {
3807 crate::ui::input::history_input_handler::HistoryInputResult::Exit
3808 }
3809 KeyCode::Esc => {
3810 let original_input = self.state_container.cancel_history_search();
3812 if let Some(buffer) = self.state_container.current_buffer_mut() {
3813 self.shadow_state.borrow_mut().set_mode(
3814 crate::buffer::AppMode::Command,
3815 buffer,
3816 "history_cancelled",
3817 );
3818 buffer.set_status_message("History search cancelled".to_string());
3819 }
3820 crate::ui::input::history_input_handler::HistoryInputResult::SwitchToCommand(Some(
3821 (original_input, 0),
3822 ))
3823 }
3824 KeyCode::Enter => {
3825 if let Some(command) = self.state_container.accept_history_search() {
3827 if let Some(buffer) = self.state_container.current_buffer_mut() {
3828 self.shadow_state.borrow_mut().set_mode(
3829 crate::buffer::AppMode::Command,
3830 buffer,
3831 "history_accepted",
3832 );
3833 buffer.set_status_message(
3834 "Command loaded from history (cursor at start)".to_string(),
3835 );
3836 }
3837 crate::ui::input::history_input_handler::HistoryInputResult::SwitchToCommand(
3839 Some((command, 0)),
3840 )
3841 } else {
3842 crate::ui::input::history_input_handler::HistoryInputResult::Continue
3843 }
3844 }
3845 KeyCode::Up => {
3846 self.state_container.history_search_previous();
3847 crate::ui::input::history_input_handler::HistoryInputResult::Continue
3848 }
3849 KeyCode::Down => {
3850 self.state_container.history_search_next();
3851 crate::ui::input::history_input_handler::HistoryInputResult::Continue
3852 }
3853 KeyCode::Char('r') if key.modifiers.contains(KeyModifiers::CONTROL) => {
3854 self.state_container.history_search_next();
3856 crate::ui::input::history_input_handler::HistoryInputResult::Continue
3857 }
3858 KeyCode::Backspace => {
3859 self.state_container.history_search_backspace();
3860 crate::ui::input::history_input_handler::HistoryInputResult::Continue
3861 }
3862 KeyCode::Char(c) => {
3863 self.state_container.history_search_add_char(c);
3864 crate::ui::input::history_input_handler::HistoryInputResult::Continue
3865 }
3866 _ => crate::ui::input::history_input_handler::HistoryInputResult::Continue,
3867 };
3868
3869 match result {
3871 crate::ui::input::history_input_handler::HistoryInputResult::Exit => return Ok(true),
3872 crate::ui::input::history_input_handler::HistoryInputResult::SwitchToCommand(
3873 input_data,
3874 ) => {
3875 if let Some((text, cursor_pos)) = input_data {
3876 self.set_input_text_with_cursor(text, cursor_pos);
3877 self.sync_all_input_states();
3879 }
3880 }
3881 crate::ui::input::history_input_handler::HistoryInputResult::Continue => {
3882 if crate::ui::input::history_input_handler::key_updates_search(key) {
3884 self.update_history_matches_in_container();
3885 }
3886 }
3887 }
3888
3889 Ok(false)
3890 }
3891
3892 fn update_history_matches_in_container(&mut self) {
3894 let (current_columns, current_source_str) =
3896 if let Some(dataview) = self.state_container.get_buffer_dataview() {
3897 (
3898 dataview.column_names(), Some(dataview.source().name.clone()), )
3901 } else {
3902 (vec![], None)
3903 };
3904
3905 let current_source = current_source_str.as_deref();
3906 let query = self.state_container.history_search().query.clone();
3907
3908 self.state_container.update_history_search_with_schema(
3909 query,
3910 ¤t_columns,
3911 current_source,
3912 );
3913 }
3914
3915 fn handle_debug_input(&mut self, key: crossterm::event::KeyEvent) -> Result<bool> {
3916 let mut ctx = crate::ui::input::input_handlers::DebugInputContext {
3918 buffer_manager: self.state_container.buffers_mut(),
3919 debug_widget: &mut self.debug_widget,
3920 shadow_state: &self.shadow_state,
3921 };
3922
3923 let should_quit = crate::ui::input::input_handlers::handle_debug_input(&mut ctx, key)?;
3924
3925 if !should_quit {
3928 match key.code {
3929 KeyCode::Char('t') if key.modifiers.contains(KeyModifiers::CONTROL) => {
3930 self.yank_as_test_case();
3932 }
3933 KeyCode::Char('y') if key.modifiers.contains(KeyModifiers::SHIFT) => {
3934 self.yank_debug_with_context();
3936 }
3937 _ => {}
3938 }
3939 }
3940
3941 Ok(should_quit)
3942 }
3944
3945 fn handle_pretty_query_input(&mut self, key: crossterm::event::KeyEvent) -> Result<bool> {
3946 let mut ctx = crate::ui::input::input_handlers::DebugInputContext {
3948 buffer_manager: self.state_container.buffers_mut(),
3949 debug_widget: &mut self.debug_widget,
3950 shadow_state: &self.shadow_state,
3951 };
3952
3953 crate::ui::input::input_handlers::handle_pretty_query_input(&mut ctx, key)
3954 }
3955
3956 pub fn execute_query_v2(&mut self, query: &str) -> Result<()> {
3957 let context = self.query_orchestrator.execute_query(
3959 query,
3960 &mut self.state_container,
3961 &self.vim_search_adapter,
3962 );
3963
3964 match context {
3965 Ok(ctx) => {
3966 self.state_container
3968 .set_dataview(Some(ctx.result.dataview.clone()));
3969
3970 self.update_viewport_manager(Some(ctx.result.dataview.clone()));
3972
3973 self.state_container
3975 .update_data_size(ctx.result.stats.row_count, ctx.result.stats.column_count);
3976
3977 self.calculate_optimal_column_widths();
3979
3980 self.state_container
3982 .set_status_message(ctx.result.status_message());
3983
3984 self.state_container
3986 .command_history_mut()
3987 .add_entry_with_schema(
3988 ctx.query.clone(),
3989 true,
3990 Some(ctx.result.stats.execution_time.as_millis() as u64),
3991 ctx.result.column_names(),
3992 Some(ctx.result.table_name()),
3993 )?;
3994
3995 self.sync_mode(AppMode::Results, "execute_query_success");
3997
3998 self.reset_table_state();
4000
4001 Ok(())
4002 }
4003 Err(e) => {
4004 let error_msg = format!("Query error: {}", e);
4005 self.state_container.set_status_message(error_msg.clone());
4006
4007 self.state_container
4009 .command_history_mut()
4010 .add_entry_with_schema(query.to_string(), false, None, vec![], None)?;
4011
4012 Err(e)
4013 }
4014 }
4015 }
4016
4017 fn handle_completion(&mut self) {
4018 let cursor_pos = self.get_input_cursor();
4019 let query_str = self.get_input_text();
4020 let query = query_str.as_str();
4021
4022 let hybrid_result = self.hybrid_parser.get_completions(query, cursor_pos);
4023 if !hybrid_result.suggestions.is_empty() {
4024 self.state_container.set_status_message(format!(
4025 "Suggestions: {}",
4026 hybrid_result.suggestions.join(", ")
4027 ));
4028 }
4029 }
4030
4031 fn apply_completion(&mut self) {
4032 let cursor_pos = self.get_input_cursor();
4033 let query = self.get_input_text();
4034
4035 let suggestion = match self.get_or_refresh_completion(&query, cursor_pos) {
4037 Some(s) => s,
4038 None => return,
4039 };
4040
4041 self.apply_completion_to_input(&query, cursor_pos, &suggestion);
4043 }
4044
4045 fn get_or_refresh_completion(&mut self, query: &str, cursor_pos: usize) -> Option<String> {
4048 let is_same_context = self
4049 .state_container
4050 .is_same_completion_context(query, cursor_pos);
4051
4052 if !is_same_context {
4053 let hybrid_result = self.hybrid_parser.get_completions(query, cursor_pos);
4055 if hybrid_result.suggestions.is_empty() {
4056 self.state_container
4057 .set_status_message("No completions available".to_string());
4058 return None;
4059 }
4060
4061 self.state_container
4062 .set_completion_suggestions(hybrid_result.suggestions);
4063 } else if self.state_container.is_completion_active() {
4064 self.state_container.next_completion();
4066 } else {
4067 self.state_container
4068 .set_status_message("No completions available".to_string());
4069 return None;
4070 }
4071
4072 match self.state_container.get_current_completion() {
4074 Some(sugg) => Some(sugg),
4075 None => {
4076 self.state_container
4077 .set_status_message("No completion selected".to_string());
4078 None
4079 }
4080 }
4081 }
4082
4083 fn apply_completion_to_input(&mut self, query: &str, cursor_pos: usize, suggestion: &str) {
4085 let partial_word =
4086 crate::ui::utils::text_operations::extract_partial_word_at_cursor(query, cursor_pos);
4087
4088 if let Some(partial) = partial_word {
4089 self.apply_partial_completion(query, cursor_pos, &partial, suggestion);
4090 } else {
4091 self.apply_full_insertion(query, cursor_pos, suggestion);
4092 }
4093 }
4094
4095 fn apply_partial_completion(
4097 &mut self,
4098 query: &str,
4099 cursor_pos: usize,
4100 partial: &str,
4101 suggestion: &str,
4102 ) {
4103 let result = crate::ui::utils::text_operations::apply_completion_to_text(
4105 query, cursor_pos, partial, suggestion,
4106 );
4107
4108 self.set_input_text_with_cursor(result.new_text.clone(), result.new_cursor_position);
4110
4111 self.state_container
4113 .update_completion_context(result.new_text.clone(), result.new_cursor_position);
4114
4115 let completion = self.state_container.completion();
4117 let suggestion_info = if completion.suggestions.len() > 1 {
4118 format!(
4119 "Completed: {} ({}/{} - Tab for next)",
4120 suggestion,
4121 completion.current_index + 1,
4122 completion.suggestions.len()
4123 )
4124 } else {
4125 format!("Completed: {}", suggestion)
4126 };
4127 drop(completion);
4128 self.state_container.set_status_message(suggestion_info);
4129 }
4130
4131 fn apply_full_insertion(&mut self, query: &str, cursor_pos: usize, suggestion: &str) {
4133 let before_cursor = &query[..cursor_pos];
4135 let after_cursor = &query[cursor_pos..];
4136 let new_query = format!("{}{}{}", before_cursor, suggestion, after_cursor);
4137
4138 let cursor_pos_new = if suggestion.ends_with("('')") {
4140 cursor_pos + suggestion.len() - 2
4142 } else {
4143 cursor_pos + suggestion.len()
4144 };
4145
4146 self.set_input_text(new_query.clone());
4148
4149 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
4151 buffer.set_input_cursor_position(cursor_pos_new);
4152 self.sync_all_input_states();
4154 }
4155
4156 self.state_container
4158 .update_completion_context(new_query, cursor_pos_new);
4159
4160 self.state_container
4161 .set_status_message(format!("Inserted: {}", suggestion));
4162 }
4163
4164 fn get_column_count(&self) -> usize {
4171 if let Some(provider) = self.get_data_provider() {
4173 provider.get_column_count()
4174 } else {
4175 0
4176 }
4177 }
4178
4179 fn get_column_names_via_provider(&self) -> Vec<String> {
4184 if let Some(provider) = self.get_data_provider() {
4185 provider.get_column_names()
4186 } else {
4187 Vec::new()
4188 }
4189 }
4190
4191 fn toggle_column_pin_impl(&mut self) {
4196 let visual_col_idx = if let Some(ref viewport_manager) = *self.viewport_manager.borrow() {
4198 viewport_manager.get_crosshair_col()
4199 } else {
4200 0
4201 };
4202
4203 let column_name = if let Some(dataview) = self.state_container.get_buffer_dataview() {
4205 let all_columns = dataview.column_names();
4206 all_columns.get(visual_col_idx).cloned()
4207 } else {
4208 None
4209 };
4210
4211 if let Some(col_name) = column_name {
4212 if let Some(dataview) = self.state_container.get_buffer_dataview_mut() {
4213 let pinned_names = dataview.get_pinned_column_names();
4215 if pinned_names.contains(&col_name) {
4216 dataview.unpin_column_by_name(&col_name);
4218 self.state_container
4219 .set_status_message(format!("Column '{}' unpinned", col_name));
4220 } else {
4221 match dataview.pin_column_by_name(&col_name) {
4223 Ok(_) => {
4224 self.state_container
4225 .set_status_message(format!("Column '{}' pinned [P]", col_name));
4226 }
4227 Err(e) => {
4228 self.state_container.set_status_message(e.to_string());
4229 }
4230 }
4231 }
4232
4233 if let Some(updated_dataview) = self.state_container.get_buffer_dataview() {
4235 self.update_viewport_manager(Some(updated_dataview.clone()));
4236 }
4237 }
4238 } else {
4239 self.state_container
4240 .set_status_message("No column to pin at current position".to_string());
4241 }
4242 }
4243
4244 fn clear_all_pinned_columns_impl(&mut self) {
4245 if let Some(dataview) = self.state_container.get_buffer_dataview_mut() {
4246 dataview.clear_pinned_columns();
4247 }
4248 self.state_container
4249 .set_status_message("All columns unpinned".to_string());
4250
4251 if let Some(updated_dataview) = self.state_container.get_buffer_dataview() {
4253 self.update_viewport_manager(Some(updated_dataview.clone()));
4254 }
4255 }
4256
4257 fn calculate_column_statistics(&mut self) {
4258 use std::time::Instant;
4259
4260 let start_total = Instant::now();
4261
4262 let (column_name, data_to_analyze) = {
4264 let headers = self.get_column_names_via_provider();
4266 if headers.is_empty() {
4267 return;
4268 }
4269
4270 let current_column = self.state_container.get_current_column();
4271 if current_column >= headers.len() {
4272 return;
4273 }
4274
4275 let column_name = headers[current_column].clone();
4276
4277 let data_to_analyze: Vec<String> = if let Some(provider) = self.get_data_provider() {
4279 let row_count = provider.get_row_count();
4280 let mut column_data = Vec::with_capacity(row_count);
4281
4282 for row_idx in 0..row_count {
4283 if let Some(row) = provider.get_row(row_idx) {
4284 if current_column < row.len() {
4285 column_data.push(row[current_column].clone());
4286 } else {
4287 column_data.push(String::new());
4289 }
4290 }
4291 }
4292
4293 column_data
4294 } else {
4295 return;
4297 };
4298
4299 (column_name, data_to_analyze)
4300 };
4301
4302 let data_refs: Vec<&str> = data_to_analyze.iter().map(|s| s.as_str()).collect();
4304
4305 let analyzer_stats = self
4307 .data_analyzer
4308 .calculate_column_statistics(&column_name, &data_refs);
4309
4310 let stats = ColumnStatistics {
4312 column_name: analyzer_stats.column_name,
4313 column_type: match analyzer_stats.data_type {
4314 data_analyzer::ColumnType::Integer | data_analyzer::ColumnType::Float => {
4315 ColumnType::Numeric
4316 }
4317 data_analyzer::ColumnType::String
4318 | data_analyzer::ColumnType::Boolean
4319 | data_analyzer::ColumnType::Date => ColumnType::String,
4320 data_analyzer::ColumnType::Mixed => ColumnType::Mixed,
4321 data_analyzer::ColumnType::Unknown => ColumnType::Mixed,
4322 },
4323 total_count: analyzer_stats.total_values,
4324 null_count: analyzer_stats.null_values,
4325 unique_count: analyzer_stats.unique_values,
4326 frequency_map: analyzer_stats.frequency_map.clone(),
4327 min: analyzer_stats
4329 .min_value
4330 .as_ref()
4331 .and_then(|s| s.parse::<f64>().ok()),
4332 max: analyzer_stats
4333 .max_value
4334 .as_ref()
4335 .and_then(|s| s.parse::<f64>().ok()),
4336 sum: analyzer_stats.sum_value,
4337 mean: analyzer_stats.avg_value,
4338 median: analyzer_stats.median_value,
4339 };
4340
4341 let elapsed = start_total.elapsed();
4343
4344 self.state_container.set_column_stats(Some(stats));
4345
4346 self.state_container.set_status_message(format!(
4348 "Column stats: {:.1}ms for {} values ({} unique)",
4349 elapsed.as_secs_f64() * 1000.0,
4350 data_to_analyze.len(),
4351 analyzer_stats.unique_values
4352 ));
4353
4354 self.state_container.set_mode(AppMode::ColumnStats);
4355 self.shadow_state
4356 .borrow_mut()
4357 .observe_mode_change(AppMode::ColumnStats, "column_stats_requested");
4358 }
4359
4360 fn check_parser_error(&self, query: &str) -> Option<String> {
4361 crate::ui::operations::simple_operations::check_parser_error(query)
4362 }
4363
4364 fn update_viewport_size(&mut self) {
4365 if let Ok((width, height)) = crossterm::terminal::size() {
4367 let data_rows_available = Self::calculate_available_data_rows(height);
4369
4370 let visible_rows = {
4372 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
4373 let viewport_manager = viewport_manager_borrow
4374 .as_mut()
4375 .expect("ViewportManager must exist for viewport size update");
4376 viewport_manager.update_terminal_size(width, data_rows_available)
4377 };
4378
4379 self.state_container.set_last_visible_rows(visible_rows);
4381
4382 self.state_container
4384 .navigation_mut()
4385 .set_viewport_size(visible_rows, width as usize);
4386
4387 info!(target: "navigation", "update_viewport_size - viewport set to: {}x{} rows", visible_rows, width);
4388 }
4389 }
4390
4391 fn perform_search(&mut self) {
4395 if let Some(dataview) = self.get_current_data() {
4396 let data: Vec<Vec<String>> = (0..dataview.row_count())
4398 .filter_map(|i| dataview.get_row(i))
4399 .map(|row| row.values.iter().map(|v| v.to_string()).collect())
4400 .collect();
4401
4402 let pattern = self.state_container.get_search_pattern().to_string();
4404
4405 info!(target: "search", "=== SEARCH START ===");
4406 info!(target: "search", "Pattern: '{}', case_insensitive: {}",
4407 pattern, self.state_container.is_case_insensitive());
4408 info!(target: "search", "Data dimensions: {} rows x {} columns",
4409 data.len(), data.first().map(|r| r.len()).unwrap_or(0));
4410
4411 let column_names = dataview.column_names();
4413 info!(target: "search", "Column names (first 5): {:?}",
4414 column_names.iter().take(5).collect::<Vec<_>>());
4415
4416 for (i, row) in data.iter().take(10).enumerate() {
4418 info!(target: "search", " Data row {}: [{}]", i,
4419 row.iter().take(5).map(|s| format!("'{}'", s)).collect::<Vec<_>>().join(", "));
4420 }
4421
4422 let visible_columns = None;
4424
4425 let match_count = {
4427 let mut search_manager = self.search_manager.borrow_mut();
4428
4429 search_manager.clear();
4431 info!(target: "search", "Cleared previous search results");
4432
4433 search_manager.set_case_sensitive(!self.state_container.is_case_insensitive());
4435 info!(target: "search", "Set case_sensitive to {}", !self.state_container.is_case_insensitive());
4436
4437 let count = search_manager.search(&pattern, &data, visible_columns);
4439 info!(target: "search", "SearchManager.search() returned {} matches", count);
4440 count
4441 };
4442
4443 info!(target: "search", "SearchManager found {} matches", match_count);
4444
4445 if match_count > 0 {
4447 let (first_row, first_col) = {
4449 let search_manager = self.search_manager.borrow();
4450 if let Some(first_match) = search_manager.first_match() {
4451 info!(target: "search", "FIRST MATCH DETAILS:");
4452 info!(target: "search", " Data coordinates: row={}, col={}",
4453 first_match.row, first_match.column);
4454 info!(target: "search", " Matched value: '{}'", first_match.value);
4455 info!(target: "search", " Highlight range: {:?}", first_match.highlight_range);
4456
4457 for (i, m) in search_manager.all_matches().iter().take(5).enumerate() {
4459 info!(target: "search", " Match #{}: row={}, col={}, value='{}'",
4460 i + 1, m.row, m.column, m.value);
4461 }
4462
4463 (first_match.row, first_match.column)
4464 } else {
4465 warn!(target: "search", "SearchManager reported matches but first_match() is None!");
4466 (0, 0)
4467 }
4468 };
4469
4470 self.state_container.set_table_selected_row(Some(first_row));
4472
4473 info!(target: "search", "Updating TableWidgetManager to navigate to ({}, {})", first_row, first_col);
4475 self.table_widget_manager
4476 .borrow_mut()
4477 .navigate_to_search_match(first_row, first_col);
4478
4479 if let Some(dataview) = self.get_current_data() {
4481 if let Some(row_data) = dataview.get_row(first_row) {
4482 if first_col < row_data.values.len() {
4483 info!(target: "search", "VALUE AT NAVIGATION TARGET ({}, {}): '{}'",
4484 first_row, first_col, row_data.values[first_col]);
4485 }
4486 }
4487 }
4488
4489 let buffer_matches: Vec<(usize, usize)> = {
4491 let search_manager = self.search_manager.borrow();
4492 search_manager
4493 .all_matches()
4494 .iter()
4495 .map(|m| (m.row, m.column))
4496 .collect()
4497 };
4498
4499 let state_matches: Vec<(usize, usize, usize, usize)> = {
4502 let search_manager = self.search_manager.borrow();
4503 search_manager
4504 .all_matches()
4505 .iter()
4506 .map(|m| {
4507 (m.row, m.column, m.row, m.column)
4509 })
4510 .collect()
4511 };
4512 self.state_container.search_mut().matches = state_matches;
4513
4514 self.state_container
4515 .set_search_matches_with_index(buffer_matches.clone(), 0);
4516 self.state_container
4517 .set_current_match(Some((first_row, first_col)));
4518 self.state_container
4519 .set_status_message(format!("Found {} matches", match_count));
4520
4521 info!(target: "search", "Search found {} matches for pattern '{}'", match_count, pattern);
4522 } else {
4523 self.state_container.search_mut().matches.clear();
4525 self.state_container.clear_search_state();
4526 self.state_container.set_current_match(None);
4527
4528 info!(target: "search", "No matches found for pattern '{}'", pattern);
4529 }
4530 }
4531 }
4532
4533 fn start_vim_search(&mut self) {
4537 info!(target: "vim_search", "Starting vim search mode");
4538
4539 self.vim_search_adapter.borrow_mut().start_search();
4541
4542 self.shadow_state.borrow_mut().observe_search_start(
4544 crate::ui::state::shadow_state::SearchType::Vim,
4545 "slash_key_pressed",
4546 );
4547
4548 self.enter_search_mode(SearchMode::Search);
4550 }
4551
4552 fn vim_search_next(&mut self) {
4554 if !self.vim_search_adapter.borrow().is_navigating() {
4555 let resumed = {
4557 let mut viewport_borrow = self.viewport_manager.borrow_mut();
4558 if let Some(ref mut viewport) = *viewport_borrow {
4559 if let Some(dataview) = self.state_container.get_buffer_dataview() {
4560 self.vim_search_adapter
4561 .borrow_mut()
4562 .resume_last_search(dataview, viewport)
4563 } else {
4564 false
4565 }
4566 } else {
4567 false
4568 }
4569 };
4570
4571 if !resumed {
4572 self.state_container
4573 .set_status_message("No previous search pattern".to_string());
4574 return;
4575 }
4576 }
4577
4578 let result = {
4580 let mut viewport_borrow = self.viewport_manager.borrow_mut();
4581 if let Some(ref mut viewport) = *viewport_borrow {
4582 let search_match = self.vim_search_adapter.borrow_mut().next_match(viewport);
4583 if search_match.is_some() {
4584 let match_info = self.vim_search_adapter.borrow().get_match_info();
4585 search_match.map(|m| (m, match_info))
4586 } else {
4587 None
4588 }
4589 } else {
4590 None
4591 }
4592 }; if let Some((ref search_match, _)) = result {
4596 info!(target: "search",
4598 "=== UPDATING TUI STATE FOR VIM SEARCH ===");
4599 info!(target: "search",
4600 "Setting selected row to: {}", search_match.row);
4601 info!(target: "search",
4602 "Setting selected column to: {} (visual col)", search_match.col);
4603
4604 if let Some(dataview) = self.state_container.get_buffer_dataview() {
4606 info!(target: "search",
4608 "DEBUG: Fetching row {} from dataview with {} total rows",
4609 search_match.row, dataview.row_count());
4610
4611 if let Some(row_data) = dataview.get_row(search_match.row) {
4612 info!(target: "search",
4614 "Row {} has {} values, first 5: {:?}",
4615 search_match.row, row_data.values.len(),
4616 row_data.values.iter().take(5).map(|v| v.to_string()).collect::<Vec<_>>());
4617
4618 if search_match.col < row_data.values.len() {
4619 let actual_value = &row_data.values[search_match.col];
4620 info!(target: "search",
4621 "Actual value at row {} col {}: '{}'",
4622 search_match.row, search_match.col, actual_value);
4623
4624 let pattern = self
4626 .vim_search_adapter
4627 .borrow()
4628 .get_pattern()
4629 .unwrap_or_default();
4630 let contains_pattern = actual_value
4631 .to_string()
4632 .to_lowercase()
4633 .contains(&pattern.to_lowercase());
4634 if !contains_pattern {
4635 warn!(target: "search",
4636 "WARNING: Cell at ({}, {}) = '{}' does NOT contain pattern '{}'!",
4637 search_match.row, search_match.col, actual_value, pattern);
4638 } else {
4639 info!(target: "search",
4640 "✓ Confirmed: Cell contains pattern '{}'", pattern);
4641 }
4642 } else {
4643 warn!(target: "search",
4644 "Column {} is out of bounds for row {} (row has {} values)",
4645 search_match.col, search_match.row, row_data.values.len());
4646 }
4647 } else {
4648 warn!(target: "search",
4649 "Could not get row data for row {}", search_match.row);
4650 }
4651
4652 let display_columns = dataview.get_display_columns();
4654 info!(target: "search",
4655 "Display columns mapping (first 10): {:?}",
4656 display_columns.iter().take(10).collect::<Vec<_>>());
4657 }
4658
4659 self.state_container
4660 .set_table_selected_row(Some(search_match.row));
4661 self.state_container
4662 .set_selected_row(Some(search_match.row));
4663
4664 info!(target: "search",
4667 "Setting column to visual index {}",
4668 search_match.col);
4669
4670 self.state_container
4672 .set_current_column_buffer(search_match.col);
4673 self.state_container.navigation_mut().selected_column = search_match.col;
4674
4675 self.state_container.select_column(search_match.col);
4677 info!(target: "search",
4678 "Updated SelectionState column to: {}", search_match.col);
4679
4680 info!(target: "search",
4682 "Column state after update: nav.selected_column={}, buffer.current_column={}, selection.selected_column={}",
4683 self.state_container.navigation().selected_column,
4684 self.state_container.get_current_column(),
4685 self.state_container.selection().selected_column);
4686
4687 self.sync_navigation_with_viewport();
4690
4691 let scroll_offset = self.state_container.navigation().scroll_offset;
4693 self.state_container.set_scroll_offset(scroll_offset);
4694
4695 info!(target: "search", "Updating TableWidgetManager for vim search navigation to ({}, {})",
4697 search_match.row, search_match.col);
4698 self.table_widget_manager
4699 .borrow_mut()
4700 .navigate_to(search_match.row, search_match.col);
4701
4702 info!(target: "search",
4704 "After TableWidgetManager update: nav.selected_column={}, buffer.current_column={}, selection.selected_column={}",
4705 self.state_container.navigation().selected_column,
4706 self.state_container.get_current_column(),
4707 self.state_container.selection().selected_column);
4708
4709 }
4713
4714 if let Some((search_match, match_info)) = result {
4716 if let Some((current, total)) = match_info {
4717 self.state_container.set_status_message(format!(
4718 "Match {}/{} at ({}, {})",
4719 current,
4720 total,
4721 search_match.row + 1,
4722 search_match.col + 1
4723 ));
4724 }
4725
4726 info!(target: "search",
4728 "FINAL vim_search_next state: nav.selected_column={}, buffer.current_column={}, selection.selected_column={}",
4729 self.state_container.navigation().selected_column,
4730 self.state_container.get_current_column(),
4731 self.state_container.selection().selected_column);
4732
4733 if let Some(dataview) = self.state_container.get_buffer_dataview() {
4735 let final_row = self.state_container.navigation().selected_row;
4736 let final_col = self.state_container.navigation().selected_column;
4737
4738 if let Some(row_data) = dataview.get_row(final_row) {
4739 if final_col < row_data.values.len() {
4740 let actual_value = &row_data.values[final_col];
4741 info!(target: "search",
4742 "VERIFICATION: Cell at final position ({}, {}) contains: '{}'",
4743 final_row, final_col, actual_value);
4744
4745 let pattern = self
4746 .vim_search_adapter
4747 .borrow()
4748 .get_pattern()
4749 .unwrap_or_default();
4750 if !actual_value
4751 .to_string()
4752 .to_lowercase()
4753 .contains(&pattern.to_lowercase())
4754 {
4755 error!(target: "search",
4756 "ERROR: Final cell '{}' does NOT contain search pattern '{}'!",
4757 actual_value, pattern);
4758 }
4759 }
4760 }
4761 }
4762 }
4763 }
4764
4765 fn vim_search_previous(&mut self) {
4767 if !self.vim_search_adapter.borrow().is_navigating() {
4768 let resumed = {
4770 let mut viewport_borrow = self.viewport_manager.borrow_mut();
4771 if let Some(ref mut viewport) = *viewport_borrow {
4772 if let Some(dataview) = self.state_container.get_buffer_dataview() {
4773 self.vim_search_adapter
4774 .borrow_mut()
4775 .resume_last_search(dataview, viewport)
4776 } else {
4777 false
4778 }
4779 } else {
4780 false
4781 }
4782 };
4783
4784 if !resumed {
4785 self.state_container
4786 .set_status_message("No previous search pattern".to_string());
4787 return;
4788 }
4789 }
4790
4791 let result = {
4793 let mut viewport_borrow = self.viewport_manager.borrow_mut();
4794 if let Some(ref mut viewport) = *viewport_borrow {
4795 let search_match = self
4796 .vim_search_adapter
4797 .borrow_mut()
4798 .previous_match(viewport);
4799 if search_match.is_some() {
4800 let match_info = self.vim_search_adapter.borrow().get_match_info();
4801 search_match.map(|m| (m, match_info))
4802 } else {
4803 None
4804 }
4805 } else {
4806 None
4807 }
4808 }; if let Some((ref search_match, _)) = result {
4812 self.state_container
4813 .set_table_selected_row(Some(search_match.row));
4814 self.state_container
4815 .set_selected_row(Some(search_match.row));
4816
4817 info!(target: "search",
4820 "Setting column to visual index {}",
4821 search_match.col);
4822
4823 self.state_container
4825 .set_current_column_buffer(search_match.col);
4826 self.state_container.navigation_mut().selected_column = search_match.col;
4827
4828 self.state_container.select_column(search_match.col);
4830 info!(target: "search",
4831 "Updated SelectionState column to: {}", search_match.col);
4832
4833 info!(target: "search",
4835 "Column state after update: nav.selected_column={}, buffer.current_column={}, selection.selected_column={}",
4836 self.state_container.navigation().selected_column,
4837 self.state_container.get_current_column(),
4838 self.state_container.selection().selected_column);
4839
4840 self.sync_navigation_with_viewport();
4843
4844 let scroll_offset = self.state_container.navigation().scroll_offset;
4846 self.state_container.set_scroll_offset(scroll_offset);
4847
4848 info!(target: "search", "Updating TableWidgetManager for vim search navigation to ({}, {})",
4850 search_match.row, search_match.col);
4851 self.table_widget_manager
4852 .borrow_mut()
4853 .navigate_to(search_match.row, search_match.col);
4854
4855 info!(target: "search",
4857 "After TableWidgetManager update: nav.selected_column={}, buffer.current_column={}, selection.selected_column={}",
4858 self.state_container.navigation().selected_column,
4859 self.state_container.get_current_column(),
4860 self.state_container.selection().selected_column);
4861
4862 }
4866
4867 if let Some((search_match, match_info)) = result {
4869 if let Some((current, total)) = match_info {
4870 self.state_container.set_status_message(format!(
4871 "Match {}/{} at ({}, {})",
4872 current,
4873 total,
4874 search_match.row + 1,
4875 search_match.col + 1
4876 ));
4877 }
4878 }
4879 }
4881
4882 fn apply_filter(&mut self, pattern: &str) {
4883 use std::sync::atomic::{AtomicUsize, Ordering};
4884
4885 static FILTER_DEPTH: AtomicUsize = AtomicUsize::new(0);
4887 let depth = FILTER_DEPTH.fetch_add(1, Ordering::SeqCst);
4888 if depth > 0 {
4889 eprintln!(
4890 "WARNING: apply_filter re-entrancy detected! depth={}, pattern='{}', thread={:?}",
4891 depth,
4892 pattern,
4893 std::thread::current().id()
4894 );
4895 }
4896
4897 info!(
4898 "Applying filter: '{}' on thread {:?}",
4899 pattern,
4900 std::thread::current().id()
4901 );
4902
4903 use crate::ui::state::state_coordinator::StateCoordinator;
4905 let _rows_after =
4906 StateCoordinator::apply_text_filter_with_refs(&mut self.state_container, pattern);
4907
4908 self.sync_dataview_to_managers();
4911
4912 FILTER_DEPTH.fetch_sub(1, Ordering::SeqCst);
4914 }
4915 fn search_columns(&mut self) {
4916 static SEARCH_DEPTH: std::sync::atomic::AtomicUsize =
4918 std::sync::atomic::AtomicUsize::new(0);
4919 let depth = SEARCH_DEPTH.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
4920
4921 if depth > 10 {
4923 error!(target: "search", "Column search depth exceeded limit, aborting to prevent infinite loop");
4924 SEARCH_DEPTH.store(0, std::sync::atomic::Ordering::SeqCst);
4925 return;
4926 }
4927
4928 struct DepthGuard;
4930 impl Drop for DepthGuard {
4931 fn drop(&mut self) {
4932 SEARCH_DEPTH.fetch_sub(1, std::sync::atomic::Ordering::SeqCst);
4933 }
4934 }
4935 let _guard = DepthGuard;
4936
4937 let pattern = self.state_container.column_search().pattern.clone();
4938 debug!(target: "search", "search_columns called with pattern: '{}', depth: {}", pattern, depth);
4939
4940 if pattern.is_empty() {
4941 debug!(target: "search", "Pattern is empty, skipping column search");
4942 return;
4943 }
4944
4945 let matching_columns = if let Some(dataview) =
4947 self.state_container.get_buffer_dataview_mut()
4948 {
4949 dataview.search_columns(&pattern);
4950
4951 let matches = dataview.get_matching_columns().to_vec();
4953 debug!(target: "search", "DataView found {} matching columns", matches.len());
4954 if !matches.is_empty() {
4955 for (idx, (col_idx, col_name)) in matches.iter().enumerate() {
4956 debug!(target: "search", " Match {}: '{}' at visual index {}", idx + 1, col_name, col_idx);
4957 }
4958 }
4959
4960 let columns: Vec<(String, usize)> = matches
4962 .iter()
4963 .map(|(idx, name)| (name.clone(), *idx))
4964 .collect();
4965 self.state_container
4966 .update_column_search_matches(&columns, &pattern);
4967
4968 matches
4969 } else {
4970 debug!(target: "search", "No DataView available for column search");
4971 Vec::new()
4972 };
4973
4974 if !matching_columns.is_empty() {
4975 let first_match_visual_idx = matching_columns[0].0;
4977 let first_match_name = &matching_columns[0].1;
4978
4979 let datatable_idx = if let Some(dataview) = self.state_container.get_buffer_dataview() {
4981 let display_columns = dataview.get_display_columns();
4982 if first_match_visual_idx < display_columns.len() {
4983 display_columns[first_match_visual_idx]
4984 } else {
4985 first_match_visual_idx }
4987 } else {
4988 first_match_visual_idx
4989 };
4990
4991 self.state_container.set_current_column(datatable_idx);
4992 self.state_container
4993 .set_current_column_buffer(datatable_idx);
4994
4995 {
4998 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
4999 if let Some(viewport_manager) = viewport_manager_borrow.as_mut() {
5000 let viewport_changed =
5001 viewport_manager.set_current_column(first_match_visual_idx);
5002
5003 if viewport_changed {
5005 let new_viewport = viewport_manager.viewport_cols().clone();
5006 let pinned_count =
5007 if let Some(dv) = self.state_container.get_buffer_dataview() {
5008 dv.get_pinned_columns().len()
5009 } else {
5010 0
5011 };
5012 let scrollable_offset = new_viewport.start.saturating_sub(pinned_count);
5013 self.state_container.navigation_mut().scroll_offset.1 = scrollable_offset;
5014
5015 debug!(target: "navigation",
5016 "Column search initial: Jumped to column {} '{}', viewport adjusted to {:?}",
5017 first_match_visual_idx, first_match_name, new_viewport);
5018 }
5019 }
5020 }
5021
5022 debug!(target: "search", "Setting current column to visual index {} ('{}')",
5023 first_match_visual_idx, first_match_name);
5024 let status_msg = format!(
5025 "Found {} columns matching '{}'. Tab/Shift-Tab to navigate.",
5026 matching_columns.len(),
5027 pattern
5028 );
5029 debug!(target: "search", "Setting status: {}", status_msg);
5030 self.state_container.set_status_message(status_msg);
5031
5032 } else {
5034 let status_msg = format!("No columns matching '{}'", pattern);
5035 debug!(target: "search", "Setting status: {}", status_msg);
5036 self.state_container.set_status_message(status_msg);
5037 }
5038
5039 }
5041
5042 fn next_column_match(&mut self) {
5043 let column_match_data =
5046 if let Some(dataview) = self.state_container.get_buffer_dataview_mut() {
5047 if let Some(visual_idx) = dataview.next_column_match() {
5048 let matching_columns = dataview.get_matching_columns();
5050 let current_match_index = dataview.current_column_match_index();
5051 let current_match = current_match_index + 1;
5052 let total_matches = matching_columns.len();
5053 let col_name = matching_columns
5054 .get(current_match_index)
5055 .map(|(_, name)| name.clone())
5056 .unwrap_or_default();
5057
5058 let display_columns = dataview.get_display_columns();
5061 let datatable_idx = if visual_idx < display_columns.len() {
5062 display_columns[visual_idx]
5063 } else {
5064 visual_idx };
5066
5067 Some((
5068 visual_idx,
5069 datatable_idx,
5070 col_name,
5071 current_match,
5072 total_matches,
5073 current_match_index,
5074 ))
5075 } else {
5076 None
5077 }
5078 } else {
5079 None
5080 };
5081
5082 if let Some((
5084 visual_idx,
5085 datatable_idx,
5086 col_name,
5087 current_match,
5088 total_matches,
5089 current_match_index,
5090 )) = column_match_data
5091 {
5092 self.state_container.set_current_column(datatable_idx);
5094 self.state_container
5095 .set_current_column_buffer(datatable_idx);
5096
5097 {
5100 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
5101 if let Some(viewport_manager) = viewport_manager_borrow.as_mut() {
5102 viewport_manager.set_current_column(visual_idx);
5103 }
5104 }
5105
5106 debug!(target: "column_search_sync", "next_column_match: About to call sync_navigation_with_viewport() - visual_idx: {}, datatable_idx: {}", visual_idx, datatable_idx);
5108 debug!(target: "column_search_sync", "next_column_match: Pre-sync - viewport current_column: {}",
5109 if let Some(vm) = self.viewport_manager.try_borrow().ok() {
5110 vm.as_ref().map(|v| v.get_crosshair_col()).unwrap_or(0)
5111 } else { 0 });
5112 self.sync_navigation_with_viewport();
5113 debug!(target: "column_search_sync", "next_column_match: Post-sync - navigation current_column: {}",
5114 self.state_container.navigation().selected_column);
5115 debug!(target: "column_search_sync", "next_column_match: sync_navigation_with_viewport() completed");
5116
5117 debug!(target: "navigation",
5118 "Column search: Jumped to visual column {} (datatable: {}) '{}', synced with viewport",
5119 visual_idx, datatable_idx, col_name);
5120
5121 {
5124 let mut column_search = self.state_container.column_search_mut();
5125 column_search.current_match = current_match_index;
5126 debug!(target: "column_search_sync", "next_column_match: Updated AppStateContainer column_search.current_match to {}", column_search.current_match);
5127 }
5128
5129 self.state_container.set_status_message(format!(
5130 "Column {}/{}: {} - Tab/Shift-Tab to navigate",
5131 current_match, total_matches, col_name
5132 ));
5133 }
5134 }
5135
5136 fn previous_column_match(&mut self) {
5137 let column_match_data =
5140 if let Some(dataview) = self.state_container.get_buffer_dataview_mut() {
5141 if let Some(visual_idx) = dataview.prev_column_match() {
5142 let matching_columns = dataview.get_matching_columns();
5144 let current_match_index = dataview.current_column_match_index();
5145 let current_match = current_match_index + 1;
5146 let total_matches = matching_columns.len();
5147 let col_name = matching_columns
5148 .get(current_match_index)
5149 .map(|(_, name)| name.clone())
5150 .unwrap_or_default();
5151
5152 let display_columns = dataview.get_display_columns();
5155 let datatable_idx = if visual_idx < display_columns.len() {
5156 display_columns[visual_idx]
5157 } else {
5158 visual_idx };
5160
5161 Some((
5162 visual_idx,
5163 datatable_idx,
5164 col_name,
5165 current_match,
5166 total_matches,
5167 current_match_index,
5168 ))
5169 } else {
5170 None
5171 }
5172 } else {
5173 None
5174 };
5175
5176 if let Some((
5178 visual_idx,
5179 datatable_idx,
5180 col_name,
5181 current_match,
5182 total_matches,
5183 current_match_index,
5184 )) = column_match_data
5185 {
5186 self.state_container.set_current_column(datatable_idx);
5188 self.state_container
5189 .set_current_column_buffer(datatable_idx);
5190
5191 {
5194 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
5195 if let Some(viewport_manager) = viewport_manager_borrow.as_mut() {
5196 viewport_manager.set_current_column(visual_idx);
5197 }
5198 }
5199
5200 debug!(target: "column_search_sync", "previous_column_match: About to call sync_navigation_with_viewport() - visual_idx: {}, datatable_idx: {}", visual_idx, datatable_idx);
5202 debug!(target: "column_search_sync", "previous_column_match: Pre-sync - viewport current_column: {}",
5203 if let Some(vm) = self.viewport_manager.try_borrow().ok() {
5204 vm.as_ref().map(|v| v.get_crosshair_col()).unwrap_or(0)
5205 } else { 0 });
5206 self.sync_navigation_with_viewport();
5207 debug!(target: "column_search_sync", "previous_column_match: Post-sync - navigation current_column: {}",
5208 self.state_container.navigation().selected_column);
5209 debug!(target: "column_search_sync", "previous_column_match: sync_navigation_with_viewport() completed");
5210
5211 debug!(target: "navigation",
5212 "Column search (prev): Jumped to visual column {} (datatable: {}) '{}', synced with viewport",
5213 visual_idx, datatable_idx, col_name);
5214
5215 {
5218 let mut column_search = self.state_container.column_search_mut();
5219 column_search.current_match = current_match_index;
5220 debug!(target: "column_search_sync", "previous_column_match: Updated AppStateContainer column_search.current_match to {}", column_search.current_match);
5221 }
5222
5223 self.state_container.set_status_message(format!(
5224 "Column {}/{}: {} - Tab/Shift-Tab to navigate",
5225 current_match, total_matches, col_name
5226 ));
5227 }
5228 }
5229
5230 fn apply_fuzzy_filter(&mut self) {
5231 info!(
5232 "apply_fuzzy_filter called on thread {:?}",
5233 std::thread::current().id()
5234 );
5235
5236 use crate::ui::state::state_coordinator::StateCoordinator;
5238 let (_match_count, indices) = StateCoordinator::apply_fuzzy_filter_with_refs(
5239 &mut self.state_container,
5240 &self.viewport_manager,
5241 );
5242
5243 self.state_container.set_fuzzy_filter_indices(indices);
5245
5246 self.sync_dataview_to_managers();
5249 }
5250
5251 fn toggle_sort_current_column(&mut self) {
5252 let visual_col_idx = if let Some(ref viewport_manager) = *self.viewport_manager.borrow() {
5254 viewport_manager.get_crosshair_col()
5255 } else {
5256 0
5257 };
5258
5259 if let Some(dataview) = self.state_container.get_buffer_dataview_mut() {
5260 let column_names = dataview.column_names();
5263 let col_name = column_names
5264 .get(visual_col_idx)
5265 .map(|s| s.clone())
5266 .unwrap_or_else(|| format!("Column {}", visual_col_idx));
5267
5268 debug!(
5269 "toggle_sort_current_column: visual_idx={}, column_name={}",
5270 visual_col_idx, col_name
5271 );
5272
5273 if let Err(e) = dataview.toggle_sort(visual_col_idx) {
5274 self.state_container
5275 .set_status_message(format!("Sort error: {}", e));
5276 } else {
5277 let sort_state = dataview.get_sort_state();
5279 let message = match sort_state.order {
5280 crate::data::data_view::SortOrder::Ascending => {
5281 format!("Sorted '{}' ascending ↑", col_name)
5282 }
5283 crate::data::data_view::SortOrder::Descending => {
5284 format!("Sorted '{}' descending ↓", col_name)
5285 }
5286 crate::data::data_view::SortOrder::None => {
5287 format!("Cleared sort on '{}'", col_name)
5288 }
5289 };
5290 self.state_container.set_status_message(message);
5291
5292 if let Some(updated_dataview) = self.state_container.get_buffer_dataview() {
5294 self.table_widget_manager
5296 .borrow_mut()
5297 .set_dataview(Arc::new(updated_dataview.clone()));
5298
5299 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
5300 if let Some(ref mut viewport_manager) = *viewport_manager_borrow {
5301 viewport_manager.set_dataview(Arc::new(updated_dataview.clone()));
5302 debug!("Updated ViewportManager with sorted DataView");
5303 }
5304 }
5305 }
5306 } else {
5307 self.state_container
5309 .set_status_message("Error: Invalid column position".to_string());
5310 }
5311 }
5312
5313 fn get_current_data(&self) -> Option<&DataView> {
5314 self.state_container.get_buffer_dataview()
5315 }
5316
5317 fn get_row_count(&self) -> usize {
5318 if self.state_container.is_fuzzy_filter_active() {
5320 self.state_container.get_fuzzy_filter_indices().len()
5322 } else if let Some(dataview) = self.state_container.get_buffer_dataview() {
5323 dataview.row_count()
5325 } else if let Some(provider) = self.get_data_provider() {
5326 provider.get_row_count()
5328 } else {
5329 0
5330 }
5331 }
5332
5333 fn sync_dataview_to_managers(&self) {
5335 if let Some(dataview) = self.state_container.get_buffer_dataview() {
5336 let arc_dataview = Arc::new(dataview.clone());
5337
5338 if let Some(ref mut viewport_manager) = *self.viewport_manager.borrow_mut() {
5340 viewport_manager.set_dataview(arc_dataview.clone());
5341 debug!(
5342 "Updated ViewportManager with DataView (row_count={})",
5343 arc_dataview.row_count()
5344 );
5345 }
5346
5347 self.table_widget_manager
5349 .borrow_mut()
5350 .set_dataview(arc_dataview);
5351 debug!("Updated TableWidgetManager with DataView");
5352 }
5353 }
5354
5355 pub fn reset_table_state(&mut self) {
5356 use crate::ui::state::state_coordinator::StateCoordinator;
5358 StateCoordinator::reset_table_state_with_refs(
5359 &mut self.state_container,
5360 &self.viewport_manager,
5361 );
5362 }
5363
5364 fn update_parser_for_current_buffer(&mut self) {
5365 self.sync_all_input_states();
5367
5368 use crate::ui::state::state_coordinator::StateCoordinator;
5370 StateCoordinator::update_parser_with_refs(&self.state_container, &mut self.hybrid_parser);
5371 }
5372
5373 fn sync_after_buffer_switch(&mut self) {
5378 self.restore_viewport_from_current_buffer();
5380
5381 self.update_parser_for_current_buffer();
5383 }
5384
5385 fn update_viewport_manager(&mut self, dataview: Option<DataView>) {
5387 if let Some(dv) = dataview {
5388 let current_column = self.state_container.get_current_column();
5390
5391 let mut new_viewport_manager = ViewportManager::new(Arc::new(dv));
5393
5394 if let Ok((width, height)) = crossterm::terminal::size() {
5396 let data_rows_available = Self::calculate_available_data_rows(height);
5398 new_viewport_manager.update_terminal_size(width, data_rows_available);
5399 debug!(
5400 "Updated new ViewportManager terminal size: {}x{} (data rows)",
5401 width, data_rows_available
5402 );
5403 }
5404
5405 if current_column < new_viewport_manager.dataview().column_count() {
5408 new_viewport_manager.set_current_column(current_column);
5409 } else {
5410 new_viewport_manager.set_current_column(0);
5412 self.state_container.set_current_column_buffer(0);
5413 }
5414
5415 *self.viewport_manager.borrow_mut() = Some(new_viewport_manager);
5416 debug!(
5417 "ViewportManager updated with new DataView, current_column={}",
5418 current_column
5419 );
5420 } else {
5421 *self.viewport_manager.borrow_mut() = None;
5423 debug!("ViewportManager cleared (no DataView)");
5424 }
5425 }
5426
5427 pub fn calculate_optimal_column_widths(&mut self) {
5428 let widths_from_viewport = {
5430 let mut viewport_opt = self.viewport_manager.borrow_mut();
5431 if let Some(ref mut viewport_manager) = *viewport_opt {
5432 Some(viewport_manager.calculate_optimal_column_widths())
5433 } else {
5434 None
5435 }
5436 };
5437
5438 if let Some(widths) = widths_from_viewport {
5439 self.state_container.set_column_widths(widths);
5440 }
5441 }
5442
5443 pub fn set_status_message(&mut self, message: impl Into<String>) {
5446 let msg = message.into();
5447 debug!("Status: {}", msg);
5448 self.state_container.set_status_message(msg.clone());
5449 }
5452
5453 fn set_error_status(&mut self, context: &str, error: impl std::fmt::Display) {
5455 let msg = format!("{}: {}", context, error);
5456 debug!("Error status: {}", msg);
5457 self.set_status_message(msg);
5458 }
5459
5460 fn export_to_csv(&mut self) {
5461 let result = {
5462 let ctx = crate::ui::operations::data_export_operations::DataExportContext {
5463 data_provider: self.get_data_provider(),
5464 };
5465 crate::ui::operations::data_export_operations::export_to_csv(&ctx)
5466 };
5467
5468 match result {
5469 crate::ui::operations::data_export_operations::ExportResult::Success(message) => {
5470 self.set_status_message(message);
5471 }
5472 crate::ui::operations::data_export_operations::ExportResult::Error(error) => {
5473 self.set_error_status("Export failed", error);
5474 }
5475 }
5476 }
5477
5478 fn paste_from_clipboard(&mut self) {
5485 match self.state_container.read_from_clipboard() {
5487 Ok(text) => {
5488 let mode = self.shadow_state.borrow().get_mode();
5489 match mode {
5490 AppMode::Command => {
5491 let cursor_pos = self.get_input_cursor();
5494 let current_value = self.get_input_text();
5495
5496 let mut new_value = String::new();
5498 new_value.push_str(¤t_value[..cursor_pos]);
5499 new_value.push_str(&text);
5500 new_value.push_str(¤t_value[cursor_pos..]);
5501
5502 self.set_input_text_with_cursor(new_value, cursor_pos + text.len());
5503
5504 self.state_container
5505 .set_status_message(format!("Pasted {} characters", text.len()));
5506 }
5507 AppMode::Filter
5508 | AppMode::FuzzyFilter
5509 | AppMode::Search
5510 | AppMode::ColumnSearch => {
5511 let cursor_pos = self.get_input_cursor();
5513 let current_value = self.get_input_text();
5514
5515 let mut new_value = String::new();
5516 new_value.push_str(¤t_value[..cursor_pos]);
5517 new_value.push_str(&text);
5518 new_value.push_str(¤t_value[cursor_pos..]);
5519
5520 self.set_input_text_with_cursor(new_value, cursor_pos + text.len());
5521
5522 match mode {
5524 AppMode::Filter => {
5525 let pattern = self.get_input_text();
5526 self.state_container.filter_mut().pattern = pattern.clone();
5527 self.apply_filter(&pattern);
5528 }
5529 AppMode::FuzzyFilter => {
5530 let input_text = self.get_input_text();
5531 self.state_container.set_fuzzy_filter_pattern(input_text);
5532 self.apply_fuzzy_filter();
5533 }
5534 AppMode::Search => {
5535 let search_text = self.get_input_text();
5536 self.state_container.set_search_pattern(search_text);
5537 }
5539 AppMode::ColumnSearch => {
5540 let input_text = self.get_input_text();
5541 self.state_container.start_column_search(input_text);
5542 }
5544 _ => {}
5545 }
5546 }
5547 _ => {
5548 self.state_container
5549 .set_status_message("Paste not available in this mode".to_string());
5550 }
5551 }
5552 }
5553 Err(e) => {
5554 self.state_container
5555 .set_status_message(format!("Failed to paste: {}", e));
5556 }
5557 }
5558 }
5559
5560 fn export_to_json(&mut self) {
5561 let result = {
5563 let ctx = crate::ui::operations::data_export_operations::DataExportContext {
5564 data_provider: self.get_data_provider(),
5565 };
5566 crate::ui::operations::data_export_operations::export_to_json(&ctx)
5567 };
5568
5569 match result {
5570 crate::ui::operations::data_export_operations::ExportResult::Success(message) => {
5571 self.set_status_message(message);
5572 }
5573 crate::ui::operations::data_export_operations::ExportResult::Error(error) => {
5574 self.set_error_status("Export failed", error);
5575 }
5576 }
5577 }
5578
5579 fn get_horizontal_scroll_offset(&self) -> u16 {
5580 let (horizontal, _vertical) = self.cursor_manager.scroll_offsets();
5582 horizontal
5583 }
5584
5585 fn update_horizontal_scroll(&mut self, terminal_width: u16) {
5586 let inner_width = terminal_width.saturating_sub(3) as usize; let cursor_pos = self.get_input_cursor();
5588
5589 self.cursor_manager
5591 .update_horizontal_scroll(cursor_pos, terminal_width.saturating_sub(3));
5592
5593 let mut scroll = self.state_container.scroll_mut();
5595 if cursor_pos < scroll.input_scroll_offset as usize {
5596 scroll.input_scroll_offset = cursor_pos as u16;
5597 }
5598 else if cursor_pos >= scroll.input_scroll_offset as usize + inner_width {
5600 scroll.input_scroll_offset = (cursor_pos + 1).saturating_sub(inner_width) as u16;
5601 }
5602 }
5603
5604 fn get_cursor_token_position(&self) -> (usize, usize) {
5605 let ctx = crate::ui::operations::simple_operations::TextNavigationContext {
5606 query: &self.get_input_text(),
5607 cursor_pos: self.get_input_cursor(),
5608 };
5609 crate::ui::operations::simple_operations::get_cursor_token_position(&ctx)
5610 }
5611
5612 fn get_token_at_cursor(&self) -> Option<String> {
5613 let ctx = crate::ui::operations::simple_operations::TextNavigationContext {
5614 query: &self.get_input_text(),
5615 cursor_pos: self.get_input_cursor(),
5616 };
5617 crate::ui::operations::simple_operations::get_token_at_cursor(&ctx)
5618 }
5619
5620 #[allow(dead_code)]
5622
5623 fn ui(&mut self, f: &mut Frame) {
5624 let input_height = INPUT_AREA_HEIGHT;
5626
5627 let buffer_count = self.state_container.buffers().all_buffers().len();
5629 let tab_bar_height = 2; let chunks = Layout::default()
5632 .direction(Direction::Vertical)
5633 .constraints(
5634 [
5635 Constraint::Length(tab_bar_height), Constraint::Length(input_height), Constraint::Min(0), Constraint::Length(STATUS_BAR_HEIGHT), ]
5640 .as_ref(),
5641 )
5642 .split(f.area());
5643
5644 if buffer_count > 0 {
5646 let buffer_names: Vec<String> = self
5647 .state_container
5648 .buffers()
5649 .all_buffers()
5650 .iter()
5651 .map(|b| b.get_name())
5652 .collect();
5653 let current_index = self.state_container.buffers().current_index();
5654
5655 let tab_widget = TabBarWidget::new(current_index, buffer_names);
5656 tab_widget.render(f, chunks[0]);
5657 }
5658
5659 let input_chunk_idx = 1;
5661 let results_chunk_idx = 2;
5662 let status_chunk_idx = 3;
5663
5664 self.update_horizontal_scroll(chunks[input_chunk_idx].width);
5666
5667 let input_text_for_count = self.get_input_text();
5670 let char_count = input_text_for_count.len();
5671 let cursor_pos = self.get_input_cursor();
5672 let char_count_display = if char_count > 0 {
5673 format!(" [{}/{} chars]", cursor_pos, char_count)
5674 } else {
5675 String::new()
5676 };
5677
5678 let scroll_offset = self.get_horizontal_scroll_offset();
5679 let scroll_indicator = if scroll_offset > 0 {
5680 " ◀ " } else {
5682 ""
5683 };
5684
5685 let input_title = match self.shadow_state.borrow().get_mode() {
5686 AppMode::Command => format!("SQL Query{}{}", char_count_display, scroll_indicator),
5687 AppMode::Results => format!(
5688 "SQL Query (Results Mode - Press ↑ to edit){}{}",
5689 char_count_display, scroll_indicator
5690 ),
5691 AppMode::Search => format!("Search Pattern{}{}", char_count_display, scroll_indicator),
5692 AppMode::Filter => format!("Filter Pattern{}{}", char_count_display, scroll_indicator),
5693 AppMode::FuzzyFilter => {
5694 format!("Fuzzy Filter{}{}", char_count_display, scroll_indicator)
5695 }
5696 AppMode::ColumnSearch => {
5697 format!("Column Search{}{}", char_count_display, scroll_indicator)
5698 }
5699 AppMode::Help => "Help".to_string(),
5700 AppMode::History => {
5701 let query = self.state_container.history_search().query.clone();
5702 format!("History Search: '{}' (Esc to cancel)", query)
5703 }
5704 AppMode::Debug => "Parser Debug (F5)".to_string(),
5705 AppMode::PrettyQuery => "Pretty Query View (F6)".to_string(),
5706 AppMode::JumpToRow => format!("Jump to row: {}", self.get_jump_to_row_input()),
5707 AppMode::ColumnStats => "Column Statistics (S to close)".to_string(),
5708 };
5709
5710 let input_block = Block::default().borders(Borders::ALL).title(input_title);
5711
5712 let use_search_widget = matches!(
5714 self.shadow_state.borrow().get_mode(),
5715 AppMode::Search | AppMode::Filter | AppMode::FuzzyFilter | AppMode::ColumnSearch
5716 ) && self.search_modes_widget.is_active();
5717
5718 if use_search_widget {
5719 self.search_modes_widget.render(f, chunks[input_chunk_idx]);
5721 } else {
5722 let input_text_string = self.get_input_text();
5724
5725 trace!(target: "render", "Rendering input: text='{}', mode={:?}, cursor={}",
5727 if input_text_string.len() > 50 {
5728 format!("{}...", &input_text_string[..50])
5729 } else {
5730 input_text_string.clone()
5731 },
5732 self.shadow_state.borrow().get_mode(),
5733 self.get_input_cursor());
5734
5735 let history_query_string = if self.shadow_state.borrow().is_in_history_mode() {
5737 self.state_container.history_search().query.clone()
5738 } else {
5739 String::new()
5740 };
5741
5742 let input_text = match self.shadow_state.borrow().get_mode() {
5743 AppMode::History => &history_query_string,
5744 _ => &input_text_string,
5745 };
5746
5747 let input_paragraph = match self.shadow_state.borrow().get_mode() {
5748 AppMode::Command => {
5749 match self.state_container.get_edit_mode() {
5750 Some(EditMode::SingleLine) => {
5751 let highlighted_line =
5753 self.sql_highlighter.simple_sql_highlight(input_text);
5754 Paragraph::new(Text::from(vec![highlighted_line]))
5755 .block(input_block)
5756 .scroll((0, self.get_horizontal_scroll_offset()))
5757 }
5758 Some(EditMode::MultiLine) => {
5759 let highlighted_line =
5761 self.sql_highlighter.simple_sql_highlight(input_text);
5762 Paragraph::new(Text::from(vec![highlighted_line]))
5763 .block(input_block)
5764 .scroll((0, self.get_horizontal_scroll_offset()))
5765 }
5766 None => {
5767 let highlighted_line =
5769 self.sql_highlighter.simple_sql_highlight(input_text);
5770 Paragraph::new(Text::from(vec![highlighted_line]))
5771 .block(input_block)
5772 .scroll((0, self.get_horizontal_scroll_offset()))
5773 }
5774 }
5775 }
5776 _ => {
5777 Paragraph::new(input_text.as_str())
5779 .block(input_block)
5780 .style(match self.shadow_state.borrow().get_mode() {
5781 AppMode::Results => Style::default().fg(Color::DarkGray),
5782 AppMode::Search => Style::default().fg(Color::Yellow),
5783 AppMode::Filter => Style::default().fg(Color::Cyan),
5784 AppMode::FuzzyFilter => Style::default().fg(Color::Magenta),
5785 AppMode::ColumnSearch => Style::default().fg(Color::Green),
5786 AppMode::Help => Style::default().fg(Color::DarkGray),
5787 AppMode::History => Style::default().fg(Color::Magenta),
5788 AppMode::Debug => Style::default().fg(Color::Yellow),
5789 AppMode::PrettyQuery => Style::default().fg(Color::Green),
5790 AppMode::JumpToRow => Style::default().fg(Color::Magenta),
5791 AppMode::ColumnStats => Style::default().fg(Color::Cyan),
5792 _ => Style::default(),
5793 })
5794 .scroll((0, self.get_horizontal_scroll_offset()))
5795 }
5796 };
5797
5798 f.render_widget(input_paragraph, chunks[input_chunk_idx]);
5800 }
5801 let results_area = chunks[results_chunk_idx];
5802
5803 if !use_search_widget {
5805 match self.shadow_state.borrow().get_mode() {
5806 AppMode::Command => {
5807 let inner_width = chunks[input_chunk_idx].width.saturating_sub(2) as usize;
5810 let cursor_pos = self.get_visual_cursor().1; let scroll_offset = self.get_horizontal_scroll_offset() as usize;
5812
5813 if cursor_pos >= scroll_offset && cursor_pos < scroll_offset + inner_width {
5815 let visible_pos = cursor_pos - scroll_offset;
5816 f.set_cursor_position((
5817 chunks[input_chunk_idx].x + visible_pos as u16 + 1,
5818 chunks[input_chunk_idx].y + 1,
5819 ));
5820 }
5821 }
5822 AppMode::Search => {
5823 f.set_cursor_position((
5824 chunks[input_chunk_idx].x + self.get_input_cursor() as u16 + 1,
5825 chunks[input_chunk_idx].y + 1,
5826 ));
5827 }
5828 AppMode::Filter => {
5829 f.set_cursor_position((
5830 chunks[input_chunk_idx].x + self.get_input_cursor() as u16 + 1,
5831 chunks[input_chunk_idx].y + 1,
5832 ));
5833 }
5834 AppMode::FuzzyFilter => {
5835 f.set_cursor_position((
5836 chunks[input_chunk_idx].x + self.get_input_cursor() as u16 + 1,
5837 chunks[input_chunk_idx].y + 1,
5838 ));
5839 }
5840 AppMode::ColumnSearch => {
5841 f.set_cursor_position((
5842 chunks[input_chunk_idx].x + self.get_input_cursor() as u16 + 1,
5843 chunks[input_chunk_idx].y + 1,
5844 ));
5845 }
5846 AppMode::JumpToRow => {
5847 f.set_cursor_position((
5848 chunks[input_chunk_idx].x + self.get_jump_to_row_input().len() as u16 + 1,
5849 chunks[input_chunk_idx].y + 1,
5850 ));
5851 }
5852 AppMode::History => {
5853 let query_len = self.state_container.history_search().query.len();
5854 f.set_cursor_position((
5855 chunks[input_chunk_idx].x + query_len as u16 + 1,
5856 chunks[input_chunk_idx].y + 1,
5857 ));
5858 }
5859 _ => {}
5860 }
5861 }
5862
5863 let mode = self.shadow_state.borrow().get_mode();
5865 match mode {
5866 AppMode::Help => self.render_help(f, results_area),
5867 AppMode::History => self.render_history(f, results_area),
5868 AppMode::Debug => self.render_debug(f, results_area),
5869 AppMode::PrettyQuery => self.render_pretty_query(f, results_area),
5870 AppMode::ColumnStats => self.render_column_stats(f, results_area),
5871 _ if self.state_container.has_dataview() => {
5872 if let Some(provider) = self.get_data_provider() {
5875 self.render_table_with_provider(f, results_area, provider.as_ref());
5876 }
5877 }
5878 _ => {
5879 let placeholder = Paragraph::new("Enter SQL query and press Enter\n\nTip: Use Tab for completion, Ctrl+R for history")
5881 .block(Block::default().borders(Borders::ALL).title("Results"))
5882 .style(Style::default().fg(Color::DarkGray));
5883 f.render_widget(placeholder, results_area);
5884 }
5885 }
5886
5887 self.render_status_line(f, chunks[status_chunk_idx]);
5889 }
5891
5892 fn add_mode_styling(&self, spans: &mut Vec<Span>) -> (Style, Color) {
5894 let (status_style, mode_color) = match self.shadow_state.borrow().get_mode() {
5896 AppMode::Command => (Style::default().fg(Color::Green), Color::Green),
5897 AppMode::Results => (Style::default().fg(Color::Blue), Color::Blue),
5898 AppMode::Search => (Style::default().fg(Color::Yellow), Color::Yellow),
5899 AppMode::Filter => (Style::default().fg(Color::Cyan), Color::Cyan),
5900 AppMode::FuzzyFilter => (Style::default().fg(Color::Magenta), Color::Magenta),
5901 AppMode::ColumnSearch => (Style::default().fg(Color::Green), Color::Green),
5902 AppMode::Help => (Style::default().fg(Color::Magenta), Color::Magenta),
5903 AppMode::History => (Style::default().fg(Color::Magenta), Color::Magenta),
5904 AppMode::Debug => (Style::default().fg(Color::Yellow), Color::Yellow),
5905 AppMode::PrettyQuery => (Style::default().fg(Color::Green), Color::Green),
5906 AppMode::JumpToRow => (Style::default().fg(Color::Magenta), Color::Magenta),
5907 AppMode::ColumnStats => (Style::default().fg(Color::Cyan), Color::Cyan),
5908 };
5909
5910 let mode_indicator = match self.shadow_state.borrow().get_mode() {
5911 AppMode::Command => "CMD",
5912 AppMode::Results => "NAV",
5913 AppMode::Search => "SEARCH",
5914 AppMode::Filter => "FILTER",
5915 AppMode::FuzzyFilter => "FUZZY",
5916 AppMode::ColumnSearch => "COL",
5917 AppMode::Help => "HELP",
5918 AppMode::History => "HISTORY",
5919 AppMode::Debug => "DEBUG",
5920 AppMode::PrettyQuery => "PRETTY",
5921 AppMode::JumpToRow => "JUMP",
5922 AppMode::ColumnStats => "STATS",
5923 };
5924
5925 spans.push(Span::styled(
5927 format!("[{}]", mode_indicator),
5928 Style::default().fg(mode_color).add_modifier(Modifier::BOLD),
5929 ));
5930
5931 (status_style, mode_color)
5932 }
5933
5934 fn add_data_source_display(&self, spans: &mut Vec<Span>) {
5936 }
5939
5940 fn add_buffer_information(&self, spans: &mut Vec<Span>) {
5942 let index = self.state_container.buffers().current_index();
5943 let total = self.state_container.buffers().all_buffers().len();
5944
5945 if total > 1 {
5947 spans.push(Span::raw(" "));
5948 spans.push(Span::styled(
5949 format!("[{}/{}]", index + 1, total),
5950 Style::default().fg(Color::Yellow),
5951 ));
5952 }
5953
5954 if let Some(buffer) = self.state_container.buffers().current() {
5957 let query = buffer.get_input_text();
5958 if let Some(from_pos) = query.to_uppercase().find(" FROM ") {
5960 let after_from = &query[from_pos + 6..];
5961 if let Some(table_name) = after_from.split_whitespace().next() {
5963 let clean_name = table_name
5965 .trim_matches('"')
5966 .trim_matches('\'')
5967 .trim_matches('`');
5968
5969 spans.push(Span::raw(" "));
5970 spans.push(Span::styled(
5971 clean_name.to_string(),
5972 Style::default().fg(Color::Cyan),
5973 ));
5974 }
5975 }
5976 }
5977 }
5978
5979 fn add_mode_specific_info(&self, spans: &mut Vec<Span>, mode_color: Color, area: Rect) {
5981 match self.shadow_state.borrow().get_mode() {
5982 AppMode::Command => {
5983 if !self.get_input_text().trim().is_empty() {
5985 let (token_pos, total_tokens) = self.get_cursor_token_position();
5986 spans.push(Span::raw(" | "));
5987 spans.push(Span::styled(
5988 format!("Token {}/{}", token_pos, total_tokens),
5989 Style::default().fg(Color::DarkGray),
5990 ));
5991
5992 if let Some(token) = self.get_token_at_cursor() {
5994 spans.push(Span::raw(" "));
5995 spans.push(Span::styled(
5996 format!("[{}]", token),
5997 Style::default().fg(Color::Cyan),
5998 ));
5999 }
6000
6001 if let Some(error_msg) = self.check_parser_error(&self.get_input_text()) {
6003 spans.push(Span::raw(" | "));
6004 spans.push(Span::styled(
6005 format!("{} {}", self.config.display.icons.warning, error_msg),
6006 Style::default().fg(Color::Red).add_modifier(Modifier::BOLD),
6007 ));
6008 }
6009 }
6010 }
6011 AppMode::Results => {
6012 self.add_results_mode_info(spans, area);
6014 }
6015 AppMode::Search | AppMode::Filter | AppMode::FuzzyFilter | AppMode::ColumnSearch => {
6016 let pattern = self.get_input_text();
6018 if !pattern.is_empty() {
6019 spans.push(Span::raw(" | Pattern: "));
6020 spans.push(Span::styled(pattern, Style::default().fg(mode_color)));
6021 }
6022 }
6023 _ => {}
6024 }
6025 }
6026
6027 fn add_results_mode_info(&self, spans: &mut Vec<Span>, area: Rect) {
6029 let total_rows = self.get_row_count();
6030 if total_rows > 0 {
6031 let selected = self.state_container.navigation().selected_row + 1;
6033 spans.push(Span::raw(" | "));
6034
6035 let selection_mode = self.get_selection_mode();
6037 let mode_text = match selection_mode {
6038 SelectionMode::Cell => "CELL",
6039 SelectionMode::Row => "ROW",
6040 SelectionMode::Column => "COL",
6041 };
6042 spans.push(Span::styled(
6043 format!("[{}]", mode_text),
6044 Style::default()
6045 .fg(Color::Cyan)
6046 .add_modifier(Modifier::BOLD),
6047 ));
6048
6049 spans.push(Span::raw(" "));
6050 spans.push(Span::styled(
6051 format!("Row {}/{}", selected, total_rows),
6052 Style::default().fg(Color::White),
6053 ));
6054
6055 let visual_col_display =
6058 if let Some(ref viewport_manager) = *self.viewport_manager.borrow() {
6059 viewport_manager.get_crosshair_col() + 1
6060 } else {
6061 1
6062 };
6063 spans.push(Span::raw(" "));
6064 spans.push(Span::styled(
6065 format!("({},{})", visual_col_display, selected),
6066 Style::default().fg(Color::DarkGray),
6067 ));
6068
6069 if let Some(ref mut viewport_manager) = *self.viewport_manager.borrow_mut() {
6071 let available_width = area.width.saturating_sub(TABLE_BORDER_WIDTH) as u16;
6072 let visual_col = viewport_manager.get_crosshair_col();
6074 if let Some(x_pos) =
6075 viewport_manager.get_column_x_position(visual_col, available_width)
6076 {
6077 let terminal_x = x_pos + 2;
6079 let terminal_y = (selected as u16)
6080 .saturating_sub(self.state_container.get_scroll_offset().0 as u16)
6081 + 3;
6082 spans.push(Span::raw(" "));
6083 spans.push(Span::styled(
6084 format!("[{}x{}]", terminal_x, terminal_y),
6085 Style::default().fg(Color::DarkGray),
6086 ));
6087 }
6088 }
6089
6090 if let Some(dataview) = self.state_container.get_buffer_dataview() {
6092 let headers = dataview.column_names();
6093
6094 let (visual_row, visual_col) =
6097 if let Some(ref viewport_manager) = *self.viewport_manager.borrow() {
6098 (
6099 viewport_manager.get_crosshair_row(),
6100 viewport_manager.get_crosshair_col(),
6101 )
6102 } else {
6103 (0, 0)
6104 };
6105
6106 if visual_col < headers.len() {
6108 spans.push(Span::raw(" | Col: "));
6109 spans.push(Span::styled(
6110 headers[visual_col].clone(),
6111 Style::default().fg(Color::Cyan),
6112 ));
6113
6114 let viewport_info =
6116 if let Some(ref viewport_manager) = *self.viewport_manager.borrow() {
6117 let viewport_rows = viewport_manager.get_viewport_rows();
6118 let viewport_height = viewport_rows.end - viewport_rows.start;
6119 format!("[V:{},{} @ {}r]", visual_row, visual_col, viewport_height)
6120 } else {
6121 format!("[V:{},{}]", visual_row, visual_col)
6122 };
6123 spans.push(Span::raw(" "));
6124 spans.push(Span::styled(
6125 viewport_info,
6126 Style::default().fg(Color::Magenta),
6127 ));
6128 }
6129 }
6130 }
6131 }
6132
6133 fn render_status_line(&self, f: &mut Frame, area: Rect) {
6134 let mut spans = Vec::new();
6135
6136 let (status_style, mode_color) = self.add_mode_styling(&mut spans);
6138
6139 self.add_data_source_display(&mut spans);
6141
6142 self.add_buffer_information(&mut spans);
6144
6145 self.add_mode_specific_info(&mut spans, mode_color, area);
6147
6148 self.add_query_source_indicator(&mut spans);
6150
6151 self.add_case_sensitivity_indicator(&mut spans);
6153
6154 self.add_column_packing_indicator(&mut spans);
6156
6157 self.add_status_message(&mut spans);
6159
6160 let help_text = self.get_help_text_for_mode();
6162
6163 self.add_global_indicators(&mut spans);
6164
6165 self.add_shadow_state_display(&mut spans);
6167
6168 self.add_help_text_display(&mut spans, help_text, area);
6169
6170 let status_line = Line::from(spans);
6171 let status = Paragraph::new(status_line)
6172 .block(Block::default().borders(Borders::ALL))
6173 .style(status_style);
6174 f.render_widget(status, area);
6175 }
6176
6177 fn build_table_context(
6180 &self,
6181 area: Rect,
6182 provider: &dyn DataProvider,
6183 ) -> crate::ui::rendering::table_render_context::TableRenderContext {
6184 use crate::ui::rendering::table_render_context::TableRenderContextBuilder;
6185
6186 let row_count = provider.get_row_count();
6187 let available_width = area.width.saturating_sub(TABLE_BORDER_WIDTH) as u16;
6188 let available_height = area.height as u16;
6191
6192 let headers = {
6194 let viewport_manager = self.viewport_manager.borrow();
6195 let viewport_manager = viewport_manager
6196 .as_ref()
6197 .expect("ViewportManager must exist");
6198 viewport_manager.get_column_names_ordered()
6199 };
6200
6201 {
6203 let mut viewport_opt = self.viewport_manager.borrow_mut();
6204 if let Some(ref mut viewport_manager) = *viewport_opt {
6205 let data_rows = Self::calculate_table_data_rows(available_height);
6207 viewport_manager.update_terminal_size(available_width, data_rows);
6208 let _ = viewport_manager.get_column_widths(); }
6210 }
6211
6212 let (pinned_visual_positions, crosshair_column_position, _) = {
6214 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
6215 let viewport_manager = viewport_manager_borrow
6216 .as_mut()
6217 .expect("ViewportManager must exist for rendering");
6218 let info = viewport_manager.get_visible_columns_info(available_width);
6219
6220 let visible_indices = &info.0;
6224 let pinned_source_indices = &info.1;
6225
6226 let mut pinned_visual_positions = Vec::new();
6229 for &source_idx in pinned_source_indices {
6230 if let Some(visual_pos) = visible_indices.iter().position(|&x| x == source_idx) {
6231 pinned_visual_positions.push(visual_pos);
6232 }
6233 }
6234
6235 let crosshair_column_position =
6239 if let Some((_, col_pos)) = viewport_manager.get_crosshair_viewport_position() {
6240 col_pos
6241 } else {
6242 0
6244 };
6245
6246 let crosshair_visual = viewport_manager.get_crosshair_col();
6247
6248 (
6249 pinned_visual_positions,
6250 crosshair_column_position,
6251 crosshair_visual,
6252 )
6253 };
6254
6255 let row_viewport_start = self
6257 .state_container
6258 .navigation()
6259 .scroll_offset
6260 .0
6261 .min(row_count.saturating_sub(1));
6262 let row_viewport_end = (row_viewport_start + available_height as usize).min(row_count);
6263 let visible_row_indices: Vec<usize> = (row_viewport_start..row_viewport_end).collect();
6264
6265 let (column_headers, data_to_display, column_widths_visual) = {
6267 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
6268 if let Some(ref mut viewport_manager) = *viewport_manager_borrow {
6269 viewport_manager.get_visual_display(available_width, &visible_row_indices)
6270 } else {
6271 let visible_rows = provider
6273 .get_visible_rows(row_viewport_start, row_viewport_end - row_viewport_start);
6274 let widths = vec![15u16; headers.len()];
6275 (headers.clone(), visible_rows, widths)
6276 }
6277 };
6278
6279 let sort_state = self
6281 .buffer()
6282 .get_dataview()
6283 .map(|dv| dv.get_sort_state().clone());
6284
6285 let fuzzy_filter_pattern = if self.state_container.is_fuzzy_filter_active() {
6287 let pattern = self.state_container.get_fuzzy_filter_pattern();
6288 if !pattern.is_empty() {
6289 Some(pattern)
6290 } else {
6291 None
6292 }
6293 } else {
6294 None
6295 };
6296
6297 let selected_row = self.state_container.navigation().selected_row;
6299 let selected_col = crosshair_column_position;
6300
6301 trace!(target: "search", "Building TableRenderContext: selected_row={}, selected_col={}, mode={:?}",
6303 selected_row, selected_col, self.state_container.get_selection_mode());
6304
6305 TableRenderContextBuilder::new()
6306 .row_count(row_count)
6307 .visible_rows(visible_row_indices.clone(), data_to_display)
6308 .columns(column_headers, column_widths_visual)
6309 .pinned_columns(pinned_visual_positions)
6310 .selection(
6311 selected_row,
6312 selected_col,
6313 self.state_container.get_selection_mode(),
6314 )
6315 .row_viewport(row_viewport_start..row_viewport_end)
6316 .sort_state(sort_state)
6317 .display_options(
6318 self.state_container.is_show_row_numbers(),
6319 self.shadow_state.borrow().get_mode(),
6320 )
6321 .filter(
6322 fuzzy_filter_pattern,
6323 self.state_container.is_case_insensitive(),
6324 )
6325 .dimensions(available_width, available_height)
6326 .build()
6327 }
6328
6329 fn render_table_with_provider(&self, f: &mut Frame, area: Rect, provider: &dyn DataProvider) {
6332 let context = self.build_table_context(area, provider);
6334
6335 crate::ui::rendering::table_renderer::render_table(f, area, &context);
6337 }
6338
6339 fn render_help(&mut self, f: &mut Frame, area: Rect) {
6340 self.render_help_two_column(f, area);
6342 }
6343
6344 fn render_help_two_column(&self, f: &mut Frame, area: Rect) {
6345 let chunks = Layout::default()
6347 .direction(Direction::Horizontal)
6348 .constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
6349 .split(area);
6350
6351 let left_content = HelpText::left_column();
6353 let right_content = HelpText::right_column();
6354
6355 let visible_height = area.height.saturating_sub(2) as usize; let left_total_lines = left_content.len();
6358 let right_total_lines = right_content.len();
6359 let max_lines = left_total_lines.max(right_total_lines);
6360
6361 let scroll_offset = { self.state_container.help_scroll_offset() as usize };
6363
6364 let left_visible: Vec<Line> = left_content
6366 .into_iter()
6367 .skip(scroll_offset)
6368 .take(visible_height)
6369 .collect();
6370
6371 let right_visible: Vec<Line> = right_content
6372 .into_iter()
6373 .skip(scroll_offset)
6374 .take(visible_height)
6375 .collect();
6376
6377 let scroll_indicator = if max_lines > visible_height {
6379 format!(
6380 " (↓/↑ to scroll, {}/{})",
6381 scroll_offset + 1,
6382 max_lines.saturating_sub(visible_height) + 1
6383 )
6384 } else {
6385 String::new()
6386 };
6387
6388 let left_paragraph = Paragraph::new(Text::from(left_visible))
6390 .block(
6391 Block::default()
6392 .borders(Borders::ALL)
6393 .title(format!("Help - Commands{}", scroll_indicator)),
6394 )
6395 .style(Style::default());
6396
6397 let right_paragraph = Paragraph::new(Text::from(right_visible))
6399 .block(
6400 Block::default()
6401 .borders(Borders::ALL)
6402 .title("Help - Navigation & Features"),
6403 )
6404 .style(Style::default());
6405
6406 f.render_widget(left_paragraph, chunks[0]);
6407 f.render_widget(right_paragraph, chunks[1]);
6408 }
6409
6410 fn render_debug(&self, f: &mut Frame, area: Rect) {
6411 <Self as DebugContext>::render_debug(self, f, area);
6412 }
6413
6414 fn render_pretty_query(&self, f: &mut Frame, area: Rect) {
6415 <Self as DebugContext>::render_pretty_query(self, f, area);
6416 }
6417
6418 fn render_history(&self, f: &mut Frame, area: Rect) {
6419 let history_search = self.state_container.history_search();
6421 let matches_empty = history_search.matches.is_empty();
6422 let search_query_empty = history_search.query.is_empty();
6423
6424 if matches_empty {
6425 let no_history = if search_query_empty {
6426 "No command history found.\nExecute some queries to build history."
6427 } else {
6428 "No matches found for your search.\nTry a different search term."
6429 };
6430
6431 let placeholder = Paragraph::new(no_history)
6432 .block(
6433 Block::default()
6434 .borders(Borders::ALL)
6435 .title("Command History"),
6436 )
6437 .style(Style::default().fg(Color::DarkGray));
6438 f.render_widget(placeholder, area);
6439 return;
6440 }
6441
6442 let chunks = Layout::default()
6444 .direction(Direction::Vertical)
6445 .constraints([
6446 Constraint::Percentage(50), Constraint::Percentage(50), ])
6449 .split(area);
6450
6451 self.render_history_list(f, chunks[0]);
6452 self.render_selected_command_preview(f, chunks[1]);
6453 }
6454
6455 fn render_history_list(&self, f: &mut Frame, area: Rect) {
6456 let history_search = self.state_container.history_search();
6458 let matches = history_search.matches.clone();
6459 let selected_index = history_search.selected_index;
6460 let match_count = matches.len();
6461
6462 let history_items: Vec<Line> = matches
6464 .iter()
6465 .enumerate()
6466 .map(|(i, history_match)| {
6467 let entry = &history_match.entry;
6468 let is_selected = i == selected_index;
6469
6470 let success_indicator = if entry.success { "✓" } else { "✗" };
6471 let time_ago = {
6472 let elapsed = chrono::Utc::now() - entry.timestamp;
6473 if elapsed.num_days() > 0 {
6474 format!("{}d", elapsed.num_days())
6475 } else if elapsed.num_hours() > 0 {
6476 format!("{}h", elapsed.num_hours())
6477 } else if elapsed.num_minutes() > 0 {
6478 format!("{}m", elapsed.num_minutes())
6479 } else {
6480 "now".to_string()
6481 }
6482 };
6483
6484 let terminal_width = area.width as usize;
6486 let metadata_space = 15; let available_for_command = terminal_width.saturating_sub(metadata_space).max(50);
6488
6489 let command_text = if entry.command.len() > available_for_command {
6490 format!(
6491 "{}…",
6492 &entry.command[..available_for_command.saturating_sub(1)]
6493 )
6494 } else {
6495 entry.command.clone()
6496 };
6497
6498 let line_text = format!(
6499 "{} {} {} {}x {}",
6500 if is_selected { "►" } else { " " },
6501 command_text,
6502 success_indicator,
6503 entry.execution_count,
6504 time_ago
6505 );
6506
6507 let mut style = Style::default();
6508 if is_selected {
6509 style = style.bg(Color::DarkGray).add_modifier(Modifier::BOLD);
6510 }
6511 if !entry.success {
6512 style = style.fg(Color::Red);
6513 }
6514
6515 if !history_match.indices.is_empty() && is_selected {
6517 style = style.fg(Color::Yellow);
6518 }
6519
6520 Line::from(line_text).style(style)
6521 })
6522 .collect();
6523
6524 let history_paragraph = Paragraph::new(history_items)
6525 .block(Block::default().borders(Borders::ALL).title(format!(
6526 "History ({} matches) - j/k to navigate, Enter to select",
6527 match_count
6528 )))
6529 .wrap(ratatui::widgets::Wrap { trim: false });
6530
6531 f.render_widget(history_paragraph, area);
6532 }
6533
6534 fn render_selected_command_preview(&self, f: &mut Frame, area: Rect) {
6535 let history_search = self.state_container.history_search();
6537 let selected_match = history_search
6538 .matches
6539 .get(history_search.selected_index)
6540 .cloned();
6541
6542 if let Some(selected_match) = selected_match {
6543 let entry = &selected_match.entry;
6544
6545 use crate::recursive_parser::format_sql_pretty_compact;
6547
6548 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);
6554
6555 let max_lines = area.height.saturating_sub(2) as usize; if pretty_lines.len() > max_lines && cols_per_line < 12 {
6558 pretty_lines = format_sql_pretty_compact(&entry.command, 15);
6560 }
6561
6562 let mut highlighted_lines = Vec::new();
6564 for line in pretty_lines {
6565 highlighted_lines.push(self.sql_highlighter.simple_sql_highlight(&line));
6566 }
6567
6568 let preview_text = Text::from(highlighted_lines);
6569
6570 let duration_text = entry
6571 .duration_ms
6572 .map(|d| format!("{}ms", d))
6573 .unwrap_or_else(|| "?ms".to_string());
6574
6575 let success_text = if entry.success {
6576 "✓ Success"
6577 } else {
6578 "✗ Failed"
6579 };
6580
6581 let preview = Paragraph::new(preview_text)
6582 .block(Block::default().borders(Borders::ALL).title(format!(
6583 "Pretty SQL Preview: {} | {} | Used {}x",
6584 success_text, duration_text, entry.execution_count
6585 )))
6586 .scroll((0, 0)); f.render_widget(preview, area);
6589 } else {
6590 let empty_preview = Paragraph::new("No command selected")
6591 .block(Block::default().borders(Borders::ALL).title("Preview"))
6592 .style(Style::default().fg(Color::DarkGray));
6593 f.render_widget(empty_preview, area);
6594 }
6595 }
6596
6597 fn handle_column_stats_input(&mut self, key: crossterm::event::KeyEvent) -> Result<bool> {
6598 let mut ctx = crate::ui::input::input_handlers::StatsInputContext {
6600 buffer_manager: self.state_container.buffers_mut(),
6601 stats_widget: &mut self.stats_widget,
6602 shadow_state: &self.shadow_state,
6603 };
6604
6605 let result = crate::ui::input::input_handlers::handle_column_stats_input(&mut ctx, key)?;
6606
6607 if self.shadow_state.borrow().get_mode() == AppMode::Results {
6609 self.shadow_state
6610 .borrow_mut()
6611 .observe_mode_change(AppMode::Results, "column_stats_closed");
6612 }
6613
6614 Ok(result)
6615 }
6616
6617 fn handle_jump_to_row_input(&mut self, key: crossterm::event::KeyEvent) -> Result<bool> {
6618 match key.code {
6619 KeyCode::Enter => {
6620 let input = self.get_jump_to_row_input();
6622 self.complete_jump_to_row(&input);
6623 }
6624 _ => {
6625 self.process_jump_to_row_key(key);
6627 }
6628 }
6629 Ok(false)
6630 }
6631
6632 fn render_column_stats(&self, f: &mut Frame, area: Rect) {
6633 self.stats_widget.render(
6635 f,
6636 area,
6637 self.state_container
6638 .current_buffer()
6639 .expect("Buffer should exist"),
6640 );
6641 }
6642
6643 fn handle_execute_query(&mut self) -> Result<bool> {
6649 use crate::ui::state::state_coordinator::StateCoordinator;
6650
6651 let query = self.get_input_text().trim().to_string();
6652 debug!(target: "action", "Executing query: {}", query);
6653
6654 let should_exit = StateCoordinator::handle_execute_query_with_refs(
6656 &mut self.state_container,
6657 &self.shadow_state,
6658 &query,
6659 )?;
6660
6661 if should_exit {
6662 return Ok(true);
6663 }
6664
6665 if !query.is_empty() && !query.starts_with(':') {
6667 if let Err(e) = self.execute_query_v2(&query) {
6668 self.state_container
6669 .set_status_message(format!("Error executing query: {}", e));
6670 }
6671 }
6673
6674 Ok(false) }
6676
6677 fn handle_buffer_action(&mut self, action: BufferAction) -> Result<bool> {
6678 match action {
6679 BufferAction::NextBuffer => {
6680 let message = self
6681 .buffer_handler
6682 .next_buffer(self.state_container.buffers_mut());
6683 debug!("{}", message);
6684 self.sync_after_buffer_switch();
6686 Ok(false)
6687 }
6688 BufferAction::PreviousBuffer => {
6689 let message = self
6690 .buffer_handler
6691 .previous_buffer(self.state_container.buffers_mut());
6692 debug!("{}", message);
6693 self.sync_after_buffer_switch();
6695 Ok(false)
6696 }
6697 BufferAction::QuickSwitch => {
6698 let message = self
6699 .buffer_handler
6700 .quick_switch(self.state_container.buffers_mut());
6701 debug!("{}", message);
6702 self.sync_after_buffer_switch();
6704 Ok(false)
6705 }
6706 BufferAction::NewBuffer => {
6707 let message = self
6708 .buffer_handler
6709 .new_buffer(self.state_container.buffers_mut(), &self.config);
6710 debug!("{}", message);
6711 Ok(false)
6712 }
6713 BufferAction::CloseBuffer => {
6714 let (success, message) = self
6715 .buffer_handler
6716 .close_buffer(self.state_container.buffers_mut());
6717 debug!("{}", message);
6718 Ok(!success) }
6720 BufferAction::ListBuffers => {
6721 let buffer_list = self
6722 .buffer_handler
6723 .list_buffers(self.state_container.buffers());
6724 for line in &buffer_list {
6726 debug!("{}", line);
6727 }
6728 Ok(false)
6729 }
6730 BufferAction::SwitchToBuffer(buffer_index) => {
6731 let message = self
6732 .buffer_handler
6733 .switch_to_buffer(self.state_container.buffers_mut(), buffer_index);
6734 debug!("{}", message);
6735
6736 self.sync_after_buffer_switch();
6738
6739 Ok(false)
6740 }
6741 }
6742 }
6743
6744 fn handle_expand_asterisk(&mut self) -> Result<bool> {
6745 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
6746 if buffer.expand_asterisk(&self.hybrid_parser) {
6747 if buffer.get_edit_mode() == EditMode::SingleLine {
6749 let text = buffer.get_input_text();
6750 let cursor = buffer.get_input_cursor_position();
6751 self.set_input_text_with_cursor(text, cursor);
6752 }
6753 }
6754 }
6755 Ok(false)
6756 }
6757
6758 pub(crate) fn toggle_debug_mode(&mut self) {
6759 DebugContext::toggle_debug_mode(self);
6761 }
6762
6763 pub(crate) fn debug_generate_parser_info(&self, query: &str) -> String {
6768 self.hybrid_parser
6769 .get_detailed_debug_info(query, query.len())
6770 }
6771
6772 fn debug_generate_navigation_state(&self) -> String {
6773 let mut debug_info = String::new();
6774 debug_info.push_str("\n========== NAVIGATION DEBUG ==========\n");
6775 let current_column = self.state_container.get_current_column();
6776 let scroll_offset = self.state_container.get_scroll_offset();
6777 let nav_state = self.state_container.navigation();
6778
6779 debug_info.push_str(&format!("Buffer Column Position: {}\n", current_column));
6780 debug_info.push_str(&format!(
6781 "Buffer Scroll Offset: row={}, col={}\n",
6782 scroll_offset.0, scroll_offset.1
6783 ));
6784 debug_info.push_str(&format!(
6785 "NavigationState Column: {}\n",
6786 nav_state.selected_column
6787 ));
6788 debug_info.push_str(&format!(
6789 "NavigationState Row: {:?}\n",
6790 nav_state.selected_row
6791 ));
6792 debug_info.push_str(&format!(
6793 "NavigationState Scroll Offset: row={}, col={}\n",
6794 nav_state.scroll_offset.0, nav_state.scroll_offset.1
6795 ));
6796
6797 if current_column != nav_state.selected_column {
6799 debug_info.push_str(&format!(
6800 "⚠️ WARNING: Column mismatch! Buffer={}, Nav={}\n",
6801 current_column, nav_state.selected_column
6802 ));
6803 }
6804 if scroll_offset.1 != nav_state.scroll_offset.1 {
6805 debug_info.push_str(&format!(
6806 "⚠️ WARNING: Scroll column mismatch! Buffer={}, Nav={}\n",
6807 scroll_offset.1, nav_state.scroll_offset.1
6808 ));
6809 }
6810
6811 debug_info.push_str("\n--- Navigation Flow ---\n");
6812 debug_info.push_str(
6813 "(Enable RUST_LOG=sql_cli::ui::viewport_manager=debug,navigation=debug to see flow)\n",
6814 );
6815
6816 if let Some(dataview) = self.state_container.get_buffer_dataview() {
6818 let pinned_count = dataview.get_pinned_columns().len();
6819 let pinned_names = dataview.get_pinned_column_names();
6820 debug_info.push_str(&format!("Pinned Column Count: {}\n", pinned_count));
6821 if !pinned_names.is_empty() {
6822 debug_info.push_str(&format!("Pinned Column Names: {:?}\n", pinned_names));
6823 }
6824 debug_info.push_str(&format!("First Scrollable Column: {}\n", pinned_count));
6825
6826 if current_column < pinned_count {
6828 debug_info.push_str(&format!(
6829 "Current Position: PINNED area (column {})\n",
6830 current_column
6831 ));
6832 } else {
6833 debug_info.push_str(&format!(
6834 "Current Position: SCROLLABLE area (column {}, scrollable index {})\n",
6835 current_column,
6836 current_column - pinned_count
6837 ));
6838 }
6839
6840 let display_columns = dataview.get_display_columns();
6842 debug_info.push_str(&format!("\n--- COLUMN ORDERING ---\n"));
6843 debug_info.push_str(&format!(
6844 "Display column order (first 10): {:?}\n",
6845 &display_columns[..display_columns.len().min(10)]
6846 ));
6847 if display_columns.len() > 10 {
6848 debug_info.push_str(&format!(
6849 "... and {} more columns\n",
6850 display_columns.len() - 10
6851 ));
6852 }
6853
6854 if let Some(display_idx) = display_columns
6856 .iter()
6857 .position(|&idx| idx == current_column)
6858 {
6859 debug_info.push_str(&format!(
6860 "Current column {} is at display index {}/{}\n",
6861 current_column,
6862 display_idx,
6863 display_columns.len()
6864 ));
6865
6866 if display_idx + 1 < display_columns.len() {
6868 let next_col = display_columns[display_idx + 1];
6869 debug_info.push_str(&format!(
6870 "Next 'l' press should move to column {} (display index {})\n",
6871 next_col,
6872 display_idx + 1
6873 ));
6874 } else {
6875 debug_info.push_str("Next 'l' press should wrap to first column\n");
6876 }
6877 } else {
6878 debug_info.push_str(&format!(
6879 "WARNING: Current column {} not found in display order!\n",
6880 current_column
6881 ));
6882 }
6883 }
6884 debug_info.push_str("==========================================\n");
6885 debug_info
6886 }
6887
6888 fn debug_generate_column_search_state(&self) -> String {
6889 let mut debug_info = String::new();
6890 let show_column_search = self.shadow_state.borrow().get_mode() == AppMode::ColumnSearch
6891 || !self.state_container.column_search().pattern.is_empty();
6892 if show_column_search {
6893 let column_search = self.state_container.column_search();
6894 debug_info.push_str("\n========== COLUMN SEARCH STATE ==========\n");
6895 debug_info.push_str(&format!("Pattern: '{}'\n", column_search.pattern));
6896 debug_info.push_str(&format!(
6897 "Matching Columns: {} found\n",
6898 column_search.matching_columns.len()
6899 ));
6900 if !column_search.matching_columns.is_empty() {
6901 debug_info.push_str("Matches:\n");
6902 for (idx, (col_idx, col_name)) in column_search.matching_columns.iter().enumerate()
6903 {
6904 let marker = if idx == column_search.current_match {
6905 " <--"
6906 } else {
6907 ""
6908 };
6909 debug_info.push_str(&format!(
6910 " [{}] {} (index {}){}\n",
6911 idx, col_name, col_idx, marker
6912 ));
6913 }
6914 }
6915 debug_info.push_str(&format!(
6916 "Current Match Index: {}\n",
6917 column_search.current_match
6918 ));
6919 debug_info.push_str(&format!(
6920 "Current Column: {}\n",
6921 self.state_container.get_current_column()
6922 ));
6923 debug_info.push_str("==========================================\n");
6924 }
6925 debug_info
6926 }
6927
6928 pub(crate) fn debug_generate_trace_logs(&self) -> String {
6929 let mut debug_info = String::from("\n========== TRACE LOGS ==========\n");
6930 debug_info.push_str("(Most recent at bottom, last 100 entries)\n");
6931
6932 if let Some(ref log_buffer) = self.log_buffer {
6933 let recent_logs = log_buffer.get_recent(100);
6934 for entry in recent_logs {
6935 debug_info.push_str(&entry.format_for_display());
6936 debug_info.push('\n');
6937 }
6938 debug_info.push_str(&format!("Total log entries: {}\n", log_buffer.len()));
6939 } else {
6940 debug_info.push_str("Log buffer not initialized\n");
6941 }
6942 debug_info.push_str("================================\n");
6943
6944 debug_info
6945 }
6946
6947 pub(crate) fn debug_generate_state_logs(&self) -> String {
6949 let mut debug_info = String::new();
6950
6951 if let Some(ref debug_service) = self.debug_service {
6952 debug_info.push_str("\n========== STATE CHANGE LOGS ==========\n");
6953 debug_info.push_str("(Most recent at bottom, from DebugService)\n");
6954 let debug_entries = debug_service.get_entries();
6955 let recent = debug_entries.iter().rev().take(50).rev();
6956 for entry in recent {
6957 debug_info.push_str(&format!(
6958 "[{}] {:?} [{}]: {}\n",
6959 entry.timestamp, entry.level, entry.component, entry.message
6960 ));
6961 }
6962 debug_info.push_str(&format!(
6963 "Total state change entries: {}\n",
6964 debug_entries.len()
6965 ));
6966 debug_info.push_str("================================\n");
6967 } else {
6968 debug_info.push_str("\n========== STATE CHANGE LOGS ==========\n");
6969 debug_info.push_str("DebugService not available (service_container is None)\n");
6970 debug_info.push_str("================================\n");
6971 }
6972
6973 debug_info
6974 }
6975
6976 pub(crate) fn debug_extract_timing(&self, s: &str) -> Option<f64> {
6978 crate::ui::rendering::ui_layout_utils::extract_timing_from_debug_string(s)
6979 }
6980
6981 fn show_pretty_query(&mut self) {
6982 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
6983 self.shadow_state.borrow_mut().set_mode(
6984 AppMode::PrettyQuery,
6985 buffer,
6986 "pretty_query_show",
6987 );
6988 let query = buffer.get_input_text();
6989 self.debug_widget.generate_pretty_sql(&query);
6990 }
6991 }
6992
6993 fn add_global_indicators(&self, spans: &mut Vec<Span>) {
6995 if self.key_sequence_renderer.has_content() {
6996 let key_display = self.key_sequence_renderer.get_display();
6997 if !key_display.is_empty() {
6998 spans.push(Span::raw(" | Keys: "));
6999 spans.push(Span::styled(
7000 key_display,
7001 Style::default()
7002 .fg(Color::Cyan)
7003 .add_modifier(Modifier::ITALIC),
7004 ));
7005 }
7006 }
7007 }
7008
7009 fn add_query_source_indicator(&self, spans: &mut Vec<Span>) {
7010 if let Some(source) = self.state_container.get_last_query_source() {
7011 spans.push(Span::raw(" | "));
7012 let (icon, label, color) = match source.as_str() {
7013 "cache" => (
7014 &self.config.display.icons.cache,
7015 "CACHE".to_string(),
7016 Color::Cyan,
7017 ),
7018 "file" | "FileDataSource" => (
7019 &self.config.display.icons.file,
7020 "FILE".to_string(),
7021 Color::Green,
7022 ),
7023 "SqlServerDataSource" => (
7024 &self.config.display.icons.database,
7025 "SQL".to_string(),
7026 Color::Blue,
7027 ),
7028 "PublicApiDataSource" => (
7029 &self.config.display.icons.api,
7030 "API".to_string(),
7031 Color::Yellow,
7032 ),
7033 _ => (
7034 &self.config.display.icons.api,
7035 source.clone(),
7036 Color::Magenta,
7037 ),
7038 };
7039 spans.push(Span::raw(format!("{} ", icon)));
7040 spans.push(Span::styled(label, Style::default().fg(color)));
7041 }
7042 }
7043
7044 fn add_case_sensitivity_indicator(&self, spans: &mut Vec<Span>) {
7045 let case_insensitive = self.state_container.is_case_insensitive();
7046 if case_insensitive {
7047 spans.push(Span::raw(" | "));
7048 let icon = self.config.display.icons.case_insensitive.clone();
7049 spans.push(Span::styled(
7050 format!("{} CASE", icon),
7051 Style::default().fg(Color::Cyan),
7052 ));
7053 }
7054 }
7055
7056 fn add_column_packing_indicator(&self, spans: &mut Vec<Span>) {
7057 if let Some(ref viewport_manager) = *self.viewport_manager.borrow() {
7058 let packing_mode = viewport_manager.get_packing_mode();
7059 spans.push(Span::raw(" | "));
7060 let (text, color) = match packing_mode {
7061 ColumnPackingMode::DataFocus => ("DATA", Color::Cyan),
7062 ColumnPackingMode::HeaderFocus => ("HEADER", Color::Yellow),
7063 ColumnPackingMode::Balanced => ("BALANCED", Color::Green),
7064 };
7065 spans.push(Span::styled(text, Style::default().fg(color)));
7066 }
7067 }
7068
7069 fn add_status_message(&self, spans: &mut Vec<Span>) {
7070 let status_msg = self.state_container.get_buffer_status_message();
7071 if !status_msg.is_empty() {
7072 spans.push(Span::raw(" | "));
7073 spans.push(Span::styled(
7074 status_msg,
7075 Style::default()
7076 .fg(Color::Yellow)
7077 .add_modifier(Modifier::BOLD),
7078 ));
7079 }
7080 }
7081
7082 fn get_help_text_for_mode(&self) -> &str {
7083 match self.shadow_state.borrow().get_mode() {
7084 AppMode::Command => "Enter:Run | Tab:Complete | ↓:Results | F1:Help",
7085 AppMode::Results => match self.get_selection_mode() {
7086 SelectionMode::Cell => "v:Row mode | y:Yank cell | ↑:Edit | F1:Help",
7087 SelectionMode::Row => "v:Cell mode | y:Yank | f:Filter | ↑:Edit | F1:Help",
7088 SelectionMode::Column => "v:Cell mode | y:Yank col | ↑:Edit | F1:Help",
7089 },
7090 AppMode::Search | AppMode::Filter | AppMode::FuzzyFilter | AppMode::ColumnSearch => {
7091 "Enter:Apply | Esc:Cancel"
7092 }
7093 AppMode::Help | AppMode::Debug | AppMode::PrettyQuery | AppMode::ColumnStats => {
7094 "Esc:Close"
7095 }
7096 AppMode::History => "Enter:Select | Esc:Cancel",
7097 AppMode::JumpToRow => "Enter:Jump | Esc:Cancel",
7098 }
7099 }
7100
7101 fn add_shadow_state_display(&self, spans: &mut Vec<Span>) {
7102 let shadow_display = self.shadow_state.borrow().status_display();
7103 spans.push(Span::raw(" "));
7104 spans.push(Span::styled(
7105 shadow_display,
7106 Style::default().fg(Color::Cyan),
7107 ));
7108 }
7109
7110 fn add_help_text_display<'a>(&self, spans: &mut Vec<Span<'a>>, help_text: &'a str, area: Rect) {
7112 let current_length: usize = spans.iter().map(|s| s.content.len()).sum();
7113 let available_width = area.width.saturating_sub(TABLE_BORDER_WIDTH) as usize;
7114 let help_length = help_text.len();
7115
7116 if current_length + help_length + 3 < available_width {
7117 let padding = available_width - current_length - help_length - 3;
7118 spans.push(Span::raw(" ".repeat(padding)));
7119 spans.push(Span::raw(" | "));
7120 spans.push(Span::styled(
7121 help_text,
7122 Style::default().fg(Color::DarkGray),
7123 ));
7124 }
7125 }
7126}
7127
7128impl ActionHandlerContext for EnhancedTuiApp {
7130 fn previous_row(&mut self) {
7132 <Self as NavigationBehavior>::previous_row(self);
7133
7134 let current_row = self.state_container.navigation().selected_row;
7136 let current_col = self.state_container.navigation().selected_column;
7137 self.table_widget_manager
7138 .borrow_mut()
7139 .navigate_to(current_row, current_col);
7140 info!(target: "navigation", "previous_row: Updated TableWidgetManager to ({}, {})", current_row, current_col);
7141 }
7142
7143 fn next_row(&mut self) {
7144 info!(target: "navigation", "next_row called - calling NavigationBehavior::next_row");
7145 <Self as NavigationBehavior>::next_row(self);
7146
7147 let current_row = self.state_container.navigation().selected_row;
7149 let current_col = self.state_container.navigation().selected_column;
7150 self.table_widget_manager
7151 .borrow_mut()
7152 .navigate_to(current_row, current_col);
7153 info!(target: "navigation", "next_row: Updated TableWidgetManager to ({}, {})", current_row, current_col);
7154 }
7155
7156 fn move_column_left(&mut self) {
7157 <Self as ColumnBehavior>::move_column_left(self);
7158
7159 let current_row = self.state_container.navigation().selected_row;
7161 let current_col = self.state_container.navigation().selected_column;
7162 self.table_widget_manager
7163 .borrow_mut()
7164 .navigate_to(current_row, current_col);
7165 info!(target: "navigation", "move_column_left: Updated TableWidgetManager to ({}, {})", current_row, current_col);
7166 }
7167
7168 fn move_column_right(&mut self) {
7169 <Self as ColumnBehavior>::move_column_right(self);
7170
7171 let current_row = self.state_container.navigation().selected_row;
7173 let current_col = self.state_container.navigation().selected_column;
7174 self.table_widget_manager
7175 .borrow_mut()
7176 .navigate_to(current_row, current_col);
7177 info!(target: "navigation", "move_column_right: Updated TableWidgetManager to ({}, {})", current_row, current_col);
7178 }
7179
7180 fn page_up(&mut self) {
7181 <Self as NavigationBehavior>::page_up(self);
7182 }
7183
7184 fn page_down(&mut self) {
7185 <Self as NavigationBehavior>::page_down(self);
7186 }
7187
7188 fn goto_first_row(&mut self) {
7189 use crate::ui::state::state_coordinator::StateCoordinator;
7190
7191 <Self as NavigationBehavior>::goto_first_row(self);
7193
7194 StateCoordinator::goto_first_row_with_refs(
7196 &mut self.state_container,
7197 Some(&self.vim_search_adapter),
7198 Some(&self.viewport_manager),
7199 );
7200 }
7201
7202 fn goto_last_row(&mut self) {
7203 use crate::ui::state::state_coordinator::StateCoordinator;
7204
7205 <Self as NavigationBehavior>::goto_last_row(self);
7207
7208 StateCoordinator::goto_last_row_with_refs(&mut self.state_container);
7210 }
7211
7212 fn goto_first_column(&mut self) {
7213 <Self as ColumnBehavior>::goto_first_column(self);
7214 }
7215
7216 fn goto_last_column(&mut self) {
7217 <Self as ColumnBehavior>::goto_last_column(self);
7218 }
7219
7220 fn goto_row(&mut self, row: usize) {
7221 use crate::ui::state::state_coordinator::StateCoordinator;
7222
7223 <Self as NavigationBehavior>::goto_line(self, row + 1); StateCoordinator::goto_row_with_refs(&mut self.state_container, row);
7228 }
7229
7230 fn goto_column(&mut self, col: usize) {
7231 let current_col = self.state_container.get_current_column();
7234 if col < current_col {
7235 for _ in 0..(current_col - col) {
7236 <Self as ColumnBehavior>::move_column_left(self);
7237 }
7238 } else if col > current_col {
7239 for _ in 0..(col - current_col) {
7240 <Self as ColumnBehavior>::move_column_right(self);
7241 }
7242 }
7243 }
7244
7245 fn set_mode(&mut self, mode: AppMode) {
7247 self.set_mode_via_shadow_state(mode, "action_handler");
7249 }
7250
7251 fn get_mode(&self) -> AppMode {
7252 self.shadow_state.borrow().get_mode()
7253 }
7254
7255 fn set_status_message(&mut self, message: String) {
7256 self.state_container.set_status_message(message);
7257 }
7258
7259 fn toggle_column_pin(&mut self) {
7261 self.toggle_column_pin_impl();
7263 }
7264
7265 fn hide_current_column(&mut self) {
7266 <Self as ColumnBehavior>::hide_current_column(self);
7267 }
7268
7269 fn unhide_all_columns(&mut self) {
7270 <Self as ColumnBehavior>::unhide_all_columns(self);
7271 }
7272
7273 fn clear_all_pinned_columns(&mut self) {
7274 self.clear_all_pinned_columns_impl();
7276 }
7277
7278 fn export_to_csv(&mut self) {
7280 self.state_container
7282 .set_status_message("CSV export not yet implemented".to_string());
7283 }
7284
7285 fn export_to_json(&mut self) {
7286 self.state_container
7288 .set_status_message("JSON export not yet implemented".to_string());
7289 }
7290
7291 fn yank_cell(&mut self) {
7293 YankBehavior::yank_cell(self);
7294 }
7295
7296 fn yank_row(&mut self) {
7297 YankBehavior::yank_row(self);
7298 }
7299
7300 fn yank_column(&mut self) {
7301 YankBehavior::yank_column(self);
7302 }
7303
7304 fn yank_all(&mut self) {
7305 YankBehavior::yank_all(self);
7306 }
7307
7308 fn yank_query(&mut self) {
7309 YankBehavior::yank_query(self);
7310 }
7311
7312 fn toggle_selection_mode(&mut self) {
7314 self.state_container.toggle_selection_mode();
7315 let new_mode = self.state_container.get_selection_mode();
7316 let msg = match new_mode {
7317 SelectionMode::Cell => "Cell mode - Navigate to select individual cells",
7318 SelectionMode::Row => "Row mode - Navigate to select rows",
7319 SelectionMode::Column => "Column mode - Navigate to select columns",
7320 };
7321 self.state_container.set_status_message(msg.to_string());
7322 }
7323
7324 fn toggle_row_numbers(&mut self) {
7325 let current = self.state_container.is_show_row_numbers();
7326 self.state_container.set_show_row_numbers(!current);
7327 let message = if !current {
7328 "Row numbers: ON (showing line numbers)".to_string()
7329 } else {
7330 "Row numbers: OFF".to_string()
7331 };
7332 self.state_container.set_status_message(message);
7333 self.calculate_optimal_column_widths();
7335 }
7336
7337 fn toggle_compact_mode(&mut self) {
7338 let current_mode = self.state_container.is_compact_mode();
7339 self.state_container.set_compact_mode(!current_mode);
7340 let message = if !current_mode {
7341 "Compact mode enabled"
7342 } else {
7343 "Compact mode disabled"
7344 };
7345 self.state_container.set_status_message(message.to_string());
7346 }
7347
7348 fn toggle_case_insensitive(&mut self) {
7349 let current = self.state_container.is_case_insensitive();
7350 self.state_container.set_case_insensitive(!current);
7351 self.state_container.set_status_message(format!(
7352 "Case-insensitive string comparisons: {}",
7353 if !current { "ON" } else { "OFF" }
7354 ));
7355 }
7356
7357 fn toggle_key_indicator(&mut self) {
7358 let enabled = !self.key_indicator.enabled;
7359 self.key_indicator.set_enabled(enabled);
7360 self.key_sequence_renderer.set_enabled(enabled);
7361 self.state_container.set_status_message(format!(
7362 "Key press indicator {}",
7363 if enabled { "enabled" } else { "disabled" }
7364 ));
7365 }
7366
7367 fn clear_filter(&mut self) {
7369 if let Some(dataview) = self.state_container.get_buffer_dataview() {
7371 if dataview.has_filter() {
7372 if let Some(dataview_mut) = self.state_container.get_buffer_dataview_mut() {
7374 dataview_mut.clear_filter();
7375 self.state_container
7376 .set_status_message("Filter cleared".to_string());
7377 }
7378
7379 self.sync_dataview_to_managers();
7382 } else {
7383 self.state_container
7384 .set_status_message("No active filter to clear".to_string());
7385 }
7386 } else {
7387 self.state_container
7388 .set_status_message("No data loaded".to_string());
7389 }
7390 }
7391
7392 fn clear_line(&mut self) {
7393 self.state_container.clear_line();
7394 }
7395
7396 fn start_search(&mut self) {
7398 self.start_vim_search();
7399 }
7400
7401 fn start_column_search(&mut self) {
7402 self.enter_search_mode(SearchMode::ColumnSearch);
7403 }
7404
7405 fn start_filter(&mut self) {
7406 self.enter_search_mode(SearchMode::Filter);
7407 }
7408
7409 fn start_fuzzy_filter(&mut self) {
7410 self.enter_search_mode(SearchMode::FuzzyFilter);
7411 }
7412
7413 fn exit_current_mode(&mut self) {
7414 let mode = self.shadow_state.borrow().get_mode();
7416 match mode {
7417 AppMode::Results => {
7418 self.state_container.set_mode(AppMode::Command);
7421 }
7422 AppMode::Command => {
7423 self.state_container.set_mode(AppMode::Results);
7424 }
7425 AppMode::Help => {
7426 self.state_container.set_mode(AppMode::Results);
7427 }
7428 AppMode::JumpToRow => {
7429 self.state_container.set_mode(AppMode::Results);
7430 <Self as InputBehavior>::clear_jump_to_row_input(self);
7431 self.state_container.jump_to_row_mut().is_active = false;
7433 self.state_container
7434 .set_status_message("Jump to row cancelled".to_string());
7435 }
7436 _ => {
7437 self.state_container.set_mode(AppMode::Results);
7439 }
7440 }
7441 }
7442
7443 fn toggle_debug_mode(&mut self) {
7444 <Self as DebugContext>::toggle_debug_mode(self);
7446 }
7447
7448 fn move_current_column_left(&mut self) {
7450 <Self as ColumnBehavior>::move_current_column_left(self);
7451 }
7452
7453 fn move_current_column_right(&mut self) {
7454 <Self as ColumnBehavior>::move_current_column_right(self);
7455 }
7456
7457 fn next_search_match(&mut self) {
7459 self.vim_search_next();
7460 }
7461
7462 fn previous_search_match(&mut self) {
7463 if self.vim_search_adapter.borrow().is_active()
7464 || self.vim_search_adapter.borrow().get_pattern().is_some()
7465 {
7466 self.vim_search_previous();
7467 }
7468 }
7469
7470 fn show_column_statistics(&mut self) {
7472 self.calculate_column_statistics();
7473 }
7474
7475 fn cycle_column_packing(&mut self) {
7476 let message = {
7477 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
7478 let viewport_manager = viewport_manager_borrow
7479 .as_mut()
7480 .expect("ViewportManager must exist");
7481 let new_mode = viewport_manager.cycle_packing_mode();
7482 format!("Column packing: {}", new_mode.display_name())
7483 };
7484 self.state_container.set_status_message(message);
7485 }
7486
7487 fn navigate_to_viewport_top(&mut self) {
7489 let result = {
7490 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
7491 if let Some(ref mut viewport_manager) = *viewport_manager_borrow {
7492 Some(viewport_manager.navigate_to_viewport_top())
7493 } else {
7494 None
7495 }
7496 };
7497
7498 if let Some(result) = result {
7499 self.sync_navigation_with_viewport();
7501
7502 self.state_container
7504 .set_selected_row(Some(result.row_position));
7505
7506 if result.viewport_changed {
7508 let scroll_offset = self.state_container.navigation().scroll_offset;
7509 self.state_container.set_scroll_offset(scroll_offset);
7510 }
7511 }
7512 }
7513
7514 fn navigate_to_viewport_middle(&mut self) {
7515 let result = {
7516 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
7517 if let Some(ref mut viewport_manager) = *viewport_manager_borrow {
7518 Some(viewport_manager.navigate_to_viewport_middle())
7519 } else {
7520 None
7521 }
7522 };
7523
7524 if let Some(result) = result {
7525 self.sync_navigation_with_viewport();
7527
7528 self.state_container
7530 .set_selected_row(Some(result.row_position));
7531
7532 if result.viewport_changed {
7534 let scroll_offset = self.state_container.navigation().scroll_offset;
7535 self.state_container.set_scroll_offset(scroll_offset);
7536 }
7537 }
7538 }
7539
7540 fn navigate_to_viewport_bottom(&mut self) {
7541 let result = {
7542 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
7543 if let Some(ref mut viewport_manager) = *viewport_manager_borrow {
7544 Some(viewport_manager.navigate_to_viewport_bottom())
7545 } else {
7546 None
7547 }
7548 };
7549
7550 if let Some(result) = result {
7551 self.sync_navigation_with_viewport();
7553
7554 self.state_container
7556 .set_selected_row(Some(result.row_position));
7557
7558 if result.viewport_changed {
7560 let scroll_offset = self.state_container.navigation().scroll_offset;
7561 self.state_container.set_scroll_offset(scroll_offset);
7562 }
7563 }
7564 }
7565
7566 fn move_input_cursor_left(&mut self) {
7568 self.state_container.move_input_cursor_left();
7569 }
7570
7571 fn move_input_cursor_right(&mut self) {
7572 self.state_container.move_input_cursor_right();
7573 }
7574
7575 fn move_input_cursor_home(&mut self) {
7576 self.state_container.set_input_cursor_position(0);
7577 }
7578
7579 fn move_input_cursor_end(&mut self) {
7580 let text_len = self.state_container.get_input_text().chars().count();
7581 self.state_container.set_input_cursor_position(text_len);
7582 }
7583
7584 fn backspace(&mut self) {
7585 self.state_container.backspace();
7586 }
7587
7588 fn delete(&mut self) {
7589 self.state_container.delete();
7590 }
7591
7592 fn undo(&mut self) {
7593 self.state_container.perform_undo();
7594 }
7595
7596 fn redo(&mut self) {
7597 self.state_container.perform_redo();
7598 }
7599
7600 fn start_jump_to_row(&mut self) {
7601 self.state_container.set_mode(AppMode::JumpToRow);
7602 self.shadow_state
7603 .borrow_mut()
7604 .observe_mode_change(AppMode::JumpToRow, "jump_to_row_requested");
7605 <Self as InputBehavior>::clear_jump_to_row_input(self);
7606
7607 self.state_container.jump_to_row_mut().is_active = true;
7609
7610 self.state_container
7611 .set_status_message("Enter row number (1-based):".to_string());
7612 }
7613
7614 fn clear_jump_to_row_input(&mut self) {
7615 <Self as InputBehavior>::clear_jump_to_row_input(self);
7616 }
7617
7618 fn toggle_cursor_lock(&mut self) {
7619 let is_locked = {
7621 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
7622 if let Some(ref mut viewport_manager) = *viewport_manager_borrow {
7623 viewport_manager.toggle_cursor_lock();
7624 Some(viewport_manager.is_cursor_locked())
7625 } else {
7626 None
7627 }
7628 };
7629
7630 if let Some(is_locked) = is_locked {
7631 let msg = if is_locked {
7632 "Cursor lock ON - cursor stays in viewport position while scrolling"
7633 } else {
7634 "Cursor lock OFF"
7635 };
7636 self.state_container.set_status_message(msg.to_string());
7637
7638 info!(target: "shadow_state",
7640 "Cursor lock toggled: {} (in {:?} mode)",
7641 if is_locked { "ON" } else { "OFF" },
7642 self.shadow_state.borrow().get_mode()
7643 );
7644 }
7645 }
7646
7647 fn toggle_viewport_lock(&mut self) {
7648 let is_locked = {
7650 let mut viewport_manager_borrow = self.viewport_manager.borrow_mut();
7651 if let Some(ref mut viewport_manager) = *viewport_manager_borrow {
7652 viewport_manager.toggle_viewport_lock();
7653 Some(viewport_manager.is_viewport_locked())
7654 } else {
7655 None
7656 }
7657 };
7658
7659 if let Some(is_locked) = is_locked {
7660 let msg = if is_locked {
7661 "Viewport lock ON - navigation constrained to current viewport"
7662 } else {
7663 "Viewport lock OFF"
7664 };
7665 self.state_container.set_status_message(msg.to_string());
7666
7667 info!(target: "shadow_state",
7669 "Viewport lock toggled: {} (in {:?} mode)",
7670 if is_locked { "ON" } else { "OFF" },
7671 self.shadow_state.borrow().get_mode()
7672 );
7673 }
7674 }
7675
7676 fn show_debug_info(&mut self) {
7678 <Self as DebugContext>::toggle_debug_mode(self);
7679 }
7680
7681 fn show_pretty_query(&mut self) {
7682 self.show_pretty_query();
7683 }
7684
7685 fn show_help(&mut self) {
7686 self.state_container.set_help_visible(true);
7687 self.set_mode_via_shadow_state(AppMode::Help, "help_requested");
7688 self.help_widget.on_enter();
7689 }
7690
7691 fn kill_line(&mut self) {
7693 use crate::ui::traits::input_ops::InputBehavior;
7694 InputBehavior::kill_line(self);
7695 let message = if !self.state_container.is_kill_ring_empty() {
7696 let kill_ring = self.state_container.get_kill_ring();
7697 format!(
7698 "Killed to end of line - {} chars in kill ring",
7699 kill_ring.len()
7700 )
7701 } else {
7702 "Kill line - nothing to kill".to_string()
7703 };
7704 self.state_container.set_status_message(message);
7705 }
7706
7707 fn kill_line_backward(&mut self) {
7708 use crate::ui::traits::input_ops::InputBehavior;
7709 InputBehavior::kill_line_backward(self);
7710 let message = if !self.state_container.is_kill_ring_empty() {
7711 let kill_ring = self.state_container.get_kill_ring();
7712 format!(
7713 "Killed to beginning of line - {} chars in kill ring",
7714 kill_ring.len()
7715 )
7716 } else {
7717 "Kill line backward - nothing to kill".to_string()
7718 };
7719 self.state_container.set_status_message(message);
7720 }
7721
7722 fn delete_word_backward(&mut self) {
7723 use crate::ui::traits::input_ops::InputBehavior;
7724 InputBehavior::delete_word_backward(self);
7725 }
7726
7727 fn delete_word_forward(&mut self) {
7728 use crate::ui::traits::input_ops::InputBehavior;
7729 InputBehavior::delete_word_forward(self);
7730 }
7731
7732 fn expand_asterisk(&mut self) {
7733 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
7734 if buffer.expand_asterisk(&self.hybrid_parser) {
7735 if buffer.get_edit_mode() == EditMode::SingleLine {
7737 let text = buffer.get_input_text();
7738 let cursor = buffer.get_input_cursor_position();
7739 self.set_input_text_with_cursor(text, cursor);
7740 }
7741 }
7742 }
7743 }
7744
7745 fn expand_asterisk_visible(&mut self) {
7746 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
7747 if buffer.expand_asterisk_visible() {
7748 if buffer.get_edit_mode() == EditMode::SingleLine {
7750 let text = buffer.get_input_text();
7751 let cursor = buffer.get_input_cursor_position();
7752 self.set_input_text_with_cursor(text, cursor);
7753 }
7754 }
7755 }
7756 }
7757
7758 fn previous_history_command(&mut self) {
7759 let history_entries = self
7760 .state_container
7761 .command_history()
7762 .get_navigation_entries();
7763 let history_commands: Vec<String> =
7764 history_entries.iter().map(|e| e.command.clone()).collect();
7765
7766 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
7767 if buffer.navigate_history_up(&history_commands) {
7768 self.sync_all_input_states();
7769 self.state_container
7770 .set_status_message("Previous command from history".to_string());
7771 }
7772 }
7773 }
7774
7775 fn next_history_command(&mut self) {
7776 let history_entries = self
7777 .state_container
7778 .command_history()
7779 .get_navigation_entries();
7780 let history_commands: Vec<String> =
7781 history_entries.iter().map(|e| e.command.clone()).collect();
7782
7783 if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
7784 if buffer.navigate_history_down(&history_commands) {
7785 self.sync_all_input_states();
7786 self.state_container
7787 .set_status_message("Next command from history".to_string());
7788 }
7789 }
7790 }
7791}
7792
7793impl NavigationBehavior for EnhancedTuiApp {
7795 fn viewport_manager(&self) -> &RefCell<Option<ViewportManager>> {
7796 &self.viewport_manager
7797 }
7798
7799 fn buffer_mut(&mut self) -> &mut dyn BufferAPI {
7800 self.state_container
7801 .current_buffer_mut()
7802 .expect("Buffer should exist")
7803 }
7804
7805 fn buffer(&self) -> &dyn BufferAPI {
7806 self.state_container
7807 .current_buffer()
7808 .expect("Buffer should exist")
7809 }
7810
7811 fn state_container(&self) -> &AppStateContainer {
7812 &self.state_container
7813 }
7814
7815 fn state_container_mut(&mut self) -> &mut AppStateContainer {
7816 &mut self.state_container
7817 }
7818
7819 fn get_row_count(&self) -> usize {
7820 self.get_row_count()
7821 }
7822
7823 fn set_mode_with_sync(&mut self, mode: AppMode, trigger: &str) {
7824 self.set_mode_via_shadow_state(mode, trigger);
7826 }
7827}
7828
7829impl ColumnBehavior for EnhancedTuiApp {
7831 fn viewport_manager(&self) -> &RefCell<Option<ViewportManager>> {
7832 &self.viewport_manager
7833 }
7834
7835 fn buffer_mut(&mut self) -> &mut dyn BufferAPI {
7836 self.state_container
7837 .current_buffer_mut()
7838 .expect("Buffer should exist")
7839 }
7840
7841 fn buffer(&self) -> &dyn BufferAPI {
7842 self.state_container
7843 .current_buffer()
7844 .expect("Buffer should exist")
7845 }
7846
7847 fn state_container(&self) -> &AppStateContainer {
7848 &self.state_container
7849 }
7850
7851 fn is_in_results_mode(&self) -> bool {
7852 self.shadow_state.borrow().is_in_results_mode()
7853 }
7854}
7855
7856impl InputBehavior for EnhancedTuiApp {
7857 fn buffer_manager(&mut self) -> &mut BufferManager {
7858 self.state_container.buffers_mut()
7859 }
7860
7861 fn cursor_manager(&mut self) -> &mut CursorManager {
7862 &mut self.cursor_manager
7863 }
7864
7865 fn set_input_text_with_cursor(&mut self, text: String, cursor: usize) {
7866 self.set_input_text_with_cursor(text, cursor)
7867 }
7868
7869 fn state_container(&self) -> &AppStateContainer {
7870 &self.state_container
7871 }
7872
7873 fn state_container_mut(&mut self) -> &mut AppStateContainer {
7874 &mut self.state_container
7875 }
7876
7877 fn buffer_mut(&mut self) -> &mut dyn BufferAPI {
7878 self.state_container
7879 .current_buffer_mut()
7880 .expect("Buffer should exist")
7881 }
7882
7883 fn set_mode_with_sync(&mut self, mode: AppMode, trigger: &str) {
7884 self.set_mode_via_shadow_state(mode, trigger);
7886 }
7887}
7888
7889impl YankBehavior for EnhancedTuiApp {
7890 fn buffer(&self) -> &dyn BufferAPI {
7891 self.state_container
7892 .current_buffer()
7893 .expect("Buffer should exist")
7894 }
7895
7896 fn buffer_mut(&mut self) -> &mut dyn BufferAPI {
7897 self.state_container
7898 .current_buffer_mut()
7899 .expect("Buffer should exist")
7900 }
7901
7902 fn state_container(&self) -> &AppStateContainer {
7903 &self.state_container
7904 }
7905
7906 fn set_status_message(&mut self, message: String) {
7907 self.state_container.set_status_message(message)
7908 }
7909
7910 fn set_error_status(&mut self, prefix: &str, error: anyhow::Error) {
7911 self.set_error_status(prefix, error)
7912 }
7913}
7914
7915impl BufferManagementBehavior for EnhancedTuiApp {
7916 fn buffer_manager(&mut self) -> &mut BufferManager {
7917 self.state_container.buffers_mut()
7918 }
7919
7920 fn buffer_handler(&mut self) -> &mut BufferHandler {
7921 &mut self.buffer_handler
7922 }
7923
7924 fn buffer(&self) -> &dyn BufferAPI {
7925 self.state_container
7926 .current_buffer()
7927 .expect("Buffer should exist")
7928 }
7929
7930 fn buffer_mut(&mut self) -> &mut dyn BufferAPI {
7931 self.state_container
7932 .current_buffer_mut()
7933 .expect("Buffer should exist")
7934 }
7935
7936 fn config(&self) -> &Config {
7937 &self.config
7938 }
7939
7940 fn cursor_manager(&mut self) -> &mut CursorManager {
7941 &mut self.cursor_manager
7942 }
7943
7944 fn set_input_text_with_cursor(&mut self, text: String, cursor: usize) {
7945 self.set_input_text_with_cursor(text, cursor)
7946 }
7947
7948 fn next_buffer(&mut self) -> String {
7949 self.save_viewport_to_current_buffer();
7951
7952 let result = self
7953 .buffer_handler
7954 .next_buffer(self.state_container.buffers_mut());
7955
7956 self.sync_after_buffer_switch();
7958
7959 result
7960 }
7961
7962 fn previous_buffer(&mut self) -> String {
7963 self.save_viewport_to_current_buffer();
7965
7966 let result = self
7967 .buffer_handler
7968 .previous_buffer(self.state_container.buffers_mut());
7969
7970 self.sync_after_buffer_switch();
7972
7973 result
7974 }
7975
7976 fn quick_switch_buffer(&mut self) -> String {
7977 self.save_viewport_to_current_buffer();
7979
7980 let result = self
7981 .buffer_handler
7982 .quick_switch(self.state_container.buffers_mut());
7983
7984 self.sync_after_buffer_switch();
7986
7987 result
7988 }
7989
7990 fn close_buffer(&mut self) -> (bool, String) {
7991 self.buffer_handler
7992 .close_buffer(self.state_container.buffers_mut())
7993 }
7994
7995 fn switch_to_buffer(&mut self, index: usize) -> String {
7996 self.save_viewport_to_current_buffer();
7998
7999 let result = self
8001 .buffer_handler
8002 .switch_to_buffer(self.state_container.buffers_mut(), index);
8003
8004 self.sync_after_buffer_switch();
8006
8007 result
8008 }
8009
8010 fn buffer_count(&self) -> usize {
8011 self.state_container.buffers().all_buffers().len()
8012 }
8013
8014 fn current_buffer_index(&self) -> usize {
8015 self.state_container.buffers().current_index()
8016 }
8017}
8018
8019pub fn run_enhanced_tui_multi(api_url: &str, data_files: Vec<&str>) -> Result<()> {
8020 let app = if !data_files.is_empty() {
8021 use crate::services::ApplicationOrchestrator;
8023
8024 let config = Config::default();
8026 let orchestrator = ApplicationOrchestrator::new(
8027 config.behavior.case_insensitive_default,
8028 config.behavior.hide_empty_columns,
8029 );
8030
8031 let mut app = orchestrator.create_tui_with_file(data_files[0])?;
8033
8034 for file_path in data_files.iter().skip(1) {
8036 if let Err(e) = orchestrator.load_additional_file(&mut app, file_path) {
8037 app.state_container
8038 .set_status_message(format!("Error loading {}: {}", file_path, e));
8039 continue;
8040 }
8041 }
8042
8043 if data_files.len() > 1 {
8045 app.state_container.buffers_mut().switch_to(0);
8046 app.sync_after_buffer_switch();
8048 app.state_container.set_status_message(format!(
8049 "Loaded {} files into separate buffers. Use Alt+Tab to switch.",
8050 data_files.len()
8051 ));
8052 } else if data_files.len() == 1 {
8053 app.update_parser_for_current_buffer();
8055 }
8056
8057 app
8058 } else {
8059 EnhancedTuiApp::new(api_url)
8060 };
8061
8062 app.run()
8063}
8064
8065pub fn run_enhanced_tui(api_url: &str, data_file: Option<&str>) -> Result<()> {
8066 let files = if let Some(file) = data_file {
8068 vec![file]
8069 } else {
8070 vec![]
8071 };
8072 run_enhanced_tui_multi(api_url, files)
8073}