1use crate::api_client::QueryResponse;
2use crate::buffer::{AppMode, BufferAPI, BufferManager, SortOrder};
3use crate::debug_service::DebugLevel;
4use crate::help_widget::HelpWidget;
5use crate::history::CommandHistory;
6use crate::history_widget::HistoryWidget;
7use crate::search_modes_widget::SearchModesWidget;
8use crate::stats_widget::StatsWidget;
9use crate::widget_traits::DebugInfoProvider;
12use anyhow::{anyhow, Result};
13use arboard::Clipboard;
14use chrono::{DateTime, Local};
15use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
16use std::cell::RefCell;
17use std::collections::{HashMap, VecDeque};
18use std::fmt;
19use std::time::{Duration, Instant};
20use tracing::{info, trace};
21
22#[derive(Debug, Clone, PartialEq)]
24pub enum Platform {
25 Windows,
26 Linux,
27 MacOS,
28 Unknown,
29}
30
31impl Platform {
32 pub fn detect() -> Self {
33 if cfg!(target_os = "windows") {
34 Platform::Windows
35 } else if cfg!(target_os = "linux") {
36 Platform::Linux
37 } else if cfg!(target_os = "macos") {
38 Platform::MacOS
39 } else {
40 Platform::Unknown
41 }
42 }
43}
44
45#[derive(Debug, Clone)]
47pub struct KeyPressEntry {
48 pub raw_event: KeyEvent,
50 pub first_timestamp: DateTime<Local>,
52 pub last_timestamp: DateTime<Local>,
54 pub repeat_count: usize,
56 pub platform: Platform,
58 pub interpreted_action: Option<String>,
60 pub app_mode: AppMode,
62 pub display_string: String,
64}
65
66impl KeyPressEntry {
67 pub fn new(key: KeyEvent, mode: AppMode, action: Option<String>) -> Self {
68 let display_string = Self::format_key(&key);
69 let now = Local::now();
70 Self {
71 raw_event: key,
72 first_timestamp: now,
73 last_timestamp: now,
74 repeat_count: 1,
75 platform: Platform::detect(),
76 interpreted_action: action,
77 app_mode: mode,
78 display_string,
79 }
80 }
81
82 pub fn is_same_key(&self, key: &KeyEvent, mode: &AppMode) -> bool {
84 let time_window = chrono::Duration::seconds(1);
88 let now = Local::now();
89 let time_diff = now - self.last_timestamp;
90
91 let code_match = self.raw_event.code == key.code;
92 let modifier_match = self.raw_event.modifiers == key.modifiers;
93 let mode_match = self.app_mode == *mode;
94 let time_match = time_diff < time_window;
95
96 tracing::trace!(
97 "is_same_key: code_match={}, modifier_match={}, mode_match={}, time_match={} ({}ms < 1000ms)",
98 code_match, modifier_match, mode_match, time_match, time_diff.num_milliseconds()
99 );
100
101 code_match && modifier_match && mode_match && time_match
102 }
103
104 pub fn add_repeat(&mut self) {
106 self.repeat_count += 1;
107 self.last_timestamp = Local::now();
108 }
109
110 pub fn display_with_count(&self) -> String {
112 if self.repeat_count > 1 {
113 match self.display_string.as_str() {
116 s if s.len() == 1 => format!("{}{}", self.repeat_count, s),
118 "↑" | "↓" | "←" | "→" => {
120 format!("{}{}", self.repeat_count, self.display_string)
121 }
122 _ => format!("{} x{}", self.display_string, self.repeat_count),
124 }
125 } else {
126 self.display_string.clone()
127 }
128 }
129
130 fn format_key(key: &KeyEvent) -> String {
132 let mut result = String::new();
133
134 if key.modifiers.contains(KeyModifiers::CONTROL) {
136 result.push_str("Ctrl+");
137 }
138 if key.modifiers.contains(KeyModifiers::ALT) {
139 result.push_str("Alt+");
140 }
141 if key.modifiers.contains(KeyModifiers::SHIFT) {
142 result.push_str("Shift+");
143 }
144
145 match key.code {
147 KeyCode::Char(c) => result.push(c),
148 KeyCode::Enter => result.push_str("Enter"),
149 KeyCode::Esc => result.push_str("Esc"),
150 KeyCode::Backspace => result.push_str("Backspace"),
151 KeyCode::Tab => result.push_str("Tab"),
152 KeyCode::Delete => result.push_str("Del"),
153 KeyCode::Insert => result.push_str("Ins"),
154 KeyCode::F(n) => result.push_str(&format!("F{}", n)),
155 KeyCode::Left => result.push_str("←"),
156 KeyCode::Right => result.push_str("→"),
157 KeyCode::Up => result.push_str("↑"),
158 KeyCode::Down => result.push_str("↓"),
159 KeyCode::Home => result.push_str("Home"),
160 KeyCode::End => result.push_str("End"),
161 KeyCode::PageUp => result.push_str("PgUp"),
162 KeyCode::PageDown => result.push_str("PgDn"),
163 _ => result.push_str("?"),
164 }
165
166 result
167 }
168
169 pub fn debug_string(&self) -> String {
171 let modifiers = if self.raw_event.modifiers.is_empty() {
172 String::new()
173 } else {
174 format!(" ({})", self.format_modifiers())
175 };
176
177 let action = self
178 .interpreted_action
179 .as_ref()
180 .map(|a| format!(" → {}", a))
181 .unwrap_or_default();
182
183 let repeat_info = if self.repeat_count > 1 {
184 format!(" x{}", self.repeat_count)
185 } else {
186 String::new()
187 };
188
189 format!(
190 "[{}] {}{}{} [{:?}]{}",
191 self.last_timestamp.format("%H:%M:%S.%3f"),
192 self.display_string,
193 repeat_info,
194 modifiers,
195 self.platform,
196 action
197 )
198 }
199
200 fn format_modifiers(&self) -> String {
201 let mut parts = Vec::new();
202 if self.raw_event.modifiers.contains(KeyModifiers::CONTROL) {
203 parts.push("Ctrl");
204 }
205 if self.raw_event.modifiers.contains(KeyModifiers::ALT) {
206 parts.push("Alt");
207 }
208 if self.raw_event.modifiers.contains(KeyModifiers::SHIFT) {
209 parts.push("Shift");
210 }
211 parts.join("+")
212 }
213}
214
215#[derive(Debug, Clone)]
217pub struct KeyPressHistory {
218 entries: VecDeque<KeyPressEntry>,
220 max_size: usize,
222}
223
224impl KeyPressHistory {
225 pub fn new(max_size: usize) -> Self {
226 Self {
227 entries: VecDeque::with_capacity(max_size),
228 max_size,
229 }
230 }
231
232 fn is_navigation_key(key: &KeyEvent) -> bool {
234 matches!(
235 key.code,
236 KeyCode::Up
237 | KeyCode::Down
238 | KeyCode::Left
239 | KeyCode::Right
240 | KeyCode::PageUp
241 | KeyCode::PageDown
242 | KeyCode::Home
243 | KeyCode::End
244 )
245 }
246
247 pub fn add(&mut self, entry: KeyPressEntry) {
249 if let Some(last_entry) = self.entries.back_mut() {
251 let time_diff = Local::now() - last_entry.last_timestamp;
253 tracing::debug!(
254 "Key coalesce check: last_key={:?}/{:?}, new_key={:?}/{:?}, mode_match={}, time_diff={}ms",
255 last_entry.raw_event.code,
256 last_entry.raw_event.modifiers,
257 entry.raw_event.code,
258 entry.raw_event.modifiers,
259 last_entry.app_mode == entry.app_mode,
260 time_diff.num_milliseconds()
261 );
262
263 if last_entry.is_same_key(&entry.raw_event, &entry.app_mode) {
264 tracing::debug!("Key coalesced! Count now: {}", last_entry.repeat_count + 1);
266 last_entry.add_repeat();
267 if entry.interpreted_action != last_entry.interpreted_action {
269 last_entry.interpreted_action = entry.interpreted_action;
270 }
271 return;
272 } else {
273 tracing::debug!("Key NOT coalesced - adding new entry");
274 }
275 }
276
277 if self.entries.len() >= self.max_size {
280 let mut removed = false;
286
287 for i in 0..self.entries.len() {
289 if Self::is_navigation_key(&self.entries[i].raw_event)
290 && self.entries[i].repeat_count == 1
291 {
292 self.entries.remove(i);
293 removed = true;
294 break;
295 }
296 }
297
298 if !removed {
300 let half = self.entries.len() / 2;
301 for i in 0..half {
302 if self.entries[i].repeat_count == 1 {
303 self.entries.remove(i);
304 removed = true;
305 break;
306 }
307 }
308 }
309
310 if !removed {
312 self.entries.pop_front();
313 }
314 }
315
316 self.entries.push_back(entry);
317 }
318
319 pub fn entries(&self) -> &VecDeque<KeyPressEntry> {
321 &self.entries
322 }
323
324 pub fn clear(&mut self) {
326 self.entries.clear();
327 }
328
329 pub fn format_history(&self) -> String {
331 let mut output = String::new();
332 output.push_str("========== KEY PRESS HISTORY ==========\n");
333 output.push_str(&format!(
334 "(Most recent at bottom, {} unique entries, max {})\n",
335 self.entries.len(),
336 self.max_size
337 ));
338
339 let total_presses: usize = self.entries.iter().map(|e| e.repeat_count).sum();
341 output.push_str(&format!(
342 "Total key presses (with repeats): {}\n",
343 total_presses
344 ));
345
346 for entry in &self.entries {
347 output.push_str(&format!(
348 "[{}] {}",
349 entry.last_timestamp.format("%H:%M:%S.%3f"),
350 entry.display_with_count()
351 ));
352
353 if !entry.raw_event.modifiers.is_empty() {
354 output.push_str(&format!(" ({})", entry.format_modifiers()));
355 }
356
357 output.push('\n');
358 }
359
360 output.push_str("========================================\n");
361 output
362 }
363
364 pub fn format_debug_history(&self) -> String {
366 let mut output = String::new();
367 output.push_str("========== DETAILED KEY HISTORY ==========\n");
368 output.push_str(&format!("Platform: {:?}\n", Platform::detect()));
369 output.push_str(&format!(
370 "(Most recent at bottom, last {} keys)\n",
371 self.max_size
372 ));
373
374 for entry in &self.entries {
375 output.push_str(&entry.debug_string());
376 output.push('\n');
377 }
378
379 output.push_str("==========================================\n");
380 output
381 }
382}
383
384#[derive(Debug, Clone)]
386pub struct InputState {
387 pub text: String,
388 pub cursor_position: usize,
389 pub last_executed_query: String,
390}
391
392impl InputState {
393 pub fn new() -> Self {
394 Self {
395 text: String::new(),
396 cursor_position: 0,
397 last_executed_query: String::new(),
398 }
399 }
400
401 pub fn clear(&mut self) {
402 let _old_text = self.text.clone();
403 self.text.clear();
404 self.cursor_position = 0;
405 }
408
409 pub fn set_text(&mut self, text: String) {
410 let _old_text = self.text.clone();
411 self.cursor_position = text.len();
414 self.text = text;
415 }
416
417 pub fn set_text_with_cursor(&mut self, text: String, cursor: usize) {
418 let _old_text = self.text.clone();
419 let _old_cursor = self.cursor_position;
420 self.text = text;
424 self.cursor_position = cursor;
425 }
426}
427
428#[derive(Debug, Clone)]
430pub enum SearchOperation {
431 StartSearch(String),
432 UpdatePattern(String, String), MatchesFound(usize),
434 NavigateToMatch(usize),
435 ClearSearch,
436 NoMatchesFound,
437}
438
439#[derive(Debug, Clone)]
441pub struct SearchHistoryEntry {
442 pub pattern: String,
443 pub match_count: usize,
444 pub timestamp: DateTime<Local>,
445 pub duration_ms: Option<u64>,
446}
447
448#[derive(Debug, Clone)]
450pub struct SearchState {
451 pub pattern: String,
452 pub matches: Vec<(usize, usize, usize, usize)>, pub current_match: usize,
454 pub is_active: bool,
455 pub history: VecDeque<SearchHistoryEntry>,
456 pub last_search_time: Option<std::time::Instant>,
457}
458
459impl SearchState {
460 pub fn new() -> Self {
461 Self {
462 pattern: String::new(),
463 matches: Vec::new(),
464 current_match: 0,
465 is_active: false,
466 history: VecDeque::with_capacity(20), last_search_time: None,
468 }
469 }
470
471 pub fn clear(&mut self) {
472 if self.is_active && !self.pattern.is_empty() {
474 let duration_ms = self
475 .last_search_time
476 .map(|t| t.elapsed().as_millis() as u64);
477 let entry = SearchHistoryEntry {
478 pattern: self.pattern.clone(),
479 match_count: self.matches.len(),
480 timestamp: Local::now(),
481 duration_ms,
482 };
483
484 if self.history.len() >= 20 {
486 self.history.pop_front();
487 }
488 self.history.push_back(entry);
489 }
490
491 self.pattern.clear();
492 self.matches.clear();
493 self.current_match = 0;
494 self.is_active = false;
495 self.last_search_time = None;
496 }
497}
498
499#[derive(Debug, Clone)]
501pub struct FilterState {
502 pub pattern: String,
503 pub filtered_indices: Vec<usize>,
504 pub filtered_data: Option<Vec<Vec<String>>>,
505 pub is_active: bool,
506 pub case_insensitive: bool,
507 pub total_filters: usize,
508 pub last_filter_time: Option<Instant>,
509 pub history: VecDeque<FilterHistoryEntry>,
510 pub max_history: usize,
511}
512
513#[derive(Debug, Clone)]
514pub struct FilterHistoryEntry {
515 pub pattern: String,
516 pub match_count: usize,
517 pub timestamp: chrono::DateTime<chrono::Local>,
518 pub duration_ms: Option<u64>,
519}
520
521impl FilterState {
522 pub fn new() -> Self {
523 Self {
524 pattern: String::new(),
525 filtered_indices: Vec::new(),
526 filtered_data: None,
527 is_active: false,
528 case_insensitive: true,
529 total_filters: 0,
530 last_filter_time: None,
531 history: VecDeque::with_capacity(20),
532 max_history: 20,
533 }
534 }
535
536 pub fn clear(&mut self) {
537 info!(target: "filter", "FilterState::clear() - had {} filtered rows for pattern '{}'",
538 self.filtered_indices.len(), self.pattern);
539
540 if !self.pattern.is_empty() && self.is_active {
542 let duration_ms = self
543 .last_filter_time
544 .as_ref()
545 .map(|t| t.elapsed().as_millis() as u64);
546 let entry = FilterHistoryEntry {
547 pattern: self.pattern.clone(),
548 match_count: self.filtered_indices.len(),
549 timestamp: chrono::Local::now(),
550 duration_ms,
551 };
552 self.history.push_front(entry);
553 if self.history.len() > self.max_history {
554 self.history.pop_back();
555 }
556 }
557
558 self.pattern.clear();
559 self.filtered_indices.clear();
560 self.filtered_data = None;
561 self.is_active = false;
562 self.last_filter_time = None;
563 }
564
565 pub fn set_pattern(&mut self, pattern: String) {
567 info!(target: "filter", "FilterState::set_pattern('{}') - was '{}'", pattern, self.pattern);
568 self.pattern = pattern;
569 if !self.pattern.is_empty() {
570 self.is_active = true;
571 self.total_filters += 1;
572 self.last_filter_time = Some(Instant::now());
573 } else {
574 self.is_active = false;
575 }
576 }
577
578 pub fn set_filtered_indices(&mut self, indices: Vec<usize>) {
580 info!(target: "filter", "FilterState::set_filtered_indices - {} rows match pattern '{}'",
581 indices.len(), self.pattern);
582 self.filtered_indices = indices;
583 }
584
585 pub fn set_filtered_data(&mut self, data: Option<Vec<Vec<String>>>) {
587 let count = data.as_ref().map(|d| d.len()).unwrap_or(0);
588 info!(target: "filter", "FilterState::set_filtered_data - {} rows", count);
589 self.filtered_data = data;
590 }
591
592 pub fn get_stats(&self) -> String {
594 format!(
595 "Total filters: {}, History items: {}, Current matches: {}",
596 self.total_filters,
597 self.history.len(),
598 self.filtered_indices.len()
599 )
600 }
601}
602
603#[derive(Debug, Clone)]
605pub struct ColumnSearchState {
606 pub pattern: String,
608
609 pub matching_columns: Vec<(usize, String)>,
611
612 pub current_match: usize,
614
615 pub is_active: bool,
617
618 pub history: VecDeque<ColumnSearchHistoryEntry>,
620
621 pub total_searches: usize,
623
624 pub last_search_time: Option<Instant>,
626}
627
628#[derive(Debug, Clone)]
629pub struct ColumnSearchHistoryEntry {
630 pub pattern: String,
632
633 pub match_count: usize,
635
636 pub matched_columns: Vec<String>,
638
639 pub timestamp: DateTime<Local>,
641
642 pub duration_ms: Option<u64>,
644}
645
646#[derive(Clone, Debug)]
648pub struct CompletionState {
649 pub suggestions: Vec<String>,
650 pub current_index: usize,
651 pub last_query: String,
652 pub last_cursor_pos: usize,
653 pub is_active: bool,
654 pub total_completions: usize,
656 pub last_completion_time: Option<std::time::Instant>,
657}
658
659impl Default for CompletionState {
660 fn default() -> Self {
661 Self::new()
662 }
663}
664
665impl CompletionState {
666 pub fn new() -> Self {
667 Self {
668 suggestions: Vec::new(),
669 current_index: 0,
670 last_query: String::new(),
671 last_cursor_pos: 0,
672 is_active: false,
673 total_completions: 0,
674 last_completion_time: None,
675 }
676 }
677
678 pub fn clear(&mut self) {
680 self.suggestions.clear();
681 self.current_index = 0;
682 self.is_active = false;
683 }
685
686 pub fn set_suggestions(&mut self, suggestions: Vec<String>) {
688 self.is_active = !suggestions.is_empty();
689 self.suggestions = suggestions;
690 self.current_index = 0;
691 if self.is_active {
692 self.last_completion_time = Some(std::time::Instant::now());
693 self.total_completions += 1;
694 }
695 }
696
697 pub fn next_suggestion(&mut self) {
699 if !self.suggestions.is_empty() {
700 self.current_index = (self.current_index + 1) % self.suggestions.len();
701 }
702 }
703
704 pub fn current_suggestion(&self) -> Option<&String> {
706 if self.is_active && !self.suggestions.is_empty() {
707 self.suggestions.get(self.current_index)
708 } else {
709 None
710 }
711 }
712
713 pub fn is_same_context(&self, query: &str, cursor_pos: usize) -> bool {
715 query == self.last_query && cursor_pos == self.last_cursor_pos
716 }
717
718 pub fn update_context(&mut self, query: String, cursor_pos: usize) {
720 self.last_query = query;
721 self.last_cursor_pos = cursor_pos;
722 }
723}
724
725impl Default for ColumnSearchState {
726 fn default() -> Self {
727 Self::new()
728 }
729}
730
731impl ColumnSearchState {
732 pub fn new() -> Self {
733 Self {
734 pattern: String::new(),
735 matching_columns: Vec::new(),
736 current_match: 0,
737 is_active: false,
738 history: VecDeque::with_capacity(20),
739 total_searches: 0,
740 last_search_time: None,
741 }
742 }
743
744 pub fn clear(&mut self) {
746 if self.is_active && !self.pattern.is_empty() {
748 let duration_ms = self
749 .last_search_time
750 .map(|t| t.elapsed().as_millis() as u64);
751 let entry = ColumnSearchHistoryEntry {
752 pattern: self.pattern.clone(),
753 match_count: self.matching_columns.len(),
754 matched_columns: self
755 .matching_columns
756 .iter()
757 .map(|(_, name)| name.clone())
758 .collect(),
759 timestamp: Local::now(),
760 duration_ms,
761 };
762 self.history.push_front(entry);
763
764 while self.history.len() > 20 {
766 self.history.pop_back();
767 }
768 }
769
770 self.pattern.clear();
771 self.matching_columns.clear();
772 self.current_match = 0;
773 self.is_active = false;
774 self.last_search_time = None;
775 }
776
777 pub fn set_matches(&mut self, matches: Vec<(usize, String)>) {
779 self.matching_columns = matches;
780 self.current_match = 0;
781 self.total_searches += 1;
782 self.last_search_time = Some(Instant::now());
783 }
784
785 pub fn next_match(&mut self) -> Option<(usize, String)> {
787 if self.matching_columns.is_empty() {
788 return None;
789 }
790
791 self.current_match = (self.current_match + 1) % self.matching_columns.len();
792 Some(self.matching_columns[self.current_match].clone())
793 }
794
795 pub fn prev_match(&mut self) -> Option<(usize, String)> {
797 if self.matching_columns.is_empty() {
798 return None;
799 }
800
801 self.current_match = if self.current_match == 0 {
802 self.matching_columns.len() - 1
803 } else {
804 self.current_match - 1
805 };
806 Some(self.matching_columns[self.current_match].clone())
807 }
808
809 pub fn current_match(&self) -> Option<(usize, String)> {
811 if self.matching_columns.is_empty() {
812 None
813 } else {
814 Some(self.matching_columns[self.current_match].clone())
815 }
816 }
817
818 pub fn get_stats(&self) -> String {
820 format!(
821 "Total searches: {}, History items: {}, Current matches: {}",
822 self.total_searches,
823 self.history.len(),
824 self.matching_columns.len()
825 )
826 }
827}
828
829#[derive(Debug, Clone)]
831pub struct CacheListState {
832 pub selected_index: usize,
833 pub cache_names: Vec<String>,
834}
835
836impl CacheListState {
837 pub fn new() -> Self {
838 Self {
839 selected_index: 0,
840 cache_names: Vec::new(),
841 }
842 }
843}
844
845#[derive(Debug, Clone)]
847pub struct ColumnStatsState {
848 pub column_index: usize,
849 pub is_visible: bool,
850}
851
852impl ColumnStatsState {
853 pub fn new() -> Self {
854 Self {
855 column_index: 0,
856 is_visible: false,
857 }
858 }
859}
860
861#[derive(Debug, Clone)]
863pub struct JumpToRowState {
864 pub input: String,
865 pub is_active: bool,
866}
867
868#[derive(Debug, Clone)]
870pub struct NavigationState {
871 pub selected_row: usize,
872 pub selected_column: usize,
873 pub scroll_offset: (usize, usize), pub viewport_rows: usize,
875 pub viewport_columns: usize,
876 pub total_rows: usize,
877 pub total_columns: usize,
878 pub last_visible_rows: usize,
879 pub viewport_lock: bool, pub viewport_lock_row: Option<usize>,
881 pub cursor_lock: bool, pub cursor_lock_position: Option<usize>, pub selection_history: VecDeque<(usize, usize)>, }
885
886impl NavigationState {
887 pub fn new() -> Self {
888 Self {
889 selected_row: 0,
890 selected_column: 0,
891 scroll_offset: (0, 0),
892 viewport_rows: 30,
893 viewport_columns: 10,
894 total_rows: 0,
895 total_columns: 0,
896 last_visible_rows: 0,
897 viewport_lock: false,
898 viewport_lock_row: None,
899 cursor_lock: false,
900 cursor_lock_position: None,
901 selection_history: VecDeque::with_capacity(50), }
903 }
904
905 pub fn reset(&mut self) {
907 self.selected_row = 0;
908 self.selected_column = 0;
909 self.scroll_offset = (0, 0);
910 self.total_rows = 0;
913 self.total_columns = 0;
914 self.last_visible_rows = 0;
915 self.viewport_lock = false;
916 self.viewport_lock_row = None;
917 self.cursor_lock = false;
918 self.cursor_lock_position = None;
919 self.selection_history.clear();
920 }
921
922 pub fn update_totals(&mut self, rows: usize, columns: usize) {
923 info!(target: "navigation", "NavigationState::update_totals - rows: {} -> {}, columns: {} -> {}",
924 self.total_rows, rows, self.total_columns, columns);
925
926 self.total_rows = rows;
927 self.total_columns = columns;
928
929 if self.selected_row >= rows && rows > 0 {
931 let old_row = self.selected_row;
932 self.selected_row = rows - 1;
933 info!(target: "navigation", "Adjusted selected_row from {} to {} (out of bounds)", old_row, self.selected_row);
934 }
935 if self.selected_column >= columns && columns > 0 {
936 let old_col = self.selected_column;
937 self.selected_column = columns - 1;
938 info!(target: "navigation", "Adjusted selected_column from {} to {} (out of bounds)", old_col, self.selected_column);
939 }
940 }
941
942 pub fn set_viewport_size(&mut self, rows: usize, columns: usize) {
943 info!(target: "navigation", "NavigationState::set_viewport_size - rows: {} -> {}, columns: {} -> {}",
944 self.viewport_rows, rows, self.viewport_columns, columns);
945 self.viewport_rows = rows;
946 self.viewport_columns = columns;
947 }
948
949 pub fn next_row(&mut self) -> bool {
951 if self.cursor_lock {
952 if let Some(lock_position) = self.cursor_lock_position {
954 let max_scroll = self.total_rows.saturating_sub(self.viewport_rows);
956 if self.scroll_offset.0 < max_scroll {
957 self.scroll_offset.0 += 1;
958 let new_data_row = self.scroll_offset.0 + lock_position;
960 if new_data_row < self.total_rows {
961 self.selected_row = new_data_row;
962 self.add_to_history(self.selected_row, self.selected_column);
963 info!(target: "navigation", "NavigationState::next_row (cursor locked) - scrolled to offset {}, cursor at row {}",
964 self.scroll_offset.0, self.selected_row);
965 return true;
966 }
967 }
968 return false;
969 }
970 }
971
972 if self.viewport_lock {
974 let viewport_bottom = self.scroll_offset.0 + self.viewport_rows - 1;
976 if self.selected_row >= viewport_bottom {
977 info!(target: "navigation", "NavigationState::next_row - at viewport bottom (row {}), viewport locked", self.selected_row);
978 return false; }
980 }
981
982 if self.selected_row < self.total_rows.saturating_sub(1) {
984 self.selected_row += 1;
985 self.add_to_history(self.selected_row, self.selected_column);
986 self.ensure_visible(self.selected_row, self.selected_column);
987 info!(target: "navigation", "NavigationState::next_row - moved to row {}", self.selected_row);
988 true
989 } else {
990 false
991 }
992 }
993
994 pub fn previous_row(&mut self) -> bool {
996 if self.cursor_lock {
997 if let Some(lock_position) = self.cursor_lock_position {
999 if self.scroll_offset.0 > 0 {
1001 self.scroll_offset.0 -= 1;
1002 let new_data_row = self.scroll_offset.0 + lock_position;
1004 self.selected_row = new_data_row;
1005 self.add_to_history(self.selected_row, self.selected_column);
1006 info!(target: "navigation", "NavigationState::previous_row (cursor locked) - scrolled to offset {}, cursor at row {}",
1007 self.scroll_offset.0, self.selected_row);
1008 return true;
1009 }
1010 return false;
1011 }
1012 }
1013
1014 if self.viewport_lock {
1016 let viewport_top = self.scroll_offset.0;
1018 if self.selected_row <= viewport_top {
1019 info!(target: "navigation", "NavigationState::previous_row - at viewport top (row {}), viewport locked", self.selected_row);
1020 return false; }
1022 }
1023
1024 if self.selected_row > 0 {
1026 self.selected_row -= 1;
1027 self.add_to_history(self.selected_row, self.selected_column);
1028 self.ensure_visible(self.selected_row, self.selected_column);
1029 info!(target: "navigation", "NavigationState::previous_row - moved to row {}", self.selected_row);
1030 true
1031 } else {
1032 false
1033 }
1034 }
1035
1036 pub fn next_column(&mut self) -> bool {
1038 if self.selected_column < self.total_columns.saturating_sub(1) {
1039 self.selected_column += 1;
1040 self.add_to_history(self.selected_row, self.selected_column);
1041 self.ensure_visible(self.selected_row, self.selected_column);
1042 info!(target: "navigation", "NavigationState::next_column - moved to column {}", self.selected_column);
1043 true
1044 } else {
1045 false
1046 }
1047 }
1048
1049 pub fn previous_column(&mut self) -> bool {
1051 if self.selected_column > 0 {
1052 self.selected_column -= 1;
1053 self.add_to_history(self.selected_row, self.selected_column);
1054 self.ensure_visible(self.selected_row, self.selected_column);
1055 info!(target: "navigation", "NavigationState::previous_column - moved to column {}", self.selected_column);
1056 true
1057 } else {
1058 false
1059 }
1060 }
1061
1062 pub fn jump_to_row(&mut self, row: usize) {
1064 let target_row = row.min(self.total_rows.saturating_sub(1));
1065 info!(target: "navigation", "NavigationState::jump_to_row - from {} to {}", self.selected_row, target_row);
1066 self.selected_row = target_row;
1067 self.add_to_history(self.selected_row, self.selected_column);
1068 self.ensure_visible(self.selected_row, self.selected_column);
1069 }
1070
1071 pub fn jump_to_first_row(&mut self) {
1073 info!(target: "navigation", "NavigationState::jump_to_first_row - from row {}", self.selected_row);
1074 self.selected_row = 0;
1075 self.add_to_history(self.selected_row, self.selected_column);
1076 self.ensure_visible(self.selected_row, self.selected_column);
1077 }
1078
1079 pub fn jump_to_last_row(&mut self) {
1081 let last_row = self.total_rows.saturating_sub(1);
1082 info!(target: "navigation", "NavigationState::jump_to_last_row - from {} to {}", self.selected_row, last_row);
1083 self.selected_row = last_row;
1084 self.add_to_history(self.selected_row, self.selected_column);
1085 self.ensure_visible(self.selected_row, self.selected_column);
1086 }
1087
1088 pub fn set_position(&mut self, row: usize, column: usize) {
1090 info!(target: "navigation", "NavigationState::set_position - ({}, {}) -> ({}, {})",
1091 self.selected_row, self.selected_column, row, column);
1092 self.selected_row = row.min(self.total_rows.saturating_sub(1));
1093 self.selected_column = column.min(self.total_columns.saturating_sub(1));
1094 self.add_to_history(self.selected_row, self.selected_column);
1095 self.ensure_visible(self.selected_row, self.selected_column);
1096 }
1097
1098 pub fn page_down(&mut self) {
1100 if self.cursor_lock {
1101 if let Some(lock_position) = self.cursor_lock_position {
1103 let max_scroll = self.total_rows.saturating_sub(self.viewport_rows);
1104 let new_scroll = (self.scroll_offset.0 + self.viewport_rows).min(max_scroll);
1105 if new_scroll != self.scroll_offset.0 {
1106 self.scroll_offset.0 = new_scroll;
1107 let new_data_row = self.scroll_offset.0 + lock_position;
1109 if new_data_row < self.total_rows {
1110 self.selected_row = new_data_row;
1111 self.add_to_history(self.selected_row, self.selected_column);
1112 info!(target: "navigation", "NavigationState::page_down (cursor locked) - scrolled to offset {}, cursor at row {}",
1113 self.scroll_offset.0, self.selected_row);
1114 }
1115 }
1116 return;
1117 }
1118 }
1119
1120 let old_row = self.selected_row;
1122 self.selected_row =
1123 (self.selected_row + self.viewport_rows).min(self.total_rows.saturating_sub(1));
1124 if self.selected_row != old_row {
1125 info!(target: "navigation", "NavigationState::page_down - from {} to {}", old_row, self.selected_row);
1126 self.add_to_history(self.selected_row, self.selected_column);
1127 self.ensure_visible(self.selected_row, self.selected_column);
1128 }
1129 }
1130
1131 pub fn page_up(&mut self) {
1133 if self.cursor_lock {
1134 if let Some(lock_position) = self.cursor_lock_position {
1136 let new_scroll = self.scroll_offset.0.saturating_sub(self.viewport_rows);
1137 if new_scroll != self.scroll_offset.0 {
1138 self.scroll_offset.0 = new_scroll;
1139 let new_data_row = self.scroll_offset.0 + lock_position;
1141 self.selected_row = new_data_row;
1142 self.add_to_history(self.selected_row, self.selected_column);
1143 info!(target: "navigation", "NavigationState::page_up (cursor locked) - scrolled to offset {}, cursor at row {}",
1144 self.scroll_offset.0, self.selected_row);
1145 }
1146 return;
1147 }
1148 }
1149
1150 let old_row = self.selected_row;
1152 self.selected_row = self.selected_row.saturating_sub(self.viewport_rows);
1153 if self.selected_row != old_row {
1154 info!(target: "navigation", "NavigationState::page_up - from {} to {}", old_row, self.selected_row);
1155 self.add_to_history(self.selected_row, self.selected_column);
1156 self.ensure_visible(self.selected_row, self.selected_column);
1157 }
1158 }
1159
1160 pub fn jump_to_viewport_top(&mut self) {
1162 let target_row = self.scroll_offset.0;
1163 if target_row != self.selected_row && target_row < self.total_rows {
1164 info!(target: "navigation", "NavigationState::jump_to_viewport_top - from {} to {} (viewport top)",
1165 self.selected_row, target_row);
1166 self.selected_row = target_row;
1167 self.add_to_history(self.selected_row, self.selected_column);
1168 }
1170 }
1171
1172 pub fn jump_to_viewport_middle(&mut self) {
1174 let viewport_start = self.scroll_offset.0;
1175 let viewport_end = (viewport_start + self.viewport_rows).min(self.total_rows);
1176 let target_row = viewport_start + (viewport_end - viewport_start) / 2;
1177
1178 if target_row != self.selected_row && target_row < self.total_rows {
1179 info!(target: "navigation", "NavigationState::jump_to_viewport_middle - from {} to {} (viewport middle)",
1180 self.selected_row, target_row);
1181 self.selected_row = target_row;
1182 self.add_to_history(self.selected_row, self.selected_column);
1183 }
1185 }
1186
1187 pub fn jump_to_viewport_bottom(&mut self) {
1189 let viewport_start = self.scroll_offset.0;
1190 let viewport_end = (viewport_start + self.viewport_rows).min(self.total_rows);
1191 let target_row = viewport_end.saturating_sub(1);
1192
1193 if target_row != self.selected_row && target_row < self.total_rows {
1194 info!(target: "navigation", "NavigationState::jump_to_viewport_bottom - from {} to {} (viewport bottom)",
1195 self.selected_row, target_row);
1196 self.selected_row = target_row;
1197 self.add_to_history(self.selected_row, self.selected_column);
1198 }
1200 }
1201
1202 pub fn is_position_visible(&self, row: usize, col: usize) -> bool {
1203 let (scroll_row, scroll_col) = self.scroll_offset;
1204 row >= scroll_row
1205 && row < scroll_row + self.viewport_rows
1206 && col >= scroll_col
1207 && col < scroll_col + self.viewport_columns
1208 }
1209
1210 pub fn ensure_visible(&mut self, row: usize, col: usize) {
1211 if self.viewport_lock {
1213 info!(target: "navigation", "NavigationState::ensure_visible - viewport locked, not adjusting scroll");
1214 return;
1215 }
1216
1217 let (mut scroll_row, mut scroll_col) = self.scroll_offset;
1218
1219 if row < scroll_row {
1221 scroll_row = row;
1222 } else if row >= scroll_row + self.viewport_rows {
1223 scroll_row = row.saturating_sub(self.viewport_rows - 1);
1224 }
1225
1226 if col < scroll_col {
1228 scroll_col = col;
1229 } else if col >= scroll_col + self.viewport_columns {
1230 scroll_col = col.saturating_sub(self.viewport_columns - 1);
1231 }
1232
1233 if self.scroll_offset != (scroll_row, scroll_col) {
1234 info!(target: "navigation", "NavigationState::ensure_visible - scroll_offset: {:?} -> {:?}",
1235 self.scroll_offset, (scroll_row, scroll_col));
1236 self.scroll_offset = (scroll_row, scroll_col);
1237 }
1238 }
1239
1240 pub fn is_at_viewport_top(&self) -> bool {
1242 self.selected_row == self.scroll_offset.0
1243 }
1244
1245 pub fn is_at_viewport_bottom(&self) -> bool {
1247 self.selected_row == self.scroll_offset.0 + self.viewport_rows - 1
1248 }
1249
1250 pub fn get_position_status(&self) -> String {
1252 if self.viewport_lock {
1253 if self.is_at_viewport_top() {
1254 " (at viewport top)".to_string()
1255 } else if self.is_at_viewport_bottom() {
1256 " (at viewport bottom)".to_string()
1257 } else {
1258 "".to_string()
1259 }
1260 } else {
1261 "".to_string()
1262 }
1263 }
1264
1265 pub fn add_to_history(&mut self, row: usize, col: usize) {
1266 if let Some(&(last_row, last_col)) = self.selection_history.back() {
1268 if last_row == row && last_col == col {
1269 return;
1270 }
1271 }
1272
1273 if self.selection_history.len() >= 50 {
1274 self.selection_history.pop_front();
1275 }
1276 self.selection_history.push_back((row, col));
1277 }
1278}
1279
1280impl JumpToRowState {
1281 pub fn new() -> Self {
1282 Self {
1283 input: String::new(),
1284 is_active: false,
1285 }
1286 }
1287}
1288
1289#[derive(Debug, Clone)]
1291pub struct SortState {
1292 pub column: Option<usize>,
1294 pub column_name: Option<String>,
1296 pub order: SortOrder,
1298 pub history: VecDeque<SortHistoryEntry>,
1300 pub max_history: usize,
1302 pub total_sorts: usize,
1304 pub last_sort_time: Option<Instant>,
1306}
1307
1308#[derive(Debug, Clone)]
1309pub struct SortHistoryEntry {
1310 pub column_index: usize,
1312 pub column_name: String,
1314 pub order: SortOrder,
1316 pub sorted_at: Instant,
1318 pub row_count: usize,
1320}
1321
1322impl Default for SortState {
1323 fn default() -> Self {
1324 Self::new()
1325 }
1326}
1327
1328impl SortState {
1329 pub fn new() -> Self {
1330 Self {
1331 column: None,
1332 column_name: None,
1333 order: SortOrder::None,
1334 history: VecDeque::with_capacity(20),
1335 max_history: 20,
1336 total_sorts: 0,
1337 last_sort_time: None,
1338 }
1339 }
1340
1341 pub fn set_sort(
1343 &mut self,
1344 column_index: usize,
1345 column_name: String,
1346 order: SortOrder,
1347 row_count: usize,
1348 ) {
1349 if self.history.len() >= self.max_history {
1351 self.history.pop_front();
1352 }
1353
1354 self.history.push_back(SortHistoryEntry {
1355 column_index,
1356 column_name: column_name.clone(),
1357 order: order.clone(),
1358 sorted_at: Instant::now(),
1359 row_count,
1360 });
1361
1362 self.column = Some(column_index);
1364 self.column_name = Some(column_name);
1365 self.order = order;
1366 self.total_sorts += 1;
1367 self.last_sort_time = Some(Instant::now());
1368 }
1369
1370 pub fn clear_sort(&mut self) {
1372 self.column = None;
1373 self.column_name = None;
1374 self.order = SortOrder::None;
1375 self.last_sort_time = Some(Instant::now());
1376 }
1377
1378 pub fn get_next_order(&self, column_index: usize) -> SortOrder {
1380 let next_order = if let Some(current_col) = self.column {
1381 if current_col == column_index {
1382 match self.order {
1384 SortOrder::None => SortOrder::Ascending,
1385 SortOrder::Ascending => SortOrder::Descending,
1386 SortOrder::Descending => SortOrder::None,
1387 }
1388 } else {
1389 SortOrder::Ascending
1391 }
1392 } else {
1393 SortOrder::Ascending
1395 };
1396
1397 next_order
1399 }
1400
1401 pub fn advance_sort_state(
1403 &mut self,
1404 column_index: usize,
1405 column_name: Option<String>,
1406 new_order: SortOrder,
1407 ) {
1408 if let (Some(col), Some(name)) = (self.column, &self.column_name) {
1410 self.history.push_back(SortHistoryEntry {
1411 column_index: col,
1412 column_name: name.clone(),
1413 order: self.order.clone(),
1414 sorted_at: std::time::Instant::now(),
1415 row_count: 0, });
1417 }
1418
1419 self.total_sorts += 1;
1421
1422 if new_order == SortOrder::None {
1424 self.column = None;
1425 self.column_name = None;
1426 } else {
1427 self.column = Some(column_index);
1428 self.column_name = column_name;
1429 }
1430 self.order = new_order;
1431 self.last_sort_time = Some(std::time::Instant::now());
1432 }
1433
1434 pub fn get_stats(&self) -> String {
1436 let current = if let (Some(col), Some(name)) = (self.column, &self.column_name) {
1437 format!(
1438 "Column {} ({}) {}",
1439 col,
1440 name,
1441 match self.order {
1442 SortOrder::Ascending => "↑",
1443 SortOrder::Descending => "↓",
1444 SortOrder::None => "-",
1445 }
1446 )
1447 } else {
1448 "None".to_string()
1449 };
1450
1451 format!(
1452 "Current: {}, Total sorts: {}, History items: {}",
1453 current,
1454 self.total_sorts,
1455 self.history.len()
1456 )
1457 }
1458}
1459
1460#[derive(Debug, Clone, PartialEq)]
1462pub enum SelectionMode {
1463 Row,
1464 Cell,
1465 Column,
1466}
1467
1468#[derive(Debug, Clone)]
1470pub struct SelectionState {
1471 pub mode: SelectionMode,
1473 pub selected_row: Option<usize>,
1475 pub selected_column: usize,
1477 pub selected_cells: Vec<(usize, usize)>,
1479 pub selection_anchor: Option<(usize, usize)>,
1481 pub history: VecDeque<SelectionHistoryEntry>,
1483 pub max_history: usize,
1485 pub total_selections: usize,
1487 pub last_selection_time: Option<Instant>,
1489}
1490
1491#[derive(Debug, Clone)]
1492pub struct SelectionHistoryEntry {
1493 pub mode: SelectionMode,
1494 pub row: Option<usize>,
1495 pub column: usize,
1496 pub cells: Vec<(usize, usize)>,
1497 pub timestamp: chrono::DateTime<chrono::Local>,
1498}
1499
1500impl SelectionState {
1501 pub fn new() -> Self {
1502 Self {
1503 mode: SelectionMode::Row,
1504 selected_row: None,
1505 selected_column: 0,
1506 selected_cells: Vec::new(),
1507 selection_anchor: None,
1508 history: VecDeque::new(),
1509 max_history: 50,
1510 total_selections: 0,
1511 last_selection_time: None,
1512 }
1513 }
1514
1515 pub fn set_mode(&mut self, mode: SelectionMode) {
1517 if self.mode != mode {
1518 self.save_to_history();
1520 self.mode = mode;
1521 self.selected_cells.clear();
1523 self.selection_anchor = None;
1524 }
1525 }
1526
1527 pub fn select_row(&mut self, row: Option<usize>) {
1529 if self.selected_row != row {
1530 self.save_to_history();
1531 self.selected_row = row;
1532 self.total_selections += 1;
1533 self.last_selection_time = Some(Instant::now());
1534 }
1535 }
1536
1537 pub fn select_column(&mut self, column: usize) {
1539 if self.selected_column != column {
1540 self.save_to_history();
1541 self.selected_column = column;
1542 self.total_selections += 1;
1543 self.last_selection_time = Some(Instant::now());
1544 }
1545 }
1546
1547 pub fn select_cell(&mut self, row: usize, column: usize) {
1549 self.save_to_history();
1550 self.selected_row = Some(row);
1551 self.selected_column = column;
1552 self.total_selections += 1;
1553 self.last_selection_time = Some(Instant::now());
1554 }
1555
1556 pub fn add_cell_to_selection(&mut self, row: usize, column: usize) {
1558 let cell = (row, column);
1559 if !self.selected_cells.contains(&cell) {
1560 self.selected_cells.push(cell);
1561 self.total_selections += 1;
1562 self.last_selection_time = Some(Instant::now());
1563 }
1564 }
1565
1566 pub fn clear_selections(&mut self) {
1568 self.save_to_history();
1569 self.selected_cells.clear();
1570 self.selection_anchor = None;
1571 }
1572
1573 fn save_to_history(&mut self) {
1575 let entry = SelectionHistoryEntry {
1576 mode: self.mode.clone(),
1577 row: self.selected_row,
1578 column: self.selected_column,
1579 cells: self.selected_cells.clone(),
1580 timestamp: chrono::Local::now(),
1581 };
1582
1583 if self.history.len() >= self.max_history {
1584 self.history.pop_front();
1585 }
1586 self.history.push_back(entry);
1587 }
1588
1589 pub fn get_stats(&self) -> String {
1591 let mode_str = match self.mode {
1592 SelectionMode::Row => "Row",
1593 SelectionMode::Cell => "Cell",
1594 SelectionMode::Column => "Column",
1595 };
1596
1597 let selection_str = match (self.selected_row, self.selected_cells.len()) {
1598 (Some(row), 0) => format!("Row {}, Col {}", row, self.selected_column),
1599 (_, n) if n > 0 => format!("{} cells selected", n),
1600 _ => format!("Col {}", self.selected_column),
1601 };
1602
1603 format!(
1604 "Mode: {}, Selection: {}, Total: {}",
1605 mode_str, selection_str, self.total_selections
1606 )
1607 }
1608}
1609
1610pub struct NavigationProxy<'a> {
1616 buffer: Option<&'a crate::buffer::Buffer>,
1617}
1618
1619impl<'a> NavigationProxy<'a> {
1620 pub fn new(buffer: Option<&'a crate::buffer::Buffer>) -> Self {
1621 Self { buffer }
1622 }
1623
1624 pub fn selected_row(&self) -> usize {
1625 self.buffer.map(|b| b.view_state.crosshair_row).unwrap_or(0)
1626 }
1627
1628 pub fn selected_column(&self) -> usize {
1629 self.buffer.map(|b| b.view_state.crosshair_col).unwrap_or(0)
1630 }
1631
1632 pub fn scroll_offset(&self) -> (usize, usize) {
1633 self.buffer
1634 .map(|b| b.view_state.scroll_offset)
1635 .unwrap_or((0, 0))
1636 }
1637
1638 pub fn viewport_lock(&self) -> bool {
1639 self.buffer
1640 .map(|b| b.view_state.viewport_lock)
1641 .unwrap_or(false)
1642 }
1643
1644 pub fn cursor_lock(&self) -> bool {
1645 self.buffer
1646 .map(|b| b.view_state.cursor_lock)
1647 .unwrap_or(false)
1648 }
1649
1650 pub fn total_rows(&self) -> usize {
1651 self.buffer.map(|b| b.view_state.total_rows).unwrap_or(0)
1652 }
1653
1654 pub fn total_columns(&self) -> usize {
1655 self.buffer.map(|b| b.view_state.total_columns).unwrap_or(0)
1656 }
1657}
1658
1659pub struct NavigationProxyMut<'a> {
1661 buffer: Option<&'a mut crate::buffer::Buffer>,
1662}
1663
1664impl<'a> NavigationProxyMut<'a> {
1665 pub fn new(buffer: Option<&'a mut crate::buffer::Buffer>) -> Self {
1666 Self { buffer }
1667 }
1668
1669 pub fn set_selected_row(&mut self, row: usize) {
1670 if let Some(buffer) = &mut self.buffer {
1671 buffer.view_state.crosshair_row = row;
1672 }
1673 }
1674
1675 pub fn set_selected_column(&mut self, col: usize) {
1676 if let Some(buffer) = &mut self.buffer {
1677 buffer.view_state.crosshair_col = col;
1678 }
1679 }
1680
1681 pub fn set_scroll_offset(&mut self, offset: (usize, usize)) {
1682 if let Some(buffer) = &mut self.buffer {
1683 buffer.view_state.scroll_offset = offset;
1684 }
1685 }
1686
1687 pub fn set_viewport_lock(&mut self, locked: bool) {
1688 if let Some(buffer) = &mut self.buffer {
1689 buffer.view_state.viewport_lock = locked;
1690 }
1691 }
1692
1693 pub fn set_cursor_lock(&mut self, locked: bool) {
1694 if let Some(buffer) = &mut self.buffer {
1695 buffer.view_state.cursor_lock = locked;
1696 }
1697 }
1698
1699 pub fn update_totals(&mut self, rows: usize, columns: usize) {
1700 if let Some(buffer) = &mut self.buffer {
1701 buffer.view_state.total_rows = rows;
1702 buffer.view_state.total_columns = columns;
1703 }
1704 }
1705}
1706
1707pub struct SelectionProxy<'a> {
1709 buffer: Option<&'a crate::buffer::Buffer>,
1710}
1711
1712impl<'a> SelectionProxy<'a> {
1713 pub fn new(buffer: Option<&'a crate::buffer::Buffer>) -> Self {
1714 Self { buffer }
1715 }
1716
1717 pub fn mode(&self) -> crate::buffer::SelectionMode {
1718 self.buffer
1719 .map(|b| b.view_state.selection_mode.clone())
1720 .unwrap_or(crate::buffer::SelectionMode::Row)
1721 }
1722
1723 pub fn selected_cells(&self) -> Vec<(usize, usize)> {
1724 self.buffer
1725 .map(|b| b.view_state.selected_cells.clone())
1726 .unwrap_or_default()
1727 }
1728
1729 pub fn selection_anchor(&self) -> Option<(usize, usize)> {
1730 self.buffer.and_then(|b| b.view_state.selection_anchor)
1731 }
1732}
1733
1734pub struct SelectionProxyMut<'a> {
1736 buffer: Option<&'a mut crate::buffer::Buffer>,
1737}
1738
1739impl<'a> SelectionProxyMut<'a> {
1740 pub fn new(buffer: Option<&'a mut crate::buffer::Buffer>) -> Self {
1741 Self { buffer }
1742 }
1743
1744 pub fn set_mode(&mut self, mode: crate::buffer::SelectionMode) {
1745 if let Some(buffer) = &mut self.buffer {
1746 buffer.view_state.selection_mode = mode;
1747 }
1748 }
1749
1750 pub fn add_selected_cell(&mut self, cell: (usize, usize)) {
1751 if let Some(buffer) = &mut self.buffer {
1752 buffer.view_state.selected_cells.push(cell);
1753 }
1754 }
1755
1756 pub fn clear_selections(&mut self) {
1757 if let Some(buffer) = &mut self.buffer {
1758 buffer.view_state.selected_cells.clear();
1759 buffer.view_state.selection_anchor = None;
1760 }
1761 }
1762
1763 pub fn set_selection_anchor(&mut self, anchor: Option<(usize, usize)>) {
1764 if let Some(buffer) = &mut self.buffer {
1765 buffer.view_state.selection_anchor = anchor;
1766 }
1767 }
1768}
1769
1770#[derive(Debug, Clone)]
1772pub struct HistorySearchState {
1773 pub query: String,
1774 pub matches: Vec<crate::history::HistoryMatch>,
1775 pub selected_index: usize,
1776 pub is_active: bool,
1777 pub original_input: String,
1778}
1779
1780impl HistorySearchState {
1781 pub fn new() -> Self {
1782 Self {
1783 query: String::new(),
1784 matches: Vec::new(),
1785 selected_index: 0,
1786 is_active: false,
1787 original_input: String::new(),
1788 }
1789 }
1790
1791 pub fn clear(&mut self) {
1792 self.query.clear();
1793 self.matches.clear();
1794 self.selected_index = 0;
1795 self.is_active = false;
1796 self.original_input.clear();
1797 }
1798}
1799
1800#[derive(Debug, Clone)]
1802pub struct HelpState {
1803 pub is_visible: bool,
1805
1806 pub scroll_offset: u16,
1808
1809 pub max_scroll: u16,
1811
1812 pub open_count: usize,
1814
1815 pub last_opened: Option<Instant>,
1817}
1818
1819impl HelpState {
1820 pub fn new() -> Self {
1821 Self {
1822 is_visible: false,
1823 scroll_offset: 0,
1824 max_scroll: 0,
1825 open_count: 0,
1826 last_opened: None,
1827 }
1828 }
1829
1830 pub fn show(&mut self) {
1832 self.is_visible = true;
1833 self.scroll_offset = 0;
1834 self.open_count += 1;
1835 self.last_opened = Some(Instant::now());
1836 }
1837
1838 pub fn hide(&mut self) {
1840 self.is_visible = false;
1841 }
1842
1843 pub fn toggle(&mut self) {
1845 if self.is_visible {
1846 self.hide();
1847 } else {
1848 self.show();
1849 }
1850 }
1851
1852 pub fn scroll_down(&mut self, amount: u16) {
1854 self.scroll_offset = (self.scroll_offset + amount).min(self.max_scroll);
1855 }
1856
1857 pub fn scroll_up(&mut self, amount: u16) {
1859 self.scroll_offset = self.scroll_offset.saturating_sub(amount);
1860 }
1861
1862 pub fn set_max_scroll(&mut self, content_lines: usize, viewport_height: usize) {
1864 self.max_scroll = content_lines.saturating_sub(viewport_height) as u16;
1865 }
1866}
1867
1868#[derive(Debug, Clone)]
1870pub struct UndoRedoState {
1871 pub undo_stack: Vec<(String, usize)>,
1873 pub redo_stack: Vec<(String, usize)>,
1875 pub max_undo_entries: usize,
1877}
1878
1879impl Default for UndoRedoState {
1880 fn default() -> Self {
1881 Self {
1882 undo_stack: Vec::new(),
1883 redo_stack: Vec::new(),
1884 max_undo_entries: 100,
1885 }
1886 }
1887}
1888
1889impl UndoRedoState {
1890 pub fn push_undo(&mut self, text: String, cursor: usize) {
1892 self.undo_stack.push((text, cursor));
1893 if self.undo_stack.len() > self.max_undo_entries {
1894 self.undo_stack.remove(0);
1895 }
1896 self.redo_stack.clear();
1898 }
1899
1900 pub fn pop_undo(&mut self) -> Option<(String, usize)> {
1902 self.undo_stack.pop()
1903 }
1904
1905 pub fn push_redo(&mut self, text: String, cursor: usize) {
1907 self.redo_stack.push((text, cursor));
1908 if self.redo_stack.len() > self.max_undo_entries {
1909 self.redo_stack.remove(0);
1910 }
1911 }
1912
1913 pub fn pop_redo(&mut self) -> Option<(String, usize)> {
1915 self.redo_stack.pop()
1916 }
1917}
1918
1919#[derive(Debug, Clone)]
1921pub struct ScrollState {
1922 pub help_scroll: u16,
1924 pub input_scroll_offset: u16,
1926 pub viewport_scroll_offset: (usize, usize),
1928 pub last_visible_rows: usize,
1930}
1931
1932impl Default for ScrollState {
1933 fn default() -> Self {
1934 Self {
1935 help_scroll: 0,
1936 input_scroll_offset: 0,
1937 viewport_scroll_offset: (0, 0),
1938 last_visible_rows: 0,
1939 }
1940 }
1941}
1942
1943#[derive(Debug, Clone)]
1945pub struct ChordState {
1946 pub current_chord: Vec<String>, pub chord_start: Option<std::time::SystemTime>,
1950 pub is_active: bool,
1952 pub description: Option<String>,
1954 pub registered_chords: std::collections::HashMap<String, String>,
1956 pub history: Vec<(String, String, std::time::SystemTime)>, }
1959
1960impl Default for ChordState {
1961 fn default() -> Self {
1962 let mut registered_chords = std::collections::HashMap::new();
1963 registered_chords.insert("yy".to_string(), "yank_row".to_string());
1965 registered_chords.insert("yr".to_string(), "yank_row".to_string());
1966 registered_chords.insert("yc".to_string(), "yank_column".to_string());
1967 registered_chords.insert("ya".to_string(), "yank_all".to_string());
1968 registered_chords.insert("yv".to_string(), "yank_cell".to_string());
1969
1970 Self {
1971 current_chord: Vec::new(),
1972 chord_start: None,
1973 is_active: false,
1974 description: None,
1975 registered_chords,
1976 history: Vec::new(),
1977 }
1978 }
1979}
1980
1981impl ChordState {
1982 pub fn clear(&mut self) {
1984 self.current_chord.clear();
1985 self.chord_start = None;
1986 self.is_active = false;
1987 self.description = None;
1988 }
1989
1990 pub fn add_key(&mut self, key: String) {
1992 if self.current_chord.is_empty() {
1993 self.chord_start = Some(std::time::SystemTime::now());
1994 }
1995 self.current_chord.push(key);
1996 self.is_active = true;
1997 }
1998
1999 pub fn get_chord_string(&self) -> String {
2001 self.current_chord.join("")
2002 }
2003
2004 pub fn check_match(&self) -> Option<String> {
2006 let chord = self.get_chord_string();
2007 self.registered_chords.get(&chord).cloned()
2008 }
2009
2010 pub fn is_partial_match(&self) -> bool {
2012 let current = self.get_chord_string();
2013 self.registered_chords
2014 .keys()
2015 .any(|chord| chord.starts_with(¤t) && chord.len() > current.len())
2016 }
2017
2018 pub fn record_completion(&mut self, chord: String, action: String) {
2020 self.history
2021 .push((chord, action, std::time::SystemTime::now()));
2022 if self.history.len() > 50 {
2024 self.history.remove(0);
2025 }
2026 }
2027}
2028
2029pub struct WidgetStates {
2031 pub search_modes: SearchModesWidget,
2032 pub history: Option<HistoryWidget>, pub help: HelpWidget,
2034 pub stats: StatsWidget,
2035 }
2037
2038impl WidgetStates {
2039 pub fn new() -> Self {
2040 Self {
2041 search_modes: SearchModesWidget::new(),
2042 history: None, help: HelpWidget::new(),
2044 stats: StatsWidget::new(),
2045 }
2047 }
2048
2049 pub fn set_history(&mut self, history: HistoryWidget) {
2050 self.history = Some(history);
2051 }
2052}
2053
2054#[derive(Debug, Clone)]
2056pub struct ResultsState {
2057 pub current_results: Option<QueryResponse>,
2059
2060 pub results_cache: HashMap<String, CachedResult>,
2062
2063 pub max_cache_size: usize,
2065
2066 pub total_memory_usage: usize,
2068
2069 pub memory_limit: usize,
2071
2072 pub last_query: String,
2074
2075 pub last_execution_time: Duration,
2077
2078 pub query_performance_history: VecDeque<QueryPerformance>,
2080
2081 pub from_cache: bool,
2083
2084 pub last_modified: Instant,
2086}
2087
2088#[derive(Debug, Clone)]
2089pub struct CachedResult {
2090 pub response: QueryResponse,
2092
2093 pub cached_at: Instant,
2095
2096 pub access_count: u32,
2098
2099 pub last_access: Instant,
2101
2102 pub memory_size: usize,
2104}
2105
2106#[derive(Debug, Clone)]
2107pub struct QueryPerformance {
2108 pub query: String,
2110
2111 pub execution_time: Duration,
2113
2114 pub row_count: usize,
2116
2117 pub from_cache: bool,
2119
2120 pub memory_usage: usize,
2122
2123 pub executed_at: Instant,
2125}
2126
2127impl Default for ResultsState {
2128 fn default() -> Self {
2129 Self {
2130 current_results: None,
2131 results_cache: HashMap::new(),
2132 max_cache_size: 100, total_memory_usage: 0,
2134 memory_limit: 512 * 1024 * 1024, last_query: String::new(),
2136 last_execution_time: Duration::from_millis(0),
2137 query_performance_history: VecDeque::with_capacity(1000),
2138 from_cache: false,
2139 last_modified: Instant::now(),
2140 }
2141 }
2142}
2143
2144#[derive(Debug, Clone)]
2146pub struct ClipboardState {
2147 pub last_yanked: Option<YankedItem>,
2149
2150 pub yank_history: VecDeque<YankedItem>,
2152
2153 pub max_history: usize,
2155
2156 pub current_register: char,
2158
2159 pub total_yanks: usize,
2161 pub last_yank_time: Option<Instant>,
2162}
2163
2164#[derive(Debug, Clone)]
2165pub struct YankedItem {
2166 pub description: String,
2168
2169 pub full_value: String,
2171
2172 pub preview: String,
2174
2175 pub yank_type: YankType,
2177
2178 pub yanked_at: DateTime<Local>,
2180
2181 pub size_bytes: usize,
2183}
2184
2185#[derive(Debug, Clone, PartialEq)]
2186pub enum YankType {
2187 Cell {
2188 row: usize,
2189 column: usize,
2190 },
2191 Row {
2192 row: usize,
2193 },
2194 Column {
2195 name: String,
2196 index: usize,
2197 },
2198 All,
2199 Selection {
2200 start: (usize, usize),
2201 end: (usize, usize),
2202 },
2203 Query,
2204 TestCase,
2205 DebugContext,
2206}
2207
2208impl Default for ClipboardState {
2209 fn default() -> Self {
2210 Self {
2211 last_yanked: None,
2212 yank_history: VecDeque::with_capacity(50),
2213 max_history: 50,
2214 current_register: '"', total_yanks: 0,
2216 last_yank_time: None,
2217 }
2218 }
2219}
2220
2221impl ClipboardState {
2222 pub fn new() -> Self {
2223 Self::default()
2224 }
2225
2226 pub fn add_yank(&mut self, item: YankedItem) {
2228 self.yank_history.push_front(item.clone());
2230
2231 while self.yank_history.len() > self.max_history {
2233 self.yank_history.pop_back();
2234 }
2235
2236 self.last_yanked = Some(item);
2238 self.total_yanks += 1;
2239 self.last_yank_time = Some(Instant::now());
2240 }
2241
2242 pub fn clear(&mut self) {
2244 self.last_yanked = None;
2245 }
2246
2247 pub fn clear_history(&mut self) {
2249 self.yank_history.clear();
2250 self.last_yanked = None;
2251 }
2252
2253 pub fn get_stats(&self) -> String {
2255 format!(
2256 "Total yanks: {}, History items: {}, Last yank: {}",
2257 self.total_yanks,
2258 self.yank_history.len(),
2259 self.last_yank_time
2260 .map(|t| format!("{:?} ago", t.elapsed()))
2261 .unwrap_or_else(|| "never".to_string())
2262 )
2263 }
2264}
2265
2266impl ResultsState {
2267 pub fn new() -> Self {
2268 Self::default()
2269 }
2270
2271 pub fn set_results(
2273 &mut self,
2274 results: QueryResponse,
2275 execution_time: Duration,
2276 from_cache: bool,
2277 ) -> Result<()> {
2278 let row_count = results.count;
2279 let memory_usage = self.estimate_memory_usage(&results);
2280
2281 let performance = QueryPerformance {
2283 query: results.query.select.join(", "),
2284 execution_time,
2285 row_count,
2286 from_cache,
2287 memory_usage,
2288 executed_at: Instant::now(),
2289 };
2290
2291 self.query_performance_history.push_back(performance);
2293 if self.query_performance_history.len() > 1000 {
2294 self.query_performance_history.pop_front();
2295 }
2296
2297 self.current_results = Some(results);
2299 self.last_execution_time = execution_time;
2300 self.from_cache = from_cache;
2301 self.last_modified = Instant::now();
2302
2303 Ok(())
2304 }
2305
2306 pub fn get_results(&self) -> Option<&QueryResponse> {
2308 self.current_results.as_ref()
2309 }
2310
2311 pub fn cache_results(&mut self, query_key: String, results: QueryResponse) -> Result<()> {
2313 let memory_usage = self.estimate_memory_usage(&results);
2314
2315 if self.total_memory_usage + memory_usage > self.memory_limit {
2317 self.evict_to_fit(memory_usage)?;
2318 }
2319
2320 let cached_result = CachedResult {
2322 response: results,
2323 cached_at: Instant::now(),
2324 access_count: 1,
2325 last_access: Instant::now(),
2326 memory_size: memory_usage,
2327 };
2328
2329 if self.results_cache.len() >= self.max_cache_size {
2331 self.evict_oldest()?;
2332 }
2333
2334 self.results_cache.insert(query_key, cached_result);
2335 self.total_memory_usage += memory_usage;
2336
2337 Ok(())
2338 }
2339
2340 pub fn get_cached_results(&mut self, query_key: &str) -> Option<&QueryResponse> {
2342 if let Some(cached) = self.results_cache.get_mut(query_key) {
2343 cached.access_count += 1;
2344 cached.last_access = Instant::now();
2345 Some(&cached.response)
2346 } else {
2347 None
2348 }
2349 }
2350
2351 pub fn clear_cache(&mut self) {
2353 self.results_cache.clear();
2354 self.total_memory_usage = 0;
2355 }
2356
2357 pub fn get_cache_stats(&self) -> CacheStats {
2359 CacheStats {
2360 entry_count: self.results_cache.len(),
2361 memory_usage: self.total_memory_usage,
2362 memory_limit: self.memory_limit,
2363 hit_rate: self.calculate_hit_rate(),
2364 }
2365 }
2366
2367 pub fn get_performance_stats(&self) -> PerformanceStats {
2369 let total_queries = self.query_performance_history.len();
2370 let cached_queries = self
2371 .query_performance_history
2372 .iter()
2373 .filter(|q| q.from_cache)
2374 .count();
2375 let avg_execution_time = if total_queries > 0 {
2376 self.query_performance_history
2377 .iter()
2378 .map(|q| q.execution_time.as_millis() as f64)
2379 .sum::<f64>()
2380 / total_queries as f64
2381 } else {
2382 0.0
2383 };
2384
2385 PerformanceStats {
2386 total_queries,
2387 cached_queries,
2388 cache_hit_rate: if total_queries > 0 {
2389 cached_queries as f64 / total_queries as f64
2390 } else {
2391 0.0
2392 },
2393 average_execution_time_ms: avg_execution_time,
2394 last_execution_time: self.last_execution_time,
2395 }
2396 }
2397
2398 fn estimate_memory_usage(&self, results: &QueryResponse) -> usize {
2401 let data_size = results
2403 .data
2404 .iter()
2405 .map(|row| serde_json::to_string(row).unwrap_or_default().len())
2406 .sum::<usize>();
2407
2408 data_size + std::mem::size_of::<QueryResponse>() + 1024 }
2411
2412 fn evict_to_fit(&mut self, needed_space: usize) -> Result<()> {
2413 while self.total_memory_usage + needed_space > self.memory_limit
2415 && !self.results_cache.is_empty()
2416 {
2417 self.evict_oldest()?;
2418 }
2419 Ok(())
2420 }
2421
2422 fn evict_oldest(&mut self) -> Result<()> {
2423 if let Some((key, cached)) = self
2424 .results_cache
2425 .iter()
2426 .min_by_key(|(_, cached)| cached.last_access)
2427 .map(|(k, v)| (k.clone(), v.memory_size))
2428 {
2429 self.results_cache.remove(&key);
2430 self.total_memory_usage = self.total_memory_usage.saturating_sub(cached);
2431 }
2432 Ok(())
2433 }
2434
2435 fn calculate_hit_rate(&self) -> f64 {
2436 let total = self.query_performance_history.len();
2438 if total == 0 {
2439 return 0.0;
2440 }
2441
2442 let hits = self
2443 .query_performance_history
2444 .iter()
2445 .filter(|q| q.from_cache)
2446 .count();
2447 hits as f64 / total as f64
2448 }
2449}
2450
2451#[derive(Debug, Clone)]
2452pub struct CacheStats {
2453 pub entry_count: usize,
2454 pub memory_usage: usize,
2455 pub memory_limit: usize,
2456 pub hit_rate: f64,
2457}
2458
2459#[derive(Debug, Clone)]
2460pub struct PerformanceStats {
2461 pub total_queries: usize,
2462 pub cached_queries: usize,
2463 pub cache_hit_rate: f64,
2464 pub average_execution_time_ms: f64,
2465 pub last_execution_time: Duration,
2466}
2467
2468#[derive(Debug, Clone)]
2470pub struct ResultsCache {
2471 cache: HashMap<String, Vec<Vec<String>>>,
2472 max_size: usize,
2473}
2474
2475impl ResultsCache {
2476 pub fn new(max_size: usize) -> Self {
2477 Self {
2478 cache: HashMap::new(),
2479 max_size,
2480 }
2481 }
2482
2483 pub fn get(&self, key: &str) -> Option<&Vec<Vec<String>>> {
2484 self.cache.get(key)
2485 }
2486
2487 pub fn insert(&mut self, key: String, value: Vec<Vec<String>>) {
2488 if self.cache.len() >= self.max_size {
2489 if let Some(first_key) = self.cache.keys().next().cloned() {
2491 self.cache.remove(&first_key);
2492 }
2493 }
2494 self.cache.insert(key, value);
2495 }
2496}
2497
2498pub struct AppStateContainer {
2500 buffers: BufferManager,
2502 current_buffer_id: usize,
2503
2504 command_input: RefCell<InputState>,
2506
2507 search: RefCell<SearchState>,
2509 filter: RefCell<FilterState>,
2510 column_search: RefCell<ColumnSearchState>,
2511 history_search: RefCell<HistorySearchState>,
2512 sort: RefCell<SortState>,
2513 selection: RefCell<SelectionState>,
2514 completion: RefCell<CompletionState>,
2516
2517 widgets: WidgetStates,
2519
2520 cache_list: CacheListState,
2522 column_stats: ColumnStatsState,
2523 jump_to_row: JumpToRowState,
2524 navigation: RefCell<NavigationState>,
2525
2526 command_history: RefCell<CommandHistory>,
2528 key_press_history: RefCell<KeyPressHistory>,
2529
2530 results: RefCell<ResultsState>,
2532
2533 clipboard: RefCell<ClipboardState>,
2535
2536 chord: RefCell<ChordState>,
2538
2539 undo_redo: RefCell<UndoRedoState>,
2541
2542 scroll: RefCell<ScrollState>,
2544
2545 results_cache: ResultsCache,
2547
2548 mode_stack: Vec<AppMode>,
2550
2551 debug_enabled: bool,
2553 debug_service: RefCell<Option<crate::debug_service::DebugService>>,
2554
2555 help: RefCell<HelpState>,
2557}
2558
2559impl AppStateContainer {
2560 pub fn format_number_compact(n: usize) -> String {
2562 if n < 1000 {
2563 n.to_string()
2564 } else if n < 1000000 {
2565 let k = n as f64 / 1000.0;
2566 if k.fract() == 0.0 {
2567 format!("{}k", k as usize)
2568 } else if k < 10.0 {
2569 format!("{:.1}k", k)
2570 } else {
2571 format!("{}k", k as usize)
2572 }
2573 } else if n < 1000000000 {
2574 let m = n as f64 / 1000000.0;
2575 if m.fract() == 0.0 {
2576 format!("{}M", m as usize)
2577 } else if m < 10.0 {
2578 format!("{:.1}M", m)
2579 } else {
2580 format!("{}M", m as usize)
2581 }
2582 } else {
2583 let b = n as f64 / 1000000000.0;
2584 if b.fract() == 0.0 {
2585 format!("{}B", b as usize)
2586 } else {
2587 format!("{:.1}B", b)
2588 }
2589 }
2590 }
2591
2592 pub fn new(buffers: BufferManager) -> Result<Self> {
2593 let command_history = CommandHistory::new()?;
2594 let mut widgets = WidgetStates::new();
2595 widgets.set_history(HistoryWidget::new(command_history.clone()));
2596
2597 Ok(Self {
2598 buffers,
2599 current_buffer_id: 0,
2600 command_input: RefCell::new(InputState::new()),
2601 search: RefCell::new(SearchState::new()),
2602 filter: RefCell::new(FilterState::new()),
2603 column_search: RefCell::new(ColumnSearchState::new()),
2604 history_search: RefCell::new(HistorySearchState::new()),
2605 sort: RefCell::new(SortState::new()),
2606 selection: RefCell::new(SelectionState::new()),
2607 completion: RefCell::new(CompletionState::new()),
2608 widgets,
2609 cache_list: CacheListState::new(),
2610 column_stats: ColumnStatsState::new(),
2611 jump_to_row: JumpToRowState::new(),
2612 command_history: RefCell::new(command_history),
2613 key_press_history: RefCell::new(KeyPressHistory::new(50)), results: RefCell::new(ResultsState::new()),
2615 clipboard: RefCell::new(ClipboardState::new()),
2616 chord: RefCell::new(ChordState::default()),
2617 undo_redo: RefCell::new(UndoRedoState::default()),
2618 scroll: RefCell::new(ScrollState::default()),
2619 navigation: RefCell::new(NavigationState::new()),
2620 results_cache: ResultsCache::new(100),
2621 mode_stack: vec![AppMode::Command],
2622 debug_enabled: false,
2623 debug_service: RefCell::new(None), help: RefCell::new(HelpState::new()),
2625 })
2626 }
2627
2628 pub fn current_buffer(&self) -> Option<&crate::buffer::Buffer> {
2630 self.buffers.current()
2631 }
2632
2633 pub fn current_buffer_mut(&mut self) -> Option<&mut crate::buffer::Buffer> {
2634 self.buffers.current_mut()
2635 }
2636
2637 pub fn buffers(&self) -> &BufferManager {
2638 &self.buffers
2639 }
2640
2641 pub fn buffers_mut(&mut self) -> &mut BufferManager {
2642 &mut self.buffers
2643 }
2644
2645 pub fn command_input(&self) -> std::cell::Ref<'_, InputState> {
2647 self.command_input.borrow()
2648 }
2649
2650 pub fn command_input_mut(&self) -> std::cell::RefMut<'_, InputState> {
2651 self.command_input.borrow_mut()
2652 }
2653
2654 pub fn set_input_text(&self, text: String) {
2656 let mut input = self.command_input.borrow_mut();
2657 input.text = text.clone();
2658 input.cursor_position = text.len();
2659 }
2660
2661 pub fn set_input_text_with_cursor(&self, text: String, cursor: usize) {
2662 let mut input = self.command_input.borrow_mut();
2663 input.text = text;
2664 input.cursor_position = cursor;
2665 }
2666
2667 pub fn set_last_executed_query(&self, query: String) {
2668 self.command_input.borrow_mut().last_executed_query = query;
2669 }
2670
2671 pub fn search(&self) -> std::cell::Ref<'_, SearchState> {
2673 self.search.borrow()
2674 }
2675
2676 pub fn search_mut(&self) -> std::cell::RefMut<'_, SearchState> {
2677 self.search.borrow_mut()
2678 }
2679
2680 pub fn start_search(&self, pattern: String) -> usize {
2684 let mut search = self.search.borrow_mut();
2685 let old_pattern = search.pattern.clone();
2686 let old_active = search.is_active;
2687
2688 search.pattern = pattern.clone();
2689 search.is_active = true;
2690 search.last_search_time = Some(std::time::Instant::now());
2691
2692 if let Some(ref debug_service) = *self.debug_service.borrow() {
2693 debug_service.info(
2694 "Search",
2695 format!(
2696 "Starting search: '{}' (was: '{}', active: {})",
2697 pattern, old_pattern, old_active
2698 ),
2699 );
2700 }
2701
2702 0
2704 }
2705
2706 pub fn update_search_matches(&self, matches: Vec<(usize, usize, usize, usize)>) {
2708 let match_count = matches.len();
2709 let mut search = self.search.borrow_mut();
2710 let pattern = search.pattern.clone();
2711 search.matches = matches;
2712 search.current_match = if match_count > 0 { 0 } else { 0 };
2713
2714 if let Some(ref debug_service) = *self.debug_service.borrow() {
2715 debug_service.info(
2716 "Search",
2717 format!(
2718 "Search found {} matches for pattern '{}'",
2719 match_count, pattern
2720 ),
2721 );
2722 }
2723
2724 if !pattern.is_empty() {
2726 let duration_ms = search
2727 .last_search_time
2728 .map(|t| t.elapsed().as_millis() as u64);
2729
2730 let entry = SearchHistoryEntry {
2731 pattern: pattern.clone(),
2732 match_count,
2733 timestamp: Local::now(),
2734 duration_ms,
2735 };
2736
2737 if search.history.len() >= 20 {
2738 search.history.pop_front();
2739 }
2740 search.history.push_back(entry);
2741 }
2742 }
2743
2744 pub fn next_search_match(&self) -> Option<(usize, usize)> {
2746 let mut search = self.search.borrow_mut();
2747 if search.matches.is_empty() {
2748 return None;
2749 }
2750
2751 let old_match = search.current_match;
2752 search.current_match = (search.current_match + 1) % search.matches.len();
2753
2754 if let Some(ref debug_service) = *self.debug_service.borrow() {
2755 debug_service.info(
2756 "Search",
2757 format!(
2758 "Navigate to next match: {} -> {} (of {})",
2759 old_match,
2760 search.current_match,
2761 search.matches.len()
2762 ),
2763 );
2764 }
2765
2766 let match_pos = search.matches[search.current_match];
2767 Some((match_pos.0, match_pos.1))
2768 }
2769
2770 pub fn previous_search_match(&self) -> Option<(usize, usize)> {
2772 let mut search = self.search.borrow_mut();
2773 if search.matches.is_empty() {
2774 return None;
2775 }
2776
2777 let old_match = search.current_match;
2778 search.current_match = if search.current_match == 0 {
2779 search.matches.len() - 1
2780 } else {
2781 search.current_match - 1
2782 };
2783
2784 if let Some(ref debug_service) = *self.debug_service.borrow() {
2785 debug_service.info(
2786 "Search",
2787 format!(
2788 "Navigate to previous match: {} -> {} (of {})",
2789 old_match,
2790 search.current_match,
2791 search.matches.len()
2792 ),
2793 );
2794 }
2795
2796 let match_pos = search.matches[search.current_match];
2797 Some((match_pos.0, match_pos.1))
2798 }
2799
2800 pub fn clear_search(&self) {
2802 let mut search = self.search.borrow_mut();
2803 let had_matches = search.matches.len();
2804 let had_pattern = search.pattern.clone();
2805
2806 search.clear();
2807
2808 if let Some(ref debug_service) = *self.debug_service.borrow() {
2809 debug_service.info(
2810 "Search",
2811 format!(
2812 "Cleared search (had pattern: '{}', {} matches)",
2813 had_pattern, had_matches
2814 ),
2815 );
2816 }
2817 }
2818
2819 pub fn perform_search(&self, data: &[Vec<String>]) -> Vec<(usize, usize, usize, usize)> {
2822 use regex::Regex;
2823
2824 let pattern = self.search.borrow().pattern.clone();
2825 if pattern.is_empty() {
2826 let mut search = self.search.borrow_mut();
2827 search.matches.clear();
2828 search.current_match = 0;
2829 return Vec::new();
2830 }
2831
2832 let start_time = std::time::Instant::now();
2833 let mut matches = Vec::new();
2834
2835 if let Some(ref debug_service) = *self.debug_service.borrow() {
2836 debug_service.info(
2837 "Search",
2838 format!(
2839 "Performing search for pattern '{}' on {} rows",
2840 pattern,
2841 data.len()
2842 ),
2843 );
2844 }
2845
2846 match Regex::new(&pattern) {
2848 Ok(regex) => {
2849 for (row_idx, row) in data.iter().enumerate() {
2850 for (col_idx, cell) in row.iter().enumerate() {
2851 if regex.is_match(cell) {
2852 matches.push((row_idx, col_idx, row_idx, col_idx));
2855 }
2856 }
2857 }
2858 }
2859 Err(e) => {
2860 if let Some(ref debug_service) = *self.debug_service.borrow() {
2861 debug_service.info(
2862 "Search",
2863 format!("Invalid regex pattern '{}': {}", pattern, e),
2864 );
2865 }
2866 let pattern_lower = pattern.to_lowercase();
2868 for (row_idx, row) in data.iter().enumerate() {
2869 for (col_idx, cell) in row.iter().enumerate() {
2870 if cell.to_lowercase().contains(&pattern_lower) {
2871 matches.push((row_idx, col_idx, row_idx, col_idx));
2872 }
2873 }
2874 }
2875 }
2876 }
2877
2878 let elapsed = start_time.elapsed();
2879 self.search.borrow_mut().last_search_time = Some(start_time);
2880
2881 self.update_search_matches(matches.clone());
2883
2884 if let Some(ref debug_service) = *self.debug_service.borrow() {
2885 debug_service.info(
2886 "Search",
2887 format!(
2888 "Search completed in {:?}: found {} matches for '{}'",
2889 elapsed,
2890 matches.len(),
2891 pattern
2892 ),
2893 );
2894 }
2895
2896 matches
2897 }
2898
2899 pub fn get_current_match(&self) -> Option<(usize, usize)> {
2901 let search = self.search.borrow();
2902 if search.matches.is_empty() || !search.is_active {
2903 return None;
2904 }
2905
2906 let match_pos = search.matches[search.current_match];
2907 Some((match_pos.0, match_pos.1))
2908 }
2909
2910 pub fn filter(&self) -> std::cell::Ref<'_, FilterState> {
2911 self.filter.borrow()
2912 }
2913
2914 pub fn filter_mut(&self) -> std::cell::RefMut<'_, FilterState> {
2915 self.filter.borrow_mut()
2916 }
2917
2918 pub fn column_search(&self) -> std::cell::Ref<'_, ColumnSearchState> {
2919 self.column_search.borrow()
2920 }
2921
2922 pub fn column_search_mut(&self) -> std::cell::RefMut<'_, ColumnSearchState> {
2923 self.column_search.borrow_mut()
2924 }
2925
2926 pub fn start_column_search(&self, pattern: String) {
2930 let mut column_search = self.column_search.borrow_mut();
2931 let old_pattern = column_search.pattern.clone();
2932 let old_active = column_search.is_active;
2933
2934 column_search.pattern = pattern.clone();
2935 column_search.is_active = true;
2936 column_search.last_search_time = Some(Instant::now());
2937
2938 if let Some(ref debug_service) = *self.debug_service.borrow() {
2939 debug_service.info(
2940 "ColumnSearch",
2941 format!(
2942 "Starting column search: '{}' (was: '{}', active: {})",
2943 pattern, old_pattern, old_active
2944 ),
2945 );
2946 }
2947 }
2948
2949 pub fn update_column_search_matches(
2951 &self,
2952 columns: &[(String, usize)],
2953 pattern: &str,
2954 ) -> Vec<(usize, String)> {
2955 let pattern_lower = pattern.to_lowercase();
2956 let mut matches = Vec::new();
2957
2958 for (name, index) in columns {
2959 if name.to_lowercase().contains(&pattern_lower) {
2960 matches.push((*index, name.clone()));
2961 }
2962 }
2963
2964 let mut column_search = self.column_search.borrow_mut();
2965 column_search.set_matches(matches.clone());
2966
2967 if let Some(ref debug_service) = *self.debug_service.borrow() {
2968 debug_service.info(
2969 "ColumnSearch",
2970 format!(
2971 "Found {} columns matching '{}': {:?}",
2972 matches.len(),
2973 pattern,
2974 matches.iter().map(|(_, name)| name).collect::<Vec<_>>()
2975 ),
2976 );
2977 }
2978
2979 matches
2980 }
2981
2982 pub fn next_column_match(&self) -> Option<(usize, String)> {
2984 let mut column_search = self.column_search.borrow_mut();
2985 if let Some((idx, name)) = column_search.next_match() {
2986 let current = column_search.current_match;
2987 let total = column_search.matching_columns.len();
2988
2989 if let Some(ref debug_service) = *self.debug_service.borrow() {
2990 debug_service.info(
2991 "ColumnSearch",
2992 format!(
2993 "Navigate to next column: {}/{} - '{}' (index {})",
2994 current + 1,
2995 total,
2996 name,
2997 idx
2998 ),
2999 );
3000 }
3001
3002 Some((idx, name))
3003 } else {
3004 None
3005 }
3006 }
3007
3008 pub fn previous_column_match(&self) -> Option<(usize, String)> {
3010 let mut column_search = self.column_search.borrow_mut();
3011 if let Some((idx, name)) = column_search.prev_match() {
3012 let current = column_search.current_match;
3013 let total = column_search.matching_columns.len();
3014
3015 if let Some(ref debug_service) = *self.debug_service.borrow() {
3016 debug_service.info(
3017 "ColumnSearch",
3018 format!(
3019 "Navigate to previous column: {}/{} - '{}' (index {})",
3020 current + 1,
3021 total,
3022 name,
3023 idx
3024 ),
3025 );
3026 }
3027
3028 Some((idx, name))
3029 } else {
3030 None
3031 }
3032 }
3033
3034 pub fn clear_column_search(&self) {
3036 let mut column_search = self.column_search.borrow_mut();
3037 let had_matches = column_search.matching_columns.len();
3038 let had_pattern = column_search.pattern.clone();
3039
3040 column_search.clear();
3041
3042 if let Some(ref debug_service) = *self.debug_service.borrow() {
3043 debug_service.info(
3044 "ColumnSearch",
3045 format!(
3046 "Cleared column search (had pattern: '{}', {} matches)",
3047 had_pattern, had_matches
3048 ),
3049 );
3050 }
3051 }
3052
3053 pub fn accept_column_match(&self) -> Option<(usize, String)> {
3055 let column_search = self.column_search.borrow();
3056 if let Some((idx, name)) = column_search.current_match() {
3057 if let Some(ref debug_service) = *self.debug_service.borrow() {
3058 debug_service.info(
3059 "ColumnSearch",
3060 format!("Accepted column: '{}' at index {}", name, idx),
3061 );
3062 }
3063 Some((idx, name))
3064 } else {
3065 None
3066 }
3067 }
3068
3069 pub fn sort_by_column(&self, column_index: usize, column_name: String, row_count: usize) {
3073 let mut sort_state = self.sort.borrow_mut();
3074
3075 let new_order = sort_state.get_next_order(column_index);
3077
3078 let old_column = sort_state.column;
3079 let old_order = sort_state.order.clone();
3080
3081 if new_order == SortOrder::None {
3082 sort_state.clear_sort();
3084
3085 if let Some(ref debug_service) = *self.debug_service.borrow() {
3086 debug_service.info(
3087 "Sort",
3088 format!(
3089 "Cleared sort on column {} ({}), returning to original order",
3090 column_index, column_name
3091 ),
3092 );
3093 }
3094 } else {
3095 sort_state.set_sort(
3097 column_index,
3098 column_name.clone(),
3099 new_order.clone(),
3100 row_count,
3101 );
3102
3103 if let Some(ref debug_service) = *self.debug_service.borrow() {
3104 debug_service.info(
3105 "Sort",
3106 format!(
3107 "Sorted column {} ({}) {}, {} rows (was: column {:?} {})",
3108 column_index,
3109 column_name,
3110 match new_order {
3111 SortOrder::Ascending => "ascending ↑",
3112 SortOrder::Descending => "descending ↓",
3113 SortOrder::None => "none",
3114 },
3115 row_count,
3116 old_column,
3117 match old_order {
3118 SortOrder::Ascending => "↑",
3119 SortOrder::Descending => "↓",
3120 SortOrder::None => "-",
3121 }
3122 ),
3123 );
3124 }
3125 }
3126 }
3127
3128 pub fn clear_sort(&self) {
3130 let mut sort_state = self.sort.borrow_mut();
3131 let had_sort = sort_state.column.is_some();
3132 let old_column = sort_state.column;
3133 let old_name = sort_state.column_name.clone();
3134
3135 sort_state.clear_sort();
3136
3137 if had_sort {
3138 if let Some(ref debug_service) = *self.debug_service.borrow() {
3139 debug_service.info(
3140 "Sort",
3141 format!(
3142 "Cleared all sorting (was: column {:?} - {})",
3143 old_column,
3144 old_name.unwrap_or_else(|| "unknown".to_string())
3145 ),
3146 );
3147 }
3148 }
3149 }
3150
3151 pub fn sort(&self) -> std::cell::Ref<SortState> {
3153 self.sort.borrow()
3154 }
3155
3156 pub fn get_next_sort_order(&self, column_index: usize) -> SortOrder {
3158 self.sort.borrow().get_next_order(column_index)
3159 }
3160
3161 pub fn advance_sort_state(
3163 &self,
3164 column_index: usize,
3165 column_name: Option<String>,
3166 new_order: SortOrder,
3167 ) {
3168 self.sort
3169 .borrow_mut()
3170 .advance_sort_state(column_index, column_name, new_order);
3171 }
3172
3173 pub fn selection(&self) -> std::cell::Ref<SelectionState> {
3175 self.selection.borrow()
3176 }
3177
3178 pub fn selection_mut(&self) -> std::cell::RefMut<SelectionState> {
3180 self.selection.borrow_mut()
3181 }
3182
3183 pub fn selection_proxy(&self) -> SelectionProxy {
3185 SelectionProxy::new(self.buffers.current())
3186 }
3187
3188 pub fn selection_proxy_mut(&mut self) -> SelectionProxyMut {
3189 SelectionProxyMut::new(self.buffers.current_mut())
3190 }
3191
3192 pub fn set_selection_mode(&self, mode: SelectionMode) {
3194 let mut selection = self.selection.borrow_mut();
3195 let old_mode = selection.mode.clone();
3196 selection.set_mode(mode.clone());
3197
3198 if old_mode != mode {
3199 if let Some(ref debug_service) = *self.debug_service.borrow() {
3200 debug_service.info(
3201 "Selection",
3202 format!("Mode changed: {:?} → {:?}", old_mode, mode),
3203 );
3204 }
3205 }
3206 }
3207
3208 pub fn select_row(&self, row: Option<usize>) {
3210 let mut selection = self.selection.borrow_mut();
3211 let old_row = selection.selected_row;
3212 selection.select_row(row);
3213
3214 if old_row != row {
3215 if let Some(ref debug_service) = *self.debug_service.borrow() {
3216 debug_service.info(
3217 "Selection",
3218 format!("Row selection: {:?} → {:?}", old_row, row),
3219 );
3220 }
3221 }
3222 }
3223
3224 pub fn select_column(&self, column: usize) {
3226 let mut selection = self.selection.borrow_mut();
3227 let old_column = selection.selected_column;
3228 selection.select_column(column);
3229
3230 if old_column != column {
3231 if let Some(ref debug_service) = *self.debug_service.borrow() {
3232 debug_service.info(
3233 "Selection",
3234 format!("Column selection: {} → {}", old_column, column),
3235 );
3236 }
3237 }
3238 }
3239
3240 pub fn select_cell(&self, row: usize, column: usize) {
3242 self.selection.borrow_mut().select_cell(row, column);
3243
3244 if let Some(ref debug_service) = *self.debug_service.borrow() {
3245 debug_service.info("Selection", format!("Cell selected: [{}, {}]", row, column));
3246 }
3247 }
3248
3249 pub fn toggle_selection_mode(&self) {
3251 let mut selection = self.selection.borrow_mut();
3252 let new_mode = match selection.mode {
3253 SelectionMode::Row => SelectionMode::Cell,
3254 SelectionMode::Cell => SelectionMode::Column,
3255 SelectionMode::Column => SelectionMode::Row,
3256 };
3257 let old_mode = selection.mode.clone();
3258 selection.set_mode(new_mode.clone());
3259
3260 if let Some(ref debug_service) = *self.debug_service.borrow() {
3261 debug_service.info(
3262 "Selection",
3263 format!("Mode toggled: {:?} → {:?}", old_mode, new_mode),
3264 );
3265 }
3266 }
3267
3268 pub fn clear_selections(&self) {
3270 let mut selection = self.selection.borrow_mut();
3271 let had_selections = !selection.selected_cells.is_empty();
3272 selection.clear_selections();
3273
3274 if had_selections {
3275 if let Some(ref debug_service) = *self.debug_service.borrow() {
3276 debug_service.info("Selection", "Cleared all selections".to_string());
3277 }
3278 }
3279 }
3280
3281 pub fn get_selection_mode(&self) -> SelectionMode {
3283 self.selection.borrow().mode.clone()
3284 }
3285
3286 pub fn get_selected_row(&self) -> Option<usize> {
3288 self.selection.borrow().selected_row
3289 }
3290
3291 pub fn get_selected_column(&self) -> usize {
3293 self.selection.borrow().selected_column
3294 }
3295
3296 pub fn get_current_position(&self) -> (usize, usize) {
3299 let nav = self.navigation.borrow();
3300 (nav.selected_row, nav.selected_column)
3301 }
3302
3303 pub fn sync_selection_with_navigation(&self) {
3306 let nav = self.navigation.borrow();
3307 let mut selection = self.selection.borrow_mut();
3308
3309 selection.selected_row = Some(nav.selected_row);
3311 selection.selected_column = nav.selected_column;
3312 selection.last_selection_time = Some(Instant::now());
3313 selection.total_selections += 1;
3314 }
3315
3316 pub fn handle_yank_by_mode(&self) -> Option<String> {
3319 let mode = self.get_selection_mode();
3320 let (_row, _col) = self.get_current_position();
3321
3322 match mode {
3323 SelectionMode::Cell => {
3324 Some("yank_cell".to_string())
3326 }
3327 SelectionMode::Row => {
3328 None }
3331 SelectionMode::Column => {
3332 Some("yank_column".to_string())
3334 }
3335 }
3336 }
3337
3338 pub fn get_table_selected_row(&self) -> Option<usize> {
3340 let nav = self.navigation.borrow();
3341 if nav.total_rows > 0 {
3343 Some(nav.selected_row)
3344 } else {
3345 tracing::debug!(
3347 "get_table_selected_row returning None: total_rows={}, selected_row={}",
3348 nav.total_rows,
3349 nav.selected_row
3350 );
3351 None
3352 }
3353 }
3354
3355 pub fn set_table_selected_row(&self, row: Option<usize>) {
3357 if let Some(row) = row {
3358 let mut nav = self.navigation.borrow_mut();
3359 if row < nav.total_rows {
3360 let old_row = nav.selected_row;
3361 let column = nav.selected_column;
3362 nav.selected_row = row;
3363 nav.add_to_history(row, column);
3364
3365 if let Some(ref debug_service) = *self.debug_service.borrow() {
3366 debug_service.info(
3367 "Navigation",
3368 format!("Table row selected: {} → {}", old_row, row),
3369 );
3370 }
3371 }
3372 }
3373 self.sync_selection_with_navigation();
3375 }
3376
3377 pub fn get_current_column(&self) -> usize {
3379 self.navigation.borrow().selected_column
3380 }
3381
3382 pub fn set_current_column(&self, column: usize) {
3384 let mut nav = self.navigation.borrow_mut();
3385 if column < nav.total_columns {
3386 let old_col = nav.selected_column;
3387 let row = nav.selected_row;
3388 nav.selected_column = column;
3389 nav.add_to_history(row, column);
3390
3391 nav.ensure_visible(row, column);
3393
3394 if let Some(ref debug_service) = *self.debug_service.borrow() {
3395 debug_service.info(
3396 "Navigation",
3397 format!("Column selected: {} → {}", old_col, column),
3398 );
3399 }
3400 }
3401 drop(nav); self.sync_selection_with_navigation();
3404 }
3405
3406 pub fn completion(&self) -> std::cell::Ref<'_, CompletionState> {
3408 self.completion.borrow()
3409 }
3410
3411 pub fn completion_mut(&self) -> std::cell::RefMut<'_, CompletionState> {
3412 self.completion.borrow_mut()
3413 }
3414
3415 pub fn clear_completion(&self) {
3416 let mut completion = self.completion.borrow_mut();
3417 let had_suggestions = completion.suggestions.len();
3418 completion.clear();
3419
3420 if had_suggestions > 0 {
3421 if let Some(ref debug_service) = *self.debug_service.borrow() {
3422 debug_service.info(
3423 "Completion",
3424 format!("Cleared {} suggestions", had_suggestions),
3425 );
3426 }
3427 }
3428 }
3429
3430 pub fn set_completion_suggestions(&self, suggestions: Vec<String>) {
3431 let mut completion = self.completion.borrow_mut();
3432 let count = suggestions.len();
3433 completion.set_suggestions(suggestions);
3434
3435 if count > 0 {
3436 if let Some(ref debug_service) = *self.debug_service.borrow() {
3437 debug_service.info(
3438 "Completion",
3439 format!("Set {} completion suggestions", count),
3440 );
3441 }
3442 }
3443 }
3444
3445 pub fn next_completion(&self) {
3446 let mut completion = self.completion.borrow_mut();
3447 if !completion.suggestions.is_empty() {
3448 completion.next_suggestion();
3449
3450 if let Some(ref debug_service) = *self.debug_service.borrow() {
3451 if let Some(current) = completion.current_suggestion() {
3452 debug_service.info(
3453 "Completion",
3454 format!(
3455 "Cycling to suggestion {}/{}: {}",
3456 completion.current_index + 1,
3457 completion.suggestions.len(),
3458 current
3459 ),
3460 );
3461 }
3462 }
3463 }
3464 }
3465
3466 pub fn get_current_completion(&self) -> Option<String> {
3467 self.completion.borrow().current_suggestion().cloned()
3468 }
3469
3470 pub fn is_completion_active(&self) -> bool {
3471 self.completion.borrow().is_active
3472 }
3473
3474 pub fn update_completion_context(&self, query: String, cursor_pos: usize) {
3475 self.completion
3476 .borrow_mut()
3477 .update_context(query, cursor_pos);
3478 }
3479
3480 pub fn is_same_completion_context(&self, query: &str, cursor_pos: usize) -> bool {
3481 self.completion.borrow().is_same_context(query, cursor_pos)
3482 }
3483
3484 pub fn start_history_search(&self, original_input: String) {
3486 info!(
3487 target: "history",
3488 "Starting history search with original input: '{}'",
3489 original_input
3490 );
3491
3492 let mut history_search = self.history_search.borrow_mut();
3493 history_search.query.clear();
3494 history_search.matches.clear();
3495 history_search.selected_index = 0;
3496 history_search.is_active = true;
3497 history_search.original_input = original_input.clone();
3498
3499 let history = self.command_history.borrow();
3501 let all_entries = history.get_all();
3502 info!(
3503 target: "history",
3504 "Loaded {} history entries for search",
3505 all_entries.len()
3506 );
3507
3508 if !all_entries.is_empty() {
3510 let recent_count = std::cmp::min(5, all_entries.len());
3511 info!(target: "history", "Most recent {} entries (newest first):", recent_count);
3512 for (i, entry) in all_entries.iter().rev().take(recent_count).enumerate() {
3514 info!(target: "history", " [{}] '{}'", i, entry.command);
3515 }
3516 }
3517
3518 history_search.matches = all_entries
3520 .iter()
3521 .rev() .cloned()
3523 .map(|entry| crate::history::HistoryMatch {
3524 entry,
3525 indices: Vec::new(),
3526 score: 0,
3527 })
3528 .collect();
3529
3530 eprintln!(
3531 "[DEBUG] Created {} matches in history_search",
3532 history_search.matches.len()
3533 );
3534
3535 if let Some(ref debug_service) = *self.debug_service.borrow() {
3536 debug_service.info(
3537 "HistorySearch",
3538 format!(
3539 "Started history search with {} entries",
3540 history_search.matches.len()
3541 ),
3542 );
3543 }
3544 }
3545
3546 pub fn update_history_search(&self, query: String) {
3547 let mut history_search = self.history_search.borrow_mut();
3548 let old_query = history_search.query.clone();
3549 history_search.query = query.clone();
3550
3551 if query.is_empty() {
3552 let history = self.command_history.borrow();
3554 let all_entries = history.get_all();
3555 history_search.matches = all_entries
3556 .iter()
3557 .cloned()
3558 .map(|entry| crate::history::HistoryMatch {
3559 entry,
3560 indices: Vec::new(),
3561 score: 0,
3562 })
3563 .collect();
3564 } else {
3565 use fuzzy_matcher::skim::SkimMatcherV2;
3567 use fuzzy_matcher::FuzzyMatcher;
3568
3569 let matcher = SkimMatcherV2::default();
3570 let history = self.command_history.borrow();
3571 let mut matches: Vec<crate::history::HistoryMatch> = history
3572 .get_all()
3573 .iter()
3574 .cloned()
3575 .filter_map(|entry| {
3576 matcher
3577 .fuzzy_indices(&entry.command, &query)
3578 .map(|(score, indices)| crate::history::HistoryMatch {
3579 entry,
3580 indices,
3581 score,
3582 })
3583 })
3584 .collect();
3585
3586 matches.sort_by(|a, b| b.score.cmp(&a.score));
3588 history_search.matches = matches;
3589 }
3590
3591 if history_search.selected_index >= history_search.matches.len() {
3593 history_search.selected_index = 0;
3594 }
3595
3596 if let Some(ref debug_service) = *self.debug_service.borrow() {
3597 debug_service.info(
3598 "HistorySearch",
3599 format!(
3600 "Updated history search: '{}' -> '{}', {} matches",
3601 old_query,
3602 query,
3603 history_search.matches.len()
3604 ),
3605 );
3606 }
3607 }
3608
3609 pub fn update_history_search_with_schema(
3611 &self,
3612 query: String,
3613 columns: &[String],
3614 source: Option<&str>,
3615 ) {
3616 let mut history_search = self.history_search.borrow_mut();
3617 let old_query = history_search.query.clone();
3618 let old_matches_count = history_search.matches.len();
3619
3620 history_search.query = query.clone();
3621
3622 history_search.matches = self
3624 .command_history
3625 .borrow()
3626 .search_with_schema(&query, columns, source);
3627
3628 history_search.selected_index = 0;
3630
3631 if let Some(ref debug_service) = *self.debug_service.borrow() {
3632 debug_service.info(
3633 "HistorySearch",
3634 format!(
3635 "Updated history search with schema: '{}' -> '{}', matches: {} -> {}, columns: {}, source: {:?}",
3636 old_query,
3637 query,
3638 old_matches_count,
3639 history_search.matches.len(),
3640 columns.len(),
3641 source
3642 ),
3643 );
3644 }
3645 }
3646
3647 pub fn history_search_add_char(&self, c: char) {
3649 let mut history_search = self.history_search.borrow_mut();
3650 let old_query = history_search.query.clone();
3651 history_search.query.push(c);
3652
3653 if let Some(ref debug_service) = *self.debug_service.borrow() {
3654 debug_service.info(
3655 "HistorySearch",
3656 format!(
3657 "Added char '{}': '{}' -> '{}'",
3658 c, old_query, history_search.query
3659 ),
3660 );
3661 }
3662 }
3663
3664 pub fn history_search_backspace(&self) {
3666 let mut history_search = self.history_search.borrow_mut();
3667 let old_query = history_search.query.clone();
3668 history_search.query.pop();
3669
3670 if let Some(ref debug_service) = *self.debug_service.borrow() {
3671 debug_service.info(
3672 "HistorySearch",
3673 format!("Backspace: '{}' -> '{}'", old_query, history_search.query),
3674 );
3675 }
3676 }
3677
3678 pub fn history_search_next(&self) {
3679 let mut history_search = self.history_search.borrow_mut();
3680 if !history_search.matches.is_empty() {
3681 let old_index = history_search.selected_index;
3682 history_search.selected_index =
3683 (history_search.selected_index + 1) % history_search.matches.len();
3684
3685 if let Some(ref debug_service) = *self.debug_service.borrow() {
3686 debug_service.info(
3687 "HistorySearch",
3688 format!(
3689 "Navigate next: {} -> {}",
3690 old_index, history_search.selected_index
3691 ),
3692 );
3693 }
3694 }
3695 }
3696
3697 pub fn history_search_previous(&self) {
3698 let mut history_search = self.history_search.borrow_mut();
3699 if !history_search.matches.is_empty() {
3700 let old_index = history_search.selected_index;
3701 history_search.selected_index = if history_search.selected_index == 0 {
3702 history_search.matches.len() - 1
3703 } else {
3704 history_search.selected_index - 1
3705 };
3706
3707 if let Some(ref debug_service) = *self.debug_service.borrow() {
3708 debug_service.info(
3709 "HistorySearch",
3710 format!(
3711 "Navigate previous: {} -> {}",
3712 old_index, history_search.selected_index
3713 ),
3714 );
3715 }
3716 }
3717 }
3718
3719 pub fn get_selected_history_command(&self) -> Option<String> {
3720 let history_search = self.history_search.borrow();
3721 history_search
3722 .matches
3723 .get(history_search.selected_index)
3724 .map(|m| m.entry.command.clone())
3725 }
3726
3727 pub fn accept_history_search(&self) -> Option<String> {
3728 let mut history_search = self.history_search.borrow_mut();
3729 if history_search.is_active {
3730 let command = history_search
3731 .matches
3732 .get(history_search.selected_index)
3733 .map(|m| m.entry.command.clone());
3734
3735 if let Some(ref debug_service) = *self.debug_service.borrow() {
3736 debug_service.info(
3737 "HistorySearch",
3738 format!("Accepted history command: {:?}", command),
3739 );
3740 }
3741
3742 history_search.clear();
3743 command
3744 } else {
3745 None
3746 }
3747 }
3748
3749 pub fn cancel_history_search(&self) -> String {
3750 let mut history_search = self.history_search.borrow_mut();
3751 let original = history_search.original_input.clone();
3752
3753 if let Some(ref debug_service) = *self.debug_service.borrow() {
3754 debug_service.info(
3755 "HistorySearch",
3756 format!("Cancelled history search, restoring: '{}'", original),
3757 );
3758 }
3759
3760 history_search.clear();
3761 original
3762 }
3763
3764 pub fn history_search(&self) -> std::cell::Ref<'_, HistorySearchState> {
3765 self.history_search.borrow()
3766 }
3767
3768 pub fn is_history_search_active(&self) -> bool {
3769 self.history_search.borrow().is_active
3770 }
3771
3772 pub fn navigate_to(&self, row: usize, col: usize) {
3774 let mut navigation = self.navigation.borrow_mut();
3775 let old_row = navigation.selected_row;
3776 let old_col = navigation.selected_column;
3777
3778 navigation.selected_row = row.min(navigation.total_rows.saturating_sub(1));
3780 navigation.selected_column = col.min(navigation.total_columns.saturating_sub(1));
3781
3782 let new_row = navigation.selected_row;
3783 let new_col = navigation.selected_column;
3784
3785 navigation.add_to_history(new_row, new_col);
3787
3788 navigation.ensure_visible(new_row, new_col);
3790
3791 let scroll_offset = navigation.scroll_offset;
3792 drop(navigation);
3793
3794 if let Some(ref debug_service) = *self.debug_service.borrow() {
3795 debug_service.log(
3796 "Navigation",
3797 DebugLevel::Info,
3798 format!(
3799 "Navigate: ({}, {}) -> ({}, {}), scroll: {:?}",
3800 old_row, old_col, new_row, new_col, scroll_offset
3801 ),
3802 Some("navigate_to".to_string()),
3803 );
3804 }
3805 }
3806
3807 pub fn navigate_relative(&self, delta_row: i32, delta_col: i32) {
3808 let navigation = self.navigation.borrow();
3809 let current_row = navigation.selected_row;
3810 let current_col = navigation.selected_column;
3811 drop(navigation);
3812
3813 let new_row = if delta_row >= 0 {
3814 current_row.saturating_add(delta_row as usize)
3815 } else {
3816 current_row.saturating_sub(delta_row.abs() as usize)
3817 };
3818
3819 let new_col = if delta_col >= 0 {
3820 current_col.saturating_add(delta_col as usize)
3821 } else {
3822 current_col.saturating_sub(delta_col.abs() as usize)
3823 };
3824
3825 self.navigate_to(new_row, new_col);
3826 }
3827
3828 pub fn navigate_to_row(&self, row: usize) {
3829 let navigation = self.navigation.borrow();
3830 let current_col = navigation.selected_column;
3831 drop(navigation);
3832
3833 if let Some(ref debug_service) = *self.debug_service.borrow() {
3834 debug_service.log(
3835 "Navigation",
3836 DebugLevel::Info,
3837 format!("Jump to row: {}", row),
3838 Some("navigate_to_row".to_string()),
3839 );
3840 }
3841
3842 self.navigate_to(row, current_col);
3843 }
3844
3845 pub fn navigate_to_column(&self, col: usize) {
3846 let navigation = self.navigation.borrow();
3847 let current_row = navigation.selected_row;
3848 drop(navigation);
3849
3850 if let Some(ref debug_service) = *self.debug_service.borrow() {
3851 debug_service.log(
3852 "Navigation",
3853 DebugLevel::Info,
3854 format!("Jump to column: {}", col),
3855 Some("navigate_to_column".to_string()),
3856 );
3857 }
3858
3859 self.navigate_to(current_row, col);
3860 }
3861
3862 pub fn update_data_size(&self, rows: usize, columns: usize) {
3863 let mut navigation = self.navigation.borrow_mut();
3864 let old_totals = (navigation.total_rows, navigation.total_columns);
3865 navigation.update_totals(rows, columns);
3866
3867 if let Some(ref debug_service) = *self.debug_service.borrow() {
3868 debug_service.log(
3869 "Navigation",
3870 DebugLevel::Info,
3871 format!(
3872 "Data size updated: {:?} -> ({}, {}), position: ({}, {})",
3873 old_totals, rows, columns, navigation.selected_row, navigation.selected_column
3874 ),
3875 Some("update_data_size".to_string()),
3876 );
3877 }
3878 }
3879
3880 pub fn set_viewport_size(&self, rows: usize, columns: usize) {
3881 let mut navigation = self.navigation.borrow_mut();
3882 let old_viewport = (navigation.viewport_rows, navigation.viewport_columns);
3883 let selected_row = navigation.selected_row;
3884 let selected_column = navigation.selected_column;
3885
3886 navigation.set_viewport_size(rows, columns);
3887
3888 navigation.ensure_visible(selected_row, selected_column);
3890
3891 let scroll_offset = navigation.scroll_offset;
3892 drop(navigation);
3893
3894 if let Some(ref debug_service) = *self.debug_service.borrow() {
3895 debug_service.log(
3896 "Navigation",
3897 DebugLevel::Info,
3898 format!(
3899 "Viewport size updated: {:?} -> ({}, {}), scroll adjusted: {:?}",
3900 old_viewport, rows, columns, scroll_offset
3901 ),
3902 Some("set_viewport_size".to_string()),
3903 );
3904 }
3905 }
3906
3907 pub fn toggle_viewport_lock(&self) {
3908 let mut navigation = self.navigation.borrow_mut();
3909 navigation.viewport_lock = !navigation.viewport_lock;
3910
3911 if navigation.viewport_lock {
3912 navigation.viewport_lock_row = Some(navigation.selected_row);
3913 } else {
3914 navigation.viewport_lock_row = None;
3915 }
3916
3917 if let Some(ref debug_service) = *self.debug_service.borrow() {
3918 debug_service.log(
3919 "Navigation",
3920 DebugLevel::Info,
3921 format!(
3922 "Viewport lock: {} at row {:?}",
3923 navigation.viewport_lock, navigation.viewport_lock_row
3924 ),
3925 Some("toggle_viewport_lock".to_string()),
3926 );
3927 }
3928 }
3929
3930 pub fn toggle_cursor_lock(&self) {
3931 let mut navigation = self.navigation.borrow_mut();
3932 navigation.cursor_lock = !navigation.cursor_lock;
3933
3934 if navigation.cursor_lock {
3935 let visual_position = navigation
3937 .selected_row
3938 .saturating_sub(navigation.scroll_offset.0);
3939 navigation.cursor_lock_position = Some(visual_position);
3940 } else {
3941 navigation.cursor_lock_position = None;
3942 }
3943
3944 if let Some(ref debug_service) = *self.debug_service.borrow() {
3945 debug_service.log(
3946 "Navigation",
3947 DebugLevel::Info,
3948 format!(
3949 "Cursor lock: {} at visual position {:?}",
3950 navigation.cursor_lock, navigation.cursor_lock_position
3951 ),
3952 Some("toggle_cursor_lock".to_string()),
3953 );
3954 }
3955 }
3956
3957 pub fn is_cursor_locked(&self) -> bool {
3958 self.navigation.borrow().cursor_lock
3959 }
3960
3961 pub fn navigation(&self) -> std::cell::Ref<'_, NavigationState> {
3963 self.navigation.borrow()
3964 }
3965
3966 pub fn navigation_mut(&self) -> std::cell::RefMut<'_, NavigationState> {
3967 self.navigation.borrow_mut()
3968 }
3969
3970 pub fn navigation_proxy(&self) -> NavigationProxy {
3972 NavigationProxy::new(self.buffers.current())
3973 }
3974
3975 pub fn navigation_proxy_mut(&mut self) -> NavigationProxyMut {
3976 NavigationProxyMut::new(self.buffers.current_mut())
3977 }
3978
3979 pub fn get_scroll_offset(&self) -> (usize, usize) {
3982 self.navigation.borrow().scroll_offset
3983 }
3984
3985 pub fn is_viewport_locked(&self) -> bool {
3986 self.navigation.borrow().viewport_lock
3987 }
3988
3989 pub fn set_results(
3992 &self,
3993 results: QueryResponse,
3994 execution_time: Duration,
3995 from_cache: bool,
3996 ) -> Result<()> {
3997 let query_text = results.query.select.join(", ");
3998 let row_count = results.count;
3999
4000 if let Some(ref debug_service) = *self.debug_service.borrow() {
4001 debug_service.log(
4002 "ResultsState",
4003 DebugLevel::Info,
4004 format!(
4005 "[RESULTS] Setting results: query='{}', rows={}, time={}ms, cached={}",
4006 query_text.chars().take(50).collect::<String>(),
4007 row_count,
4008 execution_time.as_millis(),
4009 from_cache
4010 ),
4011 Some("set_results".to_string()),
4012 );
4013 }
4014
4015 self.results
4016 .borrow_mut()
4017 .set_results(results, execution_time, from_cache)?;
4018
4019 if let Some(ref debug_service) = *self.debug_service.borrow() {
4020 let stats = self.results.borrow().get_performance_stats();
4021 debug_service.log(
4022 "ResultsState",
4023 DebugLevel::Info,
4024 format!(
4025 "[RESULTS] Performance stats: total_queries={}, cache_hit_rate={:.2}%, avg_time={:.2}ms",
4026 stats.total_queries,
4027 stats.cache_hit_rate * 100.0,
4028 stats.average_execution_time_ms
4029 ),
4030 Some("performance_stats".to_string()),
4031 );
4032 }
4033
4034 Ok(())
4035 }
4036
4037 pub fn get_results(&self) -> Option<QueryResponse> {
4039 self.results.borrow().get_results().cloned()
4040 }
4041
4042 pub fn cache_results(&self, query_key: String, results: QueryResponse) -> Result<()> {
4044 if let Some(ref debug_service) = *self.debug_service.borrow() {
4045 debug_service.log(
4046 "ResultsCache",
4047 DebugLevel::Info,
4048 format!(
4049 "[RESULTS] Caching results: key='{}', rows={}",
4050 query_key.chars().take(30).collect::<String>(),
4051 results.count
4052 ),
4053 Some("cache_results".to_string()),
4054 );
4055 }
4056
4057 let result = self
4058 .results
4059 .borrow_mut()
4060 .cache_results(query_key.clone(), results);
4061
4062 if let Some(ref debug_service) = *self.debug_service.borrow() {
4063 let cache_stats = self.results.borrow().get_cache_stats();
4064 debug_service.log(
4065 "ResultsCache",
4066 DebugLevel::Info,
4067 format!(
4068 "[RESULTS] Cache stats: entries={}, memory={}MB, hit_rate={:.2}%",
4069 cache_stats.entry_count,
4070 cache_stats.memory_usage / (1024 * 1024),
4071 cache_stats.hit_rate * 100.0
4072 ),
4073 Some("cache_stats".to_string()),
4074 );
4075 }
4076
4077 result
4078 }
4079
4080 pub fn get_cached_results(&self, query_key: &str) -> Option<QueryResponse> {
4082 if let Some(result) = self.results.borrow_mut().get_cached_results(query_key) {
4083 if let Some(ref debug_service) = *self.debug_service.borrow() {
4084 debug_service.log(
4085 "ResultsCache",
4086 DebugLevel::Trace,
4087 format!(
4088 "[RESULTS] Cache HIT for key: '{}'",
4089 query_key.chars().take(30).collect::<String>()
4090 ),
4091 Some("cache_hit".to_string()),
4092 );
4093 }
4094 Some(result.clone())
4095 } else {
4096 if let Some(ref debug_service) = *self.debug_service.borrow() {
4097 debug_service.log(
4098 "ResultsCache",
4099 DebugLevel::Trace,
4100 format!(
4101 "[RESULTS] Cache MISS for key: '{}'",
4102 query_key.chars().take(30).collect::<String>()
4103 ),
4104 Some("cache_miss".to_string()),
4105 );
4106 }
4107 None
4108 }
4109 }
4110
4111 pub fn clear_results_cache(&self) {
4113 let before_count = self.results.borrow().get_cache_stats().entry_count;
4114 self.results.borrow_mut().clear_cache();
4115
4116 if let Some(ref debug_service) = *self.debug_service.borrow() {
4117 debug_service.log(
4118 "ResultsCache",
4119 DebugLevel::Info,
4120 format!("[RESULTS] Cache cleared: removed {} entries", before_count),
4121 Some("clear_cache".to_string()),
4122 );
4123 }
4124 }
4125
4126 pub fn clipboard(&self) -> std::cell::Ref<'_, ClipboardState> {
4130 self.clipboard.borrow()
4131 }
4132
4133 pub fn clipboard_mut(&self) -> std::cell::RefMut<'_, ClipboardState> {
4135 self.clipboard.borrow_mut()
4136 }
4137
4138 pub fn chord(&self) -> std::cell::Ref<'_, ChordState> {
4142 self.chord.borrow()
4143 }
4144
4145 pub fn chord_mut(&self) -> std::cell::RefMut<'_, ChordState> {
4147 self.chord.borrow_mut()
4148 }
4149
4150 pub fn undo_redo(&self) -> std::cell::Ref<'_, UndoRedoState> {
4154 self.undo_redo.borrow()
4155 }
4156
4157 pub fn undo_redo_mut(&self) -> std::cell::RefMut<'_, UndoRedoState> {
4159 self.undo_redo.borrow_mut()
4160 }
4161
4162 pub fn scroll(&self) -> std::cell::Ref<'_, ScrollState> {
4166 self.scroll.borrow()
4167 }
4168
4169 pub fn scroll_mut(&self) -> std::cell::RefMut<'_, ScrollState> {
4171 self.scroll.borrow_mut()
4172 }
4173
4174 pub fn yank_cell(
4176 &self,
4177 row: usize,
4178 column: usize,
4179 value: String,
4180 preview: String,
4181 ) -> Result<()> {
4182 let description = format!("cell at [{}, {}]", row, column);
4183 let size_bytes = value.len();
4184
4185 trace!(
4186 "yank_cell: Starting clipboard write for {} ({} bytes)",
4187 description,
4188 size_bytes
4189 );
4190 trace!(
4191 "yank_cell: Value preview: '{}'",
4192 if value.len() > 50 {
4193 &value[..50]
4194 } else {
4195 &value
4196 }
4197 );
4198
4199 let mut system_clipboard = Clipboard::new()?;
4201 trace!("yank_cell: Setting clipboard text ({} bytes)", value.len());
4202 system_clipboard.set_text(&value)?;
4203
4204 let clipboard_content = system_clipboard.get_text().unwrap_or_default();
4206 if clipboard_content != value {
4207 return Err(anyhow!(
4208 "Clipboard write verification failed. Expected {} chars, wrote {} chars",
4209 value.len(),
4210 clipboard_content.len()
4211 ));
4212 }
4213
4214 let item = YankedItem {
4215 description: description.clone(),
4216 full_value: value.clone(),
4217 preview: preview.clone(),
4218 yank_type: YankType::Cell { row, column },
4219 yanked_at: Local::now(),
4220 size_bytes,
4221 };
4222
4223 self.clipboard.borrow_mut().add_yank(item);
4224
4225 if let Some(ref debug_service) = *self.debug_service.borrow() {
4226 debug_service.info(
4227 "Clipboard",
4228 format!(
4229 "Yanked {}: '{}' ({} bytes)",
4230 description,
4231 if preview.len() > 50 {
4232 format!("{}...", &preview[..50])
4233 } else {
4234 preview
4235 },
4236 size_bytes
4237 ),
4238 );
4239 }
4240
4241 Ok(())
4242 }
4243
4244 pub fn yank_row(&self, row: usize, value: String, preview: String) -> Result<()> {
4246 let description = format!("row {}", row);
4247 let size_bytes = value.len();
4248
4249 let mut system_clipboard = Clipboard::new()?;
4251 system_clipboard.set_text(&value)?;
4252
4253 let clipboard_content = system_clipboard.get_text().unwrap_or_default();
4255 if clipboard_content != value {
4256 return Err(anyhow!(
4257 "Clipboard write verification failed. Expected {} chars, wrote {} chars",
4258 value.len(),
4259 clipboard_content.len()
4260 ));
4261 }
4262
4263 let item = YankedItem {
4264 description: description.clone(),
4265 full_value: value.clone(),
4266 preview: preview.clone(),
4267 yank_type: YankType::Row { row },
4268 yanked_at: Local::now(),
4269 size_bytes,
4270 };
4271
4272 self.clipboard.borrow_mut().add_yank(item);
4273
4274 if let Some(ref debug_service) = *self.debug_service.borrow() {
4275 debug_service.info(
4276 "Clipboard",
4277 format!(
4278 "Yanked {}: {} columns ({} bytes)",
4279 description,
4280 value.split('\t').count(),
4281 size_bytes
4282 ),
4283 );
4284 }
4285
4286 Ok(())
4287 }
4288
4289 pub fn yank_column(
4291 &self,
4292 column_name: String,
4293 column_index: usize,
4294 value: String,
4295 preview: String,
4296 ) -> Result<()> {
4297 let description = format!("column '{}'", column_name);
4298 let size_bytes = value.len();
4299 let row_count = value.lines().count();
4300
4301 let mut system_clipboard = Clipboard::new()?;
4303 system_clipboard.set_text(&value)?;
4304
4305 let clipboard_content = system_clipboard.get_text().unwrap_or_default();
4307 if clipboard_content != value {
4308 return Err(anyhow!(
4309 "Clipboard write verification failed. Expected {} chars, wrote {} chars",
4310 value.len(),
4311 clipboard_content.len()
4312 ));
4313 }
4314
4315 let item = YankedItem {
4316 description: description.clone(),
4317 full_value: value.clone(),
4318 preview: preview.clone(),
4319 yank_type: YankType::Column {
4320 name: column_name.clone(),
4321 index: column_index,
4322 },
4323 yanked_at: Local::now(),
4324 size_bytes,
4325 };
4326
4327 self.clipboard.borrow_mut().add_yank(item);
4328
4329 if let Some(ref debug_service) = *self.debug_service.borrow() {
4330 debug_service.info(
4331 "Clipboard",
4332 format!(
4333 "Yanked {}: {} rows ({} bytes)",
4334 description, row_count, size_bytes
4335 ),
4336 );
4337 }
4338
4339 Ok(())
4340 }
4341
4342 pub fn yank_all(&self, value: String, preview: String) -> Result<()> {
4344 let size_bytes = value.len();
4345 let row_count = value.lines().count();
4346
4347 let mut system_clipboard = Clipboard::new()?;
4349 system_clipboard.set_text(&value)?;
4350
4351 let clipboard_content = system_clipboard.get_text().unwrap_or_default();
4353 if clipboard_content != value {
4354 return Err(anyhow!(
4355 "Clipboard write verification failed. Expected {} chars, wrote {} chars",
4356 value.len(),
4357 clipboard_content.len()
4358 ));
4359 }
4360
4361 let item = YankedItem {
4362 description: "all data".to_string(),
4363 full_value: value.clone(),
4364 preview: preview.clone(),
4365 yank_type: YankType::All,
4366 yanked_at: Local::now(),
4367 size_bytes,
4368 };
4369
4370 self.clipboard.borrow_mut().add_yank(item);
4371
4372 if let Some(ref debug_service) = *self.debug_service.borrow() {
4373 debug_service.info(
4374 "Clipboard",
4375 format!("Yanked all data: {} rows ({} bytes)", row_count, size_bytes),
4376 );
4377 }
4378
4379 Ok(())
4380 }
4381
4382 pub fn yank_test_case(&self, value: String) -> Result<()> {
4384 let size_bytes = value.len();
4385 let line_count = value.lines().count();
4386
4387 let mut system_clipboard = Clipboard::new()?;
4389 system_clipboard.set_text(&value)?;
4390
4391 let clipboard_content = system_clipboard.get_text().unwrap_or_default();
4393 if clipboard_content != value {
4394 return Err(anyhow!(
4395 "Clipboard write verification failed. Expected {} chars, wrote {} chars",
4396 value.len(),
4397 clipboard_content.len()
4398 ));
4399 }
4400
4401 let item = YankedItem {
4402 description: "Test Case".to_string(),
4403 full_value: value.clone(),
4404 preview: format!("{} lines of test case", line_count),
4405 yank_type: YankType::TestCase,
4406 yanked_at: Local::now(),
4407 size_bytes,
4408 };
4409
4410 self.clipboard.borrow_mut().add_yank(item);
4411
4412 if let Some(ref debug_service) = *self.debug_service.borrow() {
4413 debug_service.info(
4414 "Clipboard",
4415 format!(
4416 "Yanked test case: {} lines ({} bytes)",
4417 line_count, size_bytes
4418 ),
4419 );
4420 }
4421
4422 Ok(())
4423 }
4424
4425 pub fn yank_debug_context(&self, value: String) -> Result<()> {
4427 let size_bytes = value.len();
4428 let line_count = value.lines().count();
4429
4430 let mut system_clipboard = Clipboard::new()?;
4432 system_clipboard.set_text(&value)?;
4433
4434 let clipboard_content = system_clipboard.get_text().unwrap_or_default();
4436 if clipboard_content != value {
4437 return Err(anyhow!(
4438 "Clipboard write verification failed. Expected {} chars, wrote {} chars",
4439 value.len(),
4440 clipboard_content.len()
4441 ));
4442 }
4443
4444 let item = YankedItem {
4445 description: "Debug Context".to_string(),
4446 full_value: value.clone(),
4447 preview: "Query context with data for test creation".to_string(),
4448 yank_type: YankType::DebugContext,
4449 yanked_at: Local::now(),
4450 size_bytes,
4451 };
4452
4453 self.clipboard.borrow_mut().add_yank(item);
4454
4455 if let Some(ref debug_service) = *self.debug_service.borrow() {
4456 debug_service.info(
4457 "Clipboard",
4458 format!(
4459 "Yanked debug context: {} lines ({} bytes)",
4460 line_count, size_bytes
4461 ),
4462 );
4463 }
4464
4465 Ok(())
4466 }
4467
4468 pub fn clear_clipboard(&self) {
4470 let had_item = self.clipboard.borrow().last_yanked.is_some();
4471 self.clipboard.borrow_mut().clear();
4472
4473 if had_item {
4474 if let Some(ref debug_service) = *self.debug_service.borrow() {
4475 debug_service.info("Clipboard", "Clipboard cleared".to_string());
4476 }
4477 }
4478 }
4479
4480 pub fn get_clipboard_stats(&self) -> String {
4482 self.clipboard.borrow().get_stats()
4483 }
4484
4485 pub fn read_from_clipboard(&self) -> Result<String> {
4487 let mut system_clipboard = Clipboard::new()?;
4488 let text = system_clipboard.get_text()?;
4489 Ok(text)
4490 }
4491
4492 pub fn write_to_clipboard(&self, text: &str) -> Result<()> {
4494 let mut system_clipboard = Clipboard::new()?;
4495 system_clipboard.set_text(text)?;
4496
4497 let clipboard_content = system_clipboard.get_text().unwrap_or_default();
4499 if clipboard_content != text {
4500 return Err(anyhow!(
4501 "Clipboard write verification failed. Expected {} chars, wrote {} chars",
4502 text.len(),
4503 clipboard_content.len()
4504 ));
4505 }
4506
4507 Ok(())
4508 }
4509
4510 pub fn get_results_stats(&self) -> (CacheStats, PerformanceStats) {
4512 let results = self.results.borrow();
4513 (results.get_cache_stats(), results.get_performance_stats())
4514 }
4515
4516 pub fn is_results_from_cache(&self) -> bool {
4518 self.results.borrow().from_cache
4519 }
4520
4521 pub fn get_last_execution_time(&self) -> Duration {
4523 self.results.borrow().last_execution_time
4524 }
4525
4526 pub fn get_results_memory_usage(&self) -> (usize, usize) {
4528 let cache_stats = self.results.borrow().get_cache_stats();
4529 (cache_stats.memory_usage, cache_stats.memory_limit)
4530 }
4531 pub fn widgets(&self) -> &WidgetStates {
4533 &self.widgets
4534 }
4535
4536 pub fn widgets_mut(&mut self) -> &mut WidgetStates {
4537 &mut self.widgets
4538 }
4539
4540 pub fn cache_list(&self) -> &CacheListState {
4542 &self.cache_list
4543 }
4544
4545 pub fn cache_list_mut(&mut self) -> &mut CacheListState {
4546 &mut self.cache_list
4547 }
4548
4549 pub fn column_stats(&self) -> &ColumnStatsState {
4550 &self.column_stats
4551 }
4552
4553 pub fn column_stats_mut(&mut self) -> &mut ColumnStatsState {
4554 &mut self.column_stats
4555 }
4556
4557 pub fn jump_to_row(&self) -> &JumpToRowState {
4558 &self.jump_to_row
4559 }
4560
4561 pub fn jump_to_row_mut(&mut self) -> &mut JumpToRowState {
4562 &mut self.jump_to_row
4563 }
4564
4565 pub fn command_history(&self) -> std::cell::Ref<'_, CommandHistory> {
4567 self.command_history.borrow()
4568 }
4569
4570 pub fn command_history_mut(&self) -> std::cell::RefMut<'_, CommandHistory> {
4571 self.command_history.borrow_mut()
4572 }
4573
4574 pub fn results_cache(&self) -> &ResultsCache {
4576 &self.results_cache
4577 }
4578
4579 pub fn results_cache_mut(&mut self) -> &mut ResultsCache {
4580 &mut self.results_cache
4581 }
4582
4583 pub fn current_mode(&self) -> AppMode {
4585 self.mode_stack.last().cloned().unwrap_or(AppMode::Command)
4586 }
4587
4588 pub fn enter_mode(&mut self, mode: AppMode) -> Result<()> {
4589 let current = self.current_mode();
4590 if let Some(ref debug_service) = *self.debug_service.borrow() {
4591 debug_service.info(
4592 "AppStateContainer",
4593 format!("MODE TRANSITION: {:?} -> {:?}", current, mode),
4594 );
4595 }
4596
4597 match (current, mode.clone()) {
4599 _ => {
4601 }
4603 }
4604
4605 self.mode_stack.push(mode);
4606 if let Some(ref debug_service) = *self.debug_service.borrow() {
4607 debug_service.info(
4608 "AppStateContainer",
4609 format!("Mode stack: {:?}", self.mode_stack),
4610 );
4611 }
4612 Ok(())
4613 }
4614
4615 pub fn exit_mode(&mut self) -> Result<AppMode> {
4616 if self.mode_stack.len() > 1 {
4617 let exited = self.mode_stack.pop().unwrap();
4618 let new_mode = self.current_mode();
4619 if let Some(ref debug_service) = *self.debug_service.borrow() {
4620 debug_service.info(
4621 "AppStateContainer",
4622 format!("MODE EXIT: {:?} -> {:?}", exited, new_mode),
4623 );
4624 debug_service.info(
4625 "AppStateContainer",
4626 format!("Mode stack after exit: {:?}", self.mode_stack),
4627 );
4628 }
4629 Ok(new_mode)
4630 } else {
4631 Ok(self.current_mode())
4633 }
4634 }
4635
4636 pub fn toggle_debug(&mut self) {
4638 self.debug_enabled = !self.debug_enabled;
4639 if let Some(ref debug_service) = *self.debug_service.borrow() {
4640 debug_service.info(
4641 "AppStateContainer",
4642 format!("Debug mode: {}", self.debug_enabled),
4643 );
4644 }
4645 }
4646
4647 pub fn set_debug_service(&self, debug_service: crate::debug_service::DebugService) {
4649 *self.debug_service.borrow_mut() = Some(debug_service);
4650 if let Some(ref service) = *self.debug_service.borrow() {
4651 service.info("AppStateContainer", "Debug service connected".to_string());
4652 service.info(
4653 "AppStateContainer",
4654 "AppStateContainer constructed with debug logging".to_string(),
4655 );
4656 }
4657 }
4658
4659 pub fn is_debug_enabled(&self) -> bool {
4660 self.debug_enabled
4661 }
4662
4663 pub fn toggle_help(&self) {
4665 let mut help = self.help.borrow_mut();
4666 let old_visible = help.is_visible;
4667 help.toggle();
4668
4669 if let Some(ref debug_service) = *self.debug_service.borrow() {
4670 debug_service.info(
4671 "Help",
4672 format!(
4673 "Toggled help: {} -> {}, open_count: {}",
4674 old_visible, help.is_visible, help.open_count
4675 ),
4676 );
4677 }
4678 }
4679
4680 pub fn is_help_visible(&self) -> bool {
4681 self.help.borrow().is_visible
4682 }
4683
4684 pub fn set_help_visible(&self, visible: bool) {
4685 let mut help = self.help.borrow_mut();
4686 let old_visible = help.is_visible;
4687
4688 if visible {
4689 help.show();
4690 } else {
4691 help.hide();
4692 }
4693
4694 if let Some(ref debug_service) = *self.debug_service.borrow() {
4695 debug_service.info(
4696 "Help",
4697 format!(
4698 "Set help visibility: {} -> {}, scroll reset: {}",
4699 old_visible, help.is_visible, visible
4700 ),
4701 );
4702 }
4703 }
4704
4705 pub fn help_scroll_down(&self) {
4707 let mut help = self.help.borrow_mut();
4708 let old_scroll = help.scroll_offset;
4709 help.scroll_down(1);
4710
4711 if let Some(ref debug_service) = *self.debug_service.borrow() {
4712 if old_scroll != help.scroll_offset {
4713 debug_service.info(
4714 "Help",
4715 format!("Scrolled down: {} -> {}", old_scroll, help.scroll_offset),
4716 );
4717 }
4718 }
4719 }
4720
4721 pub fn help_scroll_up(&self) {
4723 let mut help = self.help.borrow_mut();
4724 let old_scroll = help.scroll_offset;
4725 help.scroll_up(1);
4726
4727 if let Some(ref debug_service) = *self.debug_service.borrow() {
4728 if old_scroll != help.scroll_offset {
4729 debug_service.info(
4730 "Help",
4731 format!("Scrolled up: {} -> {}", old_scroll, help.scroll_offset),
4732 );
4733 }
4734 }
4735 }
4736
4737 pub fn help_page_down(&self) {
4739 let mut help = self.help.borrow_mut();
4740 let old_scroll = help.scroll_offset;
4741 help.scroll_down(10);
4742
4743 if let Some(ref debug_service) = *self.debug_service.borrow() {
4744 debug_service.info(
4745 "Help",
4746 format!("Page down: {} -> {}", old_scroll, help.scroll_offset),
4747 );
4748 }
4749 }
4750
4751 pub fn help_page_up(&self) {
4753 let mut help = self.help.borrow_mut();
4754 let old_scroll = help.scroll_offset;
4755 help.scroll_up(10);
4756
4757 if let Some(ref debug_service) = *self.debug_service.borrow() {
4758 debug_service.info(
4759 "Help",
4760 format!("Page up: {} -> {}", old_scroll, help.scroll_offset),
4761 );
4762 }
4763 }
4764
4765 pub fn set_help_max_scroll(&self, content_lines: usize, viewport_height: usize) {
4767 let mut help = self.help.borrow_mut();
4768 let old_max = help.max_scroll;
4769 help.set_max_scroll(content_lines, viewport_height);
4770
4771 if let Some(ref debug_service) = *self.debug_service.borrow() {
4772 if old_max != help.max_scroll {
4773 debug_service.info(
4774 "Help",
4775 format!(
4776 "Updated max scroll: {} -> {} (content: {}, viewport: {})",
4777 old_max, help.max_scroll, content_lines, viewport_height
4778 ),
4779 );
4780 }
4781 }
4782 }
4783
4784 pub fn help_scroll_offset(&self) -> u16 {
4786 self.help.borrow().scroll_offset
4787 }
4788
4789 pub fn help_state(&self) -> std::cell::Ref<'_, HelpState> {
4791 self.help.borrow()
4792 }
4793
4794 pub fn log_key_press(&self, key: KeyEvent, action: Option<String>) {
4796 let mode = self.current_mode();
4797 let entry = KeyPressEntry::new(key, mode.clone(), action.clone());
4798
4799 if let Some(ref debug_service) = *self.debug_service.borrow() {
4801 let platform_info = if entry.platform == Platform::Windows
4802 && (key.code == KeyCode::Char('$') || key.code == KeyCode::Char('^'))
4803 && key.modifiers.contains(KeyModifiers::SHIFT)
4804 {
4805 " [Windows: SHIFT modifier present]"
4806 } else {
4807 ""
4808 };
4809
4810 debug_service.info(
4811 "KeyPress",
4812 format!(
4813 "Key: {:?}, Mode: {:?}, Action: {:?}, Platform: {:?}{}",
4814 key, mode, action, entry.platform, platform_info
4815 ),
4816 );
4817 }
4818
4819 self.key_press_history.borrow_mut().add(entry);
4820 }
4821
4822 pub fn clear_key_history(&self) {
4823 self.key_press_history.borrow_mut().clear();
4824 if let Some(ref debug_service) = *self.debug_service.borrow() {
4825 debug_service.info("AppStateContainer", "Key press history cleared".to_string());
4826 }
4827 }
4828
4829 pub fn normalize_key(&self, key: KeyEvent) -> KeyEvent {
4833 let platform = Platform::detect();
4834
4835 match platform {
4837 Platform::Windows => {
4838 match key.code {
4839 KeyCode::Char('$')
4841 | KeyCode::Char('^')
4842 | KeyCode::Char(':')
4843 | KeyCode::Char('!')
4844 | KeyCode::Char('@')
4845 | KeyCode::Char('#')
4846 | KeyCode::Char('%')
4847 | KeyCode::Char('&')
4848 | KeyCode::Char('*')
4849 | KeyCode::Char('(')
4850 | KeyCode::Char(')') => {
4851 let mut normalized_modifiers = key.modifiers;
4853 normalized_modifiers.remove(KeyModifiers::SHIFT);
4854
4855 if let Some(ref debug_service) = *self.debug_service.borrow() {
4856 if normalized_modifiers != key.modifiers {
4857 debug_service.info(
4858 "KeyNormalize",
4859 format!(
4860 "Windows key normalization: {:?} with {:?} -> {:?}",
4861 key.code, key.modifiers, normalized_modifiers
4862 ),
4863 );
4864 }
4865 }
4866
4867 KeyEvent::new(key.code, normalized_modifiers)
4868 }
4869 KeyCode::Left if key.modifiers.contains(KeyModifiers::SHIFT) => {
4871 if let Some(ref debug_service) = *self.debug_service.borrow() {
4872 debug_service.info(
4873 "KeyNormalize",
4874 format!("Windows: Shift+Left -> '<' character for column movement"),
4875 );
4876 }
4877 KeyEvent::new(KeyCode::Char('<'), KeyModifiers::NONE)
4879 }
4880 KeyCode::Right if key.modifiers.contains(KeyModifiers::SHIFT) => {
4881 if let Some(ref debug_service) = *self.debug_service.borrow() {
4882 debug_service.info(
4883 "KeyNormalize",
4884 format!(
4885 "Windows: Shift+Right -> '>' character for column movement"
4886 ),
4887 );
4888 }
4889 KeyEvent::new(KeyCode::Char('>'), KeyModifiers::NONE)
4891 }
4892 KeyCode::Char('<') if key.modifiers.contains(KeyModifiers::SHIFT) => {
4894 if let Some(ref debug_service) = *self.debug_service.borrow() {
4895 debug_service.info(
4896 "KeyNormalize",
4897 format!("Windows: Shift+'<' -> '<' character for column movement"),
4898 );
4899 }
4900 KeyEvent::new(KeyCode::Char('<'), KeyModifiers::NONE)
4902 }
4903 KeyCode::Char('>') if key.modifiers.contains(KeyModifiers::SHIFT) => {
4904 if let Some(ref debug_service) = *self.debug_service.borrow() {
4905 debug_service.info(
4906 "KeyNormalize",
4907 format!("Windows: Shift+'>' -> '>' character for column movement"),
4908 );
4909 }
4910 KeyEvent::new(KeyCode::Char('>'), KeyModifiers::NONE)
4912 }
4913 _ => key,
4914 }
4915 }
4916 Platform::Linux | Platform::MacOS => {
4917 key
4920 }
4921 Platform::Unknown => key,
4922 }
4923 }
4924
4925 pub fn debug_dump(&self) -> String {
4927 let mut dump = String::new();
4928
4929 dump.push_str("=== APP STATE CONTAINER DEBUG DUMP ===\n\n");
4930
4931 dump.push_str("MODE INFORMATION:\n");
4933 dump.push_str(&format!(" Current Mode: {:?}\n", self.current_mode()));
4934 dump.push_str(&format!(" Mode Stack: {:?}\n", self.mode_stack));
4935 dump.push_str("\n");
4936
4937 dump.push_str("UI FLAGS:\n");
4939 dump.push_str(&format!(" Debug Enabled: {}\n", self.debug_enabled));
4940 dump.push_str("\n");
4941
4942 let help = self.help.borrow();
4944 dump.push_str("HELP STATE:\n");
4945 dump.push_str(&format!(" Visible: {}\n", help.is_visible));
4946 dump.push_str(&format!(" Scroll Offset: {}\n", help.scroll_offset));
4947 dump.push_str(&format!(" Max Scroll: {}\n", help.max_scroll));
4948 dump.push_str(&format!(" Open Count: {}\n", help.open_count));
4949 if let Some(ref last_opened) = help.last_opened {
4950 dump.push_str(&format!(" Last Opened: {:?} ago\n", last_opened.elapsed()));
4951 }
4952 dump.push_str("\n");
4953
4954 dump.push_str("INPUT STATE:\n");
4956 let input = self.command_input.borrow();
4957 dump.push_str(&format!(" Text: '{}'\n", input.text));
4958 dump.push_str(&format!(" Cursor: {}\n", input.cursor_position));
4959 dump.push_str(&format!(
4960 " Last Query: '{}'\n",
4961 if input.last_executed_query.len() > 100 {
4962 format!("{}...", &input.last_executed_query[..100])
4963 } else {
4964 input.last_executed_query.clone()
4965 }
4966 ));
4967 dump.push_str("\n");
4968
4969 dump.push_str("SEARCH STATE:\n");
4971 let search = self.search.borrow();
4972 if search.is_active {
4973 dump.push_str(&format!(" Pattern: '{}'\n", search.pattern));
4974 dump.push_str(&format!(" Matches: {} found\n", search.matches.len()));
4975 dump.push_str(&format!(
4976 " Current: {} of {}\n",
4977 if search.matches.is_empty() {
4978 0
4979 } else {
4980 search.current_match + 1
4981 },
4982 search.matches.len()
4983 ));
4984 if let Some(ref last_time) = search.last_search_time {
4985 dump.push_str(&format!(" Search time: {:?}\n", last_time.elapsed()));
4986 }
4987 } else {
4988 dump.push_str(" [Inactive]\n");
4989 }
4990
4991 if !search.history.is_empty() {
4993 dump.push_str(" Recent searches:\n");
4994 for (i, entry) in search.history.iter().rev().take(5).enumerate() {
4995 dump.push_str(&format!(
4996 " {}. '{}' → {} matches",
4997 i + 1,
4998 if entry.pattern.len() > 30 {
4999 format!("{}...", &entry.pattern[..30])
5000 } else {
5001 entry.pattern.clone()
5002 },
5003 entry.match_count
5004 ));
5005 if let Some(duration) = entry.duration_ms {
5006 dump.push_str(&format!(" ({}ms)", duration));
5007 }
5008 dump.push_str(&format!(" at {}\n", entry.timestamp.format("%H:%M:%S")));
5009 }
5010 }
5011 dump.push_str("\n");
5012
5013 dump.push_str("FILTER STATE:\n");
5015 let filter = self.filter.borrow();
5016 if filter.is_active {
5017 dump.push_str(&format!(" Pattern: '{}'\n", filter.pattern));
5018 dump.push_str(&format!(
5019 " Filtered Rows: {}\n",
5020 filter.filtered_indices.len()
5021 ));
5022 dump.push_str(&format!(
5023 " Case Insensitive: {}\n",
5024 filter.case_insensitive
5025 ));
5026 if let Some(ref last_time) = filter.last_filter_time {
5027 dump.push_str(&format!(" Last Filter: {:?} ago\n", last_time.elapsed()));
5028 }
5029 } else {
5030 dump.push_str(" [Inactive]\n");
5031 }
5032 dump.push_str(&format!(" Total Filters: {}\n", filter.total_filters));
5033 dump.push_str(&format!(" History Items: {}\n", filter.history.len()));
5034 if !filter.history.is_empty() {
5035 dump.push_str(" Recent filters:\n");
5036 for (i, entry) in filter.history.iter().take(5).enumerate() {
5037 dump.push_str(&format!(
5038 " {}. '{}' ({} matches) at {}\n",
5039 i + 1,
5040 if entry.pattern.len() > 30 {
5041 format!("{}...", &entry.pattern[..30])
5042 } else {
5043 entry.pattern.clone()
5044 },
5045 entry.match_count,
5046 entry.timestamp.format("%H:%M:%S")
5047 ));
5048 }
5049 }
5050 dump.push_str("\n");
5051
5052 let column_search = self.column_search.borrow();
5054 if column_search.is_active {
5055 dump.push_str("COLUMN SEARCH STATE (ACTIVE):\n");
5056 dump.push_str(&format!(" Pattern: '{}'\n", column_search.pattern));
5057 dump.push_str(&format!(
5058 " Matching Columns: {}\n",
5059 column_search.matching_columns.len()
5060 ));
5061 if !column_search.matching_columns.is_empty() {
5062 for (i, (idx, name)) in column_search.matching_columns.iter().take(5).enumerate() {
5063 dump.push_str(&format!(
5064 " [{}] {}: '{}'\n",
5065 if i == column_search.current_match {
5066 "*"
5067 } else {
5068 " "
5069 },
5070 idx,
5071 name
5072 ));
5073 }
5074 }
5075 dump.push_str("\n");
5076 }
5077
5078 let history_search = self.history_search.borrow();
5080 if history_search.is_active {
5081 dump.push_str("HISTORY SEARCH STATE (ACTIVE):\n");
5082 dump.push_str(&format!(" Query: '{}'\n", history_search.query));
5083 dump.push_str(&format!(" Matches: {}\n", history_search.matches.len()));
5084 dump.push_str(&format!(" Selected: {}\n", history_search.selected_index));
5085 dump.push_str(&format!(
5086 " Original Input: '{}'\n",
5087 history_search.original_input
5088 ));
5089 if !history_search.matches.is_empty() {
5090 dump.push_str(" Top matches:\n");
5091 for (i, m) in history_search.matches.iter().take(5).enumerate() {
5092 dump.push_str(&format!(
5093 " [{}] Score: {}, '{}'\n",
5094 if i == history_search.selected_index {
5095 "*"
5096 } else {
5097 " "
5098 },
5099 m.score,
5100 if m.entry.command.len() > 50 {
5101 format!("{}...", &m.entry.command[..50])
5102 } else {
5103 m.entry.command.clone()
5104 }
5105 ));
5106 }
5107 }
5108 dump.push_str("\n");
5109 }
5110
5111 let navigation = self.navigation.borrow();
5113 dump.push_str("NAVIGATION STATE:\n");
5114 dump.push_str(&format!(
5115 " Cursor Position: row={}, col={}\n",
5116 navigation.selected_row, navigation.selected_column
5117 ));
5118 dump.push_str(&format!(
5119 " Scroll Offset: row={}, col={}\n",
5120 navigation.scroll_offset.0, navigation.scroll_offset.1
5121 ));
5122 dump.push_str(&format!(
5123 " Viewport Dimensions: {} rows x {} cols\n",
5124 navigation.viewport_rows, navigation.viewport_columns
5125 ));
5126 dump.push_str(&format!(
5127 " Data Size: {} rows x {} cols\n",
5128 navigation.total_rows, navigation.total_columns
5129 ));
5130
5131 dump.push_str("\nVIEWPORT BOUNDARIES:\n");
5133 let at_top = navigation.selected_row == 0;
5134 let at_bottom = navigation.selected_row == navigation.total_rows.saturating_sub(1);
5135 let at_left = navigation.selected_column == 0;
5136 let at_right = navigation.selected_column == navigation.total_columns.saturating_sub(1);
5137
5138 dump.push_str(&format!(" At Top Edge: {}\n", at_top));
5139 dump.push_str(&format!(" At Bottom Edge: {}\n", at_bottom));
5140 dump.push_str(&format!(" At Left Edge: {}\n", at_left));
5141 dump.push_str(&format!(" At Right Edge: {}\n", at_right));
5142
5143 let viewport_bottom = navigation.scroll_offset.0 + navigation.viewport_rows;
5145 let viewport_right = navigation.scroll_offset.1 + navigation.viewport_columns;
5146 let should_scroll_down = navigation.selected_row >= viewport_bottom.saturating_sub(1);
5147 let should_scroll_up = navigation.selected_row < navigation.scroll_offset.0;
5148 let should_scroll_right = navigation.selected_column >= viewport_right.saturating_sub(1);
5149 let should_scroll_left = navigation.selected_column < navigation.scroll_offset.1;
5150
5151 dump.push_str("\nSCROLLING STATE:\n");
5152 dump.push_str(&format!(
5153 " Visible Row Range: {} to {}\n",
5154 navigation.scroll_offset.0,
5155 viewport_bottom.min(navigation.total_rows).saturating_sub(1)
5156 ));
5157 dump.push_str(&format!(
5158 " Visible Col Range: {} to {}\n",
5159 navigation.scroll_offset.1,
5160 viewport_right
5161 .min(navigation.total_columns)
5162 .saturating_sub(1)
5163 ));
5164 dump.push_str(&format!(
5165 " Should Scroll Down: {} (cursor at {}, viewport bottom at {})\n",
5166 should_scroll_down,
5167 navigation.selected_row,
5168 viewport_bottom.saturating_sub(1)
5169 ));
5170 dump.push_str(&format!(
5171 " Should Scroll Up: {} (cursor at {}, viewport top at {})\n",
5172 should_scroll_up, navigation.selected_row, navigation.scroll_offset.0
5173 ));
5174 dump.push_str(&format!(" Should Scroll Right: {}\n", should_scroll_right));
5175 dump.push_str(&format!(" Should Scroll Left: {}\n", should_scroll_left));
5176
5177 dump.push_str(&format!(
5178 "\n Viewport Lock: {} at row {:?}\n",
5179 navigation.viewport_lock, navigation.viewport_lock_row
5180 ));
5181 dump.push_str(&format!(
5182 " Cursor Lock: {} at visual position {:?}\n",
5183 navigation.cursor_lock, navigation.cursor_lock_position
5184 ));
5185
5186 if !navigation.selection_history.is_empty() {
5187 dump.push_str("\n Recent positions:\n");
5188 for (i, &(row, col)) in navigation
5189 .selection_history
5190 .iter()
5191 .rev()
5192 .take(5)
5193 .enumerate()
5194 {
5195 dump.push_str(&format!(" {}. ({}, {})\n", i + 1, row, col));
5196 }
5197 }
5198 dump.push_str("\n");
5199
5200 dump.push_str("COLUMN SEARCH STATE:\n");
5202 let column_search = self.column_search.borrow();
5203 if column_search.is_active {
5204 dump.push_str(&format!(" Pattern: '{}'\n", column_search.pattern));
5205 dump.push_str(&format!(
5206 " Matches: {} columns found\n",
5207 column_search.matching_columns.len()
5208 ));
5209 if !column_search.matching_columns.is_empty() {
5210 dump.push_str(&format!(
5211 " Current: {} of {}\n",
5212 column_search.current_match + 1,
5213 column_search.matching_columns.len()
5214 ));
5215 dump.push_str(" Matching columns:\n");
5216 for (i, (idx, name)) in column_search.matching_columns.iter().enumerate() {
5217 dump.push_str(&format!(
5218 " {}[{}] {} (index {})\n",
5219 if i == column_search.current_match {
5220 "*"
5221 } else {
5222 " "
5223 },
5224 i + 1,
5225 name,
5226 idx
5227 ));
5228 }
5229 }
5230 if let Some(ref last_time) = column_search.last_search_time {
5231 dump.push_str(&format!(" Search time: {:?}\n", last_time.elapsed()));
5232 }
5233 } else {
5234 dump.push_str(" [Inactive]\n");
5235 }
5236 dump.push_str(&format!(
5237 " Total searches: {}\n",
5238 column_search.total_searches
5239 ));
5240 dump.push_str(&format!(
5241 " History items: {}\n",
5242 column_search.history.len()
5243 ));
5244 if !column_search.history.is_empty() {
5245 dump.push_str(" Recent searches:\n");
5246 for (i, entry) in column_search.history.iter().take(5).enumerate() {
5247 dump.push_str(&format!(
5248 " {}. '{}' ({} matches) at {}\n",
5249 i + 1,
5250 entry.pattern,
5251 entry.match_count,
5252 entry.timestamp.format("%H:%M:%S")
5253 ));
5254 }
5255 }
5256 dump.push_str("\n");
5257
5258 dump.push_str("SORT STATE:\n");
5260 let sort = self.sort.borrow();
5261 if let (Some(col), Some(name)) = (sort.column, &sort.column_name) {
5262 dump.push_str(&format!(
5263 " Current: Column {} ({}) {}\n",
5264 col,
5265 name,
5266 match sort.order {
5267 SortOrder::Ascending => "Ascending ↑",
5268 SortOrder::Descending => "Descending ↓",
5269 SortOrder::None => "None",
5270 }
5271 ));
5272 } else {
5273 dump.push_str(" Current: No sorting applied\n");
5274 }
5275 if let Some(ref last_time) = sort.last_sort_time {
5276 dump.push_str(&format!(" Last sort: {:?} ago\n", last_time.elapsed()));
5277 }
5278 dump.push_str(&format!(" Total sorts: {}\n", sort.total_sorts));
5279 dump.push_str(&format!(" History items: {}\n", sort.history.len()));
5280 if !sort.history.is_empty() {
5281 dump.push_str(" Recent sorts:\n");
5282 for (i, entry) in sort.history.iter().rev().take(5).enumerate() {
5283 dump.push_str(&format!(
5284 " {}. Column {} ({}) {} - {} rows\n",
5285 i + 1,
5286 entry.column_index,
5287 entry.column_name,
5288 match entry.order {
5289 SortOrder::Ascending => "↑",
5290 SortOrder::Descending => "↓",
5291 SortOrder::None => "-",
5292 },
5293 entry.row_count
5294 ));
5295 }
5296 }
5297 dump.push_str("\n");
5298
5299 dump.push_str("SELECTION STATE:\n");
5301 let selection = self.selection.borrow();
5302 dump.push_str(&format!(" Mode: {:?}\n", selection.mode));
5303 if let Some(row) = selection.selected_row {
5304 dump.push_str(&format!(" Selected Row: {}\n", row));
5305 } else {
5306 dump.push_str(" Selected Row: None\n");
5307 }
5308 dump.push_str(&format!(
5309 " Selected Column: {}\n",
5310 selection.selected_column
5311 ));
5312 if !selection.selected_cells.is_empty() {
5313 dump.push_str(&format!(
5314 " Selected Cells: {} cells\n",
5315 selection.selected_cells.len()
5316 ));
5317 if selection.selected_cells.len() <= 5 {
5318 for (row, col) in &selection.selected_cells {
5319 dump.push_str(&format!(" - ({}, {})\n", row, col));
5320 }
5321 } else {
5322 for (row, col) in selection.selected_cells.iter().take(3) {
5323 dump.push_str(&format!(" - ({}, {})\n", row, col));
5324 }
5325 dump.push_str(&format!(
5326 " ... and {} more\n",
5327 selection.selected_cells.len() - 3
5328 ));
5329 }
5330 }
5331 if let Some((row, col)) = selection.selection_anchor {
5332 dump.push_str(&format!(" Selection Anchor: ({}, {})\n", row, col));
5333 }
5334 dump.push_str(&format!(
5335 " Total Selections: {}\n",
5336 selection.total_selections
5337 ));
5338 if let Some(ref last_time) = selection.last_selection_time {
5339 dump.push_str(&format!(
5340 " Last Selection: {:?} ago\n",
5341 last_time.elapsed()
5342 ));
5343 }
5344 dump.push_str(&format!(" History Items: {}\n", selection.history.len()));
5345 if !selection.history.is_empty() {
5346 dump.push_str(" Recent selections:\n");
5347 for (i, entry) in selection.history.iter().rev().take(5).enumerate() {
5348 dump.push_str(&format!(
5349 " {}. {:?} mode at {}\n",
5350 i + 1,
5351 entry.mode,
5352 entry.timestamp.format("%H:%M:%S")
5353 ));
5354 }
5355 }
5356 dump.push_str("\n");
5357
5358 dump.push_str("CLIPBOARD STATE:\n");
5360 let clipboard = self.clipboard.borrow();
5361 if let Some(ref yanked) = clipboard.last_yanked {
5362 dump.push_str(&format!(" Last Yanked: {}\n", yanked.description));
5363 dump.push_str(&format!(" Type: {:?}\n", yanked.yank_type));
5364 dump.push_str(&format!(" Size: {} bytes\n", yanked.size_bytes));
5365 dump.push_str(&format!(
5366 " Preview: {}\n",
5367 if yanked.preview.len() > 60 {
5368 format!("{}...", &yanked.preview[..60])
5369 } else {
5370 yanked.preview.clone()
5371 }
5372 ));
5373 dump.push_str(&format!(
5374 " Yanked at: {}\n",
5375 yanked.yanked_at.format("%H:%M:%S")
5376 ));
5377 } else {
5378 dump.push_str(" [Empty]\n");
5379 }
5380 dump.push_str(&format!(" Total yanks: {}\n", clipboard.total_yanks));
5381 dump.push_str(&format!(
5382 " History items: {}\n",
5383 clipboard.yank_history.len()
5384 ));
5385 if !clipboard.yank_history.is_empty() {
5386 dump.push_str(" Recent yanks:\n");
5387 for (i, item) in clipboard.yank_history.iter().take(5).enumerate() {
5388 dump.push_str(&format!(
5389 " {}. {} ({} bytes) at {}\n",
5390 i + 1,
5391 item.description,
5392 item.size_bytes,
5393 item.yanked_at.format("%H:%M:%S")
5394 ));
5395 }
5396 }
5397 dump.push_str("\n");
5398
5399 dump.push_str("CHORD STATE:\n");
5401 let chord = self.chord.borrow();
5402 if !chord.current_chord.is_empty() {
5403 dump.push_str(&format!(" Active chord: '{}'\n", chord.get_chord_string()));
5404 if let Some(ref start) = chord.chord_start {
5405 if let Ok(elapsed) = start.elapsed() {
5406 dump.push_str(&format!(" Time elapsed: {:.1}s\n", elapsed.as_secs_f32()));
5407 }
5408 }
5409 if let Some(ref desc) = chord.description {
5410 dump.push_str(&format!(" Description: {}\n", desc));
5411 }
5412 } else {
5413 dump.push_str(" No active chord\n");
5414 }
5415
5416 dump.push_str("\nREGISTERED CHORDS:\n");
5417 let mut chords: Vec<_> = chord.registered_chords.iter().collect();
5418 chords.sort_by_key(|(k, _)| k.as_str());
5419 for (chord_seq, action) in chords {
5420 dump.push_str(&format!(" {} → {}\n", chord_seq, action));
5421 }
5422
5423 if !chord.history.is_empty() {
5424 dump.push_str("\nCHORD HISTORY:\n");
5425 for (i, (chord_str, action, timestamp)) in
5426 chord.history.iter().rev().take(5).enumerate()
5427 {
5428 if let Ok(elapsed) = timestamp.elapsed() {
5429 dump.push_str(&format!(
5430 " {}. {} → {} ({:.1}s ago)\n",
5431 i + 1,
5432 chord_str,
5433 action,
5434 elapsed.as_secs_f32()
5435 ));
5436 }
5437 }
5438 }
5439 dump.push_str("\n");
5440
5441 dump.push_str("UNDO/REDO STATE:\n");
5443 let undo_redo = self.undo_redo.borrow();
5444 dump.push_str(&format!(
5445 " Undo stack: {} entries\n",
5446 undo_redo.undo_stack.len()
5447 ));
5448 dump.push_str(&format!(
5449 " Redo stack: {} entries\n",
5450 undo_redo.redo_stack.len()
5451 ));
5452 if !undo_redo.undo_stack.is_empty() {
5453 dump.push_str(" Recent undo entries:\n");
5454 for (i, (text, cursor)) in undo_redo.undo_stack.iter().rev().take(3).enumerate() {
5455 let preview = if text.len() > 50 {
5456 format!("{}...", &text[..50])
5457 } else {
5458 text.clone()
5459 };
5460 dump.push_str(&format!(
5461 " {}. '{}' (cursor: {})\n",
5462 i + 1,
5463 preview,
5464 cursor
5465 ));
5466 }
5467 }
5468 dump.push_str("\n");
5469
5470 dump.push_str("SCROLL STATE:\n");
5472 let scroll = self.scroll.borrow();
5473 dump.push_str(&format!(" Help scroll: {}\n", scroll.help_scroll));
5474 dump.push_str(&format!(" Input scroll: {}\n", scroll.input_scroll_offset));
5475 dump.push_str(&format!(
5476 " Viewport scroll: ({}, {})\n",
5477 scroll.viewport_scroll_offset.0, scroll.viewport_scroll_offset.1
5478 ));
5479 dump.push_str(&format!(
5480 " Last visible rows: {}\n",
5481 scroll.last_visible_rows
5482 ));
5483 dump.push_str("\n");
5484
5485 dump.push_str(&self.widgets.search_modes.debug_info());
5487 dump.push_str("\n");
5488 if let Some(ref history) = self.widgets.history {
5489 dump.push_str(&history.debug_info());
5490 dump.push_str("\n");
5491 }
5492 dump.push_str(&self.widgets.help.debug_info());
5493 dump.push_str("\n");
5494 dump.push_str(&self.widgets.stats.debug_info());
5495 dump.push_str("\n");
5496 dump.push_str("BUFFER STATE:\n");
5502 dump.push_str(&format!(
5503 " Current Buffer ID: {}\n",
5504 self.current_buffer_id
5505 ));
5506 if let Some(_buffer) = self.current_buffer() {
5509 dump.push_str(" Buffer: Present\n");
5518 } else {
5519 dump.push_str(" Buffer: None\n");
5520 }
5521 dump.push_str("\n");
5522
5523 dump.push_str("CACHE STATE:\n");
5525 dump.push_str(&format!(
5526 " Cached Results: {}\n",
5527 self.results_cache.cache.len()
5528 ));
5529 dump.push_str(&format!(
5530 " Max Cache Size: {}\n",
5531 self.results_cache.max_size
5532 ));
5533 dump.push_str("\n");
5534
5535 dump.push_str("HISTORY STATE:\n");
5537 dump.push_str(&format!(
5538 " Total Commands: {}\n",
5539 self.command_history.borrow().get_all().len()
5540 ));
5541 dump.push_str("\n");
5542
5543 dump.push_str(&self.key_press_history.borrow().format_history());
5545 dump.push_str("\n");
5546
5547 dump.push_str("PLATFORM INFO:\n");
5549 dump.push_str(&format!(" Platform: {:?}\n", Platform::detect()));
5550 dump.push_str(" Key Normalization: ");
5551 if Platform::detect() == Platform::Windows {
5552 dump.push_str("ACTIVE (Windows special chars)\n");
5553 } else {
5554 dump.push_str("INACTIVE\n");
5555 }
5556 dump.push_str("\n");
5557
5558 dump.push_str("=== END DEBUG DUMP ===\n");
5559
5560 dump
5561 }
5562
5563 pub fn pretty_print(&self) -> String {
5565 format!("{:#?}", self)
5566 }
5567
5568 pub fn delegated_selected_row(&self) -> Option<usize> {
5576 self.current_buffer()?.get_selected_row()
5577 }
5578
5579 pub fn set_delegated_selected_row(&mut self, row: Option<usize>) {
5581 if let Some(buffer) = self.current_buffer_mut() {
5582 buffer.set_selected_row(row);
5583 }
5584 }
5585
5586 pub fn delegated_current_column(&self) -> usize {
5588 self.current_buffer()
5589 .map(|b| b.get_current_column())
5590 .unwrap_or(0)
5591 }
5592
5593 pub fn set_delegated_current_column(&mut self, col: usize) {
5595 if let Some(buffer) = self.current_buffer_mut() {
5596 buffer.set_current_column(col);
5597 }
5598 }
5599
5600 pub fn delegated_scroll_offset(&self) -> (usize, usize) {
5602 self.current_buffer()
5603 .map(|b| b.get_scroll_offset())
5604 .unwrap_or((0, 0))
5605 }
5606
5607 pub fn set_delegated_scroll_offset(&mut self, offset: (usize, usize)) {
5609 if let Some(buffer) = self.current_buffer_mut() {
5610 buffer.set_scroll_offset(offset);
5611 }
5612 }
5613
5614 pub fn delegated_search_pattern(&self) -> String {
5618 self.current_buffer()
5619 .map(|b| b.get_search_pattern())
5620 .unwrap_or_default()
5621 }
5622
5623 pub fn set_delegated_search_pattern(&mut self, pattern: String) {
5625 if let Some(buffer) = self.current_buffer_mut() {
5626 buffer.set_search_pattern(pattern);
5627 }
5628 }
5629
5630 pub fn delegated_search_matches(&self) -> Vec<(usize, usize)> {
5632 self.current_buffer()
5633 .map(|b| b.get_search_matches())
5634 .unwrap_or_default()
5635 }
5636
5637 pub fn set_delegated_search_matches(&mut self, matches: Vec<(usize, usize)>) {
5639 if let Some(buffer) = self.current_buffer_mut() {
5640 buffer.set_search_matches(matches);
5641 }
5642 }
5643
5644 pub fn delegated_filter_pattern(&self) -> String {
5648 self.current_buffer()
5649 .map(|b| b.get_filter_pattern())
5650 .unwrap_or_default()
5651 }
5652
5653 pub fn set_delegated_filter_pattern(&mut self, pattern: String) {
5655 if let Some(buffer) = self.current_buffer_mut() {
5656 buffer.set_filter_pattern(pattern);
5657 }
5658 }
5659
5660 pub fn delegated_filter_active(&self) -> bool {
5662 self.current_buffer()
5663 .map(|b| b.is_filter_active())
5664 .unwrap_or(false)
5665 }
5666
5667 pub fn set_delegated_filter_active(&mut self, active: bool) {
5669 if let Some(buffer) = self.current_buffer_mut() {
5670 buffer.set_filter_active(active);
5671 }
5672 }
5673
5674 pub fn delegated_sort_column(&self) -> Option<usize> {
5678 self.current_buffer()?.get_sort_column()
5679 }
5680
5681 pub fn set_delegated_sort_column(&mut self, column: Option<usize>) {
5683 if let Some(buffer) = self.current_buffer_mut() {
5684 buffer.set_sort_column(column);
5685 }
5686 }
5687
5688 pub fn delegated_sort_order(&self) -> SortOrder {
5690 self.current_buffer()
5691 .map(|b| b.get_sort_order())
5692 .unwrap_or(SortOrder::None)
5693 }
5694
5695 pub fn set_delegated_sort_order(&mut self, order: SortOrder) {
5697 if let Some(buffer) = self.current_buffer_mut() {
5698 buffer.set_sort_order(order);
5699 }
5700 }
5701
5702 pub fn set_mode(&mut self, mode: AppMode) {
5708 if let Some(buffer) = self.current_buffer_mut() {
5709 buffer.set_mode(mode);
5710 }
5711 }
5713
5714 pub fn get_mode(&self) -> AppMode {
5716 self.current_buffer()
5717 .map(|b| b.get_mode())
5718 .unwrap_or(AppMode::Command)
5719 }
5720
5721 pub fn set_status_message(&mut self, message: String) {
5723 if let Some(buffer) = self.current_buffer_mut() {
5724 buffer.set_status_message(message);
5725 }
5726 }
5727
5728 pub fn get_status_message(&self) -> String {
5730 self.current_buffer()
5731 .map(|b| b.get_status_message())
5732 .unwrap_or_default()
5733 }
5734
5735 pub fn set_dataview(&mut self, dataview: Option<crate::data::data_view::DataView>) {
5737 if let Some(buffer) = self.current_buffer_mut() {
5738 buffer.set_dataview(dataview);
5739 }
5740 }
5741
5742 pub fn get_dataview(&self) -> Option<&crate::data::data_view::DataView> {
5744 self.current_buffer()?.dataview.as_ref()
5745 }
5746
5747 pub fn set_last_results_row(&mut self, row: Option<usize>) {
5749 if let Some(buffer) = self.current_buffer_mut() {
5750 buffer.set_last_results_row(row);
5751 }
5752 }
5753
5754 pub fn set_last_scroll_offset(&mut self, offset: (usize, usize)) {
5756 if let Some(buffer) = self.current_buffer_mut() {
5757 buffer.set_last_scroll_offset(offset);
5758 }
5759 }
5760
5761 pub fn get_input_text(&self) -> String {
5763 self.current_buffer()
5764 .map(|b| b.get_input_text())
5765 .unwrap_or_default()
5766 }
5767
5768 pub fn get_input_cursor_position(&self) -> usize {
5770 self.current_buffer()
5771 .map(|b| b.get_input_cursor_position())
5772 .unwrap_or(0)
5773 }
5774
5775 pub fn get_last_query(&self) -> String {
5777 self.current_buffer()
5778 .map(|b| b.get_last_query())
5779 .unwrap_or_default()
5780 }
5781
5782 pub fn is_buffer_modified(&self) -> bool {
5787 self.current_buffer()
5788 .map(|b| b.is_modified())
5789 .unwrap_or(false)
5790 }
5791
5792 pub fn set_buffer_modified(&mut self, modified: bool) {
5794 if let Some(buffer) = self.current_buffer_mut() {
5795 buffer.set_modified(modified);
5796 }
5797 }
5798
5799 pub fn get_buffer_dataview(&self) -> Option<&crate::data::data_view::DataView> {
5801 self.current_buffer()?.dataview.as_ref()
5802 }
5803
5804 pub fn get_buffer_dataview_mut(&mut self) -> Option<&mut crate::data::data_view::DataView> {
5806 self.current_buffer_mut()?.dataview.as_mut()
5807 }
5808
5809 pub fn get_original_source(&self) -> Option<&crate::data::datatable::DataTable> {
5811 self.current_buffer()?.get_original_source()
5812 }
5813
5814 pub fn has_dataview(&self) -> bool {
5816 self.current_buffer()
5817 .map(|b| b.has_dataview())
5818 .unwrap_or(false)
5819 }
5820
5821 pub fn is_case_insensitive(&self) -> bool {
5823 self.current_buffer()
5824 .map(|b| b.is_case_insensitive())
5825 .unwrap_or(false)
5826 }
5827
5828 pub fn get_edit_mode(&self) -> Option<crate::buffer::EditMode> {
5830 self.current_buffer().map(|b| b.get_edit_mode())
5831 }
5832
5833 pub fn is_show_row_numbers(&self) -> bool {
5835 self.current_buffer()
5836 .map(|b| b.is_show_row_numbers())
5837 .unwrap_or(false)
5838 }
5839
5840 pub fn is_compact_mode(&self) -> bool {
5842 self.current_buffer()
5843 .map(|b| b.is_compact_mode())
5844 .unwrap_or(false)
5845 }
5846
5847 pub fn set_input_cursor_position(&mut self, pos: usize) {
5849 if let Some(buffer) = self.current_buffer_mut() {
5850 buffer.set_input_cursor_position(pos);
5852 }
5853
5854 self.command_input.borrow_mut().cursor_position = pos;
5856 }
5857
5858 pub fn set_search_pattern(&mut self, pattern: String) {
5860 if let Some(buffer) = self.current_buffer_mut() {
5861 buffer.set_search_pattern(pattern);
5862 }
5863 }
5864
5865 pub fn set_filter_pattern(&mut self, pattern: String) {
5867 if let Some(buffer) = self.current_buffer_mut() {
5868 buffer.set_filter_pattern(pattern);
5869 }
5870 }
5871
5872 pub fn set_fuzzy_filter_pattern(&mut self, pattern: String) {
5874 if let Some(buffer) = self.current_buffer_mut() {
5875 buffer.set_fuzzy_filter_pattern(pattern);
5876 }
5877 }
5878
5879 pub fn set_fuzzy_filter_active(&mut self, active: bool) {
5881 if let Some(buffer) = self.current_buffer_mut() {
5882 buffer.set_fuzzy_filter_active(active);
5883 }
5884 }
5885
5886 pub fn is_fuzzy_filter_active(&self) -> bool {
5888 self.current_buffer()
5889 .map(|b| b.is_fuzzy_filter_active())
5890 .unwrap_or(false)
5891 }
5892
5893 pub fn set_fuzzy_filter_indices(&mut self, indices: Vec<usize>) {
5895 if let Some(buffer) = self.current_buffer_mut() {
5896 buffer.set_fuzzy_filter_indices(indices);
5897 }
5898 }
5899
5900 pub fn is_kill_ring_empty(&self) -> bool {
5902 self.current_buffer()
5903 .map(|b| b.is_kill_ring_empty())
5904 .unwrap_or(true)
5905 }
5906
5907 pub fn set_selected_row(&mut self, row: Option<usize>) {
5909 if let Some(buffer) = self.current_buffer_mut() {
5910 buffer.set_selected_row(row);
5911 }
5912 }
5913
5914 pub fn set_buffer_input_text(&mut self, text: String) {
5916 if let Some(buffer) = self.current_buffer_mut() {
5918 buffer.set_input_text(text.clone());
5919 }
5920
5921 let mut input = self.command_input.borrow_mut();
5923 input.text = text.clone();
5924 input.cursor_position = text.len();
5925 }
5926
5927 pub fn get_buffer_input_text(&self) -> String {
5929 self.current_buffer()
5930 .map(|b| b.get_input_text())
5931 .unwrap_or_default()
5932 }
5933
5934 pub fn set_buffer_input_text_with_cursor(&mut self, text: String, cursor: usize) {
5936 if let Some(buffer) = self.current_buffer_mut() {
5938 buffer.set_input_text(text.clone());
5939 buffer.set_input_cursor_position(cursor);
5940 }
5941
5942 let mut input = self.command_input.borrow_mut();
5944 input.text = text;
5945 input.cursor_position = cursor;
5946 }
5947
5948 pub fn set_current_column_buffer(&mut self, col: usize) {
5950 if let Some(buffer) = self.current_buffer_mut() {
5951 buffer.set_current_column(col);
5952 }
5953 }
5954
5955 pub fn set_show_row_numbers(&mut self, show: bool) {
5957 if let Some(buffer) = self.current_buffer_mut() {
5958 buffer.set_show_row_numbers(show);
5959 }
5960 }
5961
5962 pub fn set_filter_active(&mut self, active: bool) {
5964 if let Some(buffer) = self.current_buffer_mut() {
5965 buffer.set_filter_active(active);
5966 }
5967 }
5968
5969 pub fn set_compact_mode(&mut self, compact: bool) {
5971 if let Some(buffer) = self.current_buffer_mut() {
5972 buffer.set_compact_mode(compact);
5973 }
5974 }
5975
5976 pub fn set_case_insensitive(&mut self, insensitive: bool) {
5978 if let Some(buffer) = self.current_buffer_mut() {
5979 buffer.set_case_insensitive(insensitive);
5980 }
5981 }
5982
5983 pub fn get_buffer_selected_row(&self) -> Option<usize> {
5985 self.current_buffer()?.get_selected_row()
5986 }
5987
5988 pub fn get_search_pattern(&self) -> String {
5990 self.current_buffer()
5991 .map(|b| b.get_search_pattern())
5992 .unwrap_or_default()
5993 }
5994
5995 pub fn get_fuzzy_filter_pattern(&self) -> String {
5997 self.current_buffer()
5998 .map(|b| b.get_fuzzy_filter_pattern())
5999 .unwrap_or_default()
6000 }
6001
6002 pub fn vim_search_should_handle_key(&self) -> bool {
6006 let mode = self.get_mode();
6007 let pattern = self.get_search_pattern();
6008
6009 mode == AppMode::Search || !pattern.is_empty()
6011 }
6012
6013 pub fn start_vim_search(&mut self) {
6015 self.set_mode(AppMode::Search);
6016 self.set_input_text(String::new());
6017 self.set_input_cursor_position(0);
6018 self.set_status_message("Search: /".to_string());
6019 }
6020
6021 pub fn exit_vim_search(&mut self) {
6023 self.set_mode(AppMode::Results);
6024 self.clear_search_state();
6025 self.set_status_message("Search mode exited".to_string());
6026 }
6027
6028 pub fn get_fuzzy_filter_indices(&self) -> Vec<usize> {
6030 self.current_buffer()
6031 .map(|b| b.get_fuzzy_filter_indices().clone())
6032 .unwrap_or_default()
6033 }
6034
6035 pub fn set_scroll_offset(&mut self, offset: (usize, usize)) {
6037 if let Some(buffer) = self.current_buffer_mut() {
6038 buffer.set_scroll_offset(offset);
6039 }
6040 }
6041
6042 pub fn save_state_for_undo(&mut self) {
6047 if let Some(buffer) = self.current_buffer_mut() {
6048 buffer.save_state_for_undo();
6049 }
6050 }
6051
6052 pub fn perform_undo(&mut self) -> bool {
6054 self.current_buffer_mut()
6055 .map(|b| b.perform_undo())
6056 .unwrap_or(false)
6057 }
6058
6059 pub fn perform_redo(&mut self) -> bool {
6061 self.current_buffer_mut()
6062 .map(|b| b.perform_redo())
6063 .unwrap_or(false)
6064 }
6065
6066 pub fn insert_char_at_cursor(&mut self, c: char) {
6068 if let Some(buffer) = self.current_buffer_mut() {
6069 buffer.save_state_for_undo();
6070 let pos = buffer.get_input_cursor_position();
6071 let mut text = buffer.get_input_text();
6072 let mut chars: Vec<char> = text.chars().collect();
6073 chars.insert(pos, c);
6074 text = chars.iter().collect();
6075 buffer.set_input_text(text);
6076 buffer.set_input_cursor_position(pos + 1);
6077 }
6078 }
6079
6080 pub fn handle_input_key(&mut self, key: crossterm::event::KeyEvent) -> bool {
6082 self.current_buffer_mut()
6083 .map(|b| b.handle_input_key(key))
6084 .unwrap_or(false)
6085 }
6086
6087 pub fn set_search_matches_with_index(&mut self, matches: Vec<(usize, usize)>, index: usize) {
6089 if let Some(buffer) = self.current_buffer_mut() {
6090 buffer.set_search_matches(matches);
6091 buffer.set_search_match_index(index);
6092 }
6093 }
6094
6095 pub fn clear_search_state(&mut self) {
6097 if let Some(buffer) = self.current_buffer_mut() {
6098 buffer.set_search_matches(Vec::new());
6099 buffer.set_status_message("No matches found".to_string());
6100 }
6101 }
6102
6103 pub fn set_last_state(&mut self, row: Option<usize>, scroll_offset: (usize, usize)) {
6105 if let Some(buffer) = self.current_buffer_mut() {
6106 buffer.set_last_results_row(row);
6107 buffer.set_last_scroll_offset(scroll_offset);
6108 }
6109 }
6110
6111 pub fn clear_line(&mut self) {
6113 if let Some(buffer) = self.current_buffer_mut() {
6114 buffer.save_state_for_undo();
6115 buffer.set_input_text(String::new());
6116 buffer.set_input_cursor_position(0);
6117 }
6118 }
6119
6120 pub fn move_input_cursor_left(&mut self) {
6122 if let Some(buffer) = self.current_buffer_mut() {
6123 let pos = buffer.get_input_cursor_position();
6124 if pos > 0 {
6125 buffer.set_input_cursor_position(pos - 1);
6126 }
6127 }
6128 }
6129
6130 pub fn move_input_cursor_right(&mut self) {
6132 if let Some(buffer) = self.current_buffer_mut() {
6133 let pos = buffer.get_input_cursor_position();
6134 let text_len = buffer.get_input_text().chars().count();
6135 if pos < text_len {
6136 buffer.set_input_cursor_position(pos + 1);
6137 }
6138 }
6139 }
6140
6141 pub fn backspace(&mut self) {
6143 if let Some(buffer) = self.current_buffer_mut() {
6144 let pos = buffer.get_input_cursor_position();
6145 if pos > 0 {
6146 buffer.save_state_for_undo();
6147 let mut text = buffer.get_input_text();
6148 let mut chars: Vec<char> = text.chars().collect();
6149 chars.remove(pos - 1);
6150 text = chars.iter().collect();
6151 buffer.set_input_text(text);
6152 buffer.set_input_cursor_position(pos - 1);
6153 }
6154 }
6155 }
6156
6157 pub fn delete(&mut self) {
6159 if let Some(buffer) = self.current_buffer_mut() {
6160 let pos = buffer.get_input_cursor_position();
6161 let mut text = buffer.get_input_text();
6162 let chars_len = text.chars().count();
6163 if pos < chars_len {
6164 buffer.save_state_for_undo();
6165 let mut chars: Vec<char> = text.chars().collect();
6166 chars.remove(pos);
6167 text = chars.iter().collect();
6168 buffer.set_input_text(text);
6169 }
6170 }
6171 }
6172
6173 pub fn reset_navigation_state(&mut self) {
6175 if let Some(buffer) = self.current_buffer_mut() {
6176 buffer.set_selected_row(Some(0));
6177 buffer.set_scroll_offset((0, 0));
6178 buffer.set_current_column(0);
6179 buffer.set_last_results_row(None);
6180 buffer.set_last_scroll_offset((0, 0));
6181 }
6182 }
6183
6184 pub fn clear_fuzzy_filter_state(&mut self) {
6186 if let Some(buffer) = self.current_buffer_mut() {
6187 buffer.clear_fuzzy_filter();
6188 buffer.set_fuzzy_filter_pattern(String::new());
6189 buffer.set_fuzzy_filter_active(false);
6190 buffer.set_fuzzy_filter_indices(Vec::new());
6191 }
6192 }
6193
6194 pub fn get_filter_pattern(&self) -> String {
6196 self.current_buffer()
6197 .map(|b| b.get_filter_pattern())
6198 .unwrap_or_default()
6199 }
6200
6201 pub fn set_column_stats(&mut self, stats: Option<crate::buffer::ColumnStatistics>) {
6203 if let Some(buffer) = self.current_buffer_mut() {
6204 buffer.set_column_stats(stats);
6205 }
6206 }
6207
6208 pub fn set_column_widths(&mut self, widths: Vec<u16>) {
6210 if let Some(buffer) = self.current_buffer_mut() {
6211 buffer.set_column_widths(widths);
6212 }
6213 }
6214
6215 pub fn set_current_match(&mut self, match_pos: Option<(usize, usize)>) {
6217 if let Some(buffer) = self.current_buffer_mut() {
6218 buffer.set_current_match(match_pos);
6219 }
6220 }
6221
6222 pub fn get_kill_ring(&self) -> String {
6224 self.current_buffer()
6225 .map(|b| b.get_kill_ring())
6226 .unwrap_or_default()
6227 }
6228
6229 pub fn get_buffer_status_message(&self) -> String {
6231 self.current_buffer()
6232 .map(|b| b.get_status_message())
6233 .unwrap_or_default()
6234 }
6235
6236 pub fn get_buffer_name(&self) -> String {
6238 self.current_buffer()
6239 .map(|b| b.get_name())
6240 .unwrap_or_else(|| "No Buffer".to_string())
6241 }
6242
6243 pub fn get_last_results_row(&self) -> Option<usize> {
6245 self.current_buffer()?.get_last_results_row()
6246 }
6247
6248 pub fn get_last_scroll_offset(&self) -> (usize, usize) {
6250 self.current_buffer()
6251 .map(|b| b.get_last_scroll_offset())
6252 .unwrap_or((0, 0))
6253 }
6254
6255 pub fn set_last_query(&mut self, query: String) {
6257 if let Some(buffer) = self.current_buffer_mut() {
6258 buffer.set_last_query(query);
6259 }
6260 }
6261
6262 pub fn get_last_query_source(&self) -> Option<String> {
6264 self.current_buffer()?.get_last_query_source()
6265 }
6266
6267 pub fn set_last_visible_rows(&mut self, rows: usize) {
6269 if let Some(buffer) = self.current_buffer_mut() {
6270 buffer.set_last_visible_rows(rows);
6271 }
6272 }
6273}
6274
6275impl Default for AppStateContainer {
6276 fn default() -> Self {
6277 let command_history = CommandHistory::default();
6280 let mut widgets = WidgetStates::new();
6281 widgets.set_history(HistoryWidget::new(command_history.clone()));
6282
6283 Self {
6284 buffers: BufferManager::new(),
6285 current_buffer_id: 0,
6286 command_input: RefCell::new(InputState::new()),
6287 search: RefCell::new(SearchState::new()),
6288 filter: RefCell::new(FilterState::new()),
6289 column_search: RefCell::new(ColumnSearchState::new()),
6290 history_search: RefCell::new(HistorySearchState::new()),
6291 sort: RefCell::new(SortState::new()),
6292 selection: RefCell::new(SelectionState::new()),
6293 completion: RefCell::new(CompletionState::default()),
6294 widgets,
6295 cache_list: CacheListState::new(),
6296 column_stats: ColumnStatsState::new(),
6297 jump_to_row: JumpToRowState::new(),
6298 navigation: RefCell::new(NavigationState::new()),
6299 command_history: RefCell::new(command_history),
6300 key_press_history: RefCell::new(KeyPressHistory::new(100)),
6301 results: RefCell::new(ResultsState::default()),
6302 clipboard: RefCell::new(ClipboardState::default()),
6303 chord: RefCell::new(ChordState::default()),
6304 undo_redo: RefCell::new(UndoRedoState::default()),
6305 scroll: RefCell::new(ScrollState::default()),
6306 results_cache: ResultsCache::new(100),
6307 mode_stack: Vec::new(),
6308 debug_enabled: false,
6309 debug_service: RefCell::new(None),
6310 help: RefCell::new(HelpState::new()),
6311 }
6312 }
6313}
6314
6315impl fmt::Debug for AppStateContainer {
6316 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
6317 f.debug_struct("AppStateContainer")
6318 .field("current_mode", &self.current_mode())
6319 .field("mode_stack", &self.mode_stack)
6320 .field("current_buffer_id", &self.current_buffer_id)
6323 .field("command_input", &self.command_input)
6324 .field("search_active", &self.search.borrow().is_active)
6325 .field("filter_active", &self.filter.borrow().is_active)
6326 .field(
6327 "column_search_active",
6328 &self.column_search.borrow().is_active,
6329 )
6330 .field("debug_enabled", &self.debug_enabled)
6331 .field("help_visible", &self.help.borrow().is_visible)
6332 .field("help_scroll", &self.help.borrow().scroll_offset)
6333 .field("cached_results", &self.results_cache.cache.len())
6334 .field(
6335 "history_count",
6336 &self.command_history.borrow().get_all().len(),
6337 )
6338 .finish()
6339 }
6340}
6341
6342impl fmt::Debug for WidgetStates {
6343 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
6344 f.debug_struct("WidgetStates")
6345 .field("search_modes_active", &self.search_modes.is_active())
6346 .field("history", &self.history.is_some())
6347 .field("help", &"HelpWidget")
6348 .field("stats", &"StatsWidget")
6349 .finish()
6351 }
6352}