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