magi-code 0.80.2

Repository-aware CLI coding agent for terminal work
Documentation
use super::*;
use crate::rendering::{DisplayTable, TableAlignment};

pub(super) fn layout_table(
    table: &DisplayTable,
    available: usize,
    theme: MissionControlTheme,
    role: TranscriptCardRole,
) -> Vec<Vec<AssistantVisualSpan>> {
    let rows = styled_table_rows(table, theme, role);
    let Some(widths) = table_column_widths(&rows, table.alignments.len(), available) else {
        // Keep row and cell order when columns cannot fit, even at one grapheme each.
        let mut output = Vec::new();
        for cells in rows {
            if !output.is_empty() {
                output.push(Vec::new());
            }
            for cell in cells {
                output.extend(wrap_assistant_spans_to_width(cell, available));
            }
        }
        return output;
    };
    let style = theme
        .display_role(DisplayRole::Plain)
        .bg(card_body_background(role, theme));
    let mut output = Vec::new();
    for (index, cells) in rows.into_iter().enumerate() {
        let wrapped: Vec<_> = cells
            .into_iter()
            .zip(&widths)
            .map(|(cell, width)| wrap_assistant_spans_to_width(cell, *width))
            .collect();
        let height = wrapped.iter().map(Vec::len).max().unwrap_or(1);
        for line in 0..height {
            let mut row = Vec::new();
            for (column, width) in widths.iter().enumerate() {
                if column > 0 {
                    row.push(AssistantVisualSpan::styled("", style, None));
                }
                let cell = wrapped
                    .get(column)
                    .and_then(|cell| cell.get(line))
                    .cloned()
                    .unwrap_or_default();
                append_aligned_cell(&mut row, cell, *width, table.alignments[column], style);
            }
            output.push(row);
        }
        if index == 0 {
            let rule = widths
                .iter()
                .map(|width| "".repeat(*width))
                .collect::<Vec<_>>()
                .join("─┼─");
            output.push(vec![AssistantVisualSpan::styled(rule, style, None)]);
        }
    }
    output
}

fn styled_table_rows(
    table: &DisplayTable,
    theme: MissionControlTheme,
    role: TranscriptCardRole,
) -> Vec<Vec<Vec<AssistantVisualSpan>>> {
    table
        .rows
        .iter()
        .enumerate()
        .map(|(index, cells)| {
            cells
                .iter()
                .map(|cell| {
                    cell.iter()
                        .map(|span| {
                            let mut style = theme
                                .display_role(span.role)
                                .bg(card_body_background(role, theme));
                            if index == 0 {
                                style = style.add_modifier(Modifier::BOLD);
                            }
                            AssistantVisualSpan::styled(span.text.clone(), style, Some(span.role))
                        })
                        .collect()
                })
                .collect()
        })
        .collect()
}

fn table_column_widths(
    rows: &[Vec<Vec<AssistantVisualSpan>>],
    columns: usize,
    available: usize,
) -> Option<Vec<usize>> {
    let mut desired = vec![1usize; columns];
    let mut widths = vec![1usize; columns];
    for cells in rows {
        for (column, cell) in cells.iter().take(columns).enumerate() {
            desired[column] = desired[column].max(
                cell.iter()
                    .map(|span| UnicodeWidthStr::width(span.text.as_str()))
                    .sum(),
            );
            widths[column] = widths[column].max(
                cell.iter()
                    .flat_map(|span| span.text.graphemes(true))
                    .map(UnicodeWidthStr::width)
                    .max()
                    .unwrap_or(1),
            );
        }
    }
    let separator_width = columns.saturating_sub(1) * 3;
    let minimum = widths.iter().sum::<usize>() + separator_width;
    if columns == 0 || minimum > available {
        return None;
    }
    // Share the available columns without work proportional to the source cell lengths.
    let mut remaining = available - minimum;
    while remaining > 0 {
        let mut grew = false;
        for (width, desired) in widths.iter_mut().zip(&desired) {
            if remaining > 0 && *width < *desired {
                *width += 1;
                remaining -= 1;
                grew = true;
            }
        }
        if !grew {
            break;
        }
    }
    Some(widths)
}

fn append_aligned_cell(
    row: &mut Vec<AssistantVisualSpan>,
    cell: Vec<AssistantVisualSpan>,
    width: usize,
    alignment: TableAlignment,
    style: Style,
) {
    let used: usize = cell
        .iter()
        .map(|span| UnicodeWidthStr::width(span.text.as_str()))
        .sum();
    let padding = width.saturating_sub(used);
    let left = match alignment {
        TableAlignment::Right => padding,
        TableAlignment::Center => padding / 2,
        _ => 0,
    };
    row.push(AssistantVisualSpan::styled(" ".repeat(left), style, None));
    row.extend(cell);
    row.push(AssistantVisualSpan::styled(
        " ".repeat(padding - left),
        style,
        None,
    ));
}

#[cfg(test)]
mod tests {
    use super::*;

    fn render_table(markdown: &str, width: usize) -> Vec<Vec<AssistantVisualSpan>> {
        let lines = crate::rendering::markup::render_markdown(markdown);
        let table = lines
            .iter()
            .find_map(|line| line.table.as_ref())
            .expect("table structure");
        layout_table(
            table,
            width,
            MissionControlTheme::default(),
            TranscriptCardRole::Assistant,
        )
    }

    #[test]
    fn table_columns_align_headers_and_preserve_inline_code() {
        let rows = render_table(
            "| Left | Mid | Right |\n| :--- | :---: | ---: |\n| `x` | y | z |",
            40,
        );
        let text: Vec<_> = rows
            .iter()
            .map(|row| assistant_wide_copy_text(row))
            .collect();
        assert_eq!(
            text,
            [
                "Left │ Mid │ Right",
                "─────┼─────┼──────",
                "x    │  y  │     z"
            ]
        );
        assert!(
            rows[0]
                .iter()
                .filter(|span| span.role.is_some())
                .all(|span| span.style.add_modifier.contains(Modifier::BOLD))
        );
        assert!(
            rows[2]
                .iter()
                .any(|span| span.text == "x" && span.role == Some(DisplayRole::InlineCode))
        );
    }

    #[test]
    fn table_cells_wrap_without_moving_column_boundaries() {
        let rows = render_table("| A | B |\n| --- | --- |\n| abcdefgh | 界é界 |", 11);
        let text: Vec<_> = rows
            .iter()
            .map(|row| assistant_wide_copy_text(row))
            .collect();
        assert_eq!(
            text,
            ["A    │ B   ", "─────┼─────", "abcd │ 界é ", "efgh │ 界  "]
        );
        assert!(
            text.iter()
                .all(|row| UnicodeWidthStr::width(row.as_str()) == 11)
        );
    }

    #[test]
    fn narrow_table_fallback_keeps_every_cell_and_grapheme() {
        for width in [1, 2, 4] {
            let rows = render_table("| A | B |\n| --- | --- |\n| 界é | xy |", width);
            let text: String = rows
                .iter()
                .map(|row| assistant_wide_copy_text(row))
                .collect();
            assert_eq!(text, "AB界éxy", "width {width}");
        }
    }
}