feat: Improve info table rendering by truncating wide columns

Co-authored-by: aider (openai/andrew/openrouter/deepseek/deepseek-chat-v3.1) <aider@aider.chat>
This commit is contained in:
Andrew Phillips
2025-09-08 17:35:14 -03:00
parent 44d039a7c2
commit aa8b942f2d
2 changed files with 124 additions and 32 deletions

View File

@@ -221,3 +221,57 @@ pub fn get_terminal_width() -> usize {
// Default to 80 if all else fails // Default to 80 if all else fails
80 80
} }
/// Calculate the maximum width for the last column in a table
/// Takes into account the widths of other columns and table borders
pub fn calculate_last_column_width(column_widths: &[usize], terminal_width: usize) -> usize {
if terminal_width == 0 {
return 0;
}
// Each column has 1 character padding on each side, and there are borders between columns
// The table format uses '│' characters between columns, which take 1 character each
let total_other_columns_width: usize = column_widths.iter().map(|&w| w + 2).sum();
let border_characters = column_widths.len().saturating_sub(1);
// Total width used by other columns and their formatting
let used_width = total_other_columns_width + border_characters;
if used_width >= terminal_width {
return 0;
}
// The last column also has 2 characters for padding
terminal_width - used_width - 2
}
/// Truncate a string to fit within a maximum width, adding an ellipsis if truncated
pub fn truncate_with_ellipsis(s: &str, max_width: usize) -> String {
if max_width == 0 {
return String::new();
}
if s.chars().count() <= max_width {
return s.to_string();
}
// We need to truncate and add an ellipsis
// The ellipsis takes 1 character, so we can show max_width-1 characters
if max_width == 1 {
return "".to_string();
}
let mut result = String::new();
let mut current_width = 0;
for c in s.chars() {
if current_width + 1 > max_width - 1 {
break;
}
result.push(c);
current_width += 1;
}
result.push('…');
result
}

View File

@@ -75,65 +75,103 @@ fn show_item(
table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR); table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
} }
table.add_row(Row::new(vec![ // Collect all rows to calculate column widths
let mut rows = Vec::new();
// Add all the rows
rows.push(Row::new(vec![
Cell::new("ID").with_style(Attr::Bold), Cell::new("ID").with_style(Attr::Bold),
Cell::new(&item_id.to_string()), Cell::new(&item_id.to_string()),
])); ]));
let ts_cell = Cell::new(&item.ts.with_timezone(&Local).format("%F %T %Z").to_string()); let timestamp_str = item.ts.with_timezone(&Local).format("%F %T %Z").to_string();
table.add_row(Row::new(vec![ rows.push(Row::new(vec![
Cell::new("Timestamp").with_style(Attr::Bold), Cell::new("Timestamp").with_style(Attr::Bold),
ts_cell, Cell::new(&timestamp_str),
])); ]));
let mut item_path_buf = data_path.clone(); // Renamed to avoid conflict if item_path is used later let mut item_path_buf = data_path.clone();
item_path_buf.push(item.id.unwrap().to_string()); // Again, consider safer unwrap item_path_buf.push(item.id.unwrap().to_string());
let path_str = item_path_buf.to_str().expect("Unable to get item path").to_string();
table.add_row(Row::new(vec![ rows.push(Row::new(vec![
Cell::new("Path").with_style(Attr::Bold), Cell::new("Path").with_style(Attr::Bold),
Cell::new(item_path_buf.to_str().expect("Unable to get item path")), Cell::new(&path_str),
])); ]));
let size_cell = match item.size { let size_str = match item.size {
Some(size) => Cell::new(format_size(size as u64, settings.human_readable).as_str()), Some(size) => format_size(size as u64, settings.human_readable),
None => Cell::new("Missing") None => "Missing".to_string(),
.with_style(Attr::ForegroundColor(prettytable::color::RED))
.with_style(Attr::Bold),
}; };
table.add_row(Row::new(vec![ rows.push(Row::new(vec![
Cell::new("Stream Size").with_style(Attr::Bold), Cell::new("Stream Size").with_style(Attr::Bold),
size_cell, Cell::new(&size_str),
])); ]));
table.add_row(Row::new(vec![ rows.push(Row::new(vec![
Cell::new("Compression").with_style(Attr::Bold), Cell::new("Compression").with_style(Attr::Bold),
Cell::new(&item.compression), Cell::new(&item.compression),
])); ]));
let file_size_cell = match item_path_buf.metadata() { let file_size_str = match item_path_buf.metadata() {
Ok(metadata) => { Ok(metadata) => format_size(metadata.len(), settings.human_readable),
Cell::new(format_size(metadata.len(), settings.human_readable).as_str()) Err(_) => "Missing".to_string(),
}
Err(_) => Cell::new("Missing")
.with_style(Attr::ForegroundColor(prettytable::color::RED))
.with_style(Attr::Bold),
}; };
table.add_row(Row::new(vec![ rows.push(Row::new(vec![
Cell::new("File Size").with_style(Attr::Bold), Cell::new("File Size").with_style(Attr::Bold),
file_size_cell, Cell::new(&file_size_str),
])); ]));
table.add_row(Row::new(vec![ let tags_str = item_tags.join(" ");
rows.push(Row::new(vec![
Cell::new("Tags").with_style(Attr::Bold), Cell::new("Tags").with_style(Attr::Bold),
Cell::new(&item_tags.join(" ")), Cell::new(&tags_str),
])); ]));
// Collect meta rows to potentially truncate the last one
let mut meta_rows = Vec::new();
for meta in item_with_meta.meta { for meta in item_with_meta.meta {
let meta_name = format!("Meta: {}", &meta.name); let meta_name = format!("Meta: {}", &meta.name);
table.add_row(Row::new(vec![ meta_rows.push((meta_name, meta.value));
Cell::new(meta_name.as_str()).with_style(Attr::Bold), }
Cell::new(&meta.value),
])); // Calculate the maximum width for the last column if we're in a terminal
let terminal_width = crate::modes::common::get_terminal_width();
if std::io::stdout().is_terminal() && terminal_width > 0 {
// Estimate widths of other columns (assuming first column is the longest label)
// This is a simplification, but should work for most cases
let max_label_width = rows.iter()
.map(|row| row.get_cell(0).map(|c| c.get_content().chars().count()).unwrap_or(0))
.max()
.unwrap_or(0);
// Calculate the maximum width for the value column
let max_value_width = crate::modes::common::calculate_last_column_width(&[max_label_width], terminal_width);
// Process meta rows to truncate values if necessary
for (meta_name, meta_value) in meta_rows {
let truncated_value = if max_value_width > 0 {
crate::modes::common::truncate_with_ellipsis(&meta_value, max_value_width)
} else {
meta_value
};
rows.push(Row::new(vec![
Cell::new(&meta_name).with_style(Attr::Bold),
Cell::new(&truncated_value),
]));
}
} else {
// Not a terminal or no terminal width, add all meta rows without truncation
for (meta_name, meta_value) in meta_rows {
rows.push(Row::new(vec![
Cell::new(&meta_name).with_style(Attr::Bold),
Cell::new(&meta_value),
]));
}
}
// Add all rows to the table
for row in rows {
table.add_row(row);
} }
table.printstd(); table.printstd();