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}