use unicode_width::UnicodeWidthChar;
pub fn display_width(s: &str) -> usize {
let stripped = strip_ansi(s);
let mut width = 0;
for ch in stripped.chars() {
width += ch.width().unwrap_or(0);
}
width
}
fn strip_ansi(s: &str) -> String {
let mut result = String::new();
let mut chars = s.chars().peekable();
while let Some(ch) = chars.next() {
if ch == '\x1b' {
if chars.peek() == Some(&'[') {
chars.next(); while let Some(c) = chars.next() {
if c.is_ascii_alphabetic() {
break;
}
}
} else {
}
} else {
result.push(ch);
}
}
result
}
fn format_row(cells: &[String], widths: &[usize]) -> String {
let parts: Vec<String> = cells
.iter()
.zip(widths.iter())
.map(|(cell, w)| {
let visible = display_width(cell);
let padding = if visible < *w { *w - visible } else { 0 };
format!(" {}{} ", cell, " ".repeat(padding))
})
.collect();
format!("|{}|", parts.join("|"))
}
fn format_header_row(headers: &[&str], widths: &[usize]) -> String {
let parts: Vec<String> = headers
.iter()
.zip(widths.iter())
.map(|(h, w)| {
let visible = display_width(h);
let padding = if visible < *w { *w - visible } else { 0 };
format!(" {}{} ", h, " ".repeat(padding))
})
.collect();
format!("|{}|", parts.join("|"))
}
pub fn print_table(headers: &[&str], rows: &[Vec<String>]) {
if rows.is_empty() {
return;
}
let mut widths: Vec<usize> = headers.iter().map(|h| display_width(h)).collect();
for row in rows {
for (i, cell) in row.iter().enumerate() {
if i < widths.len() {
widths[i] = widths[i].max(display_width(cell));
}
}
}
let separator: String = widths
.iter()
.map(|w| "-".repeat(w + 2))
.collect::<Vec<_>>()
.join("+");
println!("+{}+", separator);
println!("{}", format_header_row(headers, &widths));
println!("+{}+", separator);
for row in rows {
println!("{}", format_row(row, &widths));
}
println!("+{}+", separator);
}