supercode-frontend-tui 0.4.1

Attachable terminal frontend primitives for Supercode SDK runtimes.
Documentation
//! Small deterministic Markdown view for transcript content.
//!
//! This parser intentionally targets terminal display semantics rather than
//! round-tripping a Markdown AST. It handles the structures coding-agent
//! answers rely on most: paragraphs, headings, lists, tables, fenced code,
//! inline emphasis, and inline code.

use ratatui::style::Modifier;
use ratatui::style::Style;
use ratatui::text::Line;
use ratatui::text::Span;
use unicode_width::UnicodeWidthChar;
use unicode_width::UnicodeWidthStr;

use crate::foundation::wrapping::adaptive_wrap_lines;
use crate::foundation::wrapping::RtOptions;
use crate::terminal::palette::ColorCapabilities;

pub(super) fn render_markdown(
    text: &str,
    width: usize,
    capabilities: ColorCapabilities,
) -> Vec<Line<'static>> {
    let width = width.max(1);
    let source = text.lines().collect::<Vec<_>>();
    if source.is_empty() {
        return vec![Line::default()];
    }

    let mut out = Vec::new();
    let mut index = 0usize;
    let mut fence: Option<String> = None;
    while index < source.len() {
        let line = source[index];
        if let Some(marker) = line.trim_start().strip_prefix("```") {
            if fence.is_some() {
                out.push(Line::from(Span::styled(
                    "└─".to_string(),
                    code_border_style(capabilities),
                )));
                fence = None;
            } else {
                let language = marker.trim().to_string();
                let label = if language.is_empty() {
                    "┌─ code".into()
                } else {
                    format!("┌─ {language}")
                };
                out.push(Line::from(Span::styled(
                    label,
                    code_border_style(capabilities),
                )));
                fence = Some(language);
            }
            index += 1;
            continue;
        }

        if fence.is_some() {
            let options = RtOptions::new(width)
                .initial_indent(Line::from(""))
                .subsequent_indent(Line::from(""));
            out.extend(adaptive_wrap_lines(
                [Line::from(Span::styled(
                    line.to_string(),
                    Style::default().fg(capabilities.best_color((170, 205, 240))),
                ))],
                options,
            ));
            index += 1;
            continue;
        }

        if index + 1 < source.len()
            && source[index].contains('|')
            && is_table_delimiter(source[index + 1])
        {
            let start = index;
            index += 2;
            while index < source.len() && source[index].contains('|') && !source[index].is_empty() {
                index += 1;
            }
            out.extend(render_table(&source[start..index], width, capabilities));
            continue;
        }

        if line.trim().is_empty() {
            out.push(Line::default());
            index += 1;
            continue;
        }

        if let Some((level, heading)) = heading(line) {
            let style = Style::default()
                .fg(capabilities.best_color((135, 195, 250)))
                .add_modifier(Modifier::BOLD);
            let marker = "#".repeat(level.min(3));
            out.extend(adaptive_wrap_lines(
                [Line::from(vec![
                    Span::styled(format!("{marker} "), style),
                    Span::styled(heading.to_string(), style),
                ])],
                RtOptions::new(width),
            ));
            index += 1;
            continue;
        }

        if let Some((prefix, item)) = list_item(line) {
            let prefix_width = prefix.width();
            let bullet_style = Style::default()
                .fg(capabilities.best_color((135, 195, 250)))
                .add_modifier(Modifier::BOLD);
            let options = RtOptions::new(width)
                .initial_indent(Line::from(Span::styled(prefix.clone(), bullet_style)))
                .subsequent_indent(Line::from(" ".repeat(prefix_width)));
            out.extend(adaptive_wrap_lines(
                [Line::from(inline_spans(item, capabilities))],
                options,
            ));
            index += 1;
            continue;
        }

        out.extend(adaptive_wrap_lines(
            [Line::from(inline_spans(line, capabilities))],
            RtOptions::new(width),
        ));
        index += 1;
    }

    if fence.is_some() {
        out.push(Line::from(Span::styled(
            "└─".to_string(),
            code_border_style(capabilities),
        )));
    }
    out
}

fn inline_spans(text: &str, capabilities: ColorCapabilities) -> Vec<Span<'static>> {
    let mut spans = Vec::new();
    let mut remaining = text;
    while !remaining.is_empty() {
        let bold = remaining.find("**");
        let code = remaining.find('`');
        let next = match (bold, code) {
            (Some(left), Some(right)) => left.min(right),
            (Some(index), None) | (None, Some(index)) => index,
            (None, None) => {
                spans.push(Span::raw(remaining.to_string()));
                break;
            }
        };
        if next > 0 {
            spans.push(Span::raw(remaining[..next].to_string()));
            remaining = &remaining[next..];
            continue;
        }
        if let Some(rest) = remaining.strip_prefix("**") {
            if let Some(end) = rest.find("**") {
                spans.push(Span::styled(
                    rest[..end].to_string(),
                    Style::default().add_modifier(Modifier::BOLD),
                ));
                remaining = &rest[end + 2..];
            } else {
                spans.push(Span::raw("**"));
                remaining = rest;
            }
            continue;
        }
        if let Some(rest) = remaining.strip_prefix('`') {
            if let Some(end) = rest.find('`') {
                spans.push(Span::styled(
                    rest[..end].to_string(),
                    Style::default().fg(capabilities.best_color((210, 165, 105))),
                ));
                remaining = &rest[end + 1..];
            } else {
                spans.push(Span::raw("`"));
                remaining = rest;
            }
        }
    }
    spans
}

fn render_table(
    lines: &[&str],
    width: usize,
    capabilities: ColorCapabilities,
) -> Vec<Line<'static>> {
    let rows = lines
        .iter()
        .enumerate()
        .filter(|(index, _)| *index != 1)
        .map(|(_, line)| table_cells(line))
        .collect::<Vec<_>>();
    let columns = rows.iter().map(Vec::len).max().unwrap_or(1).max(1);
    let separators = columns + 1;
    let available = width.saturating_sub(separators).max(columns);
    let base = (available / columns).max(1);
    let mut widths = vec![base; columns];
    for index in 0..available.saturating_sub(base * columns) {
        widths[index % columns] += 1;
    }

    let border = Style::default().fg(capabilities.best_color((100, 130, 155)));
    let header = Style::default().add_modifier(Modifier::BOLD);
    let mut out = Vec::new();
    for (row_index, row) in rows.iter().enumerate() {
        let mut spans = vec![Span::styled("".to_string(), border)];
        for (column, cell) in row.iter().enumerate().take(columns) {
            let value = truncate_width(cell, widths[column].saturating_sub(2));
            let padding = widths[column].saturating_sub(value.width() + 1);
            spans.push(Span::styled(
                format!(" {value}{}", " ".repeat(padding)),
                if row_index == 0 {
                    header
                } else {
                    Style::default()
                },
            ));
            spans.push(Span::styled("".to_string(), border));
        }
        for width in widths.iter().skip(row.len()) {
            spans.push(Span::raw(" ".repeat(*width)));
            spans.push(Span::styled("".to_string(), border));
        }
        out.push(Line::from(spans));
        if row_index == 0 {
            out.push(Line::from(Span::styled(
                widths
                    .iter()
                    .map(|width| "".repeat(*width))
                    .collect::<Vec<_>>()
                    .join("")
                    .pipe(|middle| format!("{middle}")),
                border,
            )));
        }
    }
    out
}

trait Pipe: Sized {
    fn pipe<T>(self, f: impl FnOnce(Self) -> T) -> T {
        f(self)
    }
}
impl<T> Pipe for T {}

fn table_cells(line: &str) -> Vec<String> {
    line.trim()
        .trim_matches('|')
        .split('|')
        .map(|cell| cell.trim().to_string())
        .collect()
}

fn is_table_delimiter(line: &str) -> bool {
    let cells = table_cells(line);
    !cells.is_empty()
        && cells.iter().all(|cell| {
            let cell = cell.trim_matches(':').trim();
            cell.len() >= 3 && cell.chars().all(|ch| ch == '-')
        })
}

fn heading(line: &str) -> Option<(usize, &str)> {
    let level = line.chars().take_while(|ch| *ch == '#').count();
    (level > 0 && line.as_bytes().get(level) == Some(&b' '))
        .then(|| (level, line[level + 1..].trim()))
}

fn list_item(line: &str) -> Option<(String, &str)> {
    let trimmed = line.trim_start();
    let indentation = line.len() - trimmed.len();
    for marker in ["- ", "* ", "+ "] {
        if let Some(item) = trimmed.strip_prefix(marker) {
            return Some((format!("{}", " ".repeat(indentation)), item));
        }
    }
    let digit_count = trimmed.chars().take_while(|ch| ch.is_ascii_digit()).count();
    if digit_count > 0 && trimmed[digit_count..].starts_with(". ") {
        return Some((
            format!("{}{} ", " ".repeat(indentation), &trimmed[..=digit_count]),
            &trimmed[digit_count + 2..],
        ));
    }
    None
}

fn truncate_width(text: &str, width: usize) -> String {
    if text.width() <= width {
        return text.to_string();
    }
    if width == 0 {
        return String::new();
    }
    let target = width.saturating_sub(1);
    let mut used = 0usize;
    let mut out = String::new();
    for ch in text.chars() {
        let char_width = ch.width().unwrap_or(0);
        if used + char_width > target {
            break;
        }
        used += char_width;
        out.push(ch);
    }
    out.push('');
    out
}

fn code_border_style(capabilities: ColorCapabilities) -> Style {
    Style::default().fg(capabilities.best_color((100, 150, 190)))
}

#[cfg(test)]
mod tests {
    use crate::terminal::palette::ColorLevel;

    use super::*;

    fn capabilities() -> ColorCapabilities {
        ColorCapabilities {
            level: ColorLevel::TrueColor,
            color_enabled: true,
        }
    }

    fn visible(lines: &[Line<'_>]) -> String {
        lines
            .iter()
            .map(|line| {
                line.spans
                    .iter()
                    .map(|span| span.content.as_ref())
                    .collect::<String>()
            })
            .collect::<Vec<_>>()
            .join("\n")
    }

    #[test]
    fn paragraphs_lists_tables_and_fences_have_stable_terminal_shape() {
        let markdown = "# Result\nA **bold** paragraph with `code`.\n\n- first item\n- second item wraps across a narrow terminal width\n\n| Name | State |\n| --- | --- |\n| build | green |\n\n```rust\nfn main() { println!(\"hi\"); }\n```";
        let rendered = visible(&render_markdown(markdown, 32, capabilities()));
        assert_eq!(
            rendered,
            "# Result\nA bold paragraph with code.\n\n• first item\n• second item wraps across a\n  narrow terminal width\n\n│ Name          │ State        │\n├───────────────┼──────────────┤\n│ build         │ green        │\n\n┌─ rust\n│ fn main() { println!(\"hi\"); }\n└─"
        );
    }

    #[test]
    fn long_unbroken_unicode_text_never_panics_at_tiny_width() {
        let lines = render_markdown("東京東京東京_abcdefghijklmnop", 5, capabilities());
        assert!(!lines.is_empty());
        assert!(lines.iter().all(|line| line.width() <= 5));
    }
}