diff --git a/src/modes/common.rs b/src/modes/common.rs index fd19722..66fbb39 100644 --- a/src/modes/common.rs +++ b/src/modes/common.rs @@ -221,3 +221,57 @@ pub fn get_terminal_width() -> usize { // Default to 80 if all else fails 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 +} diff --git a/src/modes/info.rs b/src/modes/info.rs index 81cd954..27db3c9 100644 --- a/src/modes/info.rs +++ b/src/modes/info.rs @@ -75,65 +75,103 @@ fn show_item( 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(&item_id.to_string()), ])); - let ts_cell = Cell::new(&item.ts.with_timezone(&Local).format("%F %T %Z").to_string()); - table.add_row(Row::new(vec![ + let timestamp_str = item.ts.with_timezone(&Local).format("%F %T %Z").to_string(); + rows.push(Row::new(vec![ Cell::new("Timestamp").with_style(Attr::Bold), - ts_cell, + Cell::new(×tamp_str), ])); - let mut item_path_buf = data_path.clone(); // Renamed to avoid conflict if item_path is used later - item_path_buf.push(item.id.unwrap().to_string()); // Again, consider safer unwrap - - table.add_row(Row::new(vec![ + let mut item_path_buf = data_path.clone(); + 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(); + rows.push(Row::new(vec![ 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 { - Some(size) => Cell::new(format_size(size as u64, settings.human_readable).as_str()), - None => Cell::new("Missing") - .with_style(Attr::ForegroundColor(prettytable::color::RED)) - .with_style(Attr::Bold), + let size_str = match item.size { + Some(size) => format_size(size as u64, settings.human_readable), + None => "Missing".to_string(), }; - table.add_row(Row::new(vec![ + rows.push(Row::new(vec![ 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(&item.compression), ])); - let file_size_cell = match item_path_buf.metadata() { - Ok(metadata) => { - Cell::new(format_size(metadata.len(), settings.human_readable).as_str()) - } - Err(_) => Cell::new("Missing") - .with_style(Attr::ForegroundColor(prettytable::color::RED)) - .with_style(Attr::Bold), + let file_size_str = match item_path_buf.metadata() { + Ok(metadata) => format_size(metadata.len(), settings.human_readable), + Err(_) => "Missing".to_string(), }; - table.add_row(Row::new(vec![ + rows.push(Row::new(vec![ 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(&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 { let meta_name = format!("Meta: {}", &meta.name); - table.add_row(Row::new(vec![ - Cell::new(meta_name.as_str()).with_style(Attr::Bold), - Cell::new(&meta.value), - ])); + meta_rows.push((meta_name, 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();