From 1a3b1f10fefae3010691345a56b2630660aad249 Mon Sep 17 00:00:00 2001 From: Yung Siang Liau Date: Sun, 14 Jul 2024 18:33:03 +0800 Subject: [PATCH] Improve logic to reduce unused widths --- src/app.rs | 413 +++++++++++++++++++++++++++-------------------------- src/ui.rs | 52 +++++-- 2 files changed, 251 insertions(+), 214 deletions(-) diff --git a/src/app.rs b/src/app.rs index 0aafb93..54b4359 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1179,34 +1179,34 @@ mod tests { step_and_draw(&mut app, &mut terminal, Control::Nothing); let expected = vec![ "──────────────────────────────────────────────────", - " a b c ", - "───┬─────────────────────────────────────────┬────", - "1 │ 1 this is a very… 12345 │ ", - "2 │ 2 thi… 678910 │ ", - "3 │ 3 normal text now 123,456,789 │ ", - " │ │ ", - " │ │ ", - " │ │ ", - " │ │ ", - " │ │ ", - " │ │ ", - " │ │ ", - " │ │ ", - " │ │ ", - " │ │ ", - " │ │ ", - " │ │ ", - " │ │ ", - " │ │ ", - " │ │ ", - " │ │ ", - " │ │ ", - " │ │ ", - " │ │ ", - " │ │ ", - " │ │ ", - " │ │ ", - "───┴─────────────────────────────────────────┴────", + " a b c ", + "───┬─────────────────────────────────────────────┬", + "1 │ 1 this is a very lon… 12345 │", + "2 │ 2 thi… 678910 │", + "3 │ 3 normal text now 123,456,789 │", + " │ │", + " │ │", + " │ │", + " │ │", + " │ │", + " │ │", + " │ │", + " │ │", + " │ │", + " │ │", + " │ │", + " │ │", + " │ │", + " │ │", + " │ │", + " │ │", + " │ │", + " │ │", + " │ │", + " │ │", + " │ │", + " │ │", + "───┴─────────────────────────────────────────────┴", "stdin [Row 1/3, Col 1/3] ", ]; let actual_buffer = terminal.backend().buffer().clone(); @@ -1216,34 +1216,34 @@ mod tests { step_and_draw(&mut app, &mut terminal, Control::ToggleLineWrap(false)); let expected = vec![ "──────────────────────────────────────────────────", - " a b c ", - "───┬─────────────────────────────────────────┬────", - "1 │ 1 this is a very 12345 │ ", - " │ long text that │ ", - " │ surely will not │ ", - " │ fit in your sm │ ", - " │ all screen │ ", - "2 │ 2 this 678910 │ ", - " │ is │ ", - " │ an │ ", - " │ even │ ", - " │ longer │ ", - " │ text │ ", - " │ that │ ", - " │ surely │ ", - " │ will │ ", - " │ not │ ", - " │ fit │ ", - " │ in │ ", - " │ your │ ", - " │ small │ ", - " │ screen │ ", - "3 │ 3 normal text now 123,456,789 │ ", - " │ │ ", - " │ │ ", - " │ │ ", - " │ │ ", - "───┴─────────────────────────────────────────┴────", + " a b c ", + "───┬─────────────────────────────────────────────┬", + "1 │ 1 this is a very long 12345 │", + " │ text that surely w │", + " │ ill not fit in your │", + " │ small screen │", + "2 │ 2 this 678910 │", + " │ is │", + " │ an │", + " │ even │", + " │ longer │", + " │ text │", + " │ that │", + " │ surely │", + " │ will │", + " │ not │", + " │ fit │", + " │ in │", + " │ your │", + " │ small │", + " │ screen │", + "3 │ 3 normal text now 123,456,789 │", + " │ │", + " │ │", + " │ │", + " │ │", + " │ │", + "───┴─────────────────────────────────────────────┴", "Line wrap enabled ", ]; let actual_buffer = terminal.backend().buffer().clone(); @@ -1253,34 +1253,34 @@ mod tests { step_and_draw(&mut app, &mut terminal, Control::ToggleLineWrap(true)); let expected = vec![ "──────────────────────────────────────────────────", - " a b c ", - "───┬─────────────────────────────────────────┬────", - "1 │ 1 this is a very 12345 │ ", - " │ long text that │ ", - " │ surely will │ ", - " │ not fit in │ ", - " │ your small │ ", - " │ screen │ ", - "2 │ 2 this 678910 │ ", - " │ is │ ", - " │ an │ ", - " │ even │ ", - " │ longer │ ", - " │ text │ ", - " │ that │ ", - " │ surely │ ", - " │ will │ ", - " │ not │ ", - " │ fit │ ", - " │ in │ ", - " │ your │ ", - " │ small │ ", - " │ screen │ ", - "3 │ 3 normal text now 123,456,789 │ ", - " │ │ ", - " │ │ ", - " │ │ ", - "───┴─────────────────────────────────────────┴────", + " a b c ", + "───┬─────────────────────────────────────────────┬", + "1 │ 1 this is a very 12345 │", + " │ long text that │", + " │ surely will not │", + " │ fit in your small │", + " │ screen │", + "2 │ 2 this 678910 │", + " │ is │", + " │ an │", + " │ even │", + " │ longer │", + " │ text │", + " │ that │", + " │ surely │", + " │ will │", + " │ not │", + " │ fit │", + " │ in │", + " │ your │", + " │ small │", + " │ screen │", + "3 │ 3 normal text now 123,456,789 │", + " │ │", + " │ │", + " │ │", + " │ │", + "───┴─────────────────────────────────────────────┴", "Word wrap enabled ", ]; let actual_buffer = terminal.backend().buffer().clone(); @@ -1306,6 +1306,7 @@ mod tests { step_and_draw(&mut app, &mut terminal, Control::DecreaseWidth); step_and_draw(&mut app, &mut terminal, Control::DecreaseWidth); step_and_draw(&mut app, &mut terminal, Control::DecreaseWidth); + step_and_draw(&mut app, &mut terminal, Control::DecreaseWidth); let expected = vec![ "──────────────────────────────────────────────────", @@ -1358,49 +1359,49 @@ mod tests { step_and_draw(&mut app, &mut terminal, Control::ToggleLineWrap(true)); let expected = vec![ "──────────────────────────────────────────────────", - " a b c ", - "───┬─────────────────────────────────────────┬────", - "1 │ 1 this is a very 12345 │ ", - " │ long text that │ ", - " │ surely will │ ", - " │ not fit in │ ", - " │ your small │ ", - " │ screen │ ", - "2 │ 2 this 678910 │ ", - " │ │ ", - " │ is │ ", - " │ │ ", - " │ an │ ", - " │ │ ", - " │ even │ ", - " │ │ ", - " │ longer │ ", - " │ │ ", - " │ text │ ", - " │ │ ", - " │ that │ ", - " │ │ ", - " │ surely │ ", - " │ │ ", - " │ will │ ", - " │ │ ", - " │ not │ ", - " │ │ ", - " │ fit │ ", - " │ │ ", - " │ in │ ", - " │ │ ", - " │ your │ ", - " │ │ ", - " │ small │ ", - " │ │ ", - " │ screen │ ", - "3 │ 3 normal text now 123,456,789 │ ", - " │ │ ", - " │ │ ", - " │ │ ", - " │ │ ", - "───┴─────────────────────────────────────────┴────", + " a b c ", + "───┬─────────────────────────────────────────────┬", + "1 │ 1 this is a very 12345 │", + " │ long text that │", + " │ surely will not │", + " │ fit in your small │", + " │ screen │", + "2 │ 2 this 678910 │", + " │ │", + " │ is │", + " │ │", + " │ an │", + " │ │", + " │ even │", + " │ │", + " │ longer │", + " │ │", + " │ text │", + " │ │", + " │ that │", + " │ │", + " │ surely │", + " │ │", + " │ will │", + " │ │", + " │ not │", + " │ │", + " │ fit │", + " │ │", + " │ in │", + " │ │", + " │ your │", + " │ │", + " │ small │", + " │ │", + " │ screen │", + "3 │ 3 normal text now 123,456,789 │", + " │ │", + " │ │", + " │ │", + " │ │", + " │ │", + "───┴─────────────────────────────────────────────┴", "Word wrap enabled ", ]; let actual_buffer = terminal.backend().buffer().clone(); @@ -1422,49 +1423,49 @@ mod tests { step_and_draw(&mut app, &mut terminal, Control::ToggleLineWrap(true)); let expected = vec![ "──────────────────────────────────────────────────", - " a b c ", - "───┬─────────────────────────────────────────┬────", - "1 │ 1 this is a very 12345 │ ", - " │ long text that │ ", - " │ surely will │ ", - " │ not fit in │ ", - " │ your small │ ", - " │ screen │ ", - "2 │ 2 this 678910 │ ", - " │ │ ", - " │ is │ ", - " │ │ ", - " │ an │ ", - " │ │ ", - " │ even │ ", - " │ │ ", - " │ longer │ ", - " │ │ ", - " │ text │ ", - " │ │ ", - " │ that │ ", - " │ │ ", - " │ surely │ ", - " │ │ ", - " │ will │ ", - " │ │ ", - " │ not │ ", - " │ │ ", - " │ fit │ ", - " │ │ ", - " │ in │ ", - " │ │ ", - " │ your │ ", - " │ │ ", - " │ small │ ", - " │ │ ", - " │ screen │ ", - "3 │ 3 normal text now 123,456,789 │ ", - " │ │ ", - " │ │ ", - " │ │ ", - " │ │ ", - "───┴─────────────────────────────────────────┴────", + " a b c ", + "───┬─────────────────────────────────────────────┬", + "1 │ 1 this is a very 12345 │", + " │ long text that │", + " │ surely will not │", + " │ fit in your small │", + " │ screen │", + "2 │ 2 this 678910 │", + " │ │", + " │ is │", + " │ │", + " │ an │", + " │ │", + " │ even │", + " │ │", + " │ longer │", + " │ │", + " │ text │", + " │ │", + " │ that │", + " │ │", + " │ surely │", + " │ │", + " │ will │", + " │ │", + " │ not │", + " │ │", + " │ fit │", + " │ │", + " │ in │", + " │ │", + " │ your │", + " │ │", + " │ small │", + " │ │", + " │ screen │", + "3 │ 3 normal text now 123,456,789 │", + " │ │", + " │ │", + " │ │", + " │ │", + " │ │", + "───┴─────────────────────────────────────────────┴", "Word wrap enabled ", ]; let actual_buffer = terminal.backend().buffer().clone(); @@ -1485,24 +1486,24 @@ mod tests { step_and_draw(&mut app, &mut terminal, Control::Nothing); let expected = vec![ "──────────────────────────────────────────────────", - " a b c ", - "───┬─────────────────────────────────────────┬────", - "1 │ 1 this is a very… 12345 │ ", - "2 │ 2 … 678910 │ ", - "3 │ 3 normal text now 123,456,789 │ ", - " │ │ ", - " │ │ ", - " │ │ ", - " │ │ ", - " │ │ ", - " │ │ ", - " │ │ ", - " │ │ ", - " │ │ ", - " │ │ ", - " │ │ ", - " │ │ ", - "───┴─────────────────────────────────────────┴────", + " a b c ", + "───┬─────────────────────────────────────────────┬", + "1 │ 1 this is a very lon… 12345 │", + "2 │ 2 … 678910 │", + "3 │ 3 normal text now 123,456,789 │", + " │ │", + " │ │", + " │ │", + " │ │", + " │ │", + " │ │", + " │ │", + " │ │", + " │ │", + " │ │", + " │ │", + " │ │", + "───┴─────────────────────────────────────────────┴", "stdin [Row 1/3, Col 1/3] ", ]; let actual_buffer = terminal.backend().buffer().clone(); @@ -1512,24 +1513,24 @@ mod tests { step_and_draw(&mut app, &mut terminal, Control::ToggleLineWrap(true)); let expected = vec![ "──────────────────────────────────────────────────", - " a b c ", - "───┬─────────────────────────────────────────┬────", - "1 │ 1 this is a very 12345 │ ", - " │ long text that │ ", - " │ surely will │ ", - " │ not fit in │ ", - " │ your small │ ", - " │ screen │ ", - "2 │ 2 678910 │ ", - " │ starts with │ ", - " │ new line │ ", - "3 │ 3 normal text now 123,456,789 │ ", - " │ │ ", - " │ │ ", - " │ │ ", - " │ │ ", - " │ │ ", - "───┴─────────────────────────────────────────┴────", + " a b c ", + "───┬─────────────────────────────────────────────┬", + "1 │ 1 this is a very 12345 │", + " │ long text that │", + " │ surely will not │", + " │ fit in your small │", + " │ screen │", + "2 │ 2 678910 │", + " │ starts with new │", + " │ line │", + "3 │ 3 normal text now 123,456,789 │", + " │ │", + " │ │", + " │ │", + " │ │", + " │ │", + " │ │", + "───┴─────────────────────────────────────────────┴", "Word wrap enabled ", ]; let actual_buffer = terminal.backend().buffer().clone(); diff --git a/src/ui.rs b/src/ui.rs index 4c31082..8f23240 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -106,6 +106,7 @@ impl<'a> CsvTable<'a> { } } + // Limit maximum width for a column to make way for other columns let max_single_column_width = (area_width as f32 * MAX_COLUMN_WIDTH_FRACTION) as u16; let mut clipped_columns: Vec<(usize, u16)> = vec![]; for (i, w) in column_widths.iter_mut().enumerate() { @@ -121,17 +122,48 @@ impl<'a> CsvTable<'a> { } // If clipping was too aggressive, redistribute the remaining width - // TODO: adjust the columns with smaller unclipped widths first to reduce chance of wasted space + CsvTable::redistribute_widths_after_clpping( + &mut column_widths, + area_width, + clipped_columns, + ); + + column_widths + } + + fn redistribute_widths_after_clpping( + column_widths: &mut [u16], + area_width: u16, + mut clipped_columns: Vec<(usize, u16)>, + ) { + if clipped_columns.is_empty() { + // Nothing to adjust + return; + } + let total_width: u16 = column_widths.iter().sum(); - if total_width < area_width && !clipped_columns.is_empty() { - let remaining_width = area_width.saturating_sub(total_width); - let adjustment = remaining_width / clipped_columns.len() as u16; - for (i, width_before_clipping) in clipped_columns { - column_widths[i] = min(width_before_clipping, column_widths[i] + adjustment); - } + if total_width >= area_width { + // No need to adjust if we're already using the full width + return; } - column_widths + // Greedily adjust from the narrowest column by equally distributing the remaining width. If + // a column doesn't use the allocated adjustment, subsequent columns will get to use it. + clipped_columns.sort_by_key(|x| x.1); + + // Subtract 1 to leave space for the right border. If not, this will be too greedy and + // consume all the space making that border disappear. + let mut remaining_width = area_width.saturating_sub(total_width).saturating_sub(1); + + let mut num_columns_to_adjust = clipped_columns.len(); + for (i, width_before_clipping) in clipped_columns { + let adjustment = remaining_width / num_columns_to_adjust as u16; + let width_after_adjustment = min(width_before_clipping, column_widths[i] + adjustment); + let added_width = width_after_adjustment - column_widths[i]; + column_widths[i] = width_after_adjustment; + remaining_width -= added_width; + num_columns_to_adjust -= 1; + } } fn get_row_heights( @@ -247,6 +279,10 @@ impl<'a> CsvTable<'a> { let y_first_record = borders_state.y_first_record; let section_width = borders_state.x_row_separator; + if area.width < section_width { + return; + } + let line_number_block = Block::default() .borders(Borders::RIGHT) .border_style(Style::default().fg(Color::Rgb(64, 64, 64)));