use crate::app_state_container::SelectionMode;
use crate::buffer::AppMode;
use crate::data::data_view::SortState;
use std::ops::Range;
#[derive(Debug, Clone)]
pub struct TableRenderContext {
pub row_count: usize,
pub visible_row_indices: Vec<usize>,
pub data_rows: Vec<Vec<String>>,
pub column_headers: Vec<String>,
pub column_widths: Vec<u16>,
pub pinned_column_indices: Vec<usize>,
pub pinned_count: usize,
pub selected_row: usize,
pub selected_column: usize,
pub row_viewport: Range<usize>,
pub selection_mode: SelectionMode,
pub sort_state: Option<SortState>,
pub show_row_numbers: bool,
pub app_mode: AppMode,
pub fuzzy_filter_pattern: Option<String>,
pub case_insensitive: bool,
pub available_width: u16,
pub available_height: u16,
}
impl TableRenderContext {
#[must_use]
pub fn is_selected_row(&self, viewport_row_index: usize) -> bool {
let absolute_row = self.row_viewport.start + viewport_row_index;
absolute_row == self.selected_row
}
#[must_use]
pub fn is_selected_column(&self, visual_column_index: usize) -> bool {
visual_column_index == self.selected_column
}
#[must_use]
pub fn is_pinned_column(&self, visual_column_index: usize) -> bool {
visual_column_index < self.pinned_count
}
#[must_use]
pub fn get_crosshair(&self) -> (usize, usize) {
(self.selected_row, self.selected_column)
}
#[must_use]
pub fn is_crosshair_cell(&self, viewport_row_index: usize, visual_column_index: usize) -> bool {
self.is_selected_row(viewport_row_index) && self.is_selected_column(visual_column_index)
}
#[must_use]
pub fn get_sort_indicator(&self, visual_column_index: usize) -> &str {
if let Some(ref sort) = self.sort_state {
if sort.column == Some(visual_column_index) {
match sort.order {
crate::data::data_view::SortOrder::Ascending => " ↑",
crate::data::data_view::SortOrder::Descending => " ↓",
crate::data::data_view::SortOrder::None => "",
}
} else {
""
}
} else {
""
}
}
#[must_use]
pub fn cell_matches_filter(&self, cell_value: &str) -> bool {
if let Some(ref pattern) = self.fuzzy_filter_pattern {
if pattern.starts_with('\'') && pattern.len() > 1 {
let search_pattern = &pattern[1..];
if self.case_insensitive {
cell_value
.to_lowercase()
.contains(&search_pattern.to_lowercase())
} else {
cell_value.contains(search_pattern)
}
} else if !pattern.is_empty() {
use fuzzy_matcher::skim::SkimMatcherV2;
use fuzzy_matcher::FuzzyMatcher;
let matcher = if self.case_insensitive {
SkimMatcherV2::default().ignore_case()
} else {
SkimMatcherV2::default().respect_case()
};
matcher
.fuzzy_match(cell_value, pattern)
.is_some_and(|score| score > 0)
} else {
false
}
} else {
false
}
}
}
pub struct TableRenderContextBuilder {
context: TableRenderContext,
}
impl Default for TableRenderContextBuilder {
fn default() -> Self {
Self::new()
}
}
impl TableRenderContextBuilder {
#[must_use]
pub fn new() -> Self {
Self {
context: TableRenderContext {
row_count: 0,
visible_row_indices: Vec::new(),
data_rows: Vec::new(),
column_headers: Vec::new(),
column_widths: Vec::new(),
pinned_column_indices: Vec::new(),
pinned_count: 0,
selected_row: 0,
selected_column: 0,
row_viewport: 0..0,
selection_mode: SelectionMode::Cell,
sort_state: None,
show_row_numbers: false,
app_mode: AppMode::Results,
fuzzy_filter_pattern: None,
case_insensitive: false,
available_width: 0,
available_height: 0,
},
}
}
#[must_use]
pub fn row_count(mut self, count: usize) -> Self {
self.context.row_count = count;
self
}
#[must_use]
pub fn visible_rows(mut self, indices: Vec<usize>, data: Vec<Vec<String>>) -> Self {
self.context.visible_row_indices = indices;
self.context.data_rows = data;
self
}
#[must_use]
pub fn columns(mut self, headers: Vec<String>, widths: Vec<u16>) -> Self {
self.context.column_headers = headers;
self.context.column_widths = widths;
self
}
#[must_use]
pub fn pinned_columns(mut self, indices: Vec<usize>) -> Self {
self.context.pinned_count = indices.len();
self.context.pinned_column_indices = indices;
self
}
#[must_use]
pub fn selection(mut self, row: usize, column: usize, mode: SelectionMode) -> Self {
self.context.selected_row = row;
self.context.selected_column = column;
self.context.selection_mode = mode;
self
}
#[must_use]
pub fn row_viewport(mut self, range: Range<usize>) -> Self {
self.context.row_viewport = range;
self
}
#[must_use]
pub fn sort_state(mut self, state: Option<SortState>) -> Self {
self.context.sort_state = state;
self
}
#[must_use]
pub fn display_options(mut self, show_row_numbers: bool, app_mode: AppMode) -> Self {
self.context.show_row_numbers = show_row_numbers;
self.context.app_mode = app_mode;
self
}
#[must_use]
pub fn filter(mut self, pattern: Option<String>, case_insensitive: bool) -> Self {
self.context.fuzzy_filter_pattern = pattern;
self.context.case_insensitive = case_insensitive;
self
}
#[must_use]
pub fn dimensions(mut self, width: u16, height: u16) -> Self {
self.context.available_width = width;
self.context.available_height = height;
self
}
#[must_use]
pub fn build(self) -> TableRenderContext {
self.context
}
}