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