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 pub fn cycle(&self) -> Self {
28 match self {
29 ColumnPackingMode::Balanced => ColumnPackingMode::DataFocus,
30 ColumnPackingMode::DataFocus => ColumnPackingMode::HeaderFocus,
31 ColumnPackingMode::HeaderFocus => ColumnPackingMode::Balanced,
32 }
33 }
34
35 pub fn display_name(&self) -> &'static str {
37 match self {
38 ColumnPackingMode::Balanced => "Balanced",
39 ColumnPackingMode::DataFocus => "Data Focus",
40 ColumnPackingMode::HeaderFocus => "Header Focus",
41 }
42 }
43}
44
45pub type ColumnWidthDebugInfo = (String, u16, u16, u16, u32);
47
48pub struct ColumnWidthCalculator {
51 column_widths: Vec<u16>,
53 packing_mode: ColumnPackingMode,
55 column_width_debug: Vec<ColumnWidthDebugInfo>,
58 cache_dirty: bool,
60}
61
62impl ColumnWidthCalculator {
63 pub fn new() -> Self {
65 Self {
66 column_widths: Vec::new(),
67 packing_mode: ColumnPackingMode::Balanced,
68 column_width_debug: Vec::new(),
69 cache_dirty: true,
70 }
71 }
72
73 pub fn get_packing_mode(&self) -> ColumnPackingMode {
75 self.packing_mode
76 }
77
78 pub fn set_packing_mode(&mut self, mode: ColumnPackingMode) {
80 if self.packing_mode != mode {
81 self.packing_mode = mode;
82 self.cache_dirty = true;
83 }
84 }
85
86 pub fn cycle_packing_mode(&mut self) {
88 self.set_packing_mode(self.packing_mode.cycle());
89 }
90
91 pub fn get_debug_info(&self) -> &[ColumnWidthDebugInfo] {
93 &self.column_width_debug
94 }
95
96 pub fn mark_dirty(&mut self) {
98 self.cache_dirty = true;
99 }
100
101 pub fn calculate_with_terminal_width(
103 &mut self,
104 dataview: &DataView,
105 viewport_rows: &std::ops::Range<usize>,
106 terminal_width: u16,
107 ) {
108 self.recalculate_column_widths(dataview, viewport_rows);
110
111 let total_ideal_width: u16 = self.column_widths.iter().sum();
113 let separators_width = (self.column_widths.len() as u16).saturating_sub(1);
114 let borders_width = 4u16; let total_needed = total_ideal_width + separators_width + borders_width;
117
118 if total_needed < terminal_width {
121 let extra_space = terminal_width - total_needed;
123 let num_columns = self.column_widths.len() as u16;
124 let space_per_column = if num_columns > 0 {
125 extra_space / num_columns
126 } else {
127 0
128 };
129
130 for (idx, width) in self.column_widths.iter_mut().enumerate() {
132 if *width <= 10 && idx < self.column_width_debug.len() {
133 let (_, header_w, data_w, _, _) = &self.column_width_debug[idx];
134 let ideal = (*header_w).max(*data_w) + COLUMN_PADDING;
135
136 if ideal > *width && ideal <= 15 {
138 *width = ideal.min(*width + space_per_column);
139 }
140 }
141 }
142 }
143 }
144
145 pub fn get_column_width(
147 &mut self,
148 dataview: &DataView,
149 viewport_rows: &std::ops::Range<usize>,
150 col_idx: usize,
151 ) -> u16 {
152 if self.cache_dirty {
153 self.recalculate_column_widths(dataview, viewport_rows);
154 }
155
156 self.column_widths
157 .get(col_idx)
158 .copied()
159 .unwrap_or(DEFAULT_COL_WIDTH)
160 }
161
162 pub fn get_all_column_widths(
164 &mut self,
165 dataview: &DataView,
166 viewport_rows: &std::ops::Range<usize>,
167 ) -> &[u16] {
168 if self.cache_dirty {
169 self.recalculate_column_widths(dataview, viewport_rows);
170 }
171
172 &self.column_widths
173 }
174
175 fn recalculate_column_widths(
178 &mut self,
179 dataview: &DataView,
180 viewport_rows: &std::ops::Range<usize>,
181 ) {
182 let col_count = dataview.column_count();
183 self.column_widths.resize(col_count, DEFAULT_COL_WIDTH);
184
185 self.column_width_debug.clear();
187
188 let headers = dataview.column_names();
190
191 let mut ideal_widths = Vec::with_capacity(col_count);
193 let mut header_widths = Vec::with_capacity(col_count);
194 let mut max_data_widths = Vec::with_capacity(col_count);
195
196 for col_idx in 0..col_count {
198 let header_width = headers.get(col_idx).map(|h| h.len() as u16).unwrap_or(0);
200 header_widths.push(header_width);
201
202 let mut max_data_width = 0u16;
204 let mut data_samples = 0u32;
205
206 let sample_size = 100.min(viewport_rows.len());
208 let sample_step = if viewport_rows.len() > sample_size {
209 viewport_rows.len() / sample_size
210 } else {
211 1
212 };
213
214 for (i, row_idx) in viewport_rows.clone().enumerate() {
215 if i % sample_step != 0 && i != 0 && i != viewport_rows.len() - 1 {
217 continue;
218 }
219
220 if let Some(row) = dataview.get_row(row_idx) {
221 if col_idx < row.values.len() {
222 let cell_str = row.values[col_idx].to_string();
223 let cell_width = cell_str.len() as u16;
224 max_data_width = max_data_width.max(cell_width);
225 data_samples += 1;
226 }
227 }
228 }
229
230 max_data_widths.push(max_data_width);
231
232 let ideal_width = header_width.max(max_data_width) + COLUMN_PADDING;
234 ideal_widths.push(ideal_width);
235 }
236
237 for col_idx in 0..col_count {
239 let header_width = header_widths[col_idx];
240 let max_data_width = max_data_widths[col_idx];
241 let ideal_width = ideal_widths[col_idx];
242
243 let final_width = if ideal_width <= 10 {
246 ideal_width
248 } else {
249 let data_samples = if max_data_width > 0 { 1 } else { 0 };
251 let optimal_width = self.calculate_optimal_width_for_mode(
252 header_width,
253 max_data_width,
254 data_samples,
255 );
256
257 let (min_width, max_width) = match self.packing_mode {
259 ColumnPackingMode::DataFocus => (MIN_COL_WIDTH, MAX_COL_WIDTH_DATA_FOCUS),
260 _ => (MIN_COL_WIDTH, MAX_COL_WIDTH),
261 };
262
263 optimal_width.clamp(min_width, max_width)
264 };
265
266 self.column_widths[col_idx] = final_width;
267
268 let column_name = headers
270 .get(col_idx)
271 .map(|s| s.clone())
272 .unwrap_or_else(|| format!("col_{}", col_idx));
273 self.column_width_debug.push((
274 column_name,
275 header_width,
276 max_data_width,
277 final_width,
278 1, ));
280 }
281
282 self.cache_dirty = false;
283 }
284
285 fn calculate_optimal_width_for_mode(
287 &self,
288 header_width: u16,
289 max_data_width: u16,
290 data_samples: u32,
291 ) -> u16 {
292 match self.packing_mode {
293 ColumnPackingMode::DataFocus => {
294 if data_samples > 0 {
296 if max_data_width <= 3 {
299 max_data_width + COLUMN_PADDING
302 } else if max_data_width <= 10 && header_width > max_data_width * 2 {
303 (max_data_width + COLUMN_PADDING).max(MIN_HEADER_WIDTH_DATA_FOCUS)
306 } else {
307 let data_width =
309 (max_data_width + COLUMN_PADDING).min(MAX_COL_WIDTH_DATA_FOCUS);
310
311 data_width.max(MIN_HEADER_WIDTH_DATA_FOCUS)
313 }
314 } else {
315 header_width
317 .min(DEFAULT_COL_WIDTH)
318 .max(MIN_HEADER_WIDTH_DATA_FOCUS)
319 }
320 }
321 ColumnPackingMode::HeaderFocus => {
322 let header_with_padding = header_width + COLUMN_PADDING;
324
325 if data_samples > 0 {
326 header_with_padding.max(max_data_width.min(MAX_COL_WIDTH))
328 } else {
329 header_with_padding
330 }
331 }
332 ColumnPackingMode::Balanced => {
333 if data_samples > 0 {
335 let data_based_width = max_data_width + COLUMN_PADDING;
336
337 if header_width > max_data_width {
338 let max_allowed_header =
339 (max_data_width as f32 * MAX_HEADER_TO_DATA_RATIO) as u16;
340 data_based_width.max(header_width.min(max_allowed_header))
341 } else {
342 data_based_width.max(header_width)
343 }
344 } else {
345 header_width.max(DEFAULT_COL_WIDTH)
346 }
347 }
348 }
349 }
350}
351
352impl Default for ColumnWidthCalculator {
353 fn default() -> Self {
354 Self::new()
355 }
356}
357
358#[cfg(test)]
359mod tests {
360 use super::*;
361 use crate::data::datatable::{DataColumn, DataRow, DataTable, DataValue};
362 use std::sync::Arc;
363
364 fn create_test_dataview() -> DataView {
365 let mut table = DataTable::new("test");
366 table.add_column(DataColumn::new("short"));
367 table.add_column(DataColumn::new("very_long_header_name"));
368 table.add_column(DataColumn::new("normal"));
369
370 for i in 0..5 {
372 let values = vec![
373 DataValue::String("A".to_string()), DataValue::String("X".to_string()), DataValue::String(format!("Value{}", i)), ];
377 table.add_row(DataRow::new(values)).unwrap();
378 }
379
380 DataView::new(Arc::new(table))
381 }
382
383 #[test]
384 fn test_column_width_calculator_creation() {
385 let calculator = ColumnWidthCalculator::new();
386 assert_eq!(calculator.get_packing_mode(), ColumnPackingMode::Balanced);
387 assert!(calculator.cache_dirty);
388 }
389
390 #[test]
391 fn test_packing_mode_cycle() {
392 let mut calculator = ColumnWidthCalculator::new();
393
394 assert_eq!(calculator.get_packing_mode(), ColumnPackingMode::Balanced);
395
396 calculator.cycle_packing_mode();
397 assert_eq!(calculator.get_packing_mode(), ColumnPackingMode::DataFocus);
398
399 calculator.cycle_packing_mode();
400 assert_eq!(
401 calculator.get_packing_mode(),
402 ColumnPackingMode::HeaderFocus
403 );
404
405 calculator.cycle_packing_mode();
406 assert_eq!(calculator.get_packing_mode(), ColumnPackingMode::Balanced);
407 }
408
409 #[test]
410 fn test_width_calculation_different_modes() {
411 let dataview = create_test_dataview();
412 let viewport_rows = 0..5;
413 let mut calculator = ColumnWidthCalculator::new();
414
415 calculator.set_packing_mode(ColumnPackingMode::Balanced);
417 let balanced_widths = calculator
418 .get_all_column_widths(&dataview, &viewport_rows)
419 .to_vec();
420
421 calculator.set_packing_mode(ColumnPackingMode::DataFocus);
423 let data_focus_widths = calculator
424 .get_all_column_widths(&dataview, &viewport_rows)
425 .to_vec();
426
427 calculator.set_packing_mode(ColumnPackingMode::HeaderFocus);
429 let header_focus_widths = calculator
430 .get_all_column_widths(&dataview, &viewport_rows)
431 .to_vec();
432
433 assert_eq!(balanced_widths.len(), 3);
436 assert_eq!(data_focus_widths.len(), 3);
437 assert_eq!(header_focus_widths.len(), 3);
438
439 assert!(header_focus_widths[1] >= data_focus_widths[1]);
442 }
443}