use unicode_width::UnicodeWidthStr;
pub const NAME_PAD: usize = 2;
pub fn put(grid: &mut [Vec<char>], row: usize, col: usize, ch: char) {
if let Some(line) = grid.get_mut(row)
&& let Some(cell) = line.get_mut(col)
{
*cell = ch;
}
}
pub fn put_str(grid: &mut [Vec<char>], row: usize, col: usize, s: &str) {
for (c, ch) in (col..).zip(s.chars()) {
put(grid, row, c, ch);
}
}
pub fn pad_right(s: &str, width: usize) -> String {
let current = s.width();
if current >= width {
return s.to_string();
}
let mut out = String::with_capacity(s.len() + (width - current));
out.push_str(s);
for _ in current..width {
out.push(' ');
}
out
}
pub fn grid_to_string(grid: &[Vec<char>]) -> String {
let mut out = String::with_capacity(grid.iter().map(|r| r.len() + 1).sum());
for row in grid {
let line: String = row.iter().collect();
out.push_str(line.trim_end());
out.push('\n');
}
while out.ends_with('\n') {
out.pop();
}
out
}
pub fn hline(
grid: &mut [Vec<char>],
row: usize,
left: usize,
right: usize,
left_cap: char,
right_cap: char,
) {
put(grid, row, left, left_cap);
for c in (left + 1)..right {
put(grid, row, c, '─');
}
put(grid, row, right, right_cap);
}
pub fn draw_box(
grid: &mut [Vec<char>],
top_pad: usize,
left: usize,
right: usize,
header: &str,
rows: &[Vec<String>],
) {
let interior_w = right - left - 1;
let header_w = header.width();
let name_start = left + 1 + (interior_w.saturating_sub(header_w)) / 2;
hline(grid, top_pad, left, right, '┌', '┐');
put(grid, top_pad + 1, left, '│');
put_str(grid, top_pad + 1, name_start, header);
put(grid, top_pad + 1, right, '│');
if rows.is_empty() {
hline(grid, top_pad + 2, left, right, '└', '┘');
return;
}
hline(grid, top_pad + 2, left, right, '├', '┤');
for (i, row_cols) in rows.iter().enumerate() {
let row_idx = top_pad + 3 + i;
put(grid, row_idx, left, '│');
let text = row_cols.join(" ");
put_str(grid, row_idx, left + 1 + NAME_PAD, &text);
put(grid, row_idx, right, '│');
}
let bottom = top_pad + 3 + rows.len();
hline(grid, bottom, left, right, '└', '┘');
}
#[cfg(test)]
mod tests {
use super::*;
fn make_grid(rows: usize, cols: usize) -> Vec<Vec<char>> {
vec![vec![' '; cols]; rows]
}
#[test]
fn put_writes_char_in_bounds() {
let mut g = make_grid(3, 5);
put(&mut g, 1, 2, 'X');
assert_eq!(g[1][2], 'X');
}
#[test]
fn put_ignores_out_of_bounds() {
let mut g = make_grid(2, 2);
put(&mut g, 5, 5, 'Z'); }
#[test]
fn put_str_writes_consecutive_chars() {
let mut g = make_grid(1, 10);
put_str(&mut g, 0, 2, "hello");
let s: String = g[0].iter().collect();
assert_eq!(s.trim_end(), " hello");
}
#[test]
fn pad_right_pads_to_exact_width() {
assert_eq!(pad_right("ab", 5), "ab ");
assert_eq!(pad_right("abcde", 5), "abcde");
assert_eq!(pad_right("abcdef", 5), "abcdef"); }
#[test]
fn grid_to_string_trims_trailing_whitespace_and_newlines() {
let grid = vec![vec!['a', ' ', ' '], vec![' ', ' ', ' ']];
assert_eq!(grid_to_string(&grid), "a");
}
#[test]
fn hline_draws_correct_glyphs() {
let mut g = make_grid(1, 8);
hline(&mut g, 0, 0, 7, '┌', '┐');
assert_eq!(g[0][0], '┌');
assert_eq!(g[0][7], '┐');
for cell in g[0].iter().take(7).skip(1) {
assert_eq!(*cell, '─');
}
}
#[test]
fn draw_box_header_only_produces_three_rows() {
let mut g = make_grid(3, 12);
draw_box(&mut g, 0, 0, 11, "Foo", &[]);
assert_eq!(g[0][0], '┌');
assert_eq!(g[2][0], '└');
}
#[test]
fn draw_box_with_rows_produces_divider_and_body() {
let mut g = make_grid(6, 16);
let rows = vec![vec!["int".to_string(), "id".to_string()]];
draw_box(&mut g, 0, 0, 15, "MyClass", &rows);
assert_eq!(g[2][0], '├');
assert_eq!(g[2][15], '┤');
assert_eq!(g[3][0], '│');
assert_eq!(g[4][0], '└');
}
}