vtcode-tui 0.98.6

Reusable TUI primitives and session API for VT Code-style terminal interfaces
use super::MarkdownLine;
use anstyle::Style;
use std::cmp::max;

#[derive(Debug, Default)]
pub(crate) struct TableBuffer {
    pub(crate) headers: Vec<MarkdownLine>,
    pub(crate) rows: Vec<Vec<MarkdownLine>>,
    pub(crate) current_row: Vec<MarkdownLine>,
    pub(crate) in_head: bool,
}

pub(crate) fn render_table(table: &TableBuffer, base_style: Style) -> Vec<MarkdownLine> {
    let mut lines = Vec::new();
    if table.headers.is_empty() && table.rows.is_empty() {
        return lines;
    }

    let max_cols = table
        .headers
        .len()
        .max(table.rows.iter().map(|r| r.len()).max().unwrap_or(0));
    let mut col_widths: Vec<usize> = vec![0; max_cols];
    let header_widths: Vec<usize> = table.headers.iter().map(MarkdownLine::width).collect();
    let row_widths: Vec<Vec<usize>> = table
        .rows
        .iter()
        .map(|row| row.iter().map(MarkdownLine::width).collect())
        .collect();

    for (i, width) in header_widths.iter().enumerate() {
        col_widths[i] = max(col_widths[i], *width);
    }

    for widths in &row_widths {
        for (i, width) in widths.iter().enumerate() {
            col_widths[i] = max(col_widths[i], *width);
        }
    }

    let border_style = base_style.dimmed();

    if !table.headers.is_empty() {
        lines.push(render_table_row(
            &table.headers,
            &header_widths,
            &col_widths,
            border_style,
            base_style,
            true,
        ));

        let mut sep = MarkdownLine::default();
        sep.push_segment(border_style, "├─");
        for (i, width) in col_widths.iter().enumerate() {
            sep.push_segment(border_style, &"".repeat(*width));
            sep.push_segment(
                border_style,
                if i < col_widths.len() - 1 {
                    "─┼─"
                } else {
                    "─┤"
                },
            );
        }
        lines.push(sep);
    }

    for (row, widths) in table.rows.iter().zip(row_widths.iter()) {
        lines.push(render_table_row(
            row,
            widths,
            &col_widths,
            border_style,
            base_style,
            false,
        ));
    }

    lines
}

fn render_table_row(
    cells: &[MarkdownLine],
    cell_widths: &[usize],
    col_widths: &[usize],
    border_style: Style,
    base_style: Style,
    bold: bool,
) -> MarkdownLine {
    let mut line = MarkdownLine::default();
    line.push_segment(border_style, "");
    for (i, width) in col_widths.iter().enumerate() {
        if let Some(c) = cells.get(i) {
            for seg in &c.segments {
                let style = if bold { seg.style.bold() } else { seg.style };
                line.push_segment(style, &seg.text);
            }
            let cell_width = cell_widths.get(i).copied().unwrap_or(0);
            let padding = width.saturating_sub(cell_width);
            if padding > 0 {
                line.push_segment(base_style, &" ".repeat(padding));
            }
        } else {
            line.push_segment(base_style, &" ".repeat(*width));
        }
        line.push_segment(border_style, "");
    }
    line
}