ratatui-markdown 0.3.6

Markdown rendering, syntax highlighting, collapsible trees, and rich scroll widgets for ratatui
Documentation
#[path = "utils/mod.rs"]
mod common;

use common::{
    draw_frame, lorem, poll_and_handle, restore_terminal, setup_terminal, AppState, Theme,
};
use ratatui::{
    style::{Color, Modifier, Style},
    text::{Line, Span},
};
use ratatui_markdown::markdown::{MarkdownRenderer, RenderHooks};

struct TimelineCodeHooks;

impl RenderHooks for TimelineCodeHooks {
    fn code_block_header(&self, lang: &str) -> Option<Line<'static>> {
        let timestamp = "12:00:00";
        Some(Line::from(vec![
            Span::styled(
                format!("\u{256d} [{timestamp}] "),
                Style::default().fg(Color::DarkGray),
            ),
            Span::styled(
                lang.to_string(),
                Style::default()
                    .fg(Color::Cyan)
                    .add_modifier(Modifier::BOLD),
            ),
        ]))
    }

    fn code_block_footer(&self, _lang: &str, _content_line_count: usize) -> Option<Line<'static>> {
        Some(Line::from(vec![
            Span::styled("\u{2570} ", Style::default().fg(Color::DarkGray)),
            Span::styled("\u{2191} ", Style::default().fg(Color::Green)),
            Span::styled("156 ", Style::default().fg(Color::DarkGray)),
            Span::styled("\u{2193} ", Style::default().fg(Color::Red)),
            Span::styled("234 ", Style::default().fg(Color::DarkGray)),
            Span::styled("\u{21c4} ", Style::default().fg(Color::Yellow)),
            Span::styled("1 23.5s", Style::default().fg(Color::DarkGray)),
        ]))
    }

    fn code_block_line_prefix(&self, _lang: &str) -> Option<String> {
        Some("\u{2502} ".to_string())
    }
}

const MARKDOWN_TEMPLATE: &str = r#"
# Timeline View

This example shows customized code block rendering with extra content
in the rounded border header and footer, inspired by a timeline view.

## Agent Skill Execution

```rust skill::read_file
use std::fs;
let content = fs::read_to_string("PLAN.md")?;
println!("{}", content);
```

The header shows a timestamp and tool name, while the footer
displays token usage statistics: \u{2191} output \u{2193} input \u{21c4} roundtrips duration.

## Analysis Block

```python skill::analyze
def analyze(data):
    results = []
    for item in data:
        processed = transform(item)
        results.append(processed)
    return aggregate(results)
```

LOREM_3

## Build Step

```bash skill::build
cargo build --release
echo "Build complete"
cp target/release/app /opt/bin/
```

LOREM_2

## Another Block

```javascript skill::render
function render(components) {
  return components
    .map(c => c.toString())
    .join('\n');
}
```

LOREM_3
"#;

fn main() -> anyhow::Result<()> {
    let mut terminal = setup_terminal()?;

    let md = MARKDOWN_TEMPLATE
        .replace("LOREM_2", &lorem(100))
        .replace("LOREM_3", &lorem(150));

    let theme = Theme;
    let renderer = MarkdownRenderer::new(76).with_render_hooks(Box::new(TimelineCodeHooks));
    let blocks = renderer.parse(&md);
    let lines = renderer.render(&blocks, &theme);
    let mut state = AppState::new(lines.len());

    loop {
        terminal.draw(|f| {
            draw_frame(
                f,
                "Custom Code Block",
                &lines,
                &mut state,
                "\u{2191}\u{2193}/jk scroll \u{00b7} PgUp/PgDn \u{00b7} Home/End \u{00b7} q quit",
            );
        })?;
        if poll_and_handle(&mut state)? {
            break;
        }
    }

    restore_terminal(&mut terminal)?;
    Ok(())
}