sql_cli/ui/rendering/
table_render_context.rs1use crate::app_state_container::SelectionMode;
5use crate::buffer::AppMode;
6use crate::data::data_view::SortState;
7use std::ops::Range;
8
9#[derive(Debug, Clone)]
12pub struct TableRenderContext {
13 pub row_count: usize,
16
17 pub visible_row_indices: Vec<usize>,
19
20 pub data_rows: Vec<Vec<String>>,
23
24 pub column_headers: Vec<String>,
27
28 pub column_widths: Vec<u16>,
30
31 pub pinned_column_indices: Vec<usize>,
33
34 pub pinned_count: usize,
36
37 pub selected_row: usize,
40
41 pub selected_column: usize,
43
44 pub row_viewport: Range<usize>,
46
47 pub selection_mode: SelectionMode,
49
50 pub sort_state: Option<SortState>,
53
54 pub show_row_numbers: bool,
56
57 pub app_mode: AppMode,
59
60 pub fuzzy_filter_pattern: Option<String>,
63
64 pub case_insensitive: bool,
66
67 pub available_width: u16,
70
71 pub available_height: u16,
73}
74
75impl TableRenderContext {
76 pub fn is_selected_row(&self, viewport_row_index: usize) -> bool {
78 let absolute_row = self.row_viewport.start + viewport_row_index;
79 absolute_row == self.selected_row
80 }
81
82 pub fn is_selected_column(&self, visual_column_index: usize) -> bool {
84 visual_column_index == self.selected_column
85 }
86
87 pub fn is_pinned_column(&self, visual_column_index: usize) -> bool {
89 visual_column_index < self.pinned_count
90 }
91
92 pub fn get_crosshair(&self) -> (usize, usize) {
94 (self.selected_row, self.selected_column)
95 }
96
97 pub fn is_crosshair_cell(&self, viewport_row_index: usize, visual_column_index: usize) -> bool {
99 self.is_selected_row(viewport_row_index) && self.is_selected_column(visual_column_index)
100 }
101
102 pub fn get_sort_indicator(&self, visual_column_index: usize) -> &str {
104 if let Some(ref sort) = self.sort_state {
105 if sort.column == Some(visual_column_index) {
106 match sort.order {
107 crate::data::data_view::SortOrder::Ascending => " ↑",
108 crate::data::data_view::SortOrder::Descending => " ↓",
109 crate::data::data_view::SortOrder::None => "",
110 }
111 } else {
112 ""
113 }
114 } else {
115 ""
116 }
117 }
118
119 pub fn cell_matches_filter(&self, cell_value: &str) -> bool {
121 if let Some(ref pattern) = self.fuzzy_filter_pattern {
122 if pattern.starts_with('\'') && pattern.len() > 1 {
123 let search_pattern = &pattern[1..];
125 if self.case_insensitive {
126 cell_value
127 .to_lowercase()
128 .contains(&search_pattern.to_lowercase())
129 } else {
130 cell_value.contains(search_pattern)
131 }
132 } else if !pattern.is_empty() {
133 use fuzzy_matcher::skim::SkimMatcherV2;
135 use fuzzy_matcher::FuzzyMatcher;
136 let matcher = if self.case_insensitive {
137 SkimMatcherV2::default().ignore_case()
138 } else {
139 SkimMatcherV2::default().respect_case()
140 };
141 matcher
142 .fuzzy_match(cell_value, pattern)
143 .map(|score| score > 0)
144 .unwrap_or(false)
145 } else {
146 false
147 }
148 } else {
149 false
150 }
151 }
152}
153
154pub struct TableRenderContextBuilder {
156 context: TableRenderContext,
157}
158
159impl TableRenderContextBuilder {
160 pub fn new() -> Self {
161 Self {
162 context: TableRenderContext {
163 row_count: 0,
164 visible_row_indices: Vec::new(),
165 data_rows: Vec::new(),
166 column_headers: Vec::new(),
167 column_widths: Vec::new(),
168 pinned_column_indices: Vec::new(),
169 pinned_count: 0,
170 selected_row: 0,
171 selected_column: 0,
172 row_viewport: 0..0,
173 selection_mode: SelectionMode::Cell,
174 sort_state: None,
175 show_row_numbers: false,
176 app_mode: AppMode::Results,
177 fuzzy_filter_pattern: None,
178 case_insensitive: false,
179 available_width: 0,
180 available_height: 0,
181 },
182 }
183 }
184
185 pub fn row_count(mut self, count: usize) -> Self {
186 self.context.row_count = count;
187 self
188 }
189
190 pub fn visible_rows(mut self, indices: Vec<usize>, data: Vec<Vec<String>>) -> Self {
191 self.context.visible_row_indices = indices;
192 self.context.data_rows = data;
193 self
194 }
195
196 pub fn columns(mut self, headers: Vec<String>, widths: Vec<u16>) -> Self {
197 self.context.column_headers = headers;
198 self.context.column_widths = widths;
199 self
200 }
201
202 pub fn pinned_columns(mut self, indices: Vec<usize>) -> Self {
203 self.context.pinned_count = indices.len();
204 self.context.pinned_column_indices = indices;
205 self
206 }
207
208 pub fn selection(mut self, row: usize, column: usize, mode: SelectionMode) -> Self {
209 self.context.selected_row = row;
210 self.context.selected_column = column;
211 self.context.selection_mode = mode;
212 self
213 }
214
215 pub fn row_viewport(mut self, range: Range<usize>) -> Self {
216 self.context.row_viewport = range;
217 self
218 }
219
220 pub fn sort_state(mut self, state: Option<SortState>) -> Self {
221 self.context.sort_state = state;
222 self
223 }
224
225 pub fn display_options(mut self, show_row_numbers: bool, app_mode: AppMode) -> Self {
226 self.context.show_row_numbers = show_row_numbers;
227 self.context.app_mode = app_mode;
228 self
229 }
230
231 pub fn filter(mut self, pattern: Option<String>, case_insensitive: bool) -> Self {
232 self.context.fuzzy_filter_pattern = pattern;
233 self.context.case_insensitive = case_insensitive;
234 self
235 }
236
237 pub fn dimensions(mut self, width: u16, height: u16) -> Self {
238 self.context.available_width = width;
239 self.context.available_height = height;
240 self
241 }
242
243 pub fn build(self) -> TableRenderContext {
244 self.context
245 }
246}