supercode-frontend-tui 0.4.6

Attachable terminal frontend primitives for Supercode SDK runtimes.
Documentation
use ratatui::backend::TestBackend;
use ratatui::layout::Position;
use ratatui::style::Color;
use ratatui::style::Style;
use ratatui::text::Line;
use ratatui::widgets::Paragraph;
use ratatui::Terminal;
use supercode_frontend_tui::foundation::wrapping::adaptive_wrap_line;
use supercode_frontend_tui::foundation::wrapping::RtOptions;
use supercode_frontend_tui::terminal::palette::ColorCapabilities;

fn render_snapshot(width: u16) -> (String, Position) {
    let backend = TestBackend::new(width, 6);
    let mut terminal = Terminal::new(backend).expect("terminal");
    let source = Line::from(
        "assistant │ Unicode 東京 stays aligned while tools report progress without splitting grapheme clusters.",
    );
    let wrapped = adaptive_wrap_line(&source, RtOptions::new(usize::from(width)));

    terminal
        .draw(|frame| {
            frame.render_widget(Paragraph::new(wrapped.clone()), frame.area());
            frame.set_cursor_position(Position::new(width.saturating_sub(2), 3));
        })
        .expect("draw");

    let backend = terminal.backend();
    let rows = (0..6)
        .map(|y| {
            (0..width)
                .map(|x| backend.buffer()[(x, y)].symbol())
                .collect::<String>()
                .trim_end()
                .to_string()
        })
        .collect::<Vec<_>>()
        .join("\n");
    (rows, backend.cursor_position())
}

#[test]
fn stable_buffers_and_cursor_at_representative_widths() {
    let cases = [
        (
            40,
            "assistant │ Unicode 東 京  stays aligned\nwhile tools report progress without\nsplitting grapheme clusters.\n\n\n",
        ),
        (
            80,
            "assistant │ Unicode 東 京  stays aligned while tools report progress without\nsplitting grapheme clusters.\n\n\n\n",
        ),
        (
            160,
            "assistant │ Unicode 東 京  stays aligned while tools report progress without splitting grapheme clusters.\n\n\n\n\n",
        ),
    ];

    for (width, expected) in cases {
        let (snapshot, cursor) = render_snapshot(width);
        assert_eq!(snapshot, expected, "buffer changed at width {width}");
        assert_eq!(cursor, Position::new(width - 2, 3));
    }
}

fn rendered_foreground(capabilities: ColorCapabilities) -> Vec<Color> {
    let backend = TestBackend::new(8, 1);
    let mut terminal = Terminal::new(backend).expect("terminal");
    let color = capabilities.best_color((12, 34, 56));

    terminal
        .draw(|frame| {
            frame.render_widget(
                Paragraph::new("status").style(Style::default().fg(color)),
                frame.area(),
            );
        })
        .expect("draw");

    (0..6)
        .map(|x| terminal.backend().buffer()[(x, 0)].fg)
        .collect()
}

#[test]
fn color_capability_buffers_are_deterministic() {
    let truecolor =
        ColorCapabilities::resolve(false, Some("xterm-256color"), Some("truecolor"), false);
    let ansi256 = ColorCapabilities::resolve(false, Some("xterm-256color"), None, false);
    let no_color =
        ColorCapabilities::resolve(true, Some("xterm-256color"), Some("truecolor"), false);
    let dumb = ColorCapabilities::resolve(false, Some("dumb"), Some("truecolor"), false);
    let monochrome = ColorCapabilities::resolve(false, Some("xterm"), None, false);

    assert_eq!(
        rendered_foreground(truecolor),
        vec![Color::Rgb(12, 34, 56); 6]
    );
    assert_eq!(rendered_foreground(ansi256), vec![Color::Indexed(234); 6]);
    assert_eq!(rendered_foreground(no_color), vec![Color::Reset; 6]);
    assert_eq!(rendered_foreground(dumb), vec![Color::Reset; 6]);
    assert_eq!(rendered_foreground(monochrome), vec![Color::Reset; 6]);
}