rho-coding-agent 2.2.0

A lightweight agent harness inspired by Pi
use std::fmt;
use std::fmt::Write;

use crate::tui::markdown::txm::backend::Backend;
use crate::tui::markdown::txm::backend::RenderTarget;
use crate::tui::markdown::txm::layout_tree::LayoutNode;
use crate::tui::markdown::txm::style::Style;

use super::generic_backend;

pub struct TerminalBackend;

impl TerminalBackend {
    pub fn new() -> Self {
        Self
    }
}

impl Default for TerminalBackend {
    fn default() -> Self {
        Self::new()
    }
}

impl Backend for TerminalBackend {
    type Output = String;
    type Error = fmt::Error;

    fn render(&self, tree: &LayoutNode) -> Result<String, fmt::Error> {
        let mut buf = CharBuf::new(tree.width, tree.height);
        generic_backend::render_node(tree, &mut buf, 0, 0);
        Ok(buf.to_string())
    }
}

#[derive(Debug, Clone)]
pub(crate) struct CharBuf {
    pub data: Vec<char>,
    pub styles: Vec<Style>,
    pub width: usize,
    pub height: usize,
}

impl CharBuf {
    pub fn new(width: usize, height: usize) -> Self {
        Self {
            data: vec![' '; width * height],
            styles: vec![Style::default(); width * height],
            width,
            height,
        }
    }
}

impl fmt::Display for CharBuf {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let style_rows = self.styles.chunks_exact(self.width).take(self.height);
        let data_rows = self.data.chunks_exact(self.width).take(self.height);

        for (style_row, data_row) in style_rows.zip(data_rows) {
            let mut active_style: Option<&Style> = None;

            for (style, &data) in style_row.iter().zip(data_row.iter()) {
                if Some(style) != active_style {
                    if active_style.is_some_and(|s| !s.is_empty()) {
                        f.write_str("\x1b[0m")?;
                    }

                    if !style.is_empty() {
                        style.write_ansi_prefix(f)?;
                    }

                    active_style = Some(style);
                }

                f.write_char(data)?;
            }

            if active_style.is_some_and(|s| !s.is_empty()) {
                f.write_str("\x1b[0m")?;
            }

            writeln!(f)?;
        }

        Ok(())
    }
}

impl RenderTarget for CharBuf {
    fn set(&mut self, x: usize, y: usize, ch: char, style: Style) {
        if x < self.width && y < self.height {
            let i = y * self.width + x;
            self.data[i] = ch;
            self.styles[i] = style;
        }
    }

    fn fill_row(&mut self, y: usize, x_start: usize, x_end: usize, ch: char, style: Style) {
        for x in x_start..x_end.min(self.width) {
            self.set(x, y, ch, style);
        }
    }
}