1use crate::frame::Frame;
3use rich::protocol::{
4 CodeHighlighting, ConsoleCodeHighlighting, ConsoleEnvironment, RenderEnvironment, Support,
5 TargetCapabilities,
6};
7use rich::{Console, Renderable, Segment, Theme};
8use std::sync::Arc;
9
10#[derive(Clone, Copy, Debug, PartialEq, Eq)]
12pub enum TargetKind {
13 Terminal,
14 PlainStream,
15 Capture,
16 Html,
17 Svg,
18 Custom,
19}
20
21#[derive(Clone, Debug)]
23pub struct RenderTarget {
24 kind: TargetKind,
25 capabilities: TargetCapabilities,
26 theme: Theme,
27 code_highlighting: Option<CodeHighlighting>,
29}
30impl RenderTarget {
31 pub fn new(kind: TargetKind, mut caps: TargetCapabilities, theme: Theme) -> Self {
32 if !matches!(kind, TargetKind::Terminal | TargetKind::Custom) {
33 caps.interactive = false;
34 caps.sixel = Support::Unsupported;
35 }
36 if kind == TargetKind::PlainStream {
37 caps.color_system = None;
38 caps.hyperlinks = false;
39 }
40 if !caps.interactive {
41 caps.sixel = Support::Unsupported;
42 }
43 Self {
44 kind,
45 capabilities: caps,
46 theme,
47 code_highlighting: None,
48 }
49 }
50 pub fn with_code_highlighting(mut self, highlighting: Option<CodeHighlighting>) -> Self {
53 self.code_highlighting = highlighting;
54 self
55 }
56 pub fn kind(&self) -> TargetKind {
57 self.kind
58 }
59 pub fn console(&self) -> Console {
60 let c = self.capabilities;
61 let mut console = Console::builder()
62 .width(c.width)
63 .height(c.height)
64 .force_terminal(c.interactive)
65 .color_system(c.color_system)
66 .no_color(c.color_system.is_none())
67 .ascii_only(!c.unicode)
68 .legacy_windows(false)
69 .safe_box(true)
70 .emoji(c.unicode)
71 .highlight(false)
72 .theme(self.theme.clone())
73 .build();
74 console.set_render_environment(Some(Arc::new(self.clone())));
75 console.set_code_highlighting(self.code_highlighting.clone());
76 console
77 }
78 pub fn segments(&self, value: &dyn Renderable) -> Vec<Segment> {
79 self.segments_on(&self.console(), value)
80 }
81 fn segments_on(&self, console: &Console, value: &dyn Renderable) -> Vec<Segment> {
82 let c = self.capabilities;
83 if c.width == 0 || c.height == 0 {
84 return Vec::new();
85 }
86 let mut segments = value.rich_render(
87 console,
88 &console.options().update_dimensions(c.width, c.height),
89 );
90 segments.retain(|segment| !segment.control || c.interactive);
91 if !c.hyperlinks {
92 for segment in &mut segments {
93 segment.style = segment.style.as_ref().map(|s| s.update_link(None));
94 }
95 }
96 segments
97 }
98 pub fn frame(&self, value: &dyn Renderable) -> Frame {
102 Frame::from_segments(&self.segments(value))
103 }
104 pub fn frame_with_regions(&self, value: &dyn Renderable) -> Frame {
107 self.segments_and_frame_with_regions(value).1
108 }
109 pub(crate) fn segments_and_frame_with_regions(
112 &self,
113 value: &dyn Renderable,
114 ) -> (Vec<Segment>, Frame) {
115 use rich::protocol::ConsoleRegions;
116 let recorder = std::sync::Arc::new(crate::frame::RegionRecorder::new());
117 let mut console = self.console();
118 console.set_region_sink(Some(recorder.clone()));
119 let segments = self.segments_on(&console, value);
120 let frame = Frame::from_segments(&segments).with_regions(&recorder);
121 (segments, frame)
122 }
123 pub fn text(&self, value: &dyn Renderable) -> String {
124 self.console().segments_to_string(&self.segments(value))
125 }
126}
127impl RenderEnvironment for RenderTarget {
128 fn capabilities(&self) -> TargetCapabilities {
129 self.capabilities
130 }
131}
132
133#[derive(Clone, Copy, Debug, PartialEq, Eq)]
135pub enum CapabilityOrigin {
136 Configured,
137 Detected,
138 Inferred,
139 Default,
140}
141#[derive(Clone, Debug)]
142pub struct DetectedCapabilities {
143 pub capabilities: TargetCapabilities,
144 pub origins: Vec<(String, CapabilityOrigin)>,
145}
146#[derive(Clone, Copy, Debug)]
147pub struct TargetObservations {
148 pub width: Option<usize>,
149 pub height: Option<usize>,
150 pub is_terminal: bool,
151 pub color_system: Option<rich::ColorSystem>,
152 pub unicode: bool,
153 pub hyperlinks: bool,
154 pub sixel_hint: Support,
155}
156#[derive(Clone, Copy, Debug, Default)]
157pub struct TargetOverrides {
158 pub width: Option<usize>,
159 pub height: Option<usize>,
160 pub interactive: Option<bool>,
161 pub color_system: Option<Option<rich::ColorSystem>>,
162 pub unicode: Option<bool>,
163 pub hyperlinks: Option<bool>,
164 pub sixel: Option<Support>,
165}
166pub fn resolve_capabilities(
168 o: TargetObservations,
169 overrides: TargetOverrides,
170) -> DetectedCapabilities {
171 let mut origins = Vec::new();
172 fn select<T>(
173 name: &str,
174 configured: Option<T>,
175 observed: T,
176 origin: CapabilityOrigin,
177 origins: &mut Vec<(String, CapabilityOrigin)>,
178 ) -> T {
179 match configured {
180 Some(v) => {
181 origins.push((name.into(), CapabilityOrigin::Configured));
182 v
183 }
184 None => {
185 origins.push((name.into(), origin));
186 observed
187 }
188 }
189 }
190 use CapabilityOrigin::{Default, Detected, Inferred};
191 let capabilities = TargetCapabilities {
192 width: select(
193 "width",
194 overrides.width,
195 o.width.unwrap_or(80),
196 if o.width.is_some() { Detected } else { Default },
197 &mut origins,
198 ),
199 height: select(
200 "height",
201 overrides.height,
202 o.height.unwrap_or(25),
203 if o.height.is_some() {
204 Detected
205 } else {
206 Default
207 },
208 &mut origins,
209 ),
210 color_system: select(
211 "color_system",
212 overrides.color_system,
213 o.color_system,
214 Detected,
215 &mut origins,
216 ),
217 interactive: select(
218 "interactive",
219 overrides.interactive,
220 o.is_terminal,
221 Detected,
222 &mut origins,
223 ),
224 unicode: select(
225 "unicode",
226 overrides.unicode,
227 o.unicode,
228 Detected,
229 &mut origins,
230 ),
231 hyperlinks: select(
232 "hyperlinks",
233 overrides.hyperlinks,
234 o.hyperlinks,
235 Detected,
236 &mut origins,
237 ),
238 sixel: select(
239 "sixel",
240 overrides.sixel,
241 o.sixel_hint,
242 if o.sixel_hint == Support::Inferred {
243 Inferred
244 } else {
245 Detected
246 },
247 &mut origins,
248 ),
249 };
250 DetectedCapabilities {
251 capabilities,
252 origins,
253 }
254}