supercode-frontend-tui 0.4.8

Attachable terminal frontend primitives for Supercode SDK runtimes.
Documentation
//! Protocol-neutral, extension-aware diff rendering.

use ratatui::style::Modifier;
use ratatui::style::Style;
use ratatui::text::Line;
use ratatui::text::Span;

use crate::terminal::palette::ColorCapabilities;

pub(super) fn looks_like_diff(body: &str) -> bool {
    body.lines().any(|line| {
        line.starts_with("diff --git ")
            || line.starts_with("@@ ")
            || line.starts_with("*** Begin Patch")
    })
}

pub(super) fn render_diff(body: &str, capabilities: ColorCapabilities) -> Vec<Line<'static>> {
    let mut language = None;
    body.lines()
        .map(|line| {
            if let Some(path) = line.strip_prefix("+++ ") {
                language = language_for_path(path.trim_start_matches("b/"));
            } else if let Some(path) = patch_path(line) {
                language = language_for_path(path);
            }
            render_diff_line(line, language, capabilities)
        })
        .collect()
}

fn render_diff_line(
    line: &str,
    language: Option<Language>,
    capabilities: ColorCapabilities,
) -> Line<'static> {
    let inserted = Style::default().fg(capabilities.best_color((80, 200, 120)));
    let deleted = Style::default().fg(capabilities.best_color((235, 100, 100)));
    let metadata = Style::default().fg(capabilities.best_color((100, 175, 230)));
    if line.starts_with("@@") || line.starts_with("diff --git") || line.starts_with("*** ") {
        return Line::from(Span::styled(line.to_string(), metadata));
    }
    if let Some(code) = line.strip_prefix('+').filter(|_| !line.starts_with("+++")) {
        return code_line("+", code, language, inserted);
    }
    if let Some(code) = line.strip_prefix('-').filter(|_| !line.starts_with("---")) {
        return code_line("-", code, language, deleted);
    }
    Line::from(line.to_string())
}

fn code_line(prefix: &str, code: &str, language: Option<Language>, base: Style) -> Line<'static> {
    let mut spans = vec![Span::styled(prefix.to_string(), base)];
    let Some(language) = language else {
        spans.push(Span::styled(code.to_string(), base));
        return Line::from(spans);
    };

    let keyword_style = base.add_modifier(Modifier::BOLD);
    let mut token_start = 0usize;
    for (index, ch) in code.char_indices() {
        if ch.is_ascii_alphanumeric() || ch == '_' {
            continue;
        }
        push_token(
            &mut spans,
            &code[token_start..index],
            language,
            base,
            keyword_style,
        );
        spans.push(Span::styled(ch.to_string(), base));
        token_start = index + ch.len_utf8();
    }
    push_token(
        &mut spans,
        &code[token_start..],
        language,
        base,
        keyword_style,
    );
    Line::from(spans)
}

fn push_token(
    spans: &mut Vec<Span<'static>>,
    token: &str,
    language: Language,
    base: Style,
    keyword: Style,
) {
    if token.is_empty() {
        return;
    }
    spans.push(Span::styled(
        token.to_string(),
        if language.is_keyword(token) {
            keyword
        } else {
            base
        },
    ));
}

fn patch_path(line: &str) -> Option<&str> {
    line.strip_prefix("*** Add File: ")
        .or_else(|| line.strip_prefix("*** Update File: "))
        .or_else(|| line.strip_prefix("*** Delete File: "))
}

#[derive(Clone, Copy)]
enum Language {
    Rust,
    JavaScript,
    Python,
    Shell,
}

impl Language {
    fn is_keyword(self, token: &str) -> bool {
        match self {
            Self::Rust => matches!(
                token,
                "as" | "async"
                    | "await"
                    | "const"
                    | "enum"
                    | "fn"
                    | "impl"
                    | "let"
                    | "match"
                    | "mod"
                    | "pub"
                    | "self"
                    | "struct"
                    | "trait"
                    | "use"
                    | "where"
            ),
            Self::JavaScript => matches!(
                token,
                "async"
                    | "await"
                    | "class"
                    | "const"
                    | "export"
                    | "function"
                    | "import"
                    | "let"
                    | "return"
                    | "var"
            ),
            Self::Python => matches!(
                token,
                "async" | "await" | "class" | "def" | "from" | "import" | "return" | "with"
            ),
            Self::Shell => matches!(
                token,
                "case" | "do" | "done" | "esac" | "fi" | "for" | "if" | "then"
            ),
        }
    }
}

fn language_for_path(path: &str) -> Option<Language> {
    let extension = path.rsplit_once('.').map(|(_, extension)| extension)?;
    match extension {
        "rs" => Some(Language::Rust),
        "js" | "jsx" | "mjs" | "ts" | "tsx" => Some(Language::JavaScript),
        "py" => Some(Language::Python),
        "bash" | "sh" | "zsh" => Some(Language::Shell),
        _ => None,
    }
}

#[cfg(test)]
mod tests {
    use ratatui::style::Color;

    use super::*;

    #[test]
    fn rust_addition_highlights_keywords_without_protocol_context() {
        let lines = render_diff(
            "*** Update File: src/lib.rs\n+pub fn answer() -> u8 { 42 }",
            ColorCapabilities {
                level: crate::terminal::palette::ColorLevel::TrueColor,
                color_enabled: true,
            },
        );
        assert_eq!(lines[1].spans[1].content, "pub");
        assert!(lines[1].spans[1]
            .style
            .add_modifier
            .contains(Modifier::BOLD));
        assert_eq!(lines[1].spans[0].style.fg, Some(Color::Rgb(80, 200, 120)));
    }

    #[test]
    fn detects_unified_and_apply_patch_shapes() {
        assert!(looks_like_diff("diff --git a/a b/a\n@@ -1 +1 @@"));
        assert!(looks_like_diff("*** Begin Patch\n*** Add File: x"));
        assert!(!looks_like_diff("ordinary tool output"));
    }
}