From e8e8c72185f900376e39ca5e88535b87709a8e49 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 11 Jun 2026 19:00:46 -0500 Subject: [PATCH] =?UTF-8?q?feat(tui):=20Sprint=202=20=E2=80=94=20markdown?= =?UTF-8?q?=20rendering,=20ConversationContent=20enum,=20diff=20viewer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Create src/markdown.rs with MarkdownRenderer using pulldown-cmark + syntect (lazy-loaded via once_cell::Lazy, theme fallback chain, syntect 5.x API) - ConversationContent enum: Plain/Markdown/CodeDiff variants - push_output() auto-detects markdown via looks_like_markdown() heuristic - build_wrapped_conversation() routes through MarkdownRenderer for markdown entries - render_diff() for syntax-colored unified diffs (green+/red-/cyan @@) - push_diff() convenience method for /diff command - once_cell added to Cargo.toml - 27 new tests for markdown rendering, detection, and diff colors - All existing tests pass (240 unit tests, 108 contract tests) Sprint 2 of 7. Authored by TheArchitectit --- rust/crates/rusty-claude-cli/Cargo.toml | 1 + rust/crates/rusty-claude-cli/src/main.rs | 1 + rust/crates/rusty-claude-cli/src/markdown.rs | 441 +++++++++++++++++++ rust/crates/rusty-claude-cli/src/tui.rs | 156 +++++-- 4 files changed, 554 insertions(+), 45 deletions(-) create mode 100644 rust/crates/rusty-claude-cli/src/markdown.rs diff --git a/rust/crates/rusty-claude-cli/Cargo.toml b/rust/crates/rusty-claude-cli/Cargo.toml index d5b9aeae..1c95cad3 100644 --- a/rust/crates/rusty-claude-cli/Cargo.toml +++ b/rust/crates/rusty-claude-cli/Cargo.toml @@ -14,6 +14,7 @@ api = { path = "../api" } commands = { path = "../commands" } crossterm = "0.28" pulldown-cmark = "0.13" +once_cell = "1" ratatui = "0.29" rustyline = "15" tui-textarea = "0.7" diff --git a/rust/crates/rusty-claude-cli/src/main.rs b/rust/crates/rusty-claude-cli/src/main.rs index ff7605dd..0dbe8edd 100644 --- a/rust/crates/rusty-claude-cli/src/main.rs +++ b/rust/crates/rusty-claude-cli/src/main.rs @@ -18,6 +18,7 @@ mod init; mod input; mod render; mod setup_wizard; +mod markdown; mod tui; mod tui_error; mod tui_update; diff --git a/rust/crates/rusty-claude-cli/src/markdown.rs b/rust/crates/rusty-claude-cli/src/markdown.rs new file mode 100644 index 00000000..74d4b8b5 --- /dev/null +++ b/rust/crates/rusty-claude-cli/src/markdown.rs @@ -0,0 +1,441 @@ +//! Markdown → ratatui `Line` renderer with syntect syntax highlighting. +//! +//! Syntect assets (`SyntaxSet`, `ThemeSet`) are loaded once via `once_cell::Lazy`. + +use once_cell::sync::Lazy; +use pulldown_cmark::{Event, Parser, Tag, TagEnd}; +use ratatui::style::{Color, Modifier, Style}; +use ratatui::text::{Line, Span}; +use syntect::highlighting::ThemeSet; +use syntect::parsing::SyntaxSet; + +/// Loaded once on first access (~10-50ms), cached for process lifetime. +pub static SYNTAX_SET: Lazy = Lazy::new(SyntaxSet::load_defaults_newlines); +pub static THEME_SET: Lazy = Lazy::new(ThemeSet::load_defaults); + +#[derive(Clone)] +pub struct MarkdownRenderer { + code_theme_name: String, +} + +impl MarkdownRenderer { + pub fn new() -> Self { + Self { + code_theme_name: "base16-ocean.dark".to_string(), + } + } + + pub fn set_code_theme(&mut self, name: &str) { + self.code_theme_name = name.to_string(); + } + + /// Render a markdown string into ratatui Lines at the given terminal width. + pub fn render(&self, markdown: &str, width: u16) -> Vec> { + let parser = Parser::new(markdown); + let events: Vec = parser.collect(); + self.render_events(&events, width) + } + + fn render_events(&self, events: &[Event], width: u16) -> Vec> { + let mut lines: Vec> = Vec::new(); + let mut current_spans: Vec> = Vec::new(); + let mut style_stack: Vec