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 let mut data_samples = 0u32;
210
211 let sample_size = 100.min(viewport_rows.len());
213 let sample_step = if viewport_rows.len() > sample_size {
214 viewport_rows.len() / sample_size
215 } else {
216 1
217 };
218
219 for (i, row_idx) in viewport_rows.clone().enumerate() {
220 if i % sample_step != 0 && i != 0 && i != viewport_rows.len() - 1 {
222 continue;
223 }
224
225 if let Some(row) = dataview.get_row(row_idx) {
226 if col_idx < row.values.len() {
227 let cell_str = row.values[col_idx].to_string();
228 let cell_width = cell_str.len() as u16;
229 max_data_width = max_data_width.max(cell_width);
230 data_samples += 1;
231 }
232 }
233 }
234
235 max_data_widths.push(max_data_width);
236
237 let ideal_width = header_width.max(max_data_width) + COLUMN_PADDING;
239 ideal_widths.push(ideal_width);
240 }
241
242 for col_idx in 0..col_count {
244 let header_width = header_widths[col_idx];
245 let max_data_width = max_data_widths[col_idx];
246 let ideal_width = ideal_widths[col_idx];
247
248 let final_width = if ideal_width <= 10 {
251 ideal_width
253 } else {
254 let data_samples = u32::from(max_data_width > 0);
256 let optimal_width = self.calculate_optimal_width_for_mode(
257 header_width,
258 max_data_width,
259 data_samples,
260 );
261
262 let (min_width, max_width) = match self.packing_mode {
264 ColumnPackingMode::DataFocus => (MIN_COL_WIDTH, MAX_COL_WIDTH_DATA_FOCUS),
265 _ => (MIN_COL_WIDTH, MAX_COL_WIDTH),
266 };
267
268 optimal_width.clamp(min_width, max_width)
269 };
270
271 self.column_widths[col_idx] = final_width;
272
273 let column_name = headers
275 .get(col_idx)
276 .cloned()
277 .unwrap_or_else(|| format!("col_{col_idx}"));
278 self.column_width_debug.push((
279 column_name,
280 header_width,
281 max_data_width,
282 final_width,
283 1, ));
285 }
286
287 self.cache_dirty = false;
288 }
289
290 fn calculate_optimal_width_for_mode(
292 &self,
293 header_width: u16,
294 max_data_width: u16,
295 data_samples: u32,
296 ) -> u16 {
297 match self.packing_mode {
298 ColumnPackingMode::DataFocus => {
299 if data_samples > 0 {
301 if max_data_width <= 3 {
304 max_data_width + COLUMN_PADDING
307 } else if max_data_width <= 10 && header_width > max_data_width * 2 {
308 (max_data_width + COLUMN_PADDING).max(MIN_HEADER_WIDTH_DATA_FOCUS)
311 } else {
312 let data_width =
314 (max_data_width + COLUMN_PADDING).min(MAX_COL_WIDTH_DATA_FOCUS);
315
316 data_width.max(MIN_HEADER_WIDTH_DATA_FOCUS)
318 }
319 } else {
320 header_width
322 .min(DEFAULT_COL_WIDTH)
323 .max(MIN_HEADER_WIDTH_DATA_FOCUS)
324 }
325 }
326 ColumnPackingMode::HeaderFocus => {
327 let header_with_padding = header_width + COLUMN_PADDING;
329
330 if data_samples > 0 {
331 header_with_padding.max(max_data_width.min(MAX_COL_WIDTH))
333 } else {
334 header_with_padding
335 }
336 }
337 ColumnPackingMode::Balanced => {
338 if data_samples > 0 {
340 let data_based_width = max_data_width + COLUMN_PADDING;
341
342 if header_width > max_data_width {
343 let max_allowed_header =
344 (f32::from(max_data_width) * MAX_HEADER_TO_DATA_RATIO) as u16;
345 data_based_width.max(header_width.min(max_allowed_header))
346 } else {
347 data_based_width.max(header_width)
348 }
349 } else {
350 header_width.max(DEFAULT_COL_WIDTH)
351 }
352 }
353 }
354 }
355}
356
357impl Default for ColumnWidthCalculator {
358 fn default() -> Self {
359 Self::new()
360 }
361}
362
363#[cfg(test)]
364mod tests {
365 use super::*;
366 use crate::data::datatable::{DataColumn, DataRow, DataTable, DataValue};
367 use std::sync::Arc;
368
369 fn create_test_dataview() -> DataView {
370 let mut table = DataTable::new("test");
371 table.add_column(DataColumn::new("short"));
372 table.add_column(DataColumn::new("very_long_header_name"));
373 table.add_column(DataColumn::new("normal"));
374
375 for i in 0..5 {
377 let values = vec![
378 DataValue::String("A".to_string()), DataValue::String("X".to_string()), DataValue::String(format!("Value{i}")), ];
382 table.add_row(DataRow::new(values)).unwrap();
383 }
384
385 DataView::new(Arc::new(table))
386 }
387
388 #[test]
389 fn test_column_width_calculator_creation() {
390 let calculator = ColumnWidthCalculator::new();
391 assert_eq!(calculator.get_packing_mode(), ColumnPackingMode::Balanced);
392 assert!(calculator.cache_dirty);
393 }
394
395 #[test]
396 fn test_packing_mode_cycle() {
397 let mut calculator = ColumnWidthCalculator::new();
398
399 assert_eq!(calculator.get_packing_mode(), ColumnPackingMode::Balanced);
400
401 calculator.cycle_packing_mode();
402 assert_eq!(calculator.get_packing_mode(), ColumnPackingMode::DataFocus);
403
404 calculator.cycle_packing_mode();
405 assert_eq!(
406 calculator.get_packing_mode(),
407 ColumnPackingMode::HeaderFocus
408 );
409
410 calculator.cycle_packing_mode();
411 assert_eq!(calculator.get_packing_mode(), ColumnPackingMode::Balanced);
412 }
413
414 #[test]
415 fn test_width_calculation_different_modes() {
416 let dataview = create_test_dataview();
417 let viewport_rows = 0..5;
418 let mut calculator = ColumnWidthCalculator::new();
419
420 calculator.set_packing_mode(ColumnPackingMode::Balanced);
422 let balanced_widths = calculator
423 .get_all_column_widths(&dataview, &viewport_rows)
424 .to_vec();
425
426 calculator.set_packing_mode(ColumnPackingMode::DataFocus);
428 let data_focus_widths = calculator
429 .get_all_column_widths(&dataview, &viewport_rows)
430 .to_vec();
431
432 calculator.set_packing_mode(ColumnPackingMode::HeaderFocus);
434 let header_focus_widths = calculator
435 .get_all_column_widths(&dataview, &viewport_rows)
436 .to_vec();
437
438 assert_eq!(balanced_widths.len(), 3);
441 assert_eq!(data_focus_widths.len(), 3);
442 assert_eq!(header_focus_widths.len(), 3);
443
444 assert!(header_focus_widths[1] >= data_focus_widths[1]);
447 }
448}