use crate::api_client::QueryResponse;
use crate::app_state_container::ColumnSearchState;
use crate::buffer::{
AppMode, BufferAPI, ColumnStatistics, EditMode, FilterState, FuzzyFilterState, SearchState,
SortOrder, SortState,
};
use crate::data::data_view::DataView;
use crate::datatable::DataTable;
use crate::datatable_view::{DataTableView, SortOrder as ViewSortOrder};
use crate::input_manager::{create_single_line, InputManager};
use anyhow::Result;
use crossterm::event::KeyEvent;
use ratatui::style::Color;
use ratatui::widgets::TableState;
use std::path::PathBuf;
use std::sync::Arc;
use tracing::debug;
use tui_input::Input;
pub struct DataTableBuffer {
id: usize,
file_path: Option<PathBuf>,
name: String,
modified: bool,
view: DataTableView,
dataview: Option<DataView>,
mode: AppMode,
edit_mode: EditMode,
input: Input, input_manager: Box<dyn InputManager>,
_table_state: TableState,
last_results_row: Option<usize>,
last_scroll_offset: (usize, usize),
last_query: String,
status_message: String,
sort_state: SortState,
filter_state: FilterState,
fuzzy_filter_state: FuzzyFilterState,
search_state: SearchState,
_column_search_state: ColumnSearchState,
column_stats: Option<ColumnStatistics>,
column_widths: Vec<u16>,
scroll_offset: (usize, usize),
current_column: usize,
compact_mode: bool,
viewport_lock: bool,
viewport_lock_row: Option<usize>,
show_row_numbers: bool,
case_insensitive: bool,
undo_stack: Vec<(String, usize)>,
redo_stack: Vec<(String, usize)>,
kill_ring: String,
last_visible_rows: usize,
last_query_source: Option<String>,
_highlighted_text_cache: Option<Vec<(String, Color)>>,
_last_highlighted_text: String,
_saved_input_state: Option<(String, usize)>,
}
impl DataTableBuffer {
#[must_use]
pub fn new(id: usize, table: DataTable) -> Self {
let name = table.name.clone();
let view = DataTableView::new(table);
Self {
id,
file_path: None,
name,
modified: false,
view,
dataview: None,
mode: AppMode::Command,
edit_mode: EditMode::SingleLine,
input: Input::default(),
input_manager: create_single_line(String::new()),
_table_state: TableState::default(),
last_results_row: None,
last_scroll_offset: (0, 0),
last_query: String::new(),
status_message: String::new(),
sort_state: SortState {
column: None,
order: SortOrder::None,
},
filter_state: FilterState::default(),
fuzzy_filter_state: FuzzyFilterState::default(),
search_state: SearchState::default(),
_column_search_state: ColumnSearchState::default(),
column_stats: None,
column_widths: Vec::new(),
scroll_offset: (0, 0),
current_column: 0,
compact_mode: false,
viewport_lock: false,
viewport_lock_row: None,
show_row_numbers: false,
case_insensitive: false,
undo_stack: Vec::new(),
redo_stack: Vec::new(),
kill_ring: String::new(),
last_visible_rows: 0,
last_query_source: None,
_highlighted_text_cache: None,
_last_highlighted_text: String::new(),
_saved_input_state: None,
}
}
pub fn from_file(id: usize, file_path: PathBuf) -> anyhow::Result<Self> {
let table = crate::datatable_loaders::load_json_to_datatable(&file_path, "data")?;
let mut buffer = Self::new(id, table);
buffer.file_path = Some(file_path.clone());
buffer.name = file_path
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("data")
.to_string();
Ok(buffer)
}
fn update_column_widths(&mut self) {
self.column_widths = self.calculate_column_widths();
}
fn calculate_column_widths(&self) -> Vec<u16> {
let table = self.view.table();
let mut widths = Vec::new();
for (col_idx, column) in table.columns.iter().enumerate() {
let mut max_width = column.name.len() as u16;
let sample_size = 50.min(table.row_count());
for row_idx in 0..sample_size {
if let Some(value) = table.get_value(row_idx, col_idx) {
let value_len = value.to_string().len() as u16;
max_width = max_width.max(value_len);
}
}
widths.push((max_width + 2).min(50));
}
widths
}
fn sync_sort_to_view(&mut self) {
if let Some(column) = self.sort_state.column {
let view_order = match self.sort_state.order {
SortOrder::Ascending => ViewSortOrder::Ascending,
SortOrder::Descending => ViewSortOrder::Descending,
SortOrder::None => return, };
self.view.apply_sort(column, view_order);
self.update_column_widths();
}
}
fn sync_filter_to_view(&mut self) {
if self.filter_state.active && !self.filter_state.pattern.is_empty() {
self.view.apply_filter(
self.filter_state.pattern.clone(),
None, !self.case_insensitive,
);
self.update_column_widths();
} else {
self.view.clear_filter();
self.update_column_widths();
}
}
pub fn table(&self) -> &DataTable {
self.view.table()
}
pub fn view(&self) -> &DataTableView {
&self.view
}
}
impl BufferAPI for DataTableBuffer {
fn get_id(&self) -> usize {
self.id
}
fn get_query(&self) -> String {
self.input_manager.get_text()
}
fn set_query(&mut self, query: String) {
self.input_manager.set_text(query.clone());
self.input = Input::new(query.clone()).with_cursor(query.len());
}
fn get_last_query(&self) -> String {
self.last_query.clone()
}
fn set_last_query(&mut self, query: String) {
self.last_query = query;
}
fn get_datatable(&self) -> Option<&DataTable> {
Some(self.view.get_datatable())
}
fn get_datatable_mut(&mut self) -> Option<&mut DataTable> {
Some(self.view.get_datatable_mut())
}
fn has_datatable(&self) -> bool {
true }
fn get_original_source(&self) -> Option<&DataTable> {
Some(self.view.table())
}
fn set_datatable(&mut self, datatable: Option<Arc<DataTable>>) {
if let Some(dt) = datatable {
debug!(
"V50: DataTableBuffer received DataTable with {} rows",
dt.row_count()
);
}
}
fn set_results_as_datatable(&mut self, _response: Option<QueryResponse>) -> Result<(), String> {
Ok(())
}
fn get_dataview(&self) -> Option<&DataView> {
self.dataview.as_ref()
}
fn get_dataview_mut(&mut self) -> Option<&mut DataView> {
self.dataview.as_mut()
}
fn set_dataview(&mut self, dataview: Option<DataView>) {
debug!(
"V51: Setting DataView with {} rows in DataTableBuffer",
dataview
.as_ref()
.map_or(0, super::data_view::DataView::row_count)
);
self.dataview = dataview;
}
fn has_dataview(&self) -> bool {
self.dataview.is_some()
}
fn get_mode(&self) -> AppMode {
self.mode.clone()
}
fn set_mode(&mut self, mode: AppMode) {
self.mode = mode;
}
fn get_edit_mode(&self) -> EditMode {
self.edit_mode.clone()
}
fn set_edit_mode(&mut self, mode: EditMode) {
self.edit_mode = mode;
}
fn get_status_message(&self) -> String {
self.status_message.clone()
}
fn set_status_message(&mut self, message: String) {
self.status_message = message;
}
fn get_selected_row(&self) -> Option<usize> {
self.last_results_row
}
fn set_selected_row(&mut self, row: Option<usize>) {
self.last_results_row = row;
}
fn get_current_column(&self) -> usize {
self.current_column
}
fn set_current_column(&mut self, col: usize) {
self.current_column = col;
}
fn get_scroll_offset(&self) -> (usize, usize) {
self.scroll_offset
}
fn set_scroll_offset(&mut self, offset: (usize, usize)) {
self.scroll_offset = offset;
}
fn get_last_results_row(&self) -> Option<usize> {
self.last_results_row
}
fn set_last_results_row(&mut self, row: Option<usize>) {
self.last_results_row = row;
}
fn get_last_scroll_offset(&self) -> (usize, usize) {
self.last_scroll_offset
}
fn set_last_scroll_offset(&mut self, offset: (usize, usize)) {
self.last_scroll_offset = offset;
}
fn get_filter_pattern(&self) -> String {
self.filter_state.pattern.clone()
}
fn set_filter_pattern(&mut self, pattern: String) {
self.filter_state.pattern = pattern;
self.sync_filter_to_view();
}
fn is_filter_active(&self) -> bool {
self.filter_state.active
}
fn set_filter_active(&mut self, active: bool) {
self.filter_state.active = active;
self.sync_filter_to_view();
}
fn get_fuzzy_filter_pattern(&self) -> String {
self.fuzzy_filter_state.pattern.clone()
}
fn set_fuzzy_filter_pattern(&mut self, pattern: String) {
self.fuzzy_filter_state.pattern = pattern;
}
fn is_fuzzy_filter_active(&self) -> bool {
self.fuzzy_filter_state.active
}
fn set_fuzzy_filter_active(&mut self, active: bool) {
self.fuzzy_filter_state.active = active;
}
fn get_fuzzy_filter_indices(&self) -> &Vec<usize> {
&self.fuzzy_filter_state.filtered_indices
}
fn set_fuzzy_filter_indices(&mut self, indices: Vec<usize>) {
self.fuzzy_filter_state.filtered_indices = indices;
}
fn clear_fuzzy_filter(&mut self) {
self.fuzzy_filter_state.active = false;
self.fuzzy_filter_state.pattern.clear();
self.fuzzy_filter_state.filtered_indices.clear();
}
fn get_search_pattern(&self) -> String {
self.search_state.pattern.clone()
}
fn set_search_pattern(&mut self, pattern: String) {
self.search_state.pattern = pattern.clone();
if !pattern.is_empty() {
self.view.start_search(pattern, !self.case_insensitive);
}
}
fn get_search_matches(&self) -> Vec<(usize, usize)> {
self.search_state.matches.clone()
}
fn set_search_matches(&mut self, matches: Vec<(usize, usize)>) {
self.search_state.matches = matches;
}
fn get_current_match(&self) -> Option<(usize, usize)> {
self.search_state.current_match
}
fn set_current_match(&mut self, match_pos: Option<(usize, usize)>) {
self.search_state.current_match = match_pos;
}
fn get_search_match_index(&self) -> usize {
self.search_state.match_index
}
fn set_search_match_index(&mut self, index: usize) {
self.search_state.match_index = index;
}
fn clear_search_state(&mut self) {
self.search_state.pattern.clear();
self.search_state.matches.clear();
self.search_state.current_match = None;
self.search_state.match_index = 0;
self.view.clear_search();
}
fn get_column_stats(&self) -> Option<&ColumnStatistics> {
self.column_stats.as_ref()
}
fn set_column_stats(&mut self, stats: Option<ColumnStatistics>) {
self.column_stats = stats;
}
fn get_sort_column(&self) -> Option<usize> {
self.sort_state.column
}
fn set_sort_column(&mut self, column: Option<usize>) {
self.sort_state.column = column;
self.sync_sort_to_view();
}
fn get_sort_order(&self) -> SortOrder {
self.sort_state.order
}
fn set_sort_order(&mut self, order: SortOrder) {
self.sort_state.order = order;
self.sync_sort_to_view();
}
fn get_name(&self) -> String {
self.name.clone()
}
fn set_name(&mut self, name: String) {
self.name = name;
}
fn get_file_path(&self) -> Option<&PathBuf> {
self.file_path.as_ref()
}
fn set_file_path(&mut self, path: Option<String>) {
self.file_path = path.map(PathBuf::from);
}
fn is_modified(&self) -> bool {
self.modified
}
fn set_modified(&mut self, modified: bool) {
self.modified = modified;
}
fn get_last_query_source(&self) -> Option<String> {
self.last_query_source.clone()
}
fn set_last_query_source(&mut self, source: Option<String>) {
self.last_query_source = source;
}
fn get_column_widths(&self) -> &Vec<u16> {
&self.column_widths
}
fn set_column_widths(&mut self, widths: Vec<u16>) {
self.column_widths = widths;
}
fn is_compact_mode(&self) -> bool {
self.compact_mode
}
fn set_compact_mode(&mut self, compact: bool) {
self.compact_mode = compact;
}
fn is_viewport_lock(&self) -> bool {
self.viewport_lock
}
fn set_viewport_lock(&mut self, locked: bool) {
self.viewport_lock = locked;
}
fn get_viewport_lock_row(&self) -> Option<usize> {
self.viewport_lock_row
}
fn set_viewport_lock_row(&mut self, row: Option<usize>) {
self.viewport_lock_row = row;
}
fn is_show_row_numbers(&self) -> bool {
self.show_row_numbers
}
fn set_show_row_numbers(&mut self, show: bool) {
self.show_row_numbers = show;
}
fn is_case_insensitive(&self) -> bool {
self.case_insensitive
}
fn set_case_insensitive(&mut self, insensitive: bool) {
self.case_insensitive = insensitive;
}
fn get_input_value(&self) -> String {
self.input_manager.get_text()
}
fn set_input_value(&mut self, value: String) {
self.input_manager.set_text(value.clone());
self.input = Input::new(value.clone()).with_cursor(value.len());
}
fn get_input_cursor(&self) -> usize {
self.input_manager.get_cursor_position()
}
fn set_input_cursor(&mut self, pos: usize) {
self.input_manager.set_cursor_position(pos);
}
fn get_kill_ring(&self) -> String {
self.kill_ring.clone()
}
fn set_kill_ring(&mut self, text: String) {
self.kill_ring = text;
}
fn get_last_visible_rows(&self) -> usize {
self.last_visible_rows
}
fn set_last_visible_rows(&mut self, rows: usize) {
self.last_visible_rows = rows;
}
fn apply_filter(&mut self) -> Result<()> {
self.sync_filter_to_view();
Ok(())
}
fn apply_sort(&mut self) -> Result<()> {
self.sync_sort_to_view();
Ok(())
}
fn search(&mut self) -> Result<()> {
Ok(())
}
fn clear_filters(&mut self) {
self.filter_state.active = false;
self.filter_state.pattern.clear();
self.view.clear_filter();
}
fn get_row_count(&self) -> usize {
self.view.table().row_count()
}
fn get_column_count(&self) -> usize {
self.view.table().column_count()
}
fn get_column_names(&self) -> Vec<String> {
self.view
.table()
.columns
.iter()
.map(|col| col.name.clone())
.collect()
}
fn get_undo_stack(&self) -> &Vec<(String, usize)> {
&self.undo_stack
}
fn push_undo(&mut self, state: (String, usize)) {
self.undo_stack.push(state);
if self.undo_stack.len() > 100 {
self.undo_stack.remove(0);
}
}
fn pop_undo(&mut self) -> Option<(String, usize)> {
self.undo_stack.pop()
}
fn get_redo_stack(&self) -> &Vec<(String, usize)> {
&self.redo_stack
}
fn push_redo(&mut self, state: (String, usize)) {
self.redo_stack.push(state);
if self.redo_stack.len() > 100 {
self.redo_stack.remove(0);
}
}
fn pop_redo(&mut self) -> Option<(String, usize)> {
self.redo_stack.pop()
}
fn clear_redo(&mut self) {
self.redo_stack.clear();
}
fn is_kill_ring_empty(&self) -> bool {
self.kill_ring.is_empty()
}
fn perform_undo(&mut self) -> bool {
if let Some((text, cursor)) = self.pop_undo() {
let current_state = (self.get_input_value(), self.get_input_cursor());
self.push_redo(current_state);
self.set_input_value(text);
self.set_input_cursor(cursor);
true
} else {
false
}
}
fn perform_redo(&mut self) -> bool {
if let Some((text, cursor)) = self.pop_redo() {
let current_state = (self.get_input_value(), self.get_input_cursor());
self.push_undo(current_state);
self.set_input_value(text);
self.set_input_cursor(cursor);
true
} else {
false
}
}
fn save_state_for_undo(&mut self) {
let current_state = (self.get_input_value(), self.get_input_cursor());
self.push_undo(current_state);
}
fn debug_dump(&self) -> String {
format!(
"DataTableBuffer {{ id: {}, name: \"{}\", rows: {}, cols: {} }}",
self.id,
self.name,
self.get_row_count(),
self.get_column_count()
)
}
fn get_input_text(&self) -> String {
self.input_manager.get_text()
}
fn set_input_text(&mut self, text: String) {
self.input_manager.set_text(text.clone());
self.input = Input::new(text.clone()).with_cursor(text.len());
}
fn handle_input_key(&mut self, event: KeyEvent) -> bool {
self.input_manager.handle_key_event(event)
}
fn switch_input_mode(&mut self, _multiline: bool) {
self.edit_mode = EditMode::SingleLine;
let current_text = self.input_manager.get_text();
self.input_manager = create_single_line(current_text);
}
fn get_input_cursor_position(&self) -> usize {
self.input_manager.get_cursor_position()
}
fn set_input_cursor_position(&mut self, position: usize) {
self.input_manager.set_cursor_position(position);
}
fn is_input_multiline(&self) -> bool {
matches!(self.edit_mode, EditMode::MultiLine)
}
fn navigate_history_up(&mut self, _history: &[String]) -> bool {
false
}
fn navigate_history_down(&mut self, _history: &[String]) -> bool {
false
}
fn reset_history_navigation(&mut self) {
}
fn clear_results(&mut self) {
}
}