Skip to main content

shape_runtime/
content_renderer.rs

1//! ContentRenderer trait for rendering ContentNode to various output formats.
2//!
3//! Implementations can target different output formats:
4//! - Terminal (ANSI escape codes)
5//! - Plain text (no formatting)
6//! - HTML (span/table/svg)
7//! - Markdown (GFM tables, fenced code)
8//! - JSON (structured tree)
9
10use shape_value::content::ContentNode;
11
12/// Environment context for rendering — terminal width, theme, row limits.
13///
14/// Renderers that need environment awareness (e.g. terminal column sizing)
15/// store a `RenderContext` as a field and use it internally.
16#[derive(Debug, Clone)]
17pub struct RenderContext {
18    /// Max output width in columns (e.g. terminal width), or None for unlimited.
19    pub max_width: Option<usize>,
20    /// Color theme hint.
21    pub theme: Theme,
22    /// Max rows to display in tables (overrides ContentTable.max_rows when set).
23    pub max_rows: Option<usize>,
24    /// Whether the output target supports interactive elements (e.g. ECharts).
25    pub interactive: bool,
26}
27
28/// Color theme hint for renderers.
29#[derive(Debug, Clone, Copy, PartialEq)]
30pub enum Theme {
31    Dark,
32    Light,
33}
34
35impl Default for RenderContext {
36    fn default() -> Self {
37        Self {
38            max_width: Some(80),
39            theme: Theme::Dark,
40            max_rows: Some(50),
41            interactive: false,
42        }
43    }
44}
45
46impl RenderContext {
47    /// Create a terminal-aware context.
48    pub fn terminal() -> Self {
49        Self {
50            max_width: terminal_width(),
51            theme: Theme::Dark,
52            max_rows: Some(50),
53            interactive: false,
54        }
55    }
56
57    /// Create an HTML context (unlimited width, interactive).
58    pub fn html() -> Self {
59        Self {
60            max_width: None,
61            theme: Theme::Dark,
62            max_rows: Some(100),
63            interactive: true,
64        }
65    }
66}
67
68/// Try to detect terminal width via COLUMNS env var.
69fn terminal_width() -> Option<usize> {
70    std::env::var("COLUMNS")
71        .ok()
72        .and_then(|s| s.parse().ok())
73        .or(Some(80))
74}
75
76/// Describes the capabilities of a renderer.
77#[derive(Debug, Clone)]
78pub struct RendererCapabilities {
79    /// Whether the renderer supports ANSI escape codes.
80    pub ansi: bool,
81    /// Whether the renderer supports unicode box-drawing characters.
82    pub unicode: bool,
83    /// Whether the renderer supports color output.
84    pub color: bool,
85    /// Whether the renderer supports interactive/hyperlink features.
86    pub interactive: bool,
87}
88
89impl RendererCapabilities {
90    /// Full terminal capabilities (ANSI + unicode + color).
91    pub fn terminal() -> Self {
92        Self {
93            ansi: true,
94            unicode: true,
95            color: true,
96            interactive: false,
97        }
98    }
99
100    /// Plain text only — no ANSI, no unicode, no color.
101    pub fn plain() -> Self {
102        Self {
103            ansi: false,
104            unicode: false,
105            color: false,
106            interactive: false,
107        }
108    }
109
110    /// HTML capabilities — color via CSS, no ANSI.
111    pub fn html() -> Self {
112        Self {
113            ansi: false,
114            unicode: true,
115            color: true,
116            interactive: true,
117        }
118    }
119
120    /// Markdown capabilities — limited formatting.
121    pub fn markdown() -> Self {
122        Self {
123            ansi: false,
124            unicode: false,
125            color: false,
126            interactive: false,
127        }
128    }
129}
130
131/// Trait for rendering a ContentNode tree to a string output.
132///
133/// Implementations should handle all ContentNode variants:
134/// Text, Table, Code, Chart, KeyValue, Fragment.
135pub trait ContentRenderer: Send + Sync {
136    /// Describe what this renderer can handle.
137    fn capabilities(&self) -> RendererCapabilities;
138
139    /// Render the content node tree to a string.
140    fn render(&self, content: &ContentNode) -> String;
141}