use crate::api_client::QueryResponse;
use crate::buffer::{AppMode, BufferAPI, BufferManager, SortOrder};
use crate::debug_service::DebugLevel;
use crate::help_widget::HelpWidget;
use crate::history::CommandHistory;
use crate::history_widget::HistoryWidget;
use crate::search_modes_widget::SearchModesWidget;
use crate::stats_widget::StatsWidget;
use crate::widget_traits::DebugInfoProvider;
use anyhow::{anyhow, Result};
use arboard::Clipboard;
use chrono::{DateTime, Local};
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use std::cell::RefCell;
use std::collections::{HashMap, VecDeque};
use std::fmt;
use std::time::{Duration, Instant};
use tracing::{info, trace};
#[derive(Debug, Clone, PartialEq)]
pub enum Platform {
Windows,
Linux,
MacOS,
Unknown,
}
impl Platform {
#[must_use]
pub fn detect() -> Self {
if cfg!(target_os = "windows") {
Platform::Windows
} else if cfg!(target_os = "linux") {
Platform::Linux
} else if cfg!(target_os = "macos") {
Platform::MacOS
} else {
Platform::Unknown
}
}
}
#[derive(Debug, Clone)]
pub struct KeyPressEntry {
pub raw_event: KeyEvent,
pub first_timestamp: DateTime<Local>,
pub last_timestamp: DateTime<Local>,
pub repeat_count: usize,
pub platform: Platform,
pub interpreted_action: Option<String>,
pub app_mode: AppMode,
pub display_string: String,
}
impl KeyPressEntry {
#[must_use]
pub fn new(key: KeyEvent, mode: AppMode, action: Option<String>) -> Self {
let display_string = Self::format_key(&key);
let now = Local::now();
Self {
raw_event: key,
first_timestamp: now,
last_timestamp: now,
repeat_count: 1,
platform: Platform::detect(),
interpreted_action: action,
app_mode: mode,
display_string,
}
}
pub fn is_same_key(&self, key: &KeyEvent, mode: &AppMode) -> bool {
let time_window = chrono::Duration::seconds(1);
let now = Local::now();
let time_diff = now - self.last_timestamp;
let code_match = self.raw_event.code == key.code;
let modifier_match = self.raw_event.modifiers == key.modifiers;
let mode_match = self.app_mode == *mode;
let time_match = time_diff < time_window;
tracing::trace!(
"is_same_key: code_match={}, modifier_match={}, mode_match={}, time_match={} ({}ms < 1000ms)",
code_match, modifier_match, mode_match, time_match, time_diff.num_milliseconds()
);
code_match && modifier_match && mode_match && time_match
}
pub fn add_repeat(&mut self) {
self.repeat_count += 1;
self.last_timestamp = Local::now();
}
#[must_use]
pub fn display_with_count(&self) -> String {
if self.repeat_count > 1 {
match self.display_string.as_str() {
s if s.len() == 1 => format!("{}{}", self.repeat_count, s),
"↑" | "↓" | "←" | "→" => {
format!("{}{}", self.repeat_count, self.display_string)
}
_ => format!("{} x{}", self.display_string, self.repeat_count),
}
} else {
self.display_string.clone()
}
}
fn format_key(key: &KeyEvent) -> String {
let mut result = String::new();
if key.modifiers.contains(KeyModifiers::CONTROL) {
result.push_str("Ctrl+");
}
if key.modifiers.contains(KeyModifiers::ALT) {
result.push_str("Alt+");
}
if key.modifiers.contains(KeyModifiers::SHIFT) {
result.push_str("Shift+");
}
match key.code {
KeyCode::Char(c) => result.push(c),
KeyCode::Enter => result.push_str("Enter"),
KeyCode::Esc => result.push_str("Esc"),
KeyCode::Backspace => result.push_str("Backspace"),
KeyCode::Tab => result.push_str("Tab"),
KeyCode::Delete => result.push_str("Del"),
KeyCode::Insert => result.push_str("Ins"),
KeyCode::F(n) => result.push_str(&format!("F{n}")),
KeyCode::Left => result.push('←'),
KeyCode::Right => result.push('→'),
KeyCode::Up => result.push('↑'),
KeyCode::Down => result.push('↓'),
KeyCode::Home => result.push_str("Home"),
KeyCode::End => result.push_str("End"),
KeyCode::PageUp => result.push_str("PgUp"),
KeyCode::PageDown => result.push_str("PgDn"),
_ => result.push('?'),
}
result
}
#[must_use]
pub fn debug_string(&self) -> String {
let modifiers = if self.raw_event.modifiers.is_empty() {
String::new()
} else {
format!(" ({})", self.format_modifiers())
};
let action = self
.interpreted_action
.as_ref()
.map(|a| format!(" → {a}"))
.unwrap_or_default();
let repeat_info = if self.repeat_count > 1 {
format!(" x{}", self.repeat_count)
} else {
String::new()
};
format!(
"[{}] {}{}{} [{:?}]{}",
self.last_timestamp.format("%H:%M:%S.%3f"),
self.display_string,
repeat_info,
modifiers,
self.platform,
action
)
}
fn format_modifiers(&self) -> String {
let mut parts = Vec::new();
if self.raw_event.modifiers.contains(KeyModifiers::CONTROL) {
parts.push("Ctrl");
}
if self.raw_event.modifiers.contains(KeyModifiers::ALT) {
parts.push("Alt");
}
if self.raw_event.modifiers.contains(KeyModifiers::SHIFT) {
parts.push("Shift");
}
parts.join("+")
}
}
#[derive(Debug, Clone)]
pub struct KeyPressHistory {
entries: VecDeque<KeyPressEntry>,
max_size: usize,
}
impl KeyPressHistory {
#[must_use]
pub fn new(max_size: usize) -> Self {
Self {
entries: VecDeque::with_capacity(max_size),
max_size,
}
}
fn is_navigation_key(key: &KeyEvent) -> bool {
matches!(
key.code,
KeyCode::Up
| KeyCode::Down
| KeyCode::Left
| KeyCode::Right
| KeyCode::PageUp
| KeyCode::PageDown
| KeyCode::Home
| KeyCode::End
)
}
pub fn add(&mut self, entry: KeyPressEntry) {
if let Some(last_entry) = self.entries.back_mut() {
let time_diff = Local::now() - last_entry.last_timestamp;
tracing::debug!(
"Key coalesce check: last_key={:?}/{:?}, new_key={:?}/{:?}, mode_match={}, time_diff={}ms",
last_entry.raw_event.code,
last_entry.raw_event.modifiers,
entry.raw_event.code,
entry.raw_event.modifiers,
last_entry.app_mode == entry.app_mode,
time_diff.num_milliseconds()
);
if last_entry.is_same_key(&entry.raw_event, &entry.app_mode) {
tracing::debug!("Key coalesced! Count now: {}", last_entry.repeat_count + 1);
last_entry.add_repeat();
if entry.interpreted_action != last_entry.interpreted_action {
last_entry.interpreted_action = entry.interpreted_action;
}
return;
}
tracing::debug!("Key NOT coalesced - adding new entry");
}
if self.entries.len() >= self.max_size {
let mut removed = false;
for i in 0..self.entries.len() {
if Self::is_navigation_key(&self.entries[i].raw_event)
&& self.entries[i].repeat_count == 1
{
self.entries.remove(i);
removed = true;
break;
}
}
if !removed {
let half = self.entries.len() / 2;
for i in 0..half {
if self.entries[i].repeat_count == 1 {
self.entries.remove(i);
removed = true;
break;
}
}
}
if !removed {
self.entries.pop_front();
}
}
self.entries.push_back(entry);
}
#[must_use]
pub fn entries(&self) -> &VecDeque<KeyPressEntry> {
&self.entries
}
pub fn clear(&mut self) {
self.entries.clear();
}
#[must_use]
pub fn format_history(&self) -> String {
let mut output = String::new();
output.push_str("========== KEY PRESS HISTORY ==========\n");
output.push_str(&format!(
"(Most recent at bottom, {} unique entries, max {})\n",
self.entries.len(),
self.max_size
));
let total_presses: usize = self.entries.iter().map(|e| e.repeat_count).sum();
output.push_str(&format!(
"Total key presses (with repeats): {total_presses}\n"
));
for entry in &self.entries {
output.push_str(&format!(
"[{}] {}",
entry.last_timestamp.format("%H:%M:%S.%3f"),
entry.display_with_count()
));
if !entry.raw_event.modifiers.is_empty() {
output.push_str(&format!(" ({})", entry.format_modifiers()));
}
output.push('\n');
}
output.push_str("========================================\n");
output
}
#[must_use]
pub fn format_debug_history(&self) -> String {
let mut output = String::new();
output.push_str("========== DETAILED KEY HISTORY ==========\n");
output.push_str(&format!("Platform: {:?}\n", Platform::detect()));
output.push_str(&format!(
"(Most recent at bottom, last {} keys)\n",
self.max_size
));
for entry in &self.entries {
output.push_str(&entry.debug_string());
output.push('\n');
}
output.push_str("==========================================\n");
output
}
}
#[derive(Debug, Clone)]
pub struct InputState {
pub text: String,
pub cursor_position: usize,
pub last_executed_query: String,
}
impl Default for InputState {
fn default() -> Self {
Self::new()
}
}
impl InputState {
#[must_use]
pub fn new() -> Self {
Self {
text: String::new(),
cursor_position: 0,
last_executed_query: String::new(),
}
}
pub fn clear(&mut self) {
let _old_text = self.text.clone();
self.text.clear();
self.cursor_position = 0;
}
pub fn set_text(&mut self, text: String) {
let _old_text = self.text.clone();
self.cursor_position = text.len();
self.text = text;
}
pub fn set_text_with_cursor(&mut self, text: String, cursor: usize) {
let _old_text = self.text.clone();
let _old_cursor = self.cursor_position;
self.text = text;
self.cursor_position = cursor;
}
}
#[derive(Debug, Clone)]
pub enum SearchOperation {
StartSearch(String),
UpdatePattern(String, String), MatchesFound(usize),
NavigateToMatch(usize),
ClearSearch,
NoMatchesFound,
}
#[derive(Debug, Clone)]
pub struct SearchHistoryEntry {
pub pattern: String,
pub match_count: usize,
pub timestamp: DateTime<Local>,
pub duration_ms: Option<u64>,
}
#[derive(Debug, Clone)]
pub struct SearchState {
pub pattern: String,
pub matches: Vec<(usize, usize, usize, usize)>, pub current_match: usize,
pub is_active: bool,
pub history: VecDeque<SearchHistoryEntry>,
pub last_search_time: Option<std::time::Instant>,
}
impl Default for SearchState {
fn default() -> Self {
Self::new()
}
}
impl SearchState {
#[must_use]
pub fn new() -> Self {
Self {
pattern: String::new(),
matches: Vec::new(),
current_match: 0,
is_active: false,
history: VecDeque::with_capacity(20), last_search_time: None,
}
}
pub fn clear(&mut self) {
if self.is_active && !self.pattern.is_empty() {
let duration_ms = self
.last_search_time
.map(|t| t.elapsed().as_millis() as u64);
let entry = SearchHistoryEntry {
pattern: self.pattern.clone(),
match_count: self.matches.len(),
timestamp: Local::now(),
duration_ms,
};
if self.history.len() >= 20 {
self.history.pop_front();
}
self.history.push_back(entry);
}
self.pattern.clear();
self.matches.clear();
self.current_match = 0;
self.is_active = false;
self.last_search_time = None;
}
}
#[derive(Debug, Clone)]
pub struct FilterState {
pub pattern: String,
pub filtered_indices: Vec<usize>,
pub filtered_data: Option<Vec<Vec<String>>>,
pub is_active: bool,
pub case_insensitive: bool,
pub total_filters: usize,
pub last_filter_time: Option<Instant>,
pub history: VecDeque<FilterHistoryEntry>,
pub max_history: usize,
}
#[derive(Debug, Clone)]
pub struct FilterHistoryEntry {
pub pattern: String,
pub match_count: usize,
pub timestamp: chrono::DateTime<chrono::Local>,
pub duration_ms: Option<u64>,
}
impl Default for FilterState {
fn default() -> Self {
Self::new()
}
}
impl FilterState {
#[must_use]
pub fn new() -> Self {
Self {
pattern: String::new(),
filtered_indices: Vec::new(),
filtered_data: None,
is_active: false,
case_insensitive: true,
total_filters: 0,
last_filter_time: None,
history: VecDeque::with_capacity(20),
max_history: 20,
}
}
pub fn clear(&mut self) {
info!(target: "filter", "FilterState::clear() - had {} filtered rows for pattern '{}'",
self.filtered_indices.len(), self.pattern);
if !self.pattern.is_empty() && self.is_active {
let duration_ms = self
.last_filter_time
.as_ref()
.map(|t| t.elapsed().as_millis() as u64);
let entry = FilterHistoryEntry {
pattern: self.pattern.clone(),
match_count: self.filtered_indices.len(),
timestamp: chrono::Local::now(),
duration_ms,
};
self.history.push_front(entry);
if self.history.len() > self.max_history {
self.history.pop_back();
}
}
self.pattern.clear();
self.filtered_indices.clear();
self.filtered_data = None;
self.is_active = false;
self.last_filter_time = None;
}
pub fn set_pattern(&mut self, pattern: String) {
info!(target: "filter", "FilterState::set_pattern('{}') - was '{}'", pattern, self.pattern);
self.pattern = pattern;
if self.pattern.is_empty() {
self.is_active = false;
} else {
self.is_active = true;
self.total_filters += 1;
self.last_filter_time = Some(Instant::now());
}
}
pub fn set_filtered_indices(&mut self, indices: Vec<usize>) {
info!(target: "filter", "FilterState::set_filtered_indices - {} rows match pattern '{}'",
indices.len(), self.pattern);
self.filtered_indices = indices;
}
pub fn set_filtered_data(&mut self, data: Option<Vec<Vec<String>>>) {
let count = data.as_ref().map_or(0, std::vec::Vec::len);
info!(target: "filter", "FilterState::set_filtered_data - {} rows", count);
self.filtered_data = data;
}
#[must_use]
pub fn get_stats(&self) -> String {
format!(
"Total filters: {}, History items: {}, Current matches: {}",
self.total_filters,
self.history.len(),
self.filtered_indices.len()
)
}
}
#[derive(Debug, Clone)]
pub struct ColumnSearchState {
pub pattern: String,
pub matching_columns: Vec<(usize, String)>,
pub current_match: usize,
pub is_active: bool,
pub history: VecDeque<ColumnSearchHistoryEntry>,
pub total_searches: usize,
pub last_search_time: Option<Instant>,
}
#[derive(Debug, Clone)]
pub struct ColumnSearchHistoryEntry {
pub pattern: String,
pub match_count: usize,
pub matched_columns: Vec<String>,
pub timestamp: DateTime<Local>,
pub duration_ms: Option<u64>,
}
#[derive(Clone, Debug)]
pub struct CompletionState {
pub suggestions: Vec<String>,
pub current_index: usize,
pub last_query: String,
pub last_cursor_pos: usize,
pub is_active: bool,
pub total_completions: usize,
pub last_completion_time: Option<std::time::Instant>,
}
impl Default for CompletionState {
fn default() -> Self {
Self::new()
}
}
impl CompletionState {
#[must_use]
pub fn new() -> Self {
Self {
suggestions: Vec::new(),
current_index: 0,
last_query: String::new(),
last_cursor_pos: 0,
is_active: false,
total_completions: 0,
last_completion_time: None,
}
}
pub fn clear(&mut self) {
self.suggestions.clear();
self.current_index = 0;
self.is_active = false;
}
pub fn set_suggestions(&mut self, suggestions: Vec<String>) {
self.is_active = !suggestions.is_empty();
self.suggestions = suggestions;
self.current_index = 0;
if self.is_active {
self.last_completion_time = Some(std::time::Instant::now());
self.total_completions += 1;
}
}
pub fn next_suggestion(&mut self) {
if !self.suggestions.is_empty() {
self.current_index = (self.current_index + 1) % self.suggestions.len();
}
}
#[must_use]
pub fn current_suggestion(&self) -> Option<&String> {
if self.is_active && !self.suggestions.is_empty() {
self.suggestions.get(self.current_index)
} else {
None
}
}
#[must_use]
pub fn is_same_context(&self, query: &str, cursor_pos: usize) -> bool {
query == self.last_query && cursor_pos == self.last_cursor_pos
}
pub fn update_context(&mut self, query: String, cursor_pos: usize) {
self.last_query = query;
self.last_cursor_pos = cursor_pos;
}
}
impl Default for ColumnSearchState {
fn default() -> Self {
Self::new()
}
}
impl ColumnSearchState {
#[must_use]
pub fn new() -> Self {
Self {
pattern: String::new(),
matching_columns: Vec::new(),
current_match: 0,
is_active: false,
history: VecDeque::with_capacity(20),
total_searches: 0,
last_search_time: None,
}
}
pub fn clear(&mut self) {
if self.is_active && !self.pattern.is_empty() {
let duration_ms = self
.last_search_time
.map(|t| t.elapsed().as_millis() as u64);
let entry = ColumnSearchHistoryEntry {
pattern: self.pattern.clone(),
match_count: self.matching_columns.len(),
matched_columns: self
.matching_columns
.iter()
.map(|(_, name)| name.clone())
.collect(),
timestamp: Local::now(),
duration_ms,
};
self.history.push_front(entry);
while self.history.len() > 20 {
self.history.pop_back();
}
}
self.pattern.clear();
self.matching_columns.clear();
self.current_match = 0;
self.is_active = false;
self.last_search_time = None;
}
pub fn set_matches(&mut self, matches: Vec<(usize, String)>) {
self.matching_columns = matches;
self.current_match = 0;
self.total_searches += 1;
self.last_search_time = Some(Instant::now());
}
pub fn next_match(&mut self) -> Option<(usize, String)> {
if self.matching_columns.is_empty() {
return None;
}
self.current_match = (self.current_match + 1) % self.matching_columns.len();
Some(self.matching_columns[self.current_match].clone())
}
pub fn prev_match(&mut self) -> Option<(usize, String)> {
if self.matching_columns.is_empty() {
return None;
}
self.current_match = if self.current_match == 0 {
self.matching_columns.len() - 1
} else {
self.current_match - 1
};
Some(self.matching_columns[self.current_match].clone())
}
#[must_use]
pub fn current_match(&self) -> Option<(usize, String)> {
if self.matching_columns.is_empty() {
None
} else {
Some(self.matching_columns[self.current_match].clone())
}
}
#[must_use]
pub fn get_stats(&self) -> String {
format!(
"Total searches: {}, History items: {}, Current matches: {}",
self.total_searches,
self.history.len(),
self.matching_columns.len()
)
}
}
#[derive(Debug, Clone)]
pub struct CacheListState {
pub selected_index: usize,
pub cache_names: Vec<String>,
}
impl Default for CacheListState {
fn default() -> Self {
Self::new()
}
}
impl CacheListState {
#[must_use]
pub fn new() -> Self {
Self {
selected_index: 0,
cache_names: Vec::new(),
}
}
}
#[derive(Debug, Clone)]
pub struct ColumnStatsState {
pub column_index: usize,
pub is_visible: bool,
}
impl Default for ColumnStatsState {
fn default() -> Self {
Self::new()
}
}
impl ColumnStatsState {
#[must_use]
pub fn new() -> Self {
Self {
column_index: 0,
is_visible: false,
}
}
}
#[derive(Debug, Clone)]
pub struct JumpToRowState {
pub input: String,
pub is_active: bool,
}
#[derive(Debug, Clone)]
pub struct NavigationState {
pub selected_row: usize,
pub selected_column: usize,
pub scroll_offset: (usize, usize), pub viewport_rows: usize,
pub viewport_columns: usize,
pub total_rows: usize,
pub total_columns: usize,
pub last_visible_rows: usize,
pub viewport_lock: bool, pub viewport_lock_row: Option<usize>,
pub cursor_lock: bool, pub cursor_lock_position: Option<usize>, pub selection_history: VecDeque<(usize, usize)>, }
impl Default for NavigationState {
fn default() -> Self {
Self::new()
}
}
impl NavigationState {
#[must_use]
pub fn new() -> Self {
Self {
selected_row: 0,
selected_column: 0,
scroll_offset: (0, 0),
viewport_rows: 30,
viewport_columns: 10,
total_rows: 0,
total_columns: 0,
last_visible_rows: 0,
viewport_lock: false,
viewport_lock_row: None,
cursor_lock: false,
cursor_lock_position: None,
selection_history: VecDeque::with_capacity(50), }
}
pub fn reset(&mut self) {
self.selected_row = 0;
self.selected_column = 0;
self.scroll_offset = (0, 0);
self.total_rows = 0;
self.total_columns = 0;
self.last_visible_rows = 0;
self.viewport_lock = false;
self.viewport_lock_row = None;
self.cursor_lock = false;
self.cursor_lock_position = None;
self.selection_history.clear();
}
pub fn update_totals(&mut self, rows: usize, columns: usize) {
info!(target: "navigation", "NavigationState::update_totals - rows: {} -> {}, columns: {} -> {}",
self.total_rows, rows, self.total_columns, columns);
self.total_rows = rows;
self.total_columns = columns;
if self.selected_row >= rows && rows > 0 {
let old_row = self.selected_row;
self.selected_row = rows - 1;
info!(target: "navigation", "Adjusted selected_row from {} to {} (out of bounds)", old_row, self.selected_row);
}
if self.selected_column >= columns && columns > 0 {
let old_col = self.selected_column;
self.selected_column = columns - 1;
info!(target: "navigation", "Adjusted selected_column from {} to {} (out of bounds)", old_col, self.selected_column);
}
}
pub fn set_viewport_size(&mut self, rows: usize, columns: usize) {
info!(target: "navigation", "NavigationState::set_viewport_size - rows: {} -> {}, columns: {} -> {}",
self.viewport_rows, rows, self.viewport_columns, columns);
self.viewport_rows = rows;
self.viewport_columns = columns;
}
pub fn next_row(&mut self) -> bool {
if self.cursor_lock {
if let Some(lock_position) = self.cursor_lock_position {
let max_scroll = self.total_rows.saturating_sub(self.viewport_rows);
if self.scroll_offset.0 < max_scroll {
self.scroll_offset.0 += 1;
let new_data_row = self.scroll_offset.0 + lock_position;
if new_data_row < self.total_rows {
self.selected_row = new_data_row;
self.add_to_history(self.selected_row, self.selected_column);
info!(target: "navigation", "NavigationState::next_row (cursor locked) - scrolled to offset {}, cursor at row {}",
self.scroll_offset.0, self.selected_row);
return true;
}
}
return false;
}
}
if self.viewport_lock {
let viewport_bottom = self.scroll_offset.0 + self.viewport_rows - 1;
if self.selected_row >= viewport_bottom {
info!(target: "navigation", "NavigationState::next_row - at viewport bottom (row {}), viewport locked", self.selected_row);
return false; }
}
if self.selected_row < self.total_rows.saturating_sub(1) {
self.selected_row += 1;
self.add_to_history(self.selected_row, self.selected_column);
self.ensure_visible(self.selected_row, self.selected_column);
info!(target: "navigation", "NavigationState::next_row - moved to row {}", self.selected_row);
true
} else {
false
}
}
pub fn previous_row(&mut self) -> bool {
if self.cursor_lock {
if let Some(lock_position) = self.cursor_lock_position {
if self.scroll_offset.0 > 0 {
self.scroll_offset.0 -= 1;
let new_data_row = self.scroll_offset.0 + lock_position;
self.selected_row = new_data_row;
self.add_to_history(self.selected_row, self.selected_column);
info!(target: "navigation", "NavigationState::previous_row (cursor locked) - scrolled to offset {}, cursor at row {}",
self.scroll_offset.0, self.selected_row);
return true;
}
return false;
}
}
if self.viewport_lock {
let viewport_top = self.scroll_offset.0;
if self.selected_row <= viewport_top {
info!(target: "navigation", "NavigationState::previous_row - at viewport top (row {}), viewport locked", self.selected_row);
return false; }
}
if self.selected_row > 0 {
self.selected_row -= 1;
self.add_to_history(self.selected_row, self.selected_column);
self.ensure_visible(self.selected_row, self.selected_column);
info!(target: "navigation", "NavigationState::previous_row - moved to row {}", self.selected_row);
true
} else {
false
}
}
pub fn next_column(&mut self) -> bool {
if self.selected_column < self.total_columns.saturating_sub(1) {
self.selected_column += 1;
self.add_to_history(self.selected_row, self.selected_column);
self.ensure_visible(self.selected_row, self.selected_column);
info!(target: "navigation", "NavigationState::next_column - moved to column {}", self.selected_column);
true
} else {
false
}
}
pub fn previous_column(&mut self) -> bool {
if self.selected_column > 0 {
self.selected_column -= 1;
self.add_to_history(self.selected_row, self.selected_column);
self.ensure_visible(self.selected_row, self.selected_column);
info!(target: "navigation", "NavigationState::previous_column - moved to column {}", self.selected_column);
true
} else {
false
}
}
pub fn jump_to_row(&mut self, row: usize) {
let target_row = row.min(self.total_rows.saturating_sub(1));
info!(target: "navigation", "NavigationState::jump_to_row - from {} to {}", self.selected_row, target_row);
self.selected_row = target_row;
self.add_to_history(self.selected_row, self.selected_column);
self.ensure_visible(self.selected_row, self.selected_column);
}
pub fn jump_to_first_row(&mut self) {
info!(target: "navigation", "NavigationState::jump_to_first_row - from row {}", self.selected_row);
self.selected_row = 0;
self.add_to_history(self.selected_row, self.selected_column);
self.ensure_visible(self.selected_row, self.selected_column);
}
pub fn jump_to_last_row(&mut self) {
let last_row = self.total_rows.saturating_sub(1);
info!(target: "navigation", "NavigationState::jump_to_last_row - from {} to {}", self.selected_row, last_row);
self.selected_row = last_row;
self.add_to_history(self.selected_row, self.selected_column);
self.ensure_visible(self.selected_row, self.selected_column);
}
pub fn set_position(&mut self, row: usize, column: usize) {
info!(target: "navigation", "NavigationState::set_position - ({}, {}) -> ({}, {})",
self.selected_row, self.selected_column, row, column);
self.selected_row = row.min(self.total_rows.saturating_sub(1));
self.selected_column = column.min(self.total_columns.saturating_sub(1));
self.add_to_history(self.selected_row, self.selected_column);
self.ensure_visible(self.selected_row, self.selected_column);
}
pub fn page_down(&mut self) {
if self.cursor_lock {
if let Some(lock_position) = self.cursor_lock_position {
let max_scroll = self.total_rows.saturating_sub(self.viewport_rows);
let new_scroll = (self.scroll_offset.0 + self.viewport_rows).min(max_scroll);
if new_scroll != self.scroll_offset.0 {
self.scroll_offset.0 = new_scroll;
let new_data_row = self.scroll_offset.0 + lock_position;
if new_data_row < self.total_rows {
self.selected_row = new_data_row;
self.add_to_history(self.selected_row, self.selected_column);
info!(target: "navigation", "NavigationState::page_down (cursor locked) - scrolled to offset {}, cursor at row {}",
self.scroll_offset.0, self.selected_row);
}
}
return;
}
}
let old_row = self.selected_row;
self.selected_row =
(self.selected_row + self.viewport_rows).min(self.total_rows.saturating_sub(1));
if self.selected_row != old_row {
info!(target: "navigation", "NavigationState::page_down - from {} to {}", old_row, self.selected_row);
self.add_to_history(self.selected_row, self.selected_column);
self.ensure_visible(self.selected_row, self.selected_column);
}
}
pub fn page_up(&mut self) {
if self.cursor_lock {
if let Some(lock_position) = self.cursor_lock_position {
let new_scroll = self.scroll_offset.0.saturating_sub(self.viewport_rows);
if new_scroll != self.scroll_offset.0 {
self.scroll_offset.0 = new_scroll;
let new_data_row = self.scroll_offset.0 + lock_position;
self.selected_row = new_data_row;
self.add_to_history(self.selected_row, self.selected_column);
info!(target: "navigation", "NavigationState::page_up (cursor locked) - scrolled to offset {}, cursor at row {}",
self.scroll_offset.0, self.selected_row);
}
return;
}
}
let old_row = self.selected_row;
self.selected_row = self.selected_row.saturating_sub(self.viewport_rows);
if self.selected_row != old_row {
info!(target: "navigation", "NavigationState::page_up - from {} to {}", old_row, self.selected_row);
self.add_to_history(self.selected_row, self.selected_column);
self.ensure_visible(self.selected_row, self.selected_column);
}
}
pub fn jump_to_viewport_top(&mut self) {
let target_row = self.scroll_offset.0;
if target_row != self.selected_row && target_row < self.total_rows {
info!(target: "navigation", "NavigationState::jump_to_viewport_top - from {} to {} (viewport top)",
self.selected_row, target_row);
self.selected_row = target_row;
self.add_to_history(self.selected_row, self.selected_column);
}
}
pub fn jump_to_viewport_middle(&mut self) {
let viewport_start = self.scroll_offset.0;
let viewport_end = (viewport_start + self.viewport_rows).min(self.total_rows);
let target_row = viewport_start + (viewport_end - viewport_start) / 2;
if target_row != self.selected_row && target_row < self.total_rows {
info!(target: "navigation", "NavigationState::jump_to_viewport_middle - from {} to {} (viewport middle)",
self.selected_row, target_row);
self.selected_row = target_row;
self.add_to_history(self.selected_row, self.selected_column);
}
}
pub fn jump_to_viewport_bottom(&mut self) {
let viewport_start = self.scroll_offset.0;
let viewport_end = (viewport_start + self.viewport_rows).min(self.total_rows);
let target_row = viewport_end.saturating_sub(1);
if target_row != self.selected_row && target_row < self.total_rows {
info!(target: "navigation", "NavigationState::jump_to_viewport_bottom - from {} to {} (viewport bottom)",
self.selected_row, target_row);
self.selected_row = target_row;
self.add_to_history(self.selected_row, self.selected_column);
}
}
#[must_use]
pub fn is_position_visible(&self, row: usize, col: usize) -> bool {
let (scroll_row, scroll_col) = self.scroll_offset;
row >= scroll_row
&& row < scroll_row + self.viewport_rows
&& col >= scroll_col
&& col < scroll_col + self.viewport_columns
}
pub fn ensure_visible(&mut self, row: usize, col: usize) {
if self.viewport_lock {
info!(target: "navigation", "NavigationState::ensure_visible - viewport locked, not adjusting scroll");
return;
}
let (mut scroll_row, mut scroll_col) = self.scroll_offset;
if row < scroll_row {
scroll_row = row;
} else if row >= scroll_row + self.viewport_rows {
scroll_row = row.saturating_sub(self.viewport_rows - 1);
}
if col < scroll_col {
scroll_col = col;
} else if col >= scroll_col + self.viewport_columns {
scroll_col = col.saturating_sub(self.viewport_columns - 1);
}
if self.scroll_offset != (scroll_row, scroll_col) {
info!(target: "navigation", "NavigationState::ensure_visible - scroll_offset: {:?} -> {:?}",
self.scroll_offset, (scroll_row, scroll_col));
self.scroll_offset = (scroll_row, scroll_col);
}
}
#[must_use]
pub fn is_at_viewport_top(&self) -> bool {
self.selected_row == self.scroll_offset.0
}
#[must_use]
pub fn is_at_viewport_bottom(&self) -> bool {
self.selected_row == self.scroll_offset.0 + self.viewport_rows - 1
}
#[must_use]
pub fn get_position_status(&self) -> String {
if self.viewport_lock {
if self.is_at_viewport_top() {
" (at viewport top)".to_string()
} else if self.is_at_viewport_bottom() {
" (at viewport bottom)".to_string()
} else {
String::new()
}
} else {
String::new()
}
}
pub fn add_to_history(&mut self, row: usize, col: usize) {
if let Some(&(last_row, last_col)) = self.selection_history.back() {
if last_row == row && last_col == col {
return;
}
}
if self.selection_history.len() >= 50 {
self.selection_history.pop_front();
}
self.selection_history.push_back((row, col));
}
}
impl Default for JumpToRowState {
fn default() -> Self {
Self::new()
}
}
impl JumpToRowState {
#[must_use]
pub fn new() -> Self {
Self {
input: String::new(),
is_active: false,
}
}
}
#[derive(Debug, Clone)]
pub struct SortState {
pub column: Option<usize>,
pub column_name: Option<String>,
pub order: SortOrder,
pub history: VecDeque<SortHistoryEntry>,
pub max_history: usize,
pub total_sorts: usize,
pub last_sort_time: Option<Instant>,
}
#[derive(Debug, Clone)]
pub struct SortHistoryEntry {
pub column_index: usize,
pub column_name: String,
pub order: SortOrder,
pub sorted_at: Instant,
pub row_count: usize,
}
impl Default for SortState {
fn default() -> Self {
Self::new()
}
}
impl SortState {
#[must_use]
pub fn new() -> Self {
Self {
column: None,
column_name: None,
order: SortOrder::None,
history: VecDeque::with_capacity(20),
max_history: 20,
total_sorts: 0,
last_sort_time: None,
}
}
pub fn set_sort(
&mut self,
column_index: usize,
column_name: String,
order: SortOrder,
row_count: usize,
) {
if self.history.len() >= self.max_history {
self.history.pop_front();
}
self.history.push_back(SortHistoryEntry {
column_index,
column_name: column_name.clone(),
order,
sorted_at: Instant::now(),
row_count,
});
self.column = Some(column_index);
self.column_name = Some(column_name);
self.order = order;
self.total_sorts += 1;
self.last_sort_time = Some(Instant::now());
}
pub fn clear_sort(&mut self) {
self.column = None;
self.column_name = None;
self.order = SortOrder::None;
self.last_sort_time = Some(Instant::now());
}
#[must_use]
pub fn get_next_order(&self, column_index: usize) -> SortOrder {
if let Some(current_col) = self.column {
if current_col == column_index {
match self.order {
SortOrder::None => SortOrder::Ascending,
SortOrder::Ascending => SortOrder::Descending,
SortOrder::Descending => SortOrder::None,
}
} else {
SortOrder::Ascending
}
} else {
SortOrder::Ascending
}
}
pub fn advance_sort_state(
&mut self,
column_index: usize,
column_name: Option<String>,
new_order: SortOrder,
) {
if let (Some(col), Some(name)) = (self.column, &self.column_name) {
self.history.push_back(SortHistoryEntry {
column_index: col,
column_name: name.clone(),
order: self.order,
sorted_at: std::time::Instant::now(),
row_count: 0, });
}
self.total_sorts += 1;
if new_order == SortOrder::None {
self.column = None;
self.column_name = None;
} else {
self.column = Some(column_index);
self.column_name = column_name;
}
self.order = new_order;
self.last_sort_time = Some(std::time::Instant::now());
}
#[must_use]
pub fn get_stats(&self) -> String {
let current = if let (Some(col), Some(name)) = (self.column, &self.column_name) {
format!(
"Column {} ({}) {}",
col,
name,
match self.order {
SortOrder::Ascending => "↑",
SortOrder::Descending => "↓",
SortOrder::None => "-",
}
)
} else {
"None".to_string()
};
format!(
"Current: {}, Total sorts: {}, History items: {}",
current,
self.total_sorts,
self.history.len()
)
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum SelectionMode {
Row,
Cell,
Column,
}
#[derive(Debug, Clone)]
pub struct SelectionState {
pub mode: SelectionMode,
pub selected_row: Option<usize>,
pub selected_column: usize,
pub selected_cells: Vec<(usize, usize)>,
pub selection_anchor: Option<(usize, usize)>,
pub history: VecDeque<SelectionHistoryEntry>,
pub max_history: usize,
pub total_selections: usize,
pub last_selection_time: Option<Instant>,
}
#[derive(Debug, Clone)]
pub struct SelectionHistoryEntry {
pub mode: SelectionMode,
pub row: Option<usize>,
pub column: usize,
pub cells: Vec<(usize, usize)>,
pub timestamp: chrono::DateTime<chrono::Local>,
}
impl Default for SelectionState {
fn default() -> Self {
Self::new()
}
}
impl SelectionState {
#[must_use]
pub fn new() -> Self {
Self {
mode: SelectionMode::Row,
selected_row: None,
selected_column: 0,
selected_cells: Vec::new(),
selection_anchor: None,
history: VecDeque::new(),
max_history: 50,
total_selections: 0,
last_selection_time: None,
}
}
pub fn set_mode(&mut self, mode: SelectionMode) {
if self.mode != mode {
self.save_to_history();
self.mode = mode;
self.selected_cells.clear();
self.selection_anchor = None;
}
}
pub fn select_row(&mut self, row: Option<usize>) {
if self.selected_row != row {
self.save_to_history();
self.selected_row = row;
self.total_selections += 1;
self.last_selection_time = Some(Instant::now());
}
}
pub fn select_column(&mut self, column: usize) {
if self.selected_column != column {
self.save_to_history();
self.selected_column = column;
self.total_selections += 1;
self.last_selection_time = Some(Instant::now());
}
}
pub fn select_cell(&mut self, row: usize, column: usize) {
self.save_to_history();
self.selected_row = Some(row);
self.selected_column = column;
self.total_selections += 1;
self.last_selection_time = Some(Instant::now());
}
pub fn add_cell_to_selection(&mut self, row: usize, column: usize) {
let cell = (row, column);
if !self.selected_cells.contains(&cell) {
self.selected_cells.push(cell);
self.total_selections += 1;
self.last_selection_time = Some(Instant::now());
}
}
pub fn clear_selections(&mut self) {
self.save_to_history();
self.selected_cells.clear();
self.selection_anchor = None;
}
fn save_to_history(&mut self) {
let entry = SelectionHistoryEntry {
mode: self.mode.clone(),
row: self.selected_row,
column: self.selected_column,
cells: self.selected_cells.clone(),
timestamp: chrono::Local::now(),
};
if self.history.len() >= self.max_history {
self.history.pop_front();
}
self.history.push_back(entry);
}
#[must_use]
pub fn get_stats(&self) -> String {
let mode_str = match self.mode {
SelectionMode::Row => "Row",
SelectionMode::Cell => "Cell",
SelectionMode::Column => "Column",
};
let selection_str = match (self.selected_row, self.selected_cells.len()) {
(Some(row), 0) => format!("Row {}, Col {}", row, self.selected_column),
(_, n) if n > 0 => format!("{n} cells selected"),
_ => format!("Col {}", self.selected_column),
};
format!(
"Mode: {}, Selection: {}, Total: {}",
mode_str, selection_str, self.total_selections
)
}
}
pub struct NavigationProxy<'a> {
buffer: Option<&'a crate::buffer::Buffer>,
}
impl<'a> NavigationProxy<'a> {
#[must_use]
pub fn new(buffer: Option<&'a crate::buffer::Buffer>) -> Self {
Self { buffer }
}
#[must_use]
pub fn selected_row(&self) -> usize {
self.buffer.map_or(0, |b| b.view_state.crosshair_row)
}
#[must_use]
pub fn selected_column(&self) -> usize {
self.buffer.map_or(0, |b| b.view_state.crosshair_col)
}
#[must_use]
pub fn scroll_offset(&self) -> (usize, usize) {
self.buffer.map_or((0, 0), |b| b.view_state.scroll_offset)
}
#[must_use]
pub fn viewport_lock(&self) -> bool {
self.buffer.is_some_and(|b| b.view_state.viewport_lock)
}
#[must_use]
pub fn cursor_lock(&self) -> bool {
self.buffer.is_some_and(|b| b.view_state.cursor_lock)
}
#[must_use]
pub fn total_rows(&self) -> usize {
self.buffer.map_or(0, |b| b.view_state.total_rows)
}
#[must_use]
pub fn total_columns(&self) -> usize {
self.buffer.map_or(0, |b| b.view_state.total_columns)
}
}
pub struct NavigationProxyMut<'a> {
buffer: Option<&'a mut crate::buffer::Buffer>,
}
impl<'a> NavigationProxyMut<'a> {
#[must_use]
pub fn new(buffer: Option<&'a mut crate::buffer::Buffer>) -> Self {
Self { buffer }
}
pub fn set_selected_row(&mut self, row: usize) {
if let Some(buffer) = &mut self.buffer {
buffer.view_state.crosshair_row = row;
}
}
pub fn set_selected_column(&mut self, col: usize) {
if let Some(buffer) = &mut self.buffer {
buffer.view_state.crosshair_col = col;
}
}
pub fn set_scroll_offset(&mut self, offset: (usize, usize)) {
if let Some(buffer) = &mut self.buffer {
buffer.view_state.scroll_offset = offset;
}
}
pub fn set_viewport_lock(&mut self, locked: bool) {
if let Some(buffer) = &mut self.buffer {
buffer.view_state.viewport_lock = locked;
}
}
pub fn set_cursor_lock(&mut self, locked: bool) {
if let Some(buffer) = &mut self.buffer {
buffer.view_state.cursor_lock = locked;
}
}
pub fn update_totals(&mut self, rows: usize, columns: usize) {
if let Some(buffer) = &mut self.buffer {
buffer.view_state.total_rows = rows;
buffer.view_state.total_columns = columns;
}
}
}
pub struct SelectionProxy<'a> {
buffer: Option<&'a crate::buffer::Buffer>,
}
impl<'a> SelectionProxy<'a> {
#[must_use]
pub fn new(buffer: Option<&'a crate::buffer::Buffer>) -> Self {
Self { buffer }
}
#[must_use]
pub fn mode(&self) -> crate::buffer::SelectionMode {
self.buffer.map_or(crate::buffer::SelectionMode::Row, |b| {
b.view_state.selection_mode.clone()
})
}
#[must_use]
pub fn selected_cells(&self) -> Vec<(usize, usize)> {
self.buffer
.map(|b| b.view_state.selected_cells.clone())
.unwrap_or_default()
}
#[must_use]
pub fn selection_anchor(&self) -> Option<(usize, usize)> {
self.buffer.and_then(|b| b.view_state.selection_anchor)
}
}
pub struct SelectionProxyMut<'a> {
buffer: Option<&'a mut crate::buffer::Buffer>,
}
impl<'a> SelectionProxyMut<'a> {
#[must_use]
pub fn new(buffer: Option<&'a mut crate::buffer::Buffer>) -> Self {
Self { buffer }
}
pub fn set_mode(&mut self, mode: crate::buffer::SelectionMode) {
if let Some(buffer) = &mut self.buffer {
buffer.view_state.selection_mode = mode;
}
}
pub fn add_selected_cell(&mut self, cell: (usize, usize)) {
if let Some(buffer) = &mut self.buffer {
buffer.view_state.selected_cells.push(cell);
}
}
pub fn clear_selections(&mut self) {
if let Some(buffer) = &mut self.buffer {
buffer.view_state.selected_cells.clear();
buffer.view_state.selection_anchor = None;
}
}
pub fn set_selection_anchor(&mut self, anchor: Option<(usize, usize)>) {
if let Some(buffer) = &mut self.buffer {
buffer.view_state.selection_anchor = anchor;
}
}
}
#[derive(Debug, Clone)]
pub struct HistorySearchState {
pub query: String,
pub matches: Vec<crate::history::HistoryMatch>,
pub selected_index: usize,
pub is_active: bool,
pub original_input: String,
}
impl Default for HistorySearchState {
fn default() -> Self {
Self::new()
}
}
impl HistorySearchState {
#[must_use]
pub fn new() -> Self {
Self {
query: String::new(),
matches: Vec::new(),
selected_index: 0,
is_active: false,
original_input: String::new(),
}
}
pub fn clear(&mut self) {
self.query.clear();
self.matches.clear();
self.selected_index = 0;
self.is_active = false;
self.original_input.clear();
}
}
#[derive(Debug, Clone)]
pub struct HelpState {
pub is_visible: bool,
pub scroll_offset: u16,
pub max_scroll: u16,
pub open_count: usize,
pub last_opened: Option<Instant>,
}
impl Default for HelpState {
fn default() -> Self {
Self::new()
}
}
impl HelpState {
#[must_use]
pub fn new() -> Self {
Self {
is_visible: false,
scroll_offset: 0,
max_scroll: 0,
open_count: 0,
last_opened: None,
}
}
pub fn show(&mut self) {
self.is_visible = true;
self.scroll_offset = 0;
self.open_count += 1;
self.last_opened = Some(Instant::now());
}
pub fn hide(&mut self) {
self.is_visible = false;
}
pub fn toggle(&mut self) {
if self.is_visible {
self.hide();
} else {
self.show();
}
}
pub fn scroll_down(&mut self, amount: u16) {
self.scroll_offset = (self.scroll_offset + amount).min(self.max_scroll);
}
pub fn scroll_up(&mut self, amount: u16) {
self.scroll_offset = self.scroll_offset.saturating_sub(amount);
}
pub fn set_max_scroll(&mut self, content_lines: usize, viewport_height: usize) {
self.max_scroll = content_lines.saturating_sub(viewport_height) as u16;
}
}
#[derive(Debug, Clone)]
pub struct UndoRedoState {
pub undo_stack: Vec<(String, usize)>,
pub redo_stack: Vec<(String, usize)>,
pub max_undo_entries: usize,
}
impl Default for UndoRedoState {
fn default() -> Self {
Self {
undo_stack: Vec::new(),
redo_stack: Vec::new(),
max_undo_entries: 100,
}
}
}
impl UndoRedoState {
pub fn push_undo(&mut self, text: String, cursor: usize) {
self.undo_stack.push((text, cursor));
if self.undo_stack.len() > self.max_undo_entries {
self.undo_stack.remove(0);
}
self.redo_stack.clear();
}
pub fn pop_undo(&mut self) -> Option<(String, usize)> {
self.undo_stack.pop()
}
pub fn push_redo(&mut self, text: String, cursor: usize) {
self.redo_stack.push((text, cursor));
if self.redo_stack.len() > self.max_undo_entries {
self.redo_stack.remove(0);
}
}
pub fn pop_redo(&mut self) -> Option<(String, usize)> {
self.redo_stack.pop()
}
}
#[derive(Debug, Clone, Default)]
pub struct ScrollState {
pub help_scroll: u16,
pub input_scroll_offset: u16,
pub viewport_scroll_offset: (usize, usize),
pub last_visible_rows: usize,
}
#[derive(Debug, Clone)]
pub struct ChordState {
pub current_chord: Vec<String>, pub chord_start: Option<std::time::SystemTime>,
pub is_active: bool,
pub description: Option<String>,
pub registered_chords: std::collections::HashMap<String, String>,
pub history: Vec<(String, String, std::time::SystemTime)>, }
impl Default for ChordState {
fn default() -> Self {
let mut registered_chords = std::collections::HashMap::new();
registered_chords.insert("yy".to_string(), "yank_row".to_string());
registered_chords.insert("yr".to_string(), "yank_row".to_string());
registered_chords.insert("yc".to_string(), "yank_column".to_string());
registered_chords.insert("ya".to_string(), "yank_all".to_string());
registered_chords.insert("yv".to_string(), "yank_cell".to_string());
Self {
current_chord: Vec::new(),
chord_start: None,
is_active: false,
description: None,
registered_chords,
history: Vec::new(),
}
}
}
impl ChordState {
pub fn clear(&mut self) {
self.current_chord.clear();
self.chord_start = None;
self.is_active = false;
self.description = None;
}
pub fn add_key(&mut self, key: String) {
if self.current_chord.is_empty() {
self.chord_start = Some(std::time::SystemTime::now());
}
self.current_chord.push(key);
self.is_active = true;
}
#[must_use]
pub fn get_chord_string(&self) -> String {
self.current_chord.join("")
}
#[must_use]
pub fn check_match(&self) -> Option<String> {
let chord = self.get_chord_string();
self.registered_chords.get(&chord).cloned()
}
#[must_use]
pub fn is_partial_match(&self) -> bool {
let current = self.get_chord_string();
self.registered_chords
.keys()
.any(|chord| chord.starts_with(¤t) && chord.len() > current.len())
}
pub fn record_completion(&mut self, chord: String, action: String) {
self.history
.push((chord, action, std::time::SystemTime::now()));
if self.history.len() > 50 {
self.history.remove(0);
}
}
}
pub struct WidgetStates {
pub search_modes: SearchModesWidget,
pub history: Option<HistoryWidget>, pub help: HelpWidget,
pub stats: StatsWidget,
}
impl Default for WidgetStates {
fn default() -> Self {
Self::new()
}
}
impl WidgetStates {
#[must_use]
pub fn new() -> Self {
Self {
search_modes: SearchModesWidget::new(),
history: None, help: HelpWidget::new(),
stats: StatsWidget::new(),
}
}
pub fn set_history(&mut self, history: HistoryWidget) {
self.history = Some(history);
}
}
#[derive(Debug, Clone)]
pub struct ResultsState {
pub current_results: Option<QueryResponse>,
pub results_cache: HashMap<String, CachedResult>,
pub max_cache_size: usize,
pub total_memory_usage: usize,
pub memory_limit: usize,
pub last_query: String,
pub last_execution_time: Duration,
pub query_performance_history: VecDeque<QueryPerformance>,
pub from_cache: bool,
pub last_modified: Instant,
}
#[derive(Debug, Clone)]
pub struct CachedResult {
pub response: QueryResponse,
pub cached_at: Instant,
pub access_count: u32,
pub last_access: Instant,
pub memory_size: usize,
}
#[derive(Debug, Clone)]
pub struct QueryPerformance {
pub query: String,
pub execution_time: Duration,
pub row_count: usize,
pub from_cache: bool,
pub memory_usage: usize,
pub executed_at: Instant,
}
impl Default for ResultsState {
fn default() -> Self {
Self {
current_results: None,
results_cache: HashMap::new(),
max_cache_size: 100, total_memory_usage: 0,
memory_limit: 512 * 1024 * 1024, last_query: String::new(),
last_execution_time: Duration::from_millis(0),
query_performance_history: VecDeque::with_capacity(1000),
from_cache: false,
last_modified: Instant::now(),
}
}
}
#[derive(Debug, Clone)]
pub struct ClipboardState {
pub last_yanked: Option<YankedItem>,
pub yank_history: VecDeque<YankedItem>,
pub max_history: usize,
pub current_register: char,
pub total_yanks: usize,
pub last_yank_time: Option<Instant>,
}
#[derive(Debug, Clone)]
pub struct YankedItem {
pub description: String,
pub full_value: String,
pub preview: String,
pub yank_type: YankType,
pub yanked_at: DateTime<Local>,
pub size_bytes: usize,
}
#[derive(Debug, Clone, PartialEq)]
pub enum YankType {
Cell {
row: usize,
column: usize,
},
Row {
row: usize,
},
Column {
name: String,
index: usize,
},
All,
Selection {
start: (usize, usize),
end: (usize, usize),
},
Query,
TestCase,
DebugContext,
}
impl Default for ClipboardState {
fn default() -> Self {
Self {
last_yanked: None,
yank_history: VecDeque::with_capacity(50),
max_history: 50,
current_register: '"', total_yanks: 0,
last_yank_time: None,
}
}
}
impl ClipboardState {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn add_yank(&mut self, item: YankedItem) {
self.yank_history.push_front(item.clone());
while self.yank_history.len() > self.max_history {
self.yank_history.pop_back();
}
self.last_yanked = Some(item);
self.total_yanks += 1;
self.last_yank_time = Some(Instant::now());
}
pub fn clear(&mut self) {
self.last_yanked = None;
}
pub fn clear_history(&mut self) {
self.yank_history.clear();
self.last_yanked = None;
}
#[must_use]
pub fn get_stats(&self) -> String {
format!(
"Total yanks: {}, History items: {}, Last yank: {}",
self.total_yanks,
self.yank_history.len(),
self.last_yank_time
.map_or_else(|| "never".to_string(), |t| format!("{:?} ago", t.elapsed()))
)
}
}
impl ResultsState {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn set_results(
&mut self,
results: QueryResponse,
execution_time: Duration,
from_cache: bool,
) -> Result<()> {
let row_count = results.count;
let memory_usage = self.estimate_memory_usage(&results);
let performance = QueryPerformance {
query: results.query.select.join(", "),
execution_time,
row_count,
from_cache,
memory_usage,
executed_at: Instant::now(),
};
self.query_performance_history.push_back(performance);
if self.query_performance_history.len() > 1000 {
self.query_performance_history.pop_front();
}
self.current_results = Some(results);
self.last_execution_time = execution_time;
self.from_cache = from_cache;
self.last_modified = Instant::now();
Ok(())
}
#[must_use]
pub fn get_results(&self) -> Option<&QueryResponse> {
self.current_results.as_ref()
}
pub fn cache_results(&mut self, query_key: String, results: QueryResponse) -> Result<()> {
let memory_usage = self.estimate_memory_usage(&results);
if self.total_memory_usage + memory_usage > self.memory_limit {
self.evict_to_fit(memory_usage)?;
}
let cached_result = CachedResult {
response: results,
cached_at: Instant::now(),
access_count: 1,
last_access: Instant::now(),
memory_size: memory_usage,
};
if self.results_cache.len() >= self.max_cache_size {
self.evict_oldest()?;
}
self.results_cache.insert(query_key, cached_result);
self.total_memory_usage += memory_usage;
Ok(())
}
pub fn get_cached_results(&mut self, query_key: &str) -> Option<&QueryResponse> {
if let Some(cached) = self.results_cache.get_mut(query_key) {
cached.access_count += 1;
cached.last_access = Instant::now();
Some(&cached.response)
} else {
None
}
}
pub fn clear_cache(&mut self) {
self.results_cache.clear();
self.total_memory_usage = 0;
}
#[must_use]
pub fn get_cache_stats(&self) -> CacheStats {
CacheStats {
entry_count: self.results_cache.len(),
memory_usage: self.total_memory_usage,
memory_limit: self.memory_limit,
hit_rate: self.calculate_hit_rate(),
}
}
#[must_use]
pub fn get_performance_stats(&self) -> PerformanceStats {
let total_queries = self.query_performance_history.len();
let cached_queries = self
.query_performance_history
.iter()
.filter(|q| q.from_cache)
.count();
let avg_execution_time = if total_queries > 0 {
self.query_performance_history
.iter()
.map(|q| q.execution_time.as_millis() as f64)
.sum::<f64>()
/ total_queries as f64
} else {
0.0
};
PerformanceStats {
total_queries,
cached_queries,
cache_hit_rate: if total_queries > 0 {
cached_queries as f64 / total_queries as f64
} else {
0.0
},
average_execution_time_ms: avg_execution_time,
last_execution_time: self.last_execution_time,
}
}
fn estimate_memory_usage(&self, results: &QueryResponse) -> usize {
let data_size = results
.data
.iter()
.map(|row| serde_json::to_string(row).unwrap_or_default().len())
.sum::<usize>();
data_size + std::mem::size_of::<QueryResponse>() + 1024 }
fn evict_to_fit(&mut self, needed_space: usize) -> Result<()> {
while self.total_memory_usage + needed_space > self.memory_limit
&& !self.results_cache.is_empty()
{
self.evict_oldest()?;
}
Ok(())
}
fn evict_oldest(&mut self) -> Result<()> {
if let Some((key, cached)) = self
.results_cache
.iter()
.min_by_key(|(_, cached)| cached.last_access)
.map(|(k, v)| (k.clone(), v.memory_size))
{
self.results_cache.remove(&key);
self.total_memory_usage = self.total_memory_usage.saturating_sub(cached);
}
Ok(())
}
fn calculate_hit_rate(&self) -> f64 {
let total = self.query_performance_history.len();
if total == 0 {
return 0.0;
}
let hits = self
.query_performance_history
.iter()
.filter(|q| q.from_cache)
.count();
hits as f64 / total as f64
}
}
#[derive(Debug, Clone)]
pub struct CacheStats {
pub entry_count: usize,
pub memory_usage: usize,
pub memory_limit: usize,
pub hit_rate: f64,
}
#[derive(Debug, Clone)]
pub struct PerformanceStats {
pub total_queries: usize,
pub cached_queries: usize,
pub cache_hit_rate: f64,
pub average_execution_time_ms: f64,
pub last_execution_time: Duration,
}
#[derive(Debug, Clone)]
pub struct ResultsCache {
cache: HashMap<String, Vec<Vec<String>>>,
max_size: usize,
}
impl ResultsCache {
#[must_use]
pub fn new(max_size: usize) -> Self {
Self {
cache: HashMap::new(),
max_size,
}
}
#[must_use]
pub fn get(&self, key: &str) -> Option<&Vec<Vec<String>>> {
self.cache.get(key)
}
pub fn insert(&mut self, key: String, value: Vec<Vec<String>>) {
if self.cache.len() >= self.max_size {
if let Some(first_key) = self.cache.keys().next().cloned() {
self.cache.remove(&first_key);
}
}
self.cache.insert(key, value);
}
}
pub struct AppStateContainer {
buffers: BufferManager,
current_buffer_id: usize,
command_input: RefCell<InputState>,
search: RefCell<SearchState>,
filter: RefCell<FilterState>,
column_search: RefCell<ColumnSearchState>,
history_search: RefCell<HistorySearchState>,
sort: RefCell<SortState>,
selection: RefCell<SelectionState>,
completion: RefCell<CompletionState>,
widgets: WidgetStates,
cache_list: CacheListState,
column_stats: ColumnStatsState,
jump_to_row: JumpToRowState,
navigation: RefCell<NavigationState>,
command_history: RefCell<CommandHistory>,
key_press_history: RefCell<KeyPressHistory>,
results: RefCell<ResultsState>,
clipboard: RefCell<ClipboardState>,
chord: RefCell<ChordState>,
undo_redo: RefCell<UndoRedoState>,
scroll: RefCell<ScrollState>,
results_cache: ResultsCache,
mode_stack: Vec<AppMode>,
debug_enabled: bool,
debug_service: RefCell<Option<crate::debug_service::DebugService>>,
help: RefCell<HelpState>,
}
impl AppStateContainer {
#[must_use]
pub fn format_number_compact(n: usize) -> String {
if n < 1000 {
n.to_string()
} else if n < 1000000 {
let k = n as f64 / 1000.0;
if k.fract() == 0.0 {
format!("{}k", k as usize)
} else if k < 10.0 {
format!("{k:.1}k")
} else {
format!("{}k", k as usize)
}
} else if n < 1000000000 {
let m = n as f64 / 1000000.0;
if m.fract() == 0.0 {
format!("{}M", m as usize)
} else if m < 10.0 {
format!("{m:.1}M")
} else {
format!("{}M", m as usize)
}
} else {
let b = n as f64 / 1000000000.0;
if b.fract() == 0.0 {
format!("{}B", b as usize)
} else {
format!("{b:.1}B")
}
}
}
pub fn new(buffers: BufferManager) -> Result<Self> {
let command_history = CommandHistory::new()?;
let mut widgets = WidgetStates::new();
widgets.set_history(HistoryWidget::new(command_history.clone()));
Ok(Self {
buffers,
current_buffer_id: 0,
command_input: RefCell::new(InputState::new()),
search: RefCell::new(SearchState::new()),
filter: RefCell::new(FilterState::new()),
column_search: RefCell::new(ColumnSearchState::new()),
history_search: RefCell::new(HistorySearchState::new()),
sort: RefCell::new(SortState::new()),
selection: RefCell::new(SelectionState::new()),
completion: RefCell::new(CompletionState::new()),
widgets,
cache_list: CacheListState::new(),
column_stats: ColumnStatsState::new(),
jump_to_row: JumpToRowState::new(),
command_history: RefCell::new(command_history),
key_press_history: RefCell::new(KeyPressHistory::new(50)), results: RefCell::new(ResultsState::new()),
clipboard: RefCell::new(ClipboardState::new()),
chord: RefCell::new(ChordState::default()),
undo_redo: RefCell::new(UndoRedoState::default()),
scroll: RefCell::new(ScrollState::default()),
navigation: RefCell::new(NavigationState::new()),
results_cache: ResultsCache::new(100),
mode_stack: vec![AppMode::Command],
debug_enabled: false,
debug_service: RefCell::new(None), help: RefCell::new(HelpState::new()),
})
}
pub fn current_buffer(&self) -> Option<&crate::buffer::Buffer> {
self.buffers.current()
}
pub fn current_buffer_mut(&mut self) -> Option<&mut crate::buffer::Buffer> {
self.buffers.current_mut()
}
pub fn buffers(&self) -> &BufferManager {
&self.buffers
}
pub fn buffers_mut(&mut self) -> &mut BufferManager {
&mut self.buffers
}
pub fn command_input(&self) -> std::cell::Ref<'_, InputState> {
self.command_input.borrow()
}
pub fn command_input_mut(&self) -> std::cell::RefMut<'_, InputState> {
self.command_input.borrow_mut()
}
pub fn set_input_text(&self, text: String) {
let mut input = self.command_input.borrow_mut();
input.text = text.clone();
input.cursor_position = text.len();
}
pub fn set_input_text_with_cursor(&self, text: String, cursor: usize) {
let mut input = self.command_input.borrow_mut();
input.text = text;
input.cursor_position = cursor;
}
pub fn set_last_executed_query(&self, query: String) {
self.command_input.borrow_mut().last_executed_query = query;
}
pub fn search(&self) -> std::cell::Ref<'_, SearchState> {
self.search.borrow()
}
pub fn search_mut(&self) -> std::cell::RefMut<'_, SearchState> {
self.search.borrow_mut()
}
pub fn start_search(&self, pattern: String) -> usize {
let mut search = self.search.borrow_mut();
let old_pattern = search.pattern.clone();
let old_active = search.is_active;
search.pattern = pattern.clone();
search.is_active = true;
search.last_search_time = Some(std::time::Instant::now());
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.info(
"Search",
format!(
"Starting search: '{pattern}' (was: '{old_pattern}', active: {old_active})"
),
);
}
0
}
pub fn update_search_matches(&self, matches: Vec<(usize, usize, usize, usize)>) {
let match_count = matches.len();
let mut search = self.search.borrow_mut();
let pattern = search.pattern.clone();
search.matches = matches;
search.current_match = if match_count > 0 { 0 } else { 0 };
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.info(
"Search",
format!("Search found {match_count} matches for pattern '{pattern}'"),
);
}
if !pattern.is_empty() {
let duration_ms = search
.last_search_time
.map(|t| t.elapsed().as_millis() as u64);
let entry = SearchHistoryEntry {
pattern: pattern.clone(),
match_count,
timestamp: Local::now(),
duration_ms,
};
if search.history.len() >= 20 {
search.history.pop_front();
}
search.history.push_back(entry);
}
}
pub fn next_search_match(&self) -> Option<(usize, usize)> {
let mut search = self.search.borrow_mut();
if search.matches.is_empty() {
return None;
}
let old_match = search.current_match;
search.current_match = (search.current_match + 1) % search.matches.len();
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.info(
"Search",
format!(
"Navigate to next match: {} -> {} (of {})",
old_match,
search.current_match,
search.matches.len()
),
);
}
let match_pos = search.matches[search.current_match];
Some((match_pos.0, match_pos.1))
}
pub fn previous_search_match(&self) -> Option<(usize, usize)> {
let mut search = self.search.borrow_mut();
if search.matches.is_empty() {
return None;
}
let old_match = search.current_match;
search.current_match = if search.current_match == 0 {
search.matches.len() - 1
} else {
search.current_match - 1
};
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.info(
"Search",
format!(
"Navigate to previous match: {} -> {} (of {})",
old_match,
search.current_match,
search.matches.len()
),
);
}
let match_pos = search.matches[search.current_match];
Some((match_pos.0, match_pos.1))
}
pub fn clear_search(&self) {
let mut search = self.search.borrow_mut();
let had_matches = search.matches.len();
let had_pattern = search.pattern.clone();
search.clear();
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.info(
"Search",
format!("Cleared search (had pattern: '{had_pattern}', {had_matches} matches)"),
);
}
}
pub fn perform_search(&self, data: &[Vec<String>]) -> Vec<(usize, usize, usize, usize)> {
use regex::Regex;
let pattern = self.search.borrow().pattern.clone();
if pattern.is_empty() {
let mut search = self.search.borrow_mut();
search.matches.clear();
search.current_match = 0;
return Vec::new();
}
let start_time = std::time::Instant::now();
let mut matches = Vec::new();
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.info(
"Search",
format!(
"Performing search for pattern '{}' on {} rows",
pattern,
data.len()
),
);
}
match Regex::new(&pattern) {
Ok(regex) => {
for (row_idx, row) in data.iter().enumerate() {
for (col_idx, cell) in row.iter().enumerate() {
if regex.is_match(cell) {
matches.push((row_idx, col_idx, row_idx, col_idx));
}
}
}
}
Err(e) => {
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.info("Search", format!("Invalid regex pattern '{pattern}': {e}"));
}
let pattern_lower = pattern.to_lowercase();
for (row_idx, row) in data.iter().enumerate() {
for (col_idx, cell) in row.iter().enumerate() {
if cell.to_lowercase().contains(&pattern_lower) {
matches.push((row_idx, col_idx, row_idx, col_idx));
}
}
}
}
}
let elapsed = start_time.elapsed();
self.search.borrow_mut().last_search_time = Some(start_time);
self.update_search_matches(matches.clone());
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.info(
"Search",
format!(
"Search completed in {:?}: found {} matches for '{}'",
elapsed,
matches.len(),
pattern
),
);
}
matches
}
pub fn get_current_match(&self) -> Option<(usize, usize)> {
let search = self.search.borrow();
if search.matches.is_empty() || !search.is_active {
return None;
}
let match_pos = search.matches[search.current_match];
Some((match_pos.0, match_pos.1))
}
pub fn filter(&self) -> std::cell::Ref<'_, FilterState> {
self.filter.borrow()
}
pub fn filter_mut(&self) -> std::cell::RefMut<'_, FilterState> {
self.filter.borrow_mut()
}
pub fn column_search(&self) -> std::cell::Ref<'_, ColumnSearchState> {
self.column_search.borrow()
}
pub fn column_search_mut(&self) -> std::cell::RefMut<'_, ColumnSearchState> {
self.column_search.borrow_mut()
}
pub fn start_column_search(&self, pattern: String) {
let mut column_search = self.column_search.borrow_mut();
let old_pattern = column_search.pattern.clone();
let old_active = column_search.is_active;
column_search.pattern = pattern.clone();
column_search.is_active = true;
column_search.last_search_time = Some(Instant::now());
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.info(
"ColumnSearch",
format!(
"Starting column search: '{pattern}' (was: '{old_pattern}', active: {old_active})"
),
);
}
}
pub fn update_column_search_matches(
&self,
columns: &[(String, usize)],
pattern: &str,
) -> Vec<(usize, String)> {
let pattern_lower = pattern.to_lowercase();
let mut matches = Vec::new();
for (name, index) in columns {
if name.to_lowercase().contains(&pattern_lower) {
matches.push((*index, name.clone()));
}
}
let mut column_search = self.column_search.borrow_mut();
column_search.set_matches(matches.clone());
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.info(
"ColumnSearch",
format!(
"Found {} columns matching '{}': {:?}",
matches.len(),
pattern,
matches.iter().map(|(_, name)| name).collect::<Vec<_>>()
),
);
}
matches
}
pub fn next_column_match(&self) -> Option<(usize, String)> {
let mut column_search = self.column_search.borrow_mut();
if let Some((idx, name)) = column_search.next_match() {
let current = column_search.current_match;
let total = column_search.matching_columns.len();
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.info(
"ColumnSearch",
format!(
"Navigate to next column: {}/{} - '{}' (index {})",
current + 1,
total,
name,
idx
),
);
}
Some((idx, name))
} else {
None
}
}
pub fn previous_column_match(&self) -> Option<(usize, String)> {
let mut column_search = self.column_search.borrow_mut();
if let Some((idx, name)) = column_search.prev_match() {
let current = column_search.current_match;
let total = column_search.matching_columns.len();
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.info(
"ColumnSearch",
format!(
"Navigate to previous column: {}/{} - '{}' (index {})",
current + 1,
total,
name,
idx
),
);
}
Some((idx, name))
} else {
None
}
}
pub fn clear_column_search(&self) {
let mut column_search = self.column_search.borrow_mut();
let had_matches = column_search.matching_columns.len();
let had_pattern = column_search.pattern.clone();
column_search.clear();
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.info(
"ColumnSearch",
format!(
"Cleared column search (had pattern: '{had_pattern}', {had_matches} matches)"
),
);
}
}
pub fn accept_column_match(&self) -> Option<(usize, String)> {
let column_search = self.column_search.borrow();
if let Some((idx, name)) = column_search.current_match() {
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.info(
"ColumnSearch",
format!("Accepted column: '{name}' at index {idx}"),
);
}
Some((idx, name))
} else {
None
}
}
pub fn sort_by_column(&self, column_index: usize, column_name: String, row_count: usize) {
let mut sort_state = self.sort.borrow_mut();
let new_order = sort_state.get_next_order(column_index);
let old_column = sort_state.column;
let old_order = sort_state.order;
if new_order == SortOrder::None {
sort_state.clear_sort();
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.info(
"Sort",
format!(
"Cleared sort on column {column_index} ({column_name}), returning to original order"
),
);
}
} else {
sort_state.set_sort(column_index, column_name.clone(), new_order, row_count);
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.info(
"Sort",
format!(
"Sorted column {} ({}) {}, {} rows (was: column {:?} {})",
column_index,
column_name,
match new_order {
SortOrder::Ascending => "ascending ↑",
SortOrder::Descending => "descending ↓",
SortOrder::None => "none",
},
row_count,
old_column,
match old_order {
SortOrder::Ascending => "↑",
SortOrder::Descending => "↓",
SortOrder::None => "-",
}
),
);
}
}
}
pub fn clear_sort(&self) {
let mut sort_state = self.sort.borrow_mut();
let had_sort = sort_state.column.is_some();
let old_column = sort_state.column;
let old_name = sort_state.column_name.clone();
sort_state.clear_sort();
if had_sort {
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.info(
"Sort",
format!(
"Cleared all sorting (was: column {:?} - {})",
old_column,
old_name.unwrap_or_else(|| "unknown".to_string())
),
);
}
}
}
pub fn sort(&self) -> std::cell::Ref<SortState> {
self.sort.borrow()
}
pub fn get_next_sort_order(&self, column_index: usize) -> SortOrder {
self.sort.borrow().get_next_order(column_index)
}
pub fn advance_sort_state(
&self,
column_index: usize,
column_name: Option<String>,
new_order: SortOrder,
) {
self.sort
.borrow_mut()
.advance_sort_state(column_index, column_name, new_order);
}
pub fn selection(&self) -> std::cell::Ref<SelectionState> {
self.selection.borrow()
}
pub fn selection_mut(&self) -> std::cell::RefMut<SelectionState> {
self.selection.borrow_mut()
}
pub fn selection_proxy(&self) -> SelectionProxy {
SelectionProxy::new(self.buffers.current())
}
pub fn selection_proxy_mut(&mut self) -> SelectionProxyMut {
SelectionProxyMut::new(self.buffers.current_mut())
}
pub fn set_selection_mode(&self, mode: SelectionMode) {
let mut selection = self.selection.borrow_mut();
let old_mode = selection.mode.clone();
selection.set_mode(mode.clone());
if old_mode != mode {
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.info(
"Selection",
format!("Mode changed: {old_mode:?} → {mode:?}"),
);
}
}
}
pub fn select_row(&self, row: Option<usize>) {
let mut selection = self.selection.borrow_mut();
let old_row = selection.selected_row;
selection.select_row(row);
if old_row != row {
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.info("Selection", format!("Row selection: {old_row:?} → {row:?}"));
}
}
}
pub fn select_column(&self, column: usize) {
let mut selection = self.selection.borrow_mut();
let old_column = selection.selected_column;
selection.select_column(column);
if old_column != column {
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.info(
"Selection",
format!("Column selection: {old_column} → {column}"),
);
}
}
}
pub fn select_cell(&self, row: usize, column: usize) {
self.selection.borrow_mut().select_cell(row, column);
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.info("Selection", format!("Cell selected: [{row}, {column}]"));
}
}
pub fn toggle_selection_mode(&self) {
let mut selection = self.selection.borrow_mut();
let new_mode = match selection.mode {
SelectionMode::Row => SelectionMode::Cell,
SelectionMode::Cell => SelectionMode::Column,
SelectionMode::Column => SelectionMode::Row,
};
let old_mode = selection.mode.clone();
selection.set_mode(new_mode.clone());
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.info(
"Selection",
format!("Mode toggled: {old_mode:?} → {new_mode:?}"),
);
}
}
pub fn clear_selections(&self) {
let mut selection = self.selection.borrow_mut();
let had_selections = !selection.selected_cells.is_empty();
selection.clear_selections();
if had_selections {
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.info("Selection", "Cleared all selections".to_string());
}
}
}
pub fn get_selection_mode(&self) -> SelectionMode {
self.selection.borrow().mode.clone()
}
pub fn get_selected_row(&self) -> Option<usize> {
self.selection.borrow().selected_row
}
pub fn get_selected_column(&self) -> usize {
self.selection.borrow().selected_column
}
pub fn get_current_position(&self) -> (usize, usize) {
let nav = self.navigation.borrow();
(nav.selected_row, nav.selected_column)
}
pub fn sync_selection_with_navigation(&self) {
let nav = self.navigation.borrow();
let mut selection = self.selection.borrow_mut();
selection.selected_row = Some(nav.selected_row);
selection.selected_column = nav.selected_column;
selection.last_selection_time = Some(Instant::now());
selection.total_selections += 1;
}
pub fn handle_yank_by_mode(&self) -> Option<String> {
let mode = self.get_selection_mode();
let (_row, _col) = self.get_current_position();
match mode {
SelectionMode::Cell => {
Some("yank_cell".to_string())
}
SelectionMode::Row => {
None }
SelectionMode::Column => {
Some("yank_column".to_string())
}
}
}
pub fn get_table_selected_row(&self) -> Option<usize> {
let nav = self.navigation.borrow();
if nav.total_rows > 0 {
Some(nav.selected_row)
} else {
tracing::debug!(
"get_table_selected_row returning None: total_rows={}, selected_row={}",
nav.total_rows,
nav.selected_row
);
None
}
}
pub fn set_table_selected_row(&self, row: Option<usize>) {
if let Some(row) = row {
let mut nav = self.navigation.borrow_mut();
if row < nav.total_rows {
let old_row = nav.selected_row;
let column = nav.selected_column;
nav.selected_row = row;
nav.add_to_history(row, column);
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.info(
"Navigation",
format!("Table row selected: {old_row} → {row}"),
);
}
}
}
self.sync_selection_with_navigation();
}
pub fn get_current_column(&self) -> usize {
self.navigation.borrow().selected_column
}
pub fn set_current_column(&self, column: usize) {
let mut nav = self.navigation.borrow_mut();
if column < nav.total_columns {
let old_col = nav.selected_column;
let row = nav.selected_row;
nav.selected_column = column;
nav.add_to_history(row, column);
nav.ensure_visible(row, column);
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.info(
"Navigation",
format!("Column selected: {old_col} → {column}"),
);
}
}
drop(nav); self.sync_selection_with_navigation();
}
pub fn completion(&self) -> std::cell::Ref<'_, CompletionState> {
self.completion.borrow()
}
pub fn completion_mut(&self) -> std::cell::RefMut<'_, CompletionState> {
self.completion.borrow_mut()
}
pub fn clear_completion(&self) {
let mut completion = self.completion.borrow_mut();
let had_suggestions = completion.suggestions.len();
completion.clear();
if had_suggestions > 0 {
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.info(
"Completion",
format!("Cleared {had_suggestions} suggestions"),
);
}
}
}
pub fn set_completion_suggestions(&self, suggestions: Vec<String>) {
let mut completion = self.completion.borrow_mut();
let count = suggestions.len();
completion.set_suggestions(suggestions);
if count > 0 {
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.info("Completion", format!("Set {count} completion suggestions"));
}
}
}
pub fn next_completion(&self) {
let mut completion = self.completion.borrow_mut();
if !completion.suggestions.is_empty() {
completion.next_suggestion();
if let Some(ref debug_service) = *self.debug_service.borrow() {
if let Some(current) = completion.current_suggestion() {
debug_service.info(
"Completion",
format!(
"Cycling to suggestion {}/{}: {}",
completion.current_index + 1,
completion.suggestions.len(),
current
),
);
}
}
}
}
pub fn get_current_completion(&self) -> Option<String> {
self.completion.borrow().current_suggestion().cloned()
}
pub fn is_completion_active(&self) -> bool {
self.completion.borrow().is_active
}
pub fn update_completion_context(&self, query: String, cursor_pos: usize) {
self.completion
.borrow_mut()
.update_context(query, cursor_pos);
}
pub fn is_same_completion_context(&self, query: &str, cursor_pos: usize) -> bool {
self.completion.borrow().is_same_context(query, cursor_pos)
}
pub fn start_history_search(&self, original_input: String) {
info!(
target: "history",
"Starting history search with original input: '{}'",
original_input
);
let mut history_search = self.history_search.borrow_mut();
history_search.query.clear();
history_search.matches.clear();
history_search.selected_index = 0;
history_search.is_active = true;
history_search.original_input = original_input.clone();
let history = self.command_history.borrow();
let all_entries = history.get_all();
info!(
target: "history",
"Loaded {} history entries for search",
all_entries.len()
);
if !all_entries.is_empty() {
let recent_count = std::cmp::min(5, all_entries.len());
info!(target: "history", "Most recent {} entries (newest first):", recent_count);
for (i, entry) in all_entries.iter().rev().take(recent_count).enumerate() {
info!(target: "history", " [{}] '{}'", i, entry.command);
}
}
history_search.matches = all_entries
.iter()
.rev() .cloned()
.map(|entry| crate::history::HistoryMatch {
entry,
indices: Vec::new(),
score: 0,
})
.collect();
eprintln!(
"[DEBUG] Created {} matches in history_search",
history_search.matches.len()
);
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.info(
"HistorySearch",
format!(
"Started history search with {} entries",
history_search.matches.len()
),
);
}
}
pub fn update_history_search(&self, query: String) {
let mut history_search = self.history_search.borrow_mut();
let old_query = history_search.query.clone();
history_search.query = query.clone();
if query.is_empty() {
let history = self.command_history.borrow();
let all_entries = history.get_all();
history_search.matches = all_entries
.iter()
.cloned()
.map(|entry| crate::history::HistoryMatch {
entry,
indices: Vec::new(),
score: 0,
})
.collect();
} else {
use fuzzy_matcher::skim::SkimMatcherV2;
use fuzzy_matcher::FuzzyMatcher;
let matcher = SkimMatcherV2::default();
let history = self.command_history.borrow();
let mut matches: Vec<crate::history::HistoryMatch> = history
.get_all()
.iter()
.cloned()
.filter_map(|entry| {
matcher
.fuzzy_indices(&entry.command, &query)
.map(|(score, indices)| crate::history::HistoryMatch {
entry,
score,
indices,
})
})
.collect();
matches.sort_by(|a, b| b.score.cmp(&a.score));
history_search.matches = matches;
}
if history_search.selected_index >= history_search.matches.len() {
history_search.selected_index = 0;
}
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.info(
"HistorySearch",
format!(
"Updated history search: '{}' -> '{}', {} matches",
old_query,
query,
history_search.matches.len()
),
);
}
}
pub fn update_history_search_with_schema(
&self,
query: String,
columns: &[String],
source: Option<&str>,
) {
let mut history_search = self.history_search.borrow_mut();
let old_query = history_search.query.clone();
let old_matches_count = history_search.matches.len();
history_search.query = query.clone();
history_search.matches = self
.command_history
.borrow()
.search_with_schema(&query, columns, source);
history_search.selected_index = 0;
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.info(
"HistorySearch",
format!(
"Updated history search with schema: '{}' -> '{}', matches: {} -> {}, columns: {}, source: {:?}",
old_query,
query,
old_matches_count,
history_search.matches.len(),
columns.len(),
source
),
);
}
}
pub fn history_search_add_char(&self, c: char) {
let mut history_search = self.history_search.borrow_mut();
let old_query = history_search.query.clone();
history_search.query.push(c);
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.info(
"HistorySearch",
format!(
"Added char '{}': '{}' -> '{}'",
c, old_query, history_search.query
),
);
}
}
pub fn history_search_backspace(&self) {
let mut history_search = self.history_search.borrow_mut();
let old_query = history_search.query.clone();
history_search.query.pop();
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.info(
"HistorySearch",
format!("Backspace: '{}' -> '{}'", old_query, history_search.query),
);
}
}
pub fn history_search_next(&self) {
let mut history_search = self.history_search.borrow_mut();
if !history_search.matches.is_empty() {
let old_index = history_search.selected_index;
history_search.selected_index =
(history_search.selected_index + 1) % history_search.matches.len();
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.info(
"HistorySearch",
format!(
"Navigate next: {} -> {}",
old_index, history_search.selected_index
),
);
}
}
}
pub fn history_search_previous(&self) {
let mut history_search = self.history_search.borrow_mut();
if !history_search.matches.is_empty() {
let old_index = history_search.selected_index;
history_search.selected_index = if history_search.selected_index == 0 {
history_search.matches.len() - 1
} else {
history_search.selected_index - 1
};
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.info(
"HistorySearch",
format!(
"Navigate previous: {} -> {}",
old_index, history_search.selected_index
),
);
}
}
}
pub fn get_selected_history_command(&self) -> Option<String> {
let history_search = self.history_search.borrow();
history_search
.matches
.get(history_search.selected_index)
.map(|m| m.entry.command.clone())
}
pub fn accept_history_search(&self) -> Option<String> {
let mut history_search = self.history_search.borrow_mut();
if history_search.is_active {
let command = history_search
.matches
.get(history_search.selected_index)
.map(|m| m.entry.command.clone());
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.info(
"HistorySearch",
format!("Accepted history command: {command:?}"),
);
}
history_search.clear();
command
} else {
None
}
}
pub fn cancel_history_search(&self) -> String {
let mut history_search = self.history_search.borrow_mut();
let original = history_search.original_input.clone();
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.info(
"HistorySearch",
format!("Cancelled history search, restoring: '{original}'"),
);
}
history_search.clear();
original
}
pub fn history_search(&self) -> std::cell::Ref<'_, HistorySearchState> {
self.history_search.borrow()
}
pub fn is_history_search_active(&self) -> bool {
self.history_search.borrow().is_active
}
pub fn navigate_to(&self, row: usize, col: usize) {
let mut navigation = self.navigation.borrow_mut();
let old_row = navigation.selected_row;
let old_col = navigation.selected_column;
navigation.selected_row = row.min(navigation.total_rows.saturating_sub(1));
navigation.selected_column = col.min(navigation.total_columns.saturating_sub(1));
let new_row = navigation.selected_row;
let new_col = navigation.selected_column;
navigation.add_to_history(new_row, new_col);
navigation.ensure_visible(new_row, new_col);
let scroll_offset = navigation.scroll_offset;
drop(navigation);
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.log(
"Navigation",
DebugLevel::Info,
format!(
"Navigate: ({old_row}, {old_col}) -> ({new_row}, {new_col}), scroll: {scroll_offset:?}"
),
Some("navigate_to".to_string()),
);
}
}
pub fn navigate_relative(&self, delta_row: i32, delta_col: i32) {
let navigation = self.navigation.borrow();
let current_row = navigation.selected_row;
let current_col = navigation.selected_column;
drop(navigation);
let new_row = if delta_row >= 0 {
current_row.saturating_add(delta_row as usize)
} else {
current_row.saturating_sub(delta_row.unsigned_abs() as usize)
};
let new_col = if delta_col >= 0 {
current_col.saturating_add(delta_col as usize)
} else {
current_col.saturating_sub(delta_col.unsigned_abs() as usize)
};
self.navigate_to(new_row, new_col);
}
pub fn navigate_to_row(&self, row: usize) {
let navigation = self.navigation.borrow();
let current_col = navigation.selected_column;
drop(navigation);
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.log(
"Navigation",
DebugLevel::Info,
format!("Jump to row: {row}"),
Some("navigate_to_row".to_string()),
);
}
self.navigate_to(row, current_col);
}
pub fn navigate_to_column(&self, col: usize) {
let navigation = self.navigation.borrow();
let current_row = navigation.selected_row;
drop(navigation);
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.log(
"Navigation",
DebugLevel::Info,
format!("Jump to column: {col}"),
Some("navigate_to_column".to_string()),
);
}
self.navigate_to(current_row, col);
}
pub fn update_data_size(&self, rows: usize, columns: usize) {
let mut navigation = self.navigation.borrow_mut();
let old_totals = (navigation.total_rows, navigation.total_columns);
navigation.update_totals(rows, columns);
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.log(
"Navigation",
DebugLevel::Info,
format!(
"Data size updated: {:?} -> ({}, {}), position: ({}, {})",
old_totals, rows, columns, navigation.selected_row, navigation.selected_column
),
Some("update_data_size".to_string()),
);
}
}
pub fn set_viewport_size(&self, rows: usize, columns: usize) {
let mut navigation = self.navigation.borrow_mut();
let old_viewport = (navigation.viewport_rows, navigation.viewport_columns);
let selected_row = navigation.selected_row;
let selected_column = navigation.selected_column;
navigation.set_viewport_size(rows, columns);
navigation.ensure_visible(selected_row, selected_column);
let scroll_offset = navigation.scroll_offset;
drop(navigation);
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.log(
"Navigation",
DebugLevel::Info,
format!(
"Viewport size updated: {old_viewport:?} -> ({rows}, {columns}), scroll adjusted: {scroll_offset:?}"
),
Some("set_viewport_size".to_string()),
);
}
}
pub fn toggle_viewport_lock(&self) {
let mut navigation = self.navigation.borrow_mut();
navigation.viewport_lock = !navigation.viewport_lock;
if navigation.viewport_lock {
navigation.viewport_lock_row = Some(navigation.selected_row);
} else {
navigation.viewport_lock_row = None;
}
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.log(
"Navigation",
DebugLevel::Info,
format!(
"Viewport lock: {} at row {:?}",
navigation.viewport_lock, navigation.viewport_lock_row
),
Some("toggle_viewport_lock".to_string()),
);
}
}
pub fn toggle_cursor_lock(&self) {
let mut navigation = self.navigation.borrow_mut();
navigation.cursor_lock = !navigation.cursor_lock;
if navigation.cursor_lock {
let visual_position = navigation
.selected_row
.saturating_sub(navigation.scroll_offset.0);
navigation.cursor_lock_position = Some(visual_position);
} else {
navigation.cursor_lock_position = None;
}
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.log(
"Navigation",
DebugLevel::Info,
format!(
"Cursor lock: {} at visual position {:?}",
navigation.cursor_lock, navigation.cursor_lock_position
),
Some("toggle_cursor_lock".to_string()),
);
}
}
pub fn is_cursor_locked(&self) -> bool {
self.navigation.borrow().cursor_lock
}
pub fn navigation(&self) -> std::cell::Ref<'_, NavigationState> {
self.navigation.borrow()
}
pub fn navigation_mut(&self) -> std::cell::RefMut<'_, NavigationState> {
self.navigation.borrow_mut()
}
pub fn navigation_proxy(&self) -> NavigationProxy {
NavigationProxy::new(self.buffers.current())
}
pub fn navigation_proxy_mut(&mut self) -> NavigationProxyMut {
NavigationProxyMut::new(self.buffers.current_mut())
}
pub fn get_scroll_offset(&self) -> (usize, usize) {
self.navigation.borrow().scroll_offset
}
pub fn is_viewport_locked(&self) -> bool {
self.navigation.borrow().viewport_lock
}
pub fn set_results(
&self,
results: QueryResponse,
execution_time: Duration,
from_cache: bool,
) -> Result<()> {
let query_text = results.query.select.join(", ");
let row_count = results.count;
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.log(
"ResultsState",
DebugLevel::Info,
format!(
"[RESULTS] Setting results: query='{}', rows={}, time={}ms, cached={}",
query_text.chars().take(50).collect::<String>(),
row_count,
execution_time.as_millis(),
from_cache
),
Some("set_results".to_string()),
);
}
self.results
.borrow_mut()
.set_results(results, execution_time, from_cache)?;
if let Some(ref debug_service) = *self.debug_service.borrow() {
let stats = self.results.borrow().get_performance_stats();
debug_service.log(
"ResultsState",
DebugLevel::Info,
format!(
"[RESULTS] Performance stats: total_queries={}, cache_hit_rate={:.2}%, avg_time={:.2}ms",
stats.total_queries,
stats.cache_hit_rate * 100.0,
stats.average_execution_time_ms
),
Some("performance_stats".to_string()),
);
}
Ok(())
}
pub fn get_results(&self) -> Option<QueryResponse> {
self.results.borrow().get_results().cloned()
}
pub fn cache_results(&self, query_key: String, results: QueryResponse) -> Result<()> {
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.log(
"ResultsCache",
DebugLevel::Info,
format!(
"[RESULTS] Caching results: key='{}', rows={}",
query_key.chars().take(30).collect::<String>(),
results.count
),
Some("cache_results".to_string()),
);
}
let result = self
.results
.borrow_mut()
.cache_results(query_key.clone(), results);
if let Some(ref debug_service) = *self.debug_service.borrow() {
let cache_stats = self.results.borrow().get_cache_stats();
debug_service.log(
"ResultsCache",
DebugLevel::Info,
format!(
"[RESULTS] Cache stats: entries={}, memory={}MB, hit_rate={:.2}%",
cache_stats.entry_count,
cache_stats.memory_usage / (1024 * 1024),
cache_stats.hit_rate * 100.0
),
Some("cache_stats".to_string()),
);
}
result
}
pub fn get_cached_results(&self, query_key: &str) -> Option<QueryResponse> {
if let Some(result) = self.results.borrow_mut().get_cached_results(query_key) {
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.log(
"ResultsCache",
DebugLevel::Trace,
format!(
"[RESULTS] Cache HIT for key: '{}'",
query_key.chars().take(30).collect::<String>()
),
Some("cache_hit".to_string()),
);
}
Some(result.clone())
} else {
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.log(
"ResultsCache",
DebugLevel::Trace,
format!(
"[RESULTS] Cache MISS for key: '{}'",
query_key.chars().take(30).collect::<String>()
),
Some("cache_miss".to_string()),
);
}
None
}
}
pub fn clear_results_cache(&self) {
let before_count = self.results.borrow().get_cache_stats().entry_count;
self.results.borrow_mut().clear_cache();
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.log(
"ResultsCache",
DebugLevel::Info,
format!("[RESULTS] Cache cleared: removed {before_count} entries"),
Some("clear_cache".to_string()),
);
}
}
pub fn clipboard(&self) -> std::cell::Ref<'_, ClipboardState> {
self.clipboard.borrow()
}
pub fn clipboard_mut(&self) -> std::cell::RefMut<'_, ClipboardState> {
self.clipboard.borrow_mut()
}
pub fn chord(&self) -> std::cell::Ref<'_, ChordState> {
self.chord.borrow()
}
pub fn chord_mut(&self) -> std::cell::RefMut<'_, ChordState> {
self.chord.borrow_mut()
}
pub fn undo_redo(&self) -> std::cell::Ref<'_, UndoRedoState> {
self.undo_redo.borrow()
}
pub fn undo_redo_mut(&self) -> std::cell::RefMut<'_, UndoRedoState> {
self.undo_redo.borrow_mut()
}
pub fn scroll(&self) -> std::cell::Ref<'_, ScrollState> {
self.scroll.borrow()
}
pub fn scroll_mut(&self) -> std::cell::RefMut<'_, ScrollState> {
self.scroll.borrow_mut()
}
pub fn yank_cell(
&self,
row: usize,
column: usize,
value: String,
preview: String,
) -> Result<()> {
let description = format!("cell at [{row}, {column}]");
let size_bytes = value.len();
trace!(
"yank_cell: Starting clipboard write for {} ({} bytes)",
description,
size_bytes
);
trace!(
"yank_cell: Value preview: '{}'",
if value.len() > 50 {
&value[..50]
} else {
&value
}
);
let mut system_clipboard = Clipboard::new()?;
trace!("yank_cell: Setting clipboard text ({} bytes)", value.len());
system_clipboard.set_text(&value)?;
let clipboard_content = system_clipboard.get_text().unwrap_or_default();
if clipboard_content != value {
return Err(anyhow!(
"Clipboard write verification failed. Expected {} chars, wrote {} chars",
value.len(),
clipboard_content.len()
));
}
let item = YankedItem {
description: description.clone(),
full_value: value.clone(),
preview: preview.clone(),
yank_type: YankType::Cell { row, column },
yanked_at: Local::now(),
size_bytes,
};
self.clipboard.borrow_mut().add_yank(item);
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.info(
"Clipboard",
format!(
"Yanked {}: '{}' ({} bytes)",
description,
if preview.len() > 50 {
format!("{}...", &preview[..50])
} else {
preview
},
size_bytes
),
);
}
Ok(())
}
pub fn yank_row(&self, row: usize, value: String, preview: String) -> Result<()> {
let description = format!("row {row}");
let size_bytes = value.len();
let mut system_clipboard = Clipboard::new()?;
system_clipboard.set_text(&value)?;
let clipboard_content = system_clipboard.get_text().unwrap_or_default();
if clipboard_content != value {
return Err(anyhow!(
"Clipboard write verification failed. Expected {} chars, wrote {} chars",
value.len(),
clipboard_content.len()
));
}
let item = YankedItem {
description: description.clone(),
full_value: value.clone(),
preview: preview.clone(),
yank_type: YankType::Row { row },
yanked_at: Local::now(),
size_bytes,
};
self.clipboard.borrow_mut().add_yank(item);
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.info(
"Clipboard",
format!(
"Yanked {}: {} columns ({} bytes)",
description,
value.split('\t').count(),
size_bytes
),
);
}
Ok(())
}
pub fn yank_column(
&self,
column_name: String,
column_index: usize,
value: String,
preview: String,
) -> Result<()> {
let description = format!("column '{column_name}'");
let size_bytes = value.len();
let row_count = value.lines().count();
let mut system_clipboard = Clipboard::new()?;
system_clipboard.set_text(&value)?;
let clipboard_content = system_clipboard.get_text().unwrap_or_default();
if clipboard_content != value {
return Err(anyhow!(
"Clipboard write verification failed. Expected {} chars, wrote {} chars",
value.len(),
clipboard_content.len()
));
}
let item = YankedItem {
description: description.clone(),
full_value: value.clone(),
preview: preview.clone(),
yank_type: YankType::Column {
name: column_name.clone(),
index: column_index,
},
yanked_at: Local::now(),
size_bytes,
};
self.clipboard.borrow_mut().add_yank(item);
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.info(
"Clipboard",
format!("Yanked {description}: {row_count} rows ({size_bytes} bytes)"),
);
}
Ok(())
}
pub fn yank_all(&self, value: String, preview: String) -> Result<()> {
let size_bytes = value.len();
let row_count = value.lines().count();
let mut system_clipboard = Clipboard::new()?;
system_clipboard.set_text(&value)?;
let clipboard_content = system_clipboard.get_text().unwrap_or_default();
if clipboard_content != value {
return Err(anyhow!(
"Clipboard write verification failed. Expected {} chars, wrote {} chars",
value.len(),
clipboard_content.len()
));
}
let item = YankedItem {
description: "all data".to_string(),
full_value: value.clone(),
preview: preview.clone(),
yank_type: YankType::All,
yanked_at: Local::now(),
size_bytes,
};
self.clipboard.borrow_mut().add_yank(item);
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.info(
"Clipboard",
format!("Yanked all data: {row_count} rows ({size_bytes} bytes)"),
);
}
Ok(())
}
pub fn yank_test_case(&self, value: String) -> Result<()> {
let size_bytes = value.len();
let line_count = value.lines().count();
let mut system_clipboard = Clipboard::new()?;
system_clipboard.set_text(&value)?;
let clipboard_content = system_clipboard.get_text().unwrap_or_default();
if clipboard_content != value {
return Err(anyhow!(
"Clipboard write verification failed. Expected {} chars, wrote {} chars",
value.len(),
clipboard_content.len()
));
}
let item = YankedItem {
description: "Test Case".to_string(),
full_value: value.clone(),
preview: format!("{line_count} lines of test case"),
yank_type: YankType::TestCase,
yanked_at: Local::now(),
size_bytes,
};
self.clipboard.borrow_mut().add_yank(item);
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.info(
"Clipboard",
format!("Yanked test case: {line_count} lines ({size_bytes} bytes)"),
);
}
Ok(())
}
pub fn yank_debug_context(&self, value: String) -> Result<()> {
let size_bytes = value.len();
let line_count = value.lines().count();
let mut system_clipboard = Clipboard::new()?;
system_clipboard.set_text(&value)?;
let clipboard_content = system_clipboard.get_text().unwrap_or_default();
if clipboard_content != value {
return Err(anyhow!(
"Clipboard write verification failed. Expected {} chars, wrote {} chars",
value.len(),
clipboard_content.len()
));
}
let item = YankedItem {
description: "Debug Context".to_string(),
full_value: value.clone(),
preview: "Query context with data for test creation".to_string(),
yank_type: YankType::DebugContext,
yanked_at: Local::now(),
size_bytes,
};
self.clipboard.borrow_mut().add_yank(item);
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.info(
"Clipboard",
format!("Yanked debug context: {line_count} lines ({size_bytes} bytes)"),
);
}
Ok(())
}
pub fn clear_clipboard(&self) {
let had_item = self.clipboard.borrow().last_yanked.is_some();
self.clipboard.borrow_mut().clear();
if had_item {
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.info("Clipboard", "Clipboard cleared".to_string());
}
}
}
pub fn get_clipboard_stats(&self) -> String {
self.clipboard.borrow().get_stats()
}
pub fn read_from_clipboard(&self) -> Result<String> {
let mut system_clipboard = Clipboard::new()?;
let text = system_clipboard.get_text()?;
Ok(text)
}
pub fn write_to_clipboard(&self, text: &str) -> Result<()> {
let mut system_clipboard = Clipboard::new()?;
system_clipboard.set_text(text)?;
let clipboard_content = system_clipboard.get_text().unwrap_or_default();
if clipboard_content != text {
return Err(anyhow!(
"Clipboard write verification failed. Expected {} chars, wrote {} chars",
text.len(),
clipboard_content.len()
));
}
Ok(())
}
pub fn get_results_stats(&self) -> (CacheStats, PerformanceStats) {
let results = self.results.borrow();
(results.get_cache_stats(), results.get_performance_stats())
}
pub fn is_results_from_cache(&self) -> bool {
self.results.borrow().from_cache
}
pub fn get_last_execution_time(&self) -> Duration {
self.results.borrow().last_execution_time
}
pub fn get_results_memory_usage(&self) -> (usize, usize) {
let cache_stats = self.results.borrow().get_cache_stats();
(cache_stats.memory_usage, cache_stats.memory_limit)
}
pub fn widgets(&self) -> &WidgetStates {
&self.widgets
}
pub fn widgets_mut(&mut self) -> &mut WidgetStates {
&mut self.widgets
}
pub fn cache_list(&self) -> &CacheListState {
&self.cache_list
}
pub fn cache_list_mut(&mut self) -> &mut CacheListState {
&mut self.cache_list
}
pub fn column_stats(&self) -> &ColumnStatsState {
&self.column_stats
}
pub fn column_stats_mut(&mut self) -> &mut ColumnStatsState {
&mut self.column_stats
}
pub fn jump_to_row(&self) -> &JumpToRowState {
&self.jump_to_row
}
pub fn jump_to_row_mut(&mut self) -> &mut JumpToRowState {
&mut self.jump_to_row
}
pub fn command_history(&self) -> std::cell::Ref<'_, CommandHistory> {
self.command_history.borrow()
}
pub fn command_history_mut(&self) -> std::cell::RefMut<'_, CommandHistory> {
self.command_history.borrow_mut()
}
pub fn results_cache(&self) -> &ResultsCache {
&self.results_cache
}
pub fn results_cache_mut(&mut self) -> &mut ResultsCache {
&mut self.results_cache
}
pub fn current_mode(&self) -> AppMode {
self.mode_stack.last().cloned().unwrap_or(AppMode::Command)
}
pub fn enter_mode(&mut self, mode: AppMode) -> Result<()> {
let current = self.current_mode();
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.info(
"AppStateContainer",
format!("MODE TRANSITION: {current:?} -> {mode:?}"),
);
}
match (current, mode.clone()) {
_ => {
}
}
self.mode_stack.push(mode);
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.info(
"AppStateContainer",
format!("Mode stack: {:?}", self.mode_stack),
);
}
Ok(())
}
pub fn exit_mode(&mut self) -> Result<AppMode> {
if self.mode_stack.len() > 1 {
let exited = self.mode_stack.pop().unwrap();
let new_mode = self.current_mode();
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.info(
"AppStateContainer",
format!("MODE EXIT: {exited:?} -> {new_mode:?}"),
);
debug_service.info(
"AppStateContainer",
format!("Mode stack after exit: {:?}", self.mode_stack),
);
}
Ok(new_mode)
} else {
Ok(self.current_mode())
}
}
pub fn toggle_debug(&mut self) {
self.debug_enabled = !self.debug_enabled;
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.info(
"AppStateContainer",
format!("Debug mode: {}", self.debug_enabled),
);
}
}
pub fn set_debug_service(&self, debug_service: crate::debug_service::DebugService) {
*self.debug_service.borrow_mut() = Some(debug_service);
if let Some(ref service) = *self.debug_service.borrow() {
service.info("AppStateContainer", "Debug service connected".to_string());
service.info(
"AppStateContainer",
"AppStateContainer constructed with debug logging".to_string(),
);
}
}
pub fn is_debug_enabled(&self) -> bool {
self.debug_enabled
}
pub fn toggle_help(&self) {
let mut help = self.help.borrow_mut();
let old_visible = help.is_visible;
help.toggle();
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.info(
"Help",
format!(
"Toggled help: {} -> {}, open_count: {}",
old_visible, help.is_visible, help.open_count
),
);
}
}
pub fn is_help_visible(&self) -> bool {
self.help.borrow().is_visible
}
pub fn set_help_visible(&self, visible: bool) {
let mut help = self.help.borrow_mut();
let old_visible = help.is_visible;
if visible {
help.show();
} else {
help.hide();
}
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.info(
"Help",
format!(
"Set help visibility: {} -> {}, scroll reset: {}",
old_visible, help.is_visible, visible
),
);
}
}
pub fn help_scroll_down(&self) {
let mut help = self.help.borrow_mut();
let old_scroll = help.scroll_offset;
help.scroll_down(1);
if let Some(ref debug_service) = *self.debug_service.borrow() {
if old_scroll != help.scroll_offset {
debug_service.info(
"Help",
format!("Scrolled down: {} -> {}", old_scroll, help.scroll_offset),
);
}
}
}
pub fn help_scroll_up(&self) {
let mut help = self.help.borrow_mut();
let old_scroll = help.scroll_offset;
help.scroll_up(1);
if let Some(ref debug_service) = *self.debug_service.borrow() {
if old_scroll != help.scroll_offset {
debug_service.info(
"Help",
format!("Scrolled up: {} -> {}", old_scroll, help.scroll_offset),
);
}
}
}
pub fn help_page_down(&self) {
let mut help = self.help.borrow_mut();
let old_scroll = help.scroll_offset;
help.scroll_down(10);
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.info(
"Help",
format!("Page down: {} -> {}", old_scroll, help.scroll_offset),
);
}
}
pub fn help_page_up(&self) {
let mut help = self.help.borrow_mut();
let old_scroll = help.scroll_offset;
help.scroll_up(10);
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.info(
"Help",
format!("Page up: {} -> {}", old_scroll, help.scroll_offset),
);
}
}
pub fn set_help_max_scroll(&self, content_lines: usize, viewport_height: usize) {
let mut help = self.help.borrow_mut();
let old_max = help.max_scroll;
help.set_max_scroll(content_lines, viewport_height);
if let Some(ref debug_service) = *self.debug_service.borrow() {
if old_max != help.max_scroll {
debug_service.info(
"Help",
format!(
"Updated max scroll: {} -> {} (content: {}, viewport: {})",
old_max, help.max_scroll, content_lines, viewport_height
),
);
}
}
}
pub fn help_scroll_offset(&self) -> u16 {
self.help.borrow().scroll_offset
}
pub fn help_state(&self) -> std::cell::Ref<'_, HelpState> {
self.help.borrow()
}
pub fn log_key_press(&self, key: KeyEvent, action: Option<String>) {
let mode = self.current_mode();
let entry = KeyPressEntry::new(key, mode.clone(), action.clone());
if let Some(ref debug_service) = *self.debug_service.borrow() {
let platform_info = if entry.platform == Platform::Windows
&& (key.code == KeyCode::Char('$') || key.code == KeyCode::Char('^'))
&& key.modifiers.contains(KeyModifiers::SHIFT)
{
" [Windows: SHIFT modifier present]"
} else {
""
};
debug_service.info(
"KeyPress",
format!(
"Key: {:?}, Mode: {:?}, Action: {:?}, Platform: {:?}{}",
key, mode, action, entry.platform, platform_info
),
);
}
self.key_press_history.borrow_mut().add(entry);
}
pub fn clear_key_history(&self) {
self.key_press_history.borrow_mut().clear();
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.info("AppStateContainer", "Key press history cleared".to_string());
}
}
pub fn normalize_key(&self, key: KeyEvent) -> KeyEvent {
let platform = Platform::detect();
match platform {
Platform::Windows => {
match key.code {
KeyCode::Char(
'$' | '^' | ':' | '!' | '@' | '#' | '%' | '&' | '*' | '(' | ')',
) => {
let mut normalized_modifiers = key.modifiers;
normalized_modifiers.remove(KeyModifiers::SHIFT);
if let Some(ref debug_service) = *self.debug_service.borrow() {
if normalized_modifiers != key.modifiers {
debug_service.info(
"KeyNormalize",
format!(
"Windows key normalization: {:?} with {:?} -> {:?}",
key.code, key.modifiers, normalized_modifiers
),
);
}
}
KeyEvent::new(key.code, normalized_modifiers)
}
KeyCode::Left if key.modifiers.contains(KeyModifiers::SHIFT) => {
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.info(
"KeyNormalize",
"Windows: Shift+Left -> '<' character for column movement"
.to_string(),
);
}
KeyEvent::new(KeyCode::Char('<'), KeyModifiers::NONE)
}
KeyCode::Right if key.modifiers.contains(KeyModifiers::SHIFT) => {
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.info(
"KeyNormalize",
"Windows: Shift+Right -> '>' character for column movement"
.to_string(),
);
}
KeyEvent::new(KeyCode::Char('>'), KeyModifiers::NONE)
}
KeyCode::Char('<') if key.modifiers.contains(KeyModifiers::SHIFT) => {
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.info(
"KeyNormalize",
"Windows: Shift+'<' -> '<' character for column movement"
.to_string(),
);
}
KeyEvent::new(KeyCode::Char('<'), KeyModifiers::NONE)
}
KeyCode::Char('>') if key.modifiers.contains(KeyModifiers::SHIFT) => {
if let Some(ref debug_service) = *self.debug_service.borrow() {
debug_service.info(
"KeyNormalize",
"Windows: Shift+'>' -> '>' character for column movement"
.to_string(),
);
}
KeyEvent::new(KeyCode::Char('>'), KeyModifiers::NONE)
}
_ => key,
}
}
Platform::Linux | Platform::MacOS => {
key
}
Platform::Unknown => key,
}
}
pub fn debug_dump(&self) -> String {
let mut dump = String::new();
dump.push_str("=== APP STATE CONTAINER DEBUG DUMP ===\n\n");
dump.push_str("MODE INFORMATION:\n");
dump.push_str(&format!(" Current Mode: {:?}\n", self.current_mode()));
dump.push_str(&format!(" Mode Stack: {:?}\n", self.mode_stack));
dump.push('\n');
dump.push_str("UI FLAGS:\n");
dump.push_str(&format!(" Debug Enabled: {}\n", self.debug_enabled));
dump.push('\n');
let help = self.help.borrow();
dump.push_str("HELP STATE:\n");
dump.push_str(&format!(" Visible: {}\n", help.is_visible));
dump.push_str(&format!(" Scroll Offset: {}\n", help.scroll_offset));
dump.push_str(&format!(" Max Scroll: {}\n", help.max_scroll));
dump.push_str(&format!(" Open Count: {}\n", help.open_count));
if let Some(ref last_opened) = help.last_opened {
dump.push_str(&format!(" Last Opened: {:?} ago\n", last_opened.elapsed()));
}
dump.push('\n');
dump.push_str("INPUT STATE:\n");
let input = self.command_input.borrow();
dump.push_str(&format!(" Text: '{}'\n", input.text));
dump.push_str(&format!(" Cursor: {}\n", input.cursor_position));
dump.push_str(&format!(
" Last Query: '{}'\n",
if input.last_executed_query.len() > 100 {
format!("{}...", &input.last_executed_query[..100])
} else {
input.last_executed_query.clone()
}
));
dump.push('\n');
dump.push_str("SEARCH STATE:\n");
let search = self.search.borrow();
if search.is_active {
dump.push_str(&format!(" Pattern: '{}'\n", search.pattern));
dump.push_str(&format!(" Matches: {} found\n", search.matches.len()));
dump.push_str(&format!(
" Current: {} of {}\n",
if search.matches.is_empty() {
0
} else {
search.current_match + 1
},
search.matches.len()
));
if let Some(ref last_time) = search.last_search_time {
dump.push_str(&format!(" Search time: {:?}\n", last_time.elapsed()));
}
} else {
dump.push_str(" [Inactive]\n");
}
if !search.history.is_empty() {
dump.push_str(" Recent searches:\n");
for (i, entry) in search.history.iter().rev().take(5).enumerate() {
dump.push_str(&format!(
" {}. '{}' → {} matches",
i + 1,
if entry.pattern.len() > 30 {
format!("{}...", &entry.pattern[..30])
} else {
entry.pattern.clone()
},
entry.match_count
));
if let Some(duration) = entry.duration_ms {
dump.push_str(&format!(" ({duration}ms)"));
}
dump.push_str(&format!(" at {}\n", entry.timestamp.format("%H:%M:%S")));
}
}
dump.push('\n');
dump.push_str("FILTER STATE:\n");
let filter = self.filter.borrow();
if filter.is_active {
dump.push_str(&format!(" Pattern: '{}'\n", filter.pattern));
dump.push_str(&format!(
" Filtered Rows: {}\n",
filter.filtered_indices.len()
));
dump.push_str(&format!(
" Case Insensitive: {}\n",
filter.case_insensitive
));
if let Some(ref last_time) = filter.last_filter_time {
dump.push_str(&format!(" Last Filter: {:?} ago\n", last_time.elapsed()));
}
} else {
dump.push_str(" [Inactive]\n");
}
dump.push_str(&format!(" Total Filters: {}\n", filter.total_filters));
dump.push_str(&format!(" History Items: {}\n", filter.history.len()));
if !filter.history.is_empty() {
dump.push_str(" Recent filters:\n");
for (i, entry) in filter.history.iter().take(5).enumerate() {
dump.push_str(&format!(
" {}. '{}' ({} matches) at {}\n",
i + 1,
if entry.pattern.len() > 30 {
format!("{}...", &entry.pattern[..30])
} else {
entry.pattern.clone()
},
entry.match_count,
entry.timestamp.format("%H:%M:%S")
));
}
}
dump.push('\n');
let column_search = self.column_search.borrow();
if column_search.is_active {
dump.push_str("COLUMN SEARCH STATE (ACTIVE):\n");
dump.push_str(&format!(" Pattern: '{}'\n", column_search.pattern));
dump.push_str(&format!(
" Matching Columns: {}\n",
column_search.matching_columns.len()
));
if !column_search.matching_columns.is_empty() {
for (i, (idx, name)) in column_search.matching_columns.iter().take(5).enumerate() {
dump.push_str(&format!(
" [{}] {}: '{}'\n",
if i == column_search.current_match {
"*"
} else {
" "
},
idx,
name
));
}
}
dump.push('\n');
}
let history_search = self.history_search.borrow();
if history_search.is_active {
dump.push_str("HISTORY SEARCH STATE (ACTIVE):\n");
dump.push_str(&format!(" Query: '{}'\n", history_search.query));
dump.push_str(&format!(" Matches: {}\n", history_search.matches.len()));
dump.push_str(&format!(" Selected: {}\n", history_search.selected_index));
dump.push_str(&format!(
" Original Input: '{}'\n",
history_search.original_input
));
if !history_search.matches.is_empty() {
dump.push_str(" Top matches:\n");
for (i, m) in history_search.matches.iter().take(5).enumerate() {
dump.push_str(&format!(
" [{}] Score: {}, '{}'\n",
if i == history_search.selected_index {
"*"
} else {
" "
},
m.score,
if m.entry.command.len() > 50 {
format!("{}...", &m.entry.command[..50])
} else {
m.entry.command.clone()
}
));
}
}
dump.push('\n');
}
let navigation = self.navigation.borrow();
dump.push_str("NAVIGATION STATE:\n");
dump.push_str(&format!(
" Cursor Position: row={}, col={}\n",
navigation.selected_row, navigation.selected_column
));
dump.push_str(&format!(
" Scroll Offset: row={}, col={}\n",
navigation.scroll_offset.0, navigation.scroll_offset.1
));
dump.push_str(&format!(
" Viewport Dimensions: {} rows x {} cols\n",
navigation.viewport_rows, navigation.viewport_columns
));
dump.push_str(&format!(
" Data Size: {} rows x {} cols\n",
navigation.total_rows, navigation.total_columns
));
dump.push_str("\nVIEWPORT BOUNDARIES:\n");
let at_top = navigation.selected_row == 0;
let at_bottom = navigation.selected_row == navigation.total_rows.saturating_sub(1);
let at_left = navigation.selected_column == 0;
let at_right = navigation.selected_column == navigation.total_columns.saturating_sub(1);
dump.push_str(&format!(" At Top Edge: {at_top}\n"));
dump.push_str(&format!(" At Bottom Edge: {at_bottom}\n"));
dump.push_str(&format!(" At Left Edge: {at_left}\n"));
dump.push_str(&format!(" At Right Edge: {at_right}\n"));
let viewport_bottom = navigation.scroll_offset.0 + navigation.viewport_rows;
let viewport_right = navigation.scroll_offset.1 + navigation.viewport_columns;
let should_scroll_down = navigation.selected_row >= viewport_bottom.saturating_sub(1);
let should_scroll_up = navigation.selected_row < navigation.scroll_offset.0;
let should_scroll_right = navigation.selected_column >= viewport_right.saturating_sub(1);
let should_scroll_left = navigation.selected_column < navigation.scroll_offset.1;
dump.push_str("\nSCROLLING STATE:\n");
dump.push_str(&format!(
" Visible Row Range: {} to {}\n",
navigation.scroll_offset.0,
viewport_bottom.min(navigation.total_rows).saturating_sub(1)
));
dump.push_str(&format!(
" Visible Col Range: {} to {}\n",
navigation.scroll_offset.1,
viewport_right
.min(navigation.total_columns)
.saturating_sub(1)
));
dump.push_str(&format!(
" Should Scroll Down: {} (cursor at {}, viewport bottom at {})\n",
should_scroll_down,
navigation.selected_row,
viewport_bottom.saturating_sub(1)
));
dump.push_str(&format!(
" Should Scroll Up: {} (cursor at {}, viewport top at {})\n",
should_scroll_up, navigation.selected_row, navigation.scroll_offset.0
));
dump.push_str(&format!(" Should Scroll Right: {should_scroll_right}\n"));
dump.push_str(&format!(" Should Scroll Left: {should_scroll_left}\n"));
dump.push_str(&format!(
"\n Viewport Lock: {} at row {:?}\n",
navigation.viewport_lock, navigation.viewport_lock_row
));
dump.push_str(&format!(
" Cursor Lock: {} at visual position {:?}\n",
navigation.cursor_lock, navigation.cursor_lock_position
));
if !navigation.selection_history.is_empty() {
dump.push_str("\n Recent positions:\n");
for (i, &(row, col)) in navigation
.selection_history
.iter()
.rev()
.take(5)
.enumerate()
{
dump.push_str(&format!(" {}. ({}, {})\n", i + 1, row, col));
}
}
dump.push('\n');
dump.push_str("COLUMN SEARCH STATE:\n");
let column_search = self.column_search.borrow();
if column_search.is_active {
dump.push_str(&format!(" Pattern: '{}'\n", column_search.pattern));
dump.push_str(&format!(
" Matches: {} columns found\n",
column_search.matching_columns.len()
));
if !column_search.matching_columns.is_empty() {
dump.push_str(&format!(
" Current: {} of {}\n",
column_search.current_match + 1,
column_search.matching_columns.len()
));
dump.push_str(" Matching columns:\n");
for (i, (idx, name)) in column_search.matching_columns.iter().enumerate() {
dump.push_str(&format!(
" {}[{}] {} (index {})\n",
if i == column_search.current_match {
"*"
} else {
" "
},
i + 1,
name,
idx
));
}
}
if let Some(ref last_time) = column_search.last_search_time {
dump.push_str(&format!(" Search time: {:?}\n", last_time.elapsed()));
}
} else {
dump.push_str(" [Inactive]\n");
}
dump.push_str(&format!(
" Total searches: {}\n",
column_search.total_searches
));
dump.push_str(&format!(
" History items: {}\n",
column_search.history.len()
));
if !column_search.history.is_empty() {
dump.push_str(" Recent searches:\n");
for (i, entry) in column_search.history.iter().take(5).enumerate() {
dump.push_str(&format!(
" {}. '{}' ({} matches) at {}\n",
i + 1,
entry.pattern,
entry.match_count,
entry.timestamp.format("%H:%M:%S")
));
}
}
dump.push('\n');
dump.push_str("SORT STATE:\n");
let sort = self.sort.borrow();
if let (Some(col), Some(name)) = (sort.column, &sort.column_name) {
dump.push_str(&format!(
" Current: Column {} ({}) {}\n",
col,
name,
match sort.order {
SortOrder::Ascending => "Ascending ↑",
SortOrder::Descending => "Descending ↓",
SortOrder::None => "None",
}
));
} else {
dump.push_str(" Current: No sorting applied\n");
}
if let Some(ref last_time) = sort.last_sort_time {
dump.push_str(&format!(" Last sort: {:?} ago\n", last_time.elapsed()));
}
dump.push_str(&format!(" Total sorts: {}\n", sort.total_sorts));
dump.push_str(&format!(" History items: {}\n", sort.history.len()));
if !sort.history.is_empty() {
dump.push_str(" Recent sorts:\n");
for (i, entry) in sort.history.iter().rev().take(5).enumerate() {
dump.push_str(&format!(
" {}. Column {} ({}) {} - {} rows\n",
i + 1,
entry.column_index,
entry.column_name,
match entry.order {
SortOrder::Ascending => "↑",
SortOrder::Descending => "↓",
SortOrder::None => "-",
},
entry.row_count
));
}
}
dump.push('\n');
dump.push_str("SELECTION STATE:\n");
let selection = self.selection.borrow();
dump.push_str(&format!(" Mode: {:?}\n", selection.mode));
if let Some(row) = selection.selected_row {
dump.push_str(&format!(" Selected Row: {row}\n"));
} else {
dump.push_str(" Selected Row: None\n");
}
dump.push_str(&format!(
" Selected Column: {}\n",
selection.selected_column
));
if !selection.selected_cells.is_empty() {
dump.push_str(&format!(
" Selected Cells: {} cells\n",
selection.selected_cells.len()
));
if selection.selected_cells.len() <= 5 {
for (row, col) in &selection.selected_cells {
dump.push_str(&format!(" - ({row}, {col})\n"));
}
} else {
for (row, col) in selection.selected_cells.iter().take(3) {
dump.push_str(&format!(" - ({row}, {col})\n"));
}
dump.push_str(&format!(
" ... and {} more\n",
selection.selected_cells.len() - 3
));
}
}
if let Some((row, col)) = selection.selection_anchor {
dump.push_str(&format!(" Selection Anchor: ({row}, {col})\n"));
}
dump.push_str(&format!(
" Total Selections: {}\n",
selection.total_selections
));
if let Some(ref last_time) = selection.last_selection_time {
dump.push_str(&format!(
" Last Selection: {:?} ago\n",
last_time.elapsed()
));
}
dump.push_str(&format!(" History Items: {}\n", selection.history.len()));
if !selection.history.is_empty() {
dump.push_str(" Recent selections:\n");
for (i, entry) in selection.history.iter().rev().take(5).enumerate() {
dump.push_str(&format!(
" {}. {:?} mode at {}\n",
i + 1,
entry.mode,
entry.timestamp.format("%H:%M:%S")
));
}
}
dump.push('\n');
dump.push_str("CLIPBOARD STATE:\n");
let clipboard = self.clipboard.borrow();
if let Some(ref yanked) = clipboard.last_yanked {
dump.push_str(&format!(" Last Yanked: {}\n", yanked.description));
dump.push_str(&format!(" Type: {:?}\n", yanked.yank_type));
dump.push_str(&format!(" Size: {} bytes\n", yanked.size_bytes));
dump.push_str(&format!(
" Preview: {}\n",
if yanked.preview.len() > 60 {
format!("{}...", &yanked.preview[..60])
} else {
yanked.preview.clone()
}
));
dump.push_str(&format!(
" Yanked at: {}\n",
yanked.yanked_at.format("%H:%M:%S")
));
} else {
dump.push_str(" [Empty]\n");
}
dump.push_str(&format!(" Total yanks: {}\n", clipboard.total_yanks));
dump.push_str(&format!(
" History items: {}\n",
clipboard.yank_history.len()
));
if !clipboard.yank_history.is_empty() {
dump.push_str(" Recent yanks:\n");
for (i, item) in clipboard.yank_history.iter().take(5).enumerate() {
dump.push_str(&format!(
" {}. {} ({} bytes) at {}\n",
i + 1,
item.description,
item.size_bytes,
item.yanked_at.format("%H:%M:%S")
));
}
}
dump.push('\n');
dump.push_str("CHORD STATE:\n");
let chord = self.chord.borrow();
if chord.current_chord.is_empty() {
dump.push_str(" No active chord\n");
} else {
dump.push_str(&format!(" Active chord: '{}'\n", chord.get_chord_string()));
if let Some(ref start) = chord.chord_start {
if let Ok(elapsed) = start.elapsed() {
dump.push_str(&format!(" Time elapsed: {:.1}s\n", elapsed.as_secs_f32()));
}
}
if let Some(ref desc) = chord.description {
dump.push_str(&format!(" Description: {desc}\n"));
}
}
dump.push_str("\nREGISTERED CHORDS:\n");
let mut chords: Vec<_> = chord.registered_chords.iter().collect();
chords.sort_by_key(|(k, _)| k.as_str());
for (chord_seq, action) in chords {
dump.push_str(&format!(" {chord_seq} → {action}\n"));
}
if !chord.history.is_empty() {
dump.push_str("\nCHORD HISTORY:\n");
for (i, (chord_str, action, timestamp)) in
chord.history.iter().rev().take(5).enumerate()
{
if let Ok(elapsed) = timestamp.elapsed() {
dump.push_str(&format!(
" {}. {} → {} ({:.1}s ago)\n",
i + 1,
chord_str,
action,
elapsed.as_secs_f32()
));
}
}
}
dump.push('\n');
dump.push_str("UNDO/REDO STATE:\n");
let undo_redo = self.undo_redo.borrow();
dump.push_str(&format!(
" Undo stack: {} entries\n",
undo_redo.undo_stack.len()
));
dump.push_str(&format!(
" Redo stack: {} entries\n",
undo_redo.redo_stack.len()
));
if !undo_redo.undo_stack.is_empty() {
dump.push_str(" Recent undo entries:\n");
for (i, (text, cursor)) in undo_redo.undo_stack.iter().rev().take(3).enumerate() {
let preview = if text.len() > 50 {
format!("{}...", &text[..50])
} else {
text.clone()
};
dump.push_str(&format!(
" {}. '{}' (cursor: {})\n",
i + 1,
preview,
cursor
));
}
}
dump.push('\n');
dump.push_str("SCROLL STATE:\n");
let scroll = self.scroll.borrow();
dump.push_str(&format!(" Help scroll: {}\n", scroll.help_scroll));
dump.push_str(&format!(" Input scroll: {}\n", scroll.input_scroll_offset));
dump.push_str(&format!(
" Viewport scroll: ({}, {})\n",
scroll.viewport_scroll_offset.0, scroll.viewport_scroll_offset.1
));
dump.push_str(&format!(
" Last visible rows: {}\n",
scroll.last_visible_rows
));
dump.push('\n');
dump.push_str(&self.widgets.search_modes.debug_info());
dump.push('\n');
if let Some(ref history) = self.widgets.history {
dump.push_str(&history.debug_info());
dump.push('\n');
}
dump.push_str(&self.widgets.help.debug_info());
dump.push('\n');
dump.push_str(&self.widgets.stats.debug_info());
dump.push('\n');
dump.push_str("BUFFER STATE:\n");
dump.push_str(&format!(
" Current Buffer ID: {}\n",
self.current_buffer_id
));
if let Some(_buffer) = self.current_buffer() {
dump.push_str(" Buffer: Present\n");
} else {
dump.push_str(" Buffer: None\n");
}
dump.push('\n');
dump.push_str("CACHE STATE:\n");
dump.push_str(&format!(
" Cached Results: {}\n",
self.results_cache.cache.len()
));
dump.push_str(&format!(
" Max Cache Size: {}\n",
self.results_cache.max_size
));
dump.push('\n');
dump.push_str("HISTORY STATE:\n");
dump.push_str(&format!(
" Total Commands: {}\n",
self.command_history.borrow().get_all().len()
));
dump.push('\n');
dump.push_str(&self.key_press_history.borrow().format_history());
dump.push('\n');
dump.push_str("PLATFORM INFO:\n");
dump.push_str(&format!(" Platform: {:?}\n", Platform::detect()));
dump.push_str(" Key Normalization: ");
if Platform::detect() == Platform::Windows {
dump.push_str("ACTIVE (Windows special chars)\n");
} else {
dump.push_str("INACTIVE\n");
}
dump.push('\n');
dump.push_str("=== END DEBUG DUMP ===\n");
dump
}
pub fn pretty_print(&self) -> String {
format!("{self:#?}")
}
pub fn delegated_selected_row(&self) -> Option<usize> {
self.current_buffer()?.get_selected_row()
}
pub fn set_delegated_selected_row(&mut self, row: Option<usize>) {
if let Some(buffer) = self.current_buffer_mut() {
buffer.set_selected_row(row);
}
}
pub fn delegated_current_column(&self) -> usize {
self.current_buffer()
.map_or(0, super::buffer::BufferAPI::get_current_column)
}
pub fn set_delegated_current_column(&mut self, col: usize) {
if let Some(buffer) = self.current_buffer_mut() {
buffer.set_current_column(col);
}
}
pub fn delegated_scroll_offset(&self) -> (usize, usize) {
self.current_buffer()
.map_or((0, 0), super::buffer::BufferAPI::get_scroll_offset)
}
pub fn set_delegated_scroll_offset(&mut self, offset: (usize, usize)) {
if let Some(buffer) = self.current_buffer_mut() {
buffer.set_scroll_offset(offset);
}
}
pub fn delegated_search_pattern(&self) -> String {
self.current_buffer()
.map(super::buffer::BufferAPI::get_search_pattern)
.unwrap_or_default()
}
pub fn set_delegated_search_pattern(&mut self, pattern: String) {
if let Some(buffer) = self.current_buffer_mut() {
buffer.set_search_pattern(pattern);
}
}
pub fn delegated_search_matches(&self) -> Vec<(usize, usize)> {
self.current_buffer()
.map(super::buffer::BufferAPI::get_search_matches)
.unwrap_or_default()
}
pub fn set_delegated_search_matches(&mut self, matches: Vec<(usize, usize)>) {
if let Some(buffer) = self.current_buffer_mut() {
buffer.set_search_matches(matches);
}
}
pub fn delegated_filter_pattern(&self) -> String {
self.current_buffer()
.map(super::buffer::BufferAPI::get_filter_pattern)
.unwrap_or_default()
}
pub fn set_delegated_filter_pattern(&mut self, pattern: String) {
if let Some(buffer) = self.current_buffer_mut() {
buffer.set_filter_pattern(pattern);
}
}
pub fn delegated_filter_active(&self) -> bool {
self.current_buffer()
.is_some_and(super::buffer::BufferAPI::is_filter_active)
}
pub fn set_delegated_filter_active(&mut self, active: bool) {
if let Some(buffer) = self.current_buffer_mut() {
buffer.set_filter_active(active);
}
}
pub fn delegated_sort_column(&self) -> Option<usize> {
self.current_buffer()?.get_sort_column()
}
pub fn set_delegated_sort_column(&mut self, column: Option<usize>) {
if let Some(buffer) = self.current_buffer_mut() {
buffer.set_sort_column(column);
}
}
pub fn delegated_sort_order(&self) -> SortOrder {
self.current_buffer()
.map_or(SortOrder::None, super::buffer::BufferAPI::get_sort_order)
}
pub fn set_delegated_sort_order(&mut self, order: SortOrder) {
if let Some(buffer) = self.current_buffer_mut() {
buffer.set_sort_order(order);
}
}
pub fn set_mode(&mut self, mode: AppMode) {
if let Some(buffer) = self.current_buffer_mut() {
buffer.set_mode(mode);
}
}
pub fn get_mode(&self) -> AppMode {
self.current_buffer()
.map_or(AppMode::Command, super::buffer::BufferAPI::get_mode)
}
pub fn set_status_message(&mut self, message: String) {
if let Some(buffer) = self.current_buffer_mut() {
buffer.set_status_message(message);
}
}
pub fn get_status_message(&self) -> String {
self.current_buffer()
.map(super::buffer::BufferAPI::get_status_message)
.unwrap_or_default()
}
pub fn set_dataview(&mut self, dataview: Option<crate::data::data_view::DataView>) {
if let Some(buffer) = self.current_buffer_mut() {
buffer.set_dataview(dataview);
}
}
pub fn get_dataview(&self) -> Option<&crate::data::data_view::DataView> {
self.current_buffer()?.dataview.as_ref()
}
pub fn set_last_results_row(&mut self, row: Option<usize>) {
if let Some(buffer) = self.current_buffer_mut() {
buffer.set_last_results_row(row);
}
}
pub fn set_last_scroll_offset(&mut self, offset: (usize, usize)) {
if let Some(buffer) = self.current_buffer_mut() {
buffer.set_last_scroll_offset(offset);
}
}
pub fn get_input_text(&self) -> String {
self.current_buffer()
.map(super::buffer::BufferAPI::get_input_text)
.unwrap_or_default()
}
pub fn get_input_cursor_position(&self) -> usize {
self.current_buffer()
.map_or(0, super::buffer::BufferAPI::get_input_cursor_position)
}
pub fn get_last_query(&self) -> String {
self.current_buffer()
.map(super::buffer::BufferAPI::get_last_query)
.unwrap_or_default()
}
pub fn is_buffer_modified(&self) -> bool {
self.current_buffer()
.is_some_and(super::buffer::BufferAPI::is_modified)
}
pub fn set_buffer_modified(&mut self, modified: bool) {
if let Some(buffer) = self.current_buffer_mut() {
buffer.set_modified(modified);
}
}
pub fn get_buffer_dataview(&self) -> Option<&crate::data::data_view::DataView> {
self.current_buffer()?.dataview.as_ref()
}
pub fn get_buffer_dataview_mut(&mut self) -> Option<&mut crate::data::data_view::DataView> {
self.current_buffer_mut()?.dataview.as_mut()
}
pub fn get_original_source(&self) -> Option<&crate::data::datatable::DataTable> {
self.current_buffer()?.get_original_source()
}
pub fn has_dataview(&self) -> bool {
self.current_buffer()
.is_some_and(super::buffer::BufferAPI::has_dataview)
}
pub fn is_case_insensitive(&self) -> bool {
self.current_buffer()
.is_some_and(super::buffer::BufferAPI::is_case_insensitive)
}
pub fn get_edit_mode(&self) -> Option<crate::buffer::EditMode> {
self.current_buffer()
.map(super::buffer::BufferAPI::get_edit_mode)
}
pub fn is_show_row_numbers(&self) -> bool {
self.current_buffer()
.is_some_and(super::buffer::BufferAPI::is_show_row_numbers)
}
pub fn is_compact_mode(&self) -> bool {
self.current_buffer()
.is_some_and(super::buffer::BufferAPI::is_compact_mode)
}
pub fn set_input_cursor_position(&mut self, pos: usize) {
if let Some(buffer) = self.current_buffer_mut() {
buffer.set_input_cursor_position(pos);
}
self.command_input.borrow_mut().cursor_position = pos;
}
pub fn set_search_pattern(&mut self, pattern: String) {
if let Some(buffer) = self.current_buffer_mut() {
buffer.set_search_pattern(pattern);
}
}
pub fn set_filter_pattern(&mut self, pattern: String) {
if let Some(buffer) = self.current_buffer_mut() {
buffer.set_filter_pattern(pattern);
}
}
pub fn set_fuzzy_filter_pattern(&mut self, pattern: String) {
if let Some(buffer) = self.current_buffer_mut() {
buffer.set_fuzzy_filter_pattern(pattern);
}
}
pub fn set_fuzzy_filter_active(&mut self, active: bool) {
if let Some(buffer) = self.current_buffer_mut() {
buffer.set_fuzzy_filter_active(active);
}
}
pub fn is_fuzzy_filter_active(&self) -> bool {
self.current_buffer()
.is_some_and(super::buffer::BufferAPI::is_fuzzy_filter_active)
}
pub fn set_fuzzy_filter_indices(&mut self, indices: Vec<usize>) {
if let Some(buffer) = self.current_buffer_mut() {
buffer.set_fuzzy_filter_indices(indices);
}
}
pub fn is_kill_ring_empty(&self) -> bool {
self.current_buffer()
.is_none_or(super::buffer::BufferAPI::is_kill_ring_empty)
}
pub fn set_selected_row(&mut self, row: Option<usize>) {
if let Some(buffer) = self.current_buffer_mut() {
buffer.set_selected_row(row);
}
}
pub fn set_buffer_input_text(&mut self, text: String) {
if let Some(buffer) = self.current_buffer_mut() {
buffer.set_input_text(text.clone());
}
let mut input = self.command_input.borrow_mut();
input.text = text.clone();
input.cursor_position = text.len();
}
pub fn get_buffer_input_text(&self) -> String {
self.current_buffer()
.map(super::buffer::BufferAPI::get_input_text)
.unwrap_or_default()
}
pub fn set_buffer_input_text_with_cursor(&mut self, text: String, cursor: usize) {
if let Some(buffer) = self.current_buffer_mut() {
buffer.set_input_text(text.clone());
buffer.set_input_cursor_position(cursor);
}
let mut input = self.command_input.borrow_mut();
input.text = text;
input.cursor_position = cursor;
}
pub fn set_current_column_buffer(&mut self, col: usize) {
if let Some(buffer) = self.current_buffer_mut() {
buffer.set_current_column(col);
}
}
pub fn set_show_row_numbers(&mut self, show: bool) {
if let Some(buffer) = self.current_buffer_mut() {
buffer.set_show_row_numbers(show);
}
}
pub fn set_filter_active(&mut self, active: bool) {
if let Some(buffer) = self.current_buffer_mut() {
buffer.set_filter_active(active);
}
}
pub fn set_compact_mode(&mut self, compact: bool) {
if let Some(buffer) = self.current_buffer_mut() {
buffer.set_compact_mode(compact);
}
}
pub fn set_case_insensitive(&mut self, insensitive: bool) {
if let Some(buffer) = self.current_buffer_mut() {
buffer.set_case_insensitive(insensitive);
}
}
pub fn get_buffer_selected_row(&self) -> Option<usize> {
self.current_buffer()?.get_selected_row()
}
pub fn get_search_pattern(&self) -> String {
self.current_buffer()
.map(super::buffer::BufferAPI::get_search_pattern)
.unwrap_or_default()
}
pub fn get_fuzzy_filter_pattern(&self) -> String {
self.current_buffer()
.map(super::buffer::BufferAPI::get_fuzzy_filter_pattern)
.unwrap_or_default()
}
pub fn vim_search_should_handle_key(&self) -> bool {
let mode = self.get_mode();
let pattern = self.get_search_pattern();
mode == AppMode::Search || !pattern.is_empty()
}
pub fn start_vim_search(&mut self) {
self.set_mode(AppMode::Search);
self.set_input_text(String::new());
self.set_input_cursor_position(0);
self.set_status_message("Search: /".to_string());
}
pub fn exit_vim_search(&mut self) {
self.set_mode(AppMode::Results);
self.clear_search_state();
self.set_status_message("Search mode exited".to_string());
}
pub fn get_fuzzy_filter_indices(&self) -> Vec<usize> {
self.current_buffer()
.map(|b| b.get_fuzzy_filter_indices().clone())
.unwrap_or_default()
}
pub fn set_scroll_offset(&mut self, offset: (usize, usize)) {
if let Some(buffer) = self.current_buffer_mut() {
buffer.set_scroll_offset(offset);
}
}
pub fn save_state_for_undo(&mut self) {
if let Some(buffer) = self.current_buffer_mut() {
buffer.save_state_for_undo();
}
}
pub fn perform_undo(&mut self) -> bool {
self.current_buffer_mut()
.is_some_and(super::buffer::BufferAPI::perform_undo)
}
pub fn perform_redo(&mut self) -> bool {
self.current_buffer_mut()
.is_some_and(super::buffer::BufferAPI::perform_redo)
}
pub fn insert_char_at_cursor(&mut self, c: char) {
if let Some(buffer) = self.current_buffer_mut() {
buffer.save_state_for_undo();
let pos = buffer.get_input_cursor_position();
let mut text = buffer.get_input_text();
let mut chars: Vec<char> = text.chars().collect();
chars.insert(pos, c);
text = chars.iter().collect();
buffer.set_input_text(text);
buffer.set_input_cursor_position(pos + 1);
}
}
pub fn handle_input_key(&mut self, key: crossterm::event::KeyEvent) -> bool {
self.current_buffer_mut()
.is_some_and(|b| b.handle_input_key(key))
}
pub fn set_search_matches_with_index(&mut self, matches: Vec<(usize, usize)>, index: usize) {
if let Some(buffer) = self.current_buffer_mut() {
buffer.set_search_matches(matches);
buffer.set_search_match_index(index);
}
}
pub fn clear_search_state(&mut self) {
if let Some(buffer) = self.current_buffer_mut() {
buffer.set_search_matches(Vec::new());
buffer.set_status_message("No matches found".to_string());
}
}
pub fn set_last_state(&mut self, row: Option<usize>, scroll_offset: (usize, usize)) {
if let Some(buffer) = self.current_buffer_mut() {
buffer.set_last_results_row(row);
buffer.set_last_scroll_offset(scroll_offset);
}
}
pub fn clear_line(&mut self) {
if let Some(buffer) = self.current_buffer_mut() {
buffer.save_state_for_undo();
buffer.set_input_text(String::new());
buffer.set_input_cursor_position(0);
}
}
pub fn move_input_cursor_left(&mut self) {
if let Some(buffer) = self.current_buffer_mut() {
let pos = buffer.get_input_cursor_position();
if pos > 0 {
buffer.set_input_cursor_position(pos - 1);
}
}
}
pub fn move_input_cursor_right(&mut self) {
if let Some(buffer) = self.current_buffer_mut() {
let pos = buffer.get_input_cursor_position();
let text_len = buffer.get_input_text().chars().count();
if pos < text_len {
buffer.set_input_cursor_position(pos + 1);
}
}
}
pub fn backspace(&mut self) {
if let Some(buffer) = self.current_buffer_mut() {
let pos = buffer.get_input_cursor_position();
if pos > 0 {
buffer.save_state_for_undo();
let mut text = buffer.get_input_text();
let mut chars: Vec<char> = text.chars().collect();
chars.remove(pos - 1);
text = chars.iter().collect();
buffer.set_input_text(text);
buffer.set_input_cursor_position(pos - 1);
}
}
}
pub fn delete(&mut self) {
if let Some(buffer) = self.current_buffer_mut() {
let pos = buffer.get_input_cursor_position();
let mut text = buffer.get_input_text();
let chars_len = text.chars().count();
if pos < chars_len {
buffer.save_state_for_undo();
let mut chars: Vec<char> = text.chars().collect();
chars.remove(pos);
text = chars.iter().collect();
buffer.set_input_text(text);
}
}
}
pub fn reset_navigation_state(&mut self) {
if let Some(buffer) = self.current_buffer_mut() {
buffer.set_selected_row(Some(0));
buffer.set_scroll_offset((0, 0));
buffer.set_current_column(0);
buffer.set_last_results_row(None);
buffer.set_last_scroll_offset((0, 0));
}
}
pub fn clear_fuzzy_filter_state(&mut self) {
if let Some(buffer) = self.current_buffer_mut() {
buffer.clear_fuzzy_filter();
buffer.set_fuzzy_filter_pattern(String::new());
buffer.set_fuzzy_filter_active(false);
buffer.set_fuzzy_filter_indices(Vec::new());
}
}
pub fn get_filter_pattern(&self) -> String {
self.current_buffer()
.map(super::buffer::BufferAPI::get_filter_pattern)
.unwrap_or_default()
}
pub fn set_column_stats(&mut self, stats: Option<crate::buffer::ColumnStatistics>) {
if let Some(buffer) = self.current_buffer_mut() {
buffer.set_column_stats(stats);
}
}
pub fn set_column_widths(&mut self, widths: Vec<u16>) {
if let Some(buffer) = self.current_buffer_mut() {
buffer.set_column_widths(widths);
}
}
pub fn set_current_match(&mut self, match_pos: Option<(usize, usize)>) {
if let Some(buffer) = self.current_buffer_mut() {
buffer.set_current_match(match_pos);
}
}
pub fn get_kill_ring(&self) -> String {
self.current_buffer()
.map(super::buffer::BufferAPI::get_kill_ring)
.unwrap_or_default()
}
pub fn get_buffer_status_message(&self) -> String {
self.current_buffer()
.map(super::buffer::BufferAPI::get_status_message)
.unwrap_or_default()
}
pub fn get_buffer_name(&self) -> String {
self.current_buffer().map_or_else(
|| "No Buffer".to_string(),
super::buffer::BufferAPI::get_name,
)
}
pub fn get_last_results_row(&self) -> Option<usize> {
self.current_buffer()?.get_last_results_row()
}
pub fn get_last_scroll_offset(&self) -> (usize, usize) {
self.current_buffer()
.map_or((0, 0), super::buffer::BufferAPI::get_last_scroll_offset)
}
pub fn set_last_query(&mut self, query: String) {
if let Some(buffer) = self.current_buffer_mut() {
buffer.set_last_query(query);
}
}
pub fn get_last_query_source(&self) -> Option<String> {
self.current_buffer()?.get_last_query_source()
}
pub fn set_last_visible_rows(&mut self, rows: usize) {
if let Some(buffer) = self.current_buffer_mut() {
buffer.set_last_visible_rows(rows);
}
}
}
impl Default for AppStateContainer {
fn default() -> Self {
let command_history = CommandHistory::default();
let mut widgets = WidgetStates::new();
widgets.set_history(HistoryWidget::new(command_history.clone()));
Self {
buffers: BufferManager::new(),
current_buffer_id: 0,
command_input: RefCell::new(InputState::new()),
search: RefCell::new(SearchState::new()),
filter: RefCell::new(FilterState::new()),
column_search: RefCell::new(ColumnSearchState::new()),
history_search: RefCell::new(HistorySearchState::new()),
sort: RefCell::new(SortState::new()),
selection: RefCell::new(SelectionState::new()),
completion: RefCell::new(CompletionState::default()),
widgets,
cache_list: CacheListState::new(),
column_stats: ColumnStatsState::new(),
jump_to_row: JumpToRowState::new(),
navigation: RefCell::new(NavigationState::new()),
command_history: RefCell::new(command_history),
key_press_history: RefCell::new(KeyPressHistory::new(100)),
results: RefCell::new(ResultsState::default()),
clipboard: RefCell::new(ClipboardState::default()),
chord: RefCell::new(ChordState::default()),
undo_redo: RefCell::new(UndoRedoState::default()),
scroll: RefCell::new(ScrollState::default()),
results_cache: ResultsCache::new(100),
mode_stack: Vec::new(),
debug_enabled: false,
debug_service: RefCell::new(None),
help: RefCell::new(HelpState::new()),
}
}
}
impl fmt::Debug for AppStateContainer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("AppStateContainer")
.field("current_mode", &self.current_mode())
.field("mode_stack", &self.mode_stack)
.field("current_buffer_id", &self.current_buffer_id)
.field("command_input", &self.command_input)
.field("search_active", &self.search.borrow().is_active)
.field("filter_active", &self.filter.borrow().is_active)
.field(
"column_search_active",
&self.column_search.borrow().is_active,
)
.field("debug_enabled", &self.debug_enabled)
.field("help_visible", &self.help.borrow().is_visible)
.field("help_scroll", &self.help.borrow().scroll_offset)
.field("cached_results", &self.results_cache.cache.len())
.field(
"history_count",
&self.command_history.borrow().get_all().len(),
)
.finish()
}
}
impl fmt::Debug for WidgetStates {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("WidgetStates")
.field("search_modes_active", &self.search_modes.is_active())
.field("history", &self.history.is_some())
.field("help", &"HelpWidget")
.field("stats", &"StatsWidget")
.finish()
}
}