1use crate::data::data_view::DataView;
2
3pub const DEFAULT_COL_WIDTH: u16 = 15;
5pub const MIN_COL_WIDTH: u16 = 3;
6pub const MAX_COL_WIDTH: u16 = 50;
7pub const MAX_COL_WIDTH_DATA_FOCUS: u16 = 100;
8pub const COLUMN_PADDING: u16 = 2;
9pub const MIN_HEADER_WIDTH_DATA_FOCUS: u16 = 5;
10pub const MAX_HEADER_TO_DATA_RATIO: f32 = 1.5;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum ColumnPackingMode {
15 DataFocus,
18 HeaderFocus,
21 Balanced,
23}
24
25impl ColumnPackingMode {
26 #[must_use]
28 pub fn cycle(&self) -> Self {
29 match self {
30 ColumnPackingMode::Balanced => ColumnPackingMode::DataFocus,
31 ColumnPackingMode::DataFocus => ColumnPackingMode::HeaderFocus,
32 ColumnPackingMode::HeaderFocus => ColumnPackingMode::Balanced,
33 }
34 }
35
36 #[must_use]
38 pub fn display_name(&self) -> &'static str {
39 match self {
40 ColumnPackingMode::Balanced => "Balanced",
41 ColumnPackingMode::DataFocus => "Data Focus",
42 ColumnPackingMode::HeaderFocus => "Header Focus",
43 }
44 }
45}
46
47pub type ColumnWidthDebugInfo = (String, u16, u16, u16, u32);
49
50pub struct ColumnWidthCalculator {
53 column_widths: Vec<u16>,
55 packing_mode: ColumnPackingMode,
57 column_width_debug: Vec<ColumnWidthDebugInfo>,
60 cache_dirty: bool,
62}
63
64impl ColumnWidthCalculator {
65 #[must_use]
67 pub fn new() -> Self {
68 Self {
69 column_widths: Vec::new(),
70 packing_mode: ColumnPackingMode::Balanced,
71 column_width_debug: Vec::new(),
72 cache_dirty: true,
73 }
74 }
75
76 #[must_use]
78 pub fn get_packing_mode(&self) -> ColumnPackingMode {
79 self.packing_mode
80 }
81
82 pub fn set_packing_mode(&mut self, mode: ColumnPackingMode) {
84 if self.packing_mode != mode {
85 self.packing_mode = mode;
86 self.cache_dirty = true;
87 }
88 }
89
90 pub fn cycle_packing_mode(&mut self) {
92 self.set_packing_mode(self.packing_mode.cycle());
93 }
94
95 #[must_use]
97 pub fn get_debug_info(&self) -> &[ColumnWidthDebugInfo] {
98 &self.column_width_debug
99 }
100
101 pub fn mark_dirty(&mut self) {
103 self.cache_dirty = true;
104 }
105
106 pub fn calculate_with_terminal_width(
108 &mut self,
109 dataview: &DataView,
110 viewport_rows: &std::ops::Range<usize>,
111 terminal_width: u16,
112 ) {
113 self.recalculate_column_widths(dataview, viewport_rows);
115
116 let total_ideal_width: u16 = self.column_widths.iter().sum();
118 let separators_width = (self.column_widths.len() as u16).saturating_sub(1);
119 let borders_width = 4u16; let total_needed = total_ideal_width + separators_width + borders_width;
122
123 if total_needed < terminal_width {
126 let extra_space = terminal_width - total_needed;
128 let num_columns = self.column_widths.len() as u16;
129 let space_per_column = if num_columns > 0 {
130 extra_space / num_columns
131 } else {
132 0
133 };
134
135 for (idx, width) in self.column_widths.iter_mut().enumerate() {
137 if *width <= 10 && idx < self.column_width_debug.len() {
138 let (_, header_w, data_w, _, _) = &self.column_width_debug[idx];
139 let ideal = (*header_w).max(*data_w) + COLUMN_PADDING;
140
141 if ideal > *width && ideal <= 15 {
143 *width = ideal.min(*width + space_per_column);
144 }
145 }
146 }
147 }
148 }
149
150 pub fn get_column_width(
152 &mut self,
153 dataview: &DataView,
154 viewport_rows: &std::ops::Range<usize>,
155 col_idx: usize,
156 ) -> u16 {
157 if self.cache_dirty {
158 self.recalculate_column_widths(dataview, viewport_rows);
159 }
160
161 self.column_widths
162 .get(col_idx)
163 .copied()
164 .unwrap_or(DEFAULT_COL_WIDTH)
165 }
166
167 pub fn get_all_column_widths(
169 &mut self,
170 dataview: &DataView,
171 viewport_rows: &std::ops::Range<usize>,
172 ) -> &[u16] {
173 if self.cache_dirty {
174 self.recalculate_column_widths(dataview, viewport_rows);
175 }
176
177 &self.column_widths
178 }
179
180 fn recalculate_column_widths(
183 &mut self,
184 dataview: &DataView,
185 viewport_rows: &std::ops::Range<usize>,
186 ) {
187 let col_count = dataview.column_count();
188 self.column_widths.resize(col_count, DEFAULT_COL_WIDTH);
189
190 self.column_width_debug.clear();
192
193 let headers = dataview.column_names();
195
196 let mut ideal_widths = Vec::with_capacity(col_count);
198 let mut header_widths = Vec::with_capacity(col_count);
199 let mut max_data_widths = Vec::with_capacity(col_count);
200
201 for col_idx in 0..col_count {
203 let header_width = headers.get(col_idx).map_or(0, |h| h.len() as u16);
205 header_widths.push(header_width);
206
207 let mut max_data_width = 0u16;
209
210 let sample_size = 100.min(viewport_rows.len());
212 let sample_step = if viewport_rows.len() > sample_size {
213 viewport_rows.len() / sample_size
214 } else {
215 1
216 };
217
218 for (i, row_idx) in viewport_rows.clone().enumerate() {
219 if i % sample_step != 0 && i != 0 && i != viewport_rows.len() - 1 {
221 continue;
222 }
223
224 if let Some(row) = dataview.get_row(row_idx) {
225 if col_idx < row.values.len() {
226 let cell_str = row.values[col_idx].to_string();
227 let cell_width = cell_str.len() as u16;
228 max_data_width = max_data_width.max(cell_width);
229 }
230 }
231 }
232
233 max_data_widths.push(max_data_width);
234
235 let ideal_width = header_width.max(max_data_width) + COLUMN_PADDING;
237 ideal_widths.push(ideal_width);
238 }
239
240 for col_idx in 0..col_count {
242 let header_width = header_widths[col_idx];
243 let max_data_width = max_data_widths[col_idx];
244 let ideal_width = ideal_widths[col_idx];
245
246 let final_width = if ideal_width <= 10 {
249 ideal_width
251 } else {
252 let data_samples = u32::from(max_data_width > 0);
254 let optimal_width = self.calculate_optimal_width_for_mode(
255 header_width,
256 max_data_width,
257 data_samples,
258 );
259
260 let (min_width, max_width) = match self.packing_mode {
262 ColumnPackingMode::DataFocus => (MIN_COL_WIDTH, MAX_COL_WIDTH_DATA_FOCUS),
263 _ => (MIN_COL_WIDTH, MAX_COL_WIDTH),
264 };
265
266 optimal_width.clamp(min_width, max_width)
267 };
268
269 self.column_widths[col_idx] = final_width;
270
271 let column_name = headers
273 .get(col_idx)
274 .cloned()
275 .unwrap_or_else(|| format!("col_{col_idx}"));
276 self.column_width_debug.push((
277 column_name,
278 header_width,
279 max_data_width,
280 final_width,
281 1, ));
283 }
284
285 self.cache_dirty = false;
286 }
287
288 fn calculate_optimal_width_for_mode(
290 &self,
291 header_width: u16,
292 max_data_width: u16,
293 data_samples: u32,
294 ) -> u16 {
295 match self.packing_mode {
296 ColumnPackingMode::DataFocus => {
297 if data_samples > 0 {
299 if max_data_width <= 3 {
302 max_data_width + COLUMN_PADDING
305 } else if max_data_width <= 10 && header_width > max_data_width * 2 {
306 (max_data_width + COLUMN_PADDING).max(MIN_HEADER_WIDTH_DATA_FOCUS)
309 } else {
310 let data_width =
312 (max_data_width + COLUMN_PADDING).min(MAX_COL_WIDTH_DATA_FOCUS);
313
314 data_width.max(MIN_HEADER_WIDTH_DATA_FOCUS)
316 }
317 } else {
318 header_width
320 .min(DEFAULT_COL_WIDTH)
321 .max(MIN_HEADER_WIDTH_DATA_FOCUS)
322 }
323 }
324 ColumnPackingMode::HeaderFocus => {
325 let header_with_padding = header_width + COLUMN_PADDING;
327
328 if data_samples > 0 {
329 header_with_padding.max(max_data_width.min(MAX_COL_WIDTH))
331 } else {
332 header_with_padding
333 }
334 }
335 ColumnPackingMode::Balanced => {
336 if data_samples > 0 {
338 let data_based_width = max_data_width + COLUMN_PADDING;
339
340 if header_width > max_data_width {
341 let max_allowed_header =
342 (f32::from(max_data_width) * MAX_HEADER_TO_DATA_RATIO) as u16;
343 data_based_width.max(header_width.min(max_allowed_header))
344 } else {
345 data_based_width.max(header_width)
346 }
347 } else {
348 header_width.max(DEFAULT_COL_WIDTH)
349 }
350 }
351 }
352 }
353}
354
355impl Default for ColumnWidthCalculator {
356 fn default() -> Self {
357 Self::new()
358 }
359}
360
361#[cfg(test)]
362mod tests {
363 use super::*;
364 use crate::data::datatable::{DataColumn, DataRow, DataTable, DataValue};
365 use std::sync::Arc;
366
367 fn create_test_dataview() -> DataView {
368 let mut table = DataTable::new("test");
369 table.add_column(DataColumn::new("short"));
370 table.add_column(DataColumn::new("very_long_header_name"));
371 table.add_column(DataColumn::new("normal"));
372
373 for i in 0..5 {
375 let values = vec![
376 DataValue::String("A".to_string()), DataValue::String("X".to_string()), DataValue::String(format!("Value{i}")), ];
380 table.add_row(DataRow::new(values)).unwrap();
381 }
382
383 DataView::new(Arc::new(table))
384 }
385
386 #[test]
387 fn test_column_width_calculator_creation() {
388 let calculator = ColumnWidthCalculator::new();
389 assert_eq!(calculator.get_packing_mode(), ColumnPackingMode::Balanced);
390 assert!(calculator.cache_dirty);
391 }
392
393 #[test]
394 fn test_packing_mode_cycle() {
395 let mut calculator = ColumnWidthCalculator::new();
396
397 assert_eq!(calculator.get_packing_mode(), ColumnPackingMode::Balanced);
398
399 calculator.cycle_packing_mode();
400 assert_eq!(calculator.get_packing_mode(), ColumnPackingMode::DataFocus);
401
402 calculator.cycle_packing_mode();
403 assert_eq!(
404 calculator.get_packing_mode(),
405 ColumnPackingMode::HeaderFocus
406 );
407
408 calculator.cycle_packing_mode();
409 assert_eq!(calculator.get_packing_mode(), ColumnPackingMode::Balanced);
410 }
411
412 #[test]
413 fn test_width_calculation_different_modes() {
414 let dataview = create_test_dataview();
415 let viewport_rows = 0..5;
416 let mut calculator = ColumnWidthCalculator::new();
417
418 calculator.set_packing_mode(ColumnPackingMode::Balanced);
420 let balanced_widths = calculator
421 .get_all_column_widths(&dataview, &viewport_rows)
422 .to_vec();
423
424 calculator.set_packing_mode(ColumnPackingMode::DataFocus);
426 let data_focus_widths = calculator
427 .get_all_column_widths(&dataview, &viewport_rows)
428 .to_vec();
429
430 calculator.set_packing_mode(ColumnPackingMode::HeaderFocus);
432 let header_focus_widths = calculator
433 .get_all_column_widths(&dataview, &viewport_rows)
434 .to_vec();
435
436 assert_eq!(balanced_widths.len(), 3);
439 assert_eq!(data_focus_widths.len(), 3);
440 assert_eq!(header_focus_widths.len(), 3);
441
442 assert!(header_focus_widths[1] >= data_focus_widths[1]);
445 }
446}