rho-coding-agent 1.13.0

A lightweight agent harness inspired by Pi
Documentation
use ratatui::{
    style::{Modifier, Style},
    text::{Line, Span},
};

use super::{
    render::{truncate_one_line, wrap_line_at_whitespace},
    theme::Theme,
};

const FIELD_LABEL_WIDTH: usize = 14;
const SIDE_BY_SIDE_MIN_WIDTH: usize = 32;

pub(super) struct CommandBlock {
    lines: Vec<Line<'static>>,
    style: Style,
    width: usize,
}

impl CommandBlock {
    pub(super) fn new(width: usize) -> Self {
        Self {
            lines: Vec::new(),
            style: Theme::command_block(),
            width,
        }
    }

    pub(super) fn push_header(&mut self, title: &str, detail: &str) {
        if self.width == 0 {
            self.lines.push(Line::from(Span::styled("", self.style)));
            return;
        }
        let title = truncate_one_line(title, self.width);
        let mut spans = vec![Span::styled(
            title.clone(),
            self.style.add_modifier(Modifier::BOLD),
        )];
        let remaining = self.width.saturating_sub(Line::from(title).width());
        if remaining > 2 {
            spans.push(Span::styled("  ", self.style));
            spans.push(Span::styled(
                truncate_one_line(detail, remaining - 2),
                self.style.add_modifier(Modifier::DIM),
            ));
        }
        self.lines.push(Line::from(spans));
    }

    pub(super) fn push_section(&mut self, title: &str) {
        self.lines.push(Line::from(Span::styled("", self.style)));
        self.lines.push(Line::from(Span::styled(
            truncate_one_line(title, self.width),
            self.style.add_modifier(Modifier::BOLD),
        )));
    }

    pub(super) fn push_field(&mut self, label: &str, value: &str) {
        if self.width == 0 {
            self.lines.push(Line::from(Span::styled("", self.style)));
            return;
        }
        let label_width = FIELD_LABEL_WIDTH.min(self.width.saturating_sub(1));
        let value_start = label_width.saturating_add(2);
        if self.width >= SIDE_BY_SIDE_MIN_WIDTH && value_start < self.width {
            let mut values = wrap_line_at_whitespace(value, self.width - value_start).into_iter();
            let first = values.next().unwrap_or_default();
            self.lines.push(Line::from(vec![
                Span::styled(
                    format!("  {label:label_width$}"),
                    self.style.add_modifier(Modifier::DIM),
                ),
                Span::styled(first, self.style),
            ]));
            self.lines.extend(values.map(|value| {
                Line::from(vec![
                    Span::styled(" ".repeat(value_start), self.style),
                    Span::styled(value.trim_start().to_string(), self.style),
                ])
            }));
        } else {
            self.lines.push(Line::from(Span::styled(
                truncate_one_line(&format!("  {label}"), self.width),
                self.style.add_modifier(Modifier::DIM),
            )));
            let indent_width = 4.min(self.width.saturating_sub(1));
            let value_width = self.width.saturating_sub(indent_width).max(1);
            let indent = " ".repeat(indent_width);
            self.lines
                .extend(
                    wrap_line_at_whitespace(value, value_width)
                        .into_iter()
                        .map(|value| {
                            Line::from(Span::styled(
                                format!("{indent}{}", value.trim_start()),
                                self.style,
                            ))
                        }),
                );
        }
    }

    pub(super) fn push_note(&mut self, note: &str) {
        if self.width == 0 {
            self.lines.push(Line::from(Span::styled("", self.style)));
            return;
        }
        let indent_width = 2.min(self.width.saturating_sub(1));
        let note_width = self.width.saturating_sub(indent_width).max(1);
        let indent = " ".repeat(indent_width);
        self.lines.extend(
            wrap_line_at_whitespace(note, note_width)
                .into_iter()
                .map(|part| {
                    Line::from(Span::styled(
                        format!("{indent}{}", part.trim_start()),
                        self.style.add_modifier(Modifier::DIM | Modifier::ITALIC),
                    ))
                }),
        );
    }

    pub(super) fn finish(self) -> Vec<Line<'static>> {
        debug_assert!(self.lines.iter().all(|line| line.width() <= self.width));
        fill_lines(self.lines, self.width, self.style)
    }
}

pub(super) fn fill_lines(
    lines: Vec<Line<'static>>,
    width: usize,
    style: Style,
) -> Vec<Line<'static>> {
    lines
        .into_iter()
        .map(|mut line| {
            let padding = width.saturating_sub(line.width());
            if padding > 0 {
                line.spans.push(Span::styled(" ".repeat(padding), style));
            }
            line
        })
        .collect()
}