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