use super::{formatting::pad_to_width, types::RenderedFile};
pub const COLUMN_GAP: usize = 4;
pub const MIN_FILE_WIDTH: usize = 40;
#[inline]
pub fn calculate_columns(files: &[RenderedFile], term_width: usize) -> usize {
if files.is_empty() {
return 1;
}
let max_file_width = files
.iter()
.map(|f| f.width)
.max()
.unwrap_or(MIN_FILE_WIDTH)
.max(MIN_FILE_WIDTH);
for cols in (1..=files.len()).rev() {
let total_width = cols * max_file_width + (cols.saturating_sub(1)) * COLUMN_GAP;
if total_width <= term_width {
return cols;
}
}
1
}
pub fn render_grid(files: &[RenderedFile], columns: usize) {
if files.is_empty() {
return;
}
if columns == 1 {
render_single_column(files);
return;
}
let col_width = files
.iter()
.map(|f| f.width)
.max()
.unwrap_or(MIN_FILE_WIDTH);
for chunk in files.chunks(columns) {
let max_lines = chunk.iter().map(|f| f.line_count()).max().unwrap_or(0);
for row_idx in 0..max_lines {
let mut row_output = String::with_capacity(columns * (col_width + COLUMN_GAP));
for (col_idx, file) in chunk.iter().enumerate() {
let line = file.lines.get(row_idx).map(String::as_str).unwrap_or("");
let padded = pad_to_width(line, col_width);
row_output.push_str(&padded);
if col_idx < chunk.len() - 1 {
row_output.push_str(&" ".repeat(COLUMN_GAP));
}
}
println!("{}", row_output);
}
println!();
}
}
#[inline]
fn render_single_column(files: &[RenderedFile]) {
for file in files {
for line in &file.lines {
println!("{}", line);
}
println!();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_calculate_columns_empty() {
let files: Vec<RenderedFile> = vec![];
let cols = calculate_columns(&files, 100);
assert_eq!(cols, 1);
}
#[test]
fn test_calculate_columns_single_narrow() {
let files = vec![RenderedFile {
lines: Vec::new(),
width: 40
}];
let cols = calculate_columns(&files, 200);
assert_eq!(cols, 1);
}
#[test]
fn test_calculate_columns_two_fit() {
let files = vec![
RenderedFile {
lines: Vec::new(),
width: 50
},
RenderedFile {
lines: Vec::new(),
width: 50
},
];
let cols = calculate_columns(&files, 150);
assert!(cols >= 1);
}
#[test]
fn test_calculate_columns_narrow_terminal() {
let files = vec![
RenderedFile {
lines: Vec::new(),
width: 100
},
RenderedFile {
lines: Vec::new(),
width: 100
},
];
let cols = calculate_columns(&files, 80);
assert_eq!(cols, 1);
}
#[test]
fn test_calculate_columns_wide_terminal() {
let files = vec![
RenderedFile {
lines: Vec::new(),
width: 40
},
RenderedFile {
lines: Vec::new(),
width: 40
},
RenderedFile {
lines: Vec::new(),
width: 40
},
];
let cols = calculate_columns(&files, 250);
assert!(cols >= 2);
}
#[test]
fn test_render_grid_single_column() {
let file = RenderedFile {
lines: vec!["line1".to_string(), "line2".to_string()],
width: 40
};
render_grid(&[file], 1);
}
#[test]
fn test_render_grid_empty() {
let files: Vec<RenderedFile> = vec![];
render_grid(&files, 2);
}
#[test]
fn test_render_grid_multiple_columns() {
let file1 = RenderedFile {
lines: vec!["file1".to_string()],
width: 40
};
let file2 = RenderedFile {
lines: vec!["file2".to_string()],
width: 40
};
render_grid(&[file1, file2], 2);
}
#[test]
fn test_calculate_columns_respects_min_width() {
let files = vec![RenderedFile {
lines: Vec::new(),
width: 30
}];
let cols = calculate_columns(&files, 200);
assert_eq!(cols, 1);
}
#[test]
fn test_render_single_column_multiple_files() {
let file1 = RenderedFile {
lines: vec!["test1".to_string()],
width: 40
};
let file2 = RenderedFile {
lines: vec!["test2".to_string()],
width: 40
};
render_single_column(&[file1, file2]);
}
}