#[derive(Debug, Clone)]
pub struct ResizerColumn {
pub index: usize,
pub min: usize,
pub max: usize,
pub median: usize,
pub rows: Vec<Vec<String>>,
pub x_padding: usize,
pub fixed_width: usize,
}
impl ResizerColumn {
pub fn new(index: usize) -> Self {
Self {
index,
min: 0,
max: 0,
median: 0,
rows: Vec::new(),
x_padding: 0,
fixed_width: 0,
}
}
}
#[derive(Debug)]
pub struct Resizer {
pub table_width: i32,
pub table_height: i32,
pub headers: Vec<String>,
pub all_rows: Vec<Vec<String>>,
pub row_heights: Vec<usize>,
pub columns: Vec<ResizerColumn>,
pub wrap: bool,
pub border_column: bool,
pub y_paddings: Vec<Vec<usize>>,
}
impl Resizer {
pub fn new(
table_width: i32,
table_height: i32,
headers: Vec<String>,
rows: Vec<Vec<String>>,
) -> Self {
let mut resizer = Self {
table_width,
table_height,
headers: headers.clone(),
all_rows: Vec::new(),
row_heights: Vec::new(),
columns: Vec::new(),
wrap: true,
border_column: true,
y_paddings: Vec::new(),
};
if !headers.is_empty() {
resizer.all_rows.push(headers);
}
resizer.all_rows.extend(rows);
let max_cols = resizer
.all_rows
.iter()
.map(|row| row.len())
.max()
.unwrap_or(0);
for col_idx in 0..max_cols {
let mut column = ResizerColumn::new(col_idx);
column.rows = resizer.all_rows.clone();
let mut widths = Vec::new();
for row in &resizer.all_rows {
let cell_content = row.get(col_idx).map(|s| s.as_str()).unwrap_or("");
let width = display_width(cell_content);
widths.push(width);
column.min = column.min.max(width);
column.max = column.max.max(width);
}
column.median = calculate_median(&widths);
resizer.columns.push(column);
}
resizer
}
pub fn default_row_heights(&self) -> Vec<usize> {
vec![1; self.all_rows.len()]
}
pub fn detect_table_width(&self) -> i32 {
let content_width: usize = self.columns.iter().map(|col| col.max + col.x_padding).sum();
let border_width = self.total_horizontal_border();
(content_width + border_width) as i32
}
pub fn max_total(&self) -> usize {
let content_width: usize = self.columns.iter().map(|col| col.max + col.x_padding).sum();
content_width + self.total_horizontal_border()
}
pub fn max_column_widths(&self) -> Vec<usize> {
self.columns
.iter()
.map(|col| {
if col.fixed_width > 0 {
col.fixed_width
} else {
col.max + col.x_padding
}
})
.collect()
}
pub fn total_horizontal_border(&self) -> usize {
if self.border_column && !self.columns.is_empty() {
self.columns.len() + 1 } else {
0
}
}
pub fn optimized_widths(&mut self) -> (Vec<usize>, Vec<usize>) {
if self.max_total() <= self.table_width as usize {
self.expand_table_width()
} else {
self.shrink_table_width()
}
}
fn expand_table_width(&mut self) -> (Vec<usize>, Vec<usize>) {
let mut col_widths = self.max_column_widths();
loop {
let total_width = col_widths.iter().sum::<usize>() + self.total_horizontal_border();
if total_width >= self.table_width as usize {
break;
}
let mut shortest_idx = 0;
let mut shortest_width = usize::MAX;
for (j, &width) in col_widths.iter().enumerate() {
if self.columns[j].fixed_width > 0 {
continue; }
if width < shortest_width {
shortest_width = width;
shortest_idx = j;
}
}
col_widths[shortest_idx] += 1;
}
let row_heights = self.expand_row_heights(&col_widths);
(col_widths, row_heights)
}
fn shrink_table_width(&mut self) -> (Vec<usize>, Vec<usize>) {
let mut col_widths = self.max_column_widths();
self.shrink_biggest_columns(&mut col_widths, true);
self.shrink_to_median(&mut col_widths);
self.shrink_biggest_columns(&mut col_widths, false);
let row_heights = self.expand_row_heights(&col_widths);
(col_widths, row_heights)
}
fn shrink_biggest_columns(&mut self, col_widths: &mut [usize], very_big_only: bool) {
loop {
let total_width = col_widths.iter().sum::<usize>() + self.total_horizontal_border();
if total_width <= self.table_width as usize {
break;
}
let mut biggest_idx = None;
let mut biggest_width = 0;
for (j, &width) in col_widths.iter().enumerate() {
if self.columns[j].fixed_width > 0 {
continue; }
if very_big_only && width < (self.table_width as usize / 2) {
continue; }
if width > biggest_width {
biggest_width = width;
biggest_idx = Some(j);
}
}
if let Some(idx) = biggest_idx {
if col_widths[idx] > 0 {
col_widths[idx] -= 1;
} else {
break; }
} else {
break; }
}
}
fn shrink_to_median(&mut self, col_widths: &mut [usize]) {
loop {
let total_width = col_widths.iter().sum::<usize>() + self.total_horizontal_border();
if total_width <= self.table_width as usize {
break;
}
let mut target_idx = None;
let mut max_diff = 0;
for (j, &width) in col_widths.iter().enumerate() {
if self.columns[j].fixed_width > 0 {
continue; }
let median_width = self.columns[j].median + self.columns[j].x_padding;
if width > median_width {
let diff = width - median_width;
if diff > max_diff {
max_diff = diff;
target_idx = Some(j);
}
}
}
if let Some(idx) = target_idx {
if col_widths[idx] > 0 {
col_widths[idx] -= 1;
} else {
break;
}
} else {
let mut biggest_idx = None;
let mut biggest_width = 0;
for (j, &width) in col_widths.iter().enumerate() {
if self.columns[j].fixed_width > 0 {
continue;
}
if width > biggest_width {
biggest_width = width;
biggest_idx = Some(j);
}
}
if let Some(idx) = biggest_idx {
if col_widths[idx] > 0 {
col_widths[idx] -= 1;
} else {
break;
}
} else {
break;
}
}
}
}
fn expand_row_heights(&self, col_widths: &[usize]) -> Vec<usize> {
let mut row_heights = self.default_row_heights();
let has_headers = !self.headers.is_empty();
for (i, row) in self.all_rows.iter().enumerate() {
for (j, cell) in row.iter().enumerate() {
if has_headers && i == 0 {
continue;
}
if j >= col_widths.len() {
continue;
}
let content_width = col_widths[j].saturating_sub(self.x_padding_for_col(j));
let cell_height =
self.detect_content_height(cell, content_width) + self.y_padding_for_cell(i, j);
row_heights[i] = row_heights[i].max(cell_height);
}
}
row_heights
}
fn detect_content_height(&self, content: &str, width: usize) -> usize {
if width == 0 {
return 1;
}
let content = content.replace("\r\n", "\n");
let mut height = 0;
for line in content.lines() {
if line.is_empty() {
height += 1;
} else {
height += self.calculate_wrapped_line_height(line, width);
}
}
height.max(1)
}
fn calculate_wrapped_line_height(&self, line: &str, width: usize) -> usize {
let line_width = display_width(line);
if line_width <= width {
return 1;
}
let words: Vec<&str> = line.split_whitespace().collect();
if words.is_empty() {
return 1;
}
let mut lines = 1;
let mut current_width = 0;
for word in words {
let word_width = display_width(word);
if word_width > width {
if current_width > 0 {
lines += 1; }
lines += word_width.div_ceil(width);
current_width = word_width % width;
} else {
let needed_width = if current_width > 0 {
current_width + 1 + word_width } else {
word_width
};
if needed_width > width {
lines += 1; current_width = word_width;
} else {
current_width = needed_width;
}
}
}
lines
}
fn x_padding_for_col(&self, col: usize) -> usize {
if col < self.columns.len() {
self.columns[col].x_padding
} else {
0
}
}
fn y_padding_for_cell(&self, row: usize, col: usize) -> usize {
if row < self.y_paddings.len() && col < self.y_paddings[row].len() {
self.y_paddings[row][col]
} else {
0
}
}
}
fn calculate_median(numbers: &[usize]) -> usize {
if numbers.is_empty() {
return 0;
}
let mut sorted = numbers.to_vec();
sorted.sort_unstable();
let len = sorted.len();
if len.is_multiple_of(2) {
let h = len / 2;
(sorted[h - 1] + sorted[h]) / 2
} else {
sorted[len / 2]
}
}
fn display_width(s: &str) -> usize {
lipgloss::width(s)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_resizer_new() {
let headers = vec!["Name".to_string(), "Age".to_string()];
let rows = vec![
vec!["Alice".to_string(), "30".to_string()],
vec!["Bob".to_string(), "25".to_string()],
];
let resizer = Resizer::new(80, 0, headers, rows);
assert_eq!(resizer.columns.len(), 2);
assert_eq!(resizer.all_rows.len(), 3); }
#[test]
fn test_calculate_median() {
assert_eq!(calculate_median(&[]), 0);
assert_eq!(calculate_median(&[5]), 5);
assert_eq!(calculate_median(&[1, 3, 5]), 3);
assert_eq!(calculate_median(&[1, 2, 3, 4]), 2); assert_eq!(calculate_median(&[4, 1, 3, 2]), 2); }
#[test]
fn test_display_width() {
assert_eq!(display_width("hello"), 5);
assert_eq!(display_width(""), 0);
assert_eq!(display_width("测试"), 4); }
#[test]
fn test_detect_content_height() {
let resizer = Resizer::new(80, 0, vec![], vec![]);
assert_eq!(resizer.detect_content_height("hello", 10), 1);
assert_eq!(resizer.detect_content_height("hello\nworld", 10), 2);
assert_eq!(resizer.detect_content_height("", 10), 1);
assert_eq!(resizer.detect_content_height("hello world", 5), 2); }
#[test]
fn test_max_column_widths() {
let headers = vec!["Name".to_string(), "Age".to_string()];
let rows = vec![
vec!["Alice".to_string(), "30".to_string()],
vec!["Bob".to_string(), "25".to_string()],
];
let resizer = Resizer::new(80, 0, headers, rows);
let widths = resizer.max_column_widths();
assert_eq!(widths.len(), 2);
assert_eq!(widths[0], 5); assert_eq!(widths[1], 3); }
}