1use std::fmt;
2
3use crate::highlighters::Highlighter;
4use crate::highlighters::MietteHighlighter;
5use crate::protocol::Diagnostic;
6use crate::GraphicalReportHandler;
7use crate::GraphicalTheme;
8use crate::NarratableReportHandler;
9use crate::ReportHandler;
10use crate::ThemeCharacters;
11use crate::ThemeStyles;
12
13#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)]
15pub enum RgbColors {
16 Always,
18 Preferred,
20 #[default]
22 Never,
23}
24
25#[derive(Default, Debug, Clone)]
42pub struct MietteHandlerOpts {
43 pub(crate) linkify: Option<bool>,
44 pub(crate) width: Option<usize>,
45 pub(crate) theme: Option<GraphicalTheme>,
46 pub(crate) force_graphical: Option<bool>,
47 pub(crate) force_narrated: Option<bool>,
48 pub(crate) rgb_colors: RgbColors,
49 pub(crate) color: Option<bool>,
50 pub(crate) unicode: Option<bool>,
51 pub(crate) footer: Option<String>,
52 pub(crate) context_lines: Option<usize>,
53 pub(crate) tab_width: Option<usize>,
54 pub(crate) with_cause_chain: Option<bool>,
55 pub(crate) break_words: Option<bool>,
56 pub(crate) wrap_lines: Option<bool>,
57 pub(crate) word_separator: Option<textwrap::WordSeparator>,
58 pub(crate) word_splitter: Option<textwrap::WordSplitter>,
59 pub(crate) highlighter: Option<MietteHighlighter>,
60}
61
62impl MietteHandlerOpts {
63 pub fn new() -> Self {
65 Default::default()
66 }
67
68 pub fn terminal_links(mut self, linkify: bool) -> Self {
72 self.linkify = Some(linkify);
73 self
74 }
75
76 pub fn graphical_theme(mut self, theme: GraphicalTheme) -> Self {
81 self.theme = Some(theme);
82 self
83 }
84
85 pub fn with_syntax_highlighting(
103 mut self,
104 highlighter: impl Highlighter + Send + Sync + 'static,
105 ) -> Self {
106 self.highlighter = Some(MietteHighlighter::from(highlighter));
107 self
108 }
109
110 pub fn without_syntax_highlighting(mut self) -> Self {
118 self.highlighter = Some(MietteHighlighter::nocolor());
119 self
120 }
121
122 pub fn width(mut self, width: usize) -> Self {
124 self.width = Some(width);
125 self
126 }
127
128 pub fn wrap_lines(mut self, wrap_lines: bool) -> Self {
134 self.wrap_lines = Some(wrap_lines);
135 self
136 }
137
138 pub fn break_words(mut self, break_words: bool) -> Self {
144 self.break_words = Some(break_words);
145 self
146 }
147 pub fn word_separator(mut self, word_separator: textwrap::WordSeparator) -> Self {
149 self.word_separator = Some(word_separator);
150 self
151 }
152
153 pub fn word_splitter(mut self, word_splitter: textwrap::WordSplitter) -> Self {
155 self.word_splitter = Some(word_splitter);
156 self
157 }
158 pub fn with_cause_chain(mut self) -> Self {
160 self.with_cause_chain = Some(true);
161 self
162 }
163
164 pub fn without_cause_chain(mut self) -> Self {
166 self.with_cause_chain = Some(false);
167 self
168 }
169
170 pub fn color(mut self, color: bool) -> Self {
180 self.color = Some(color);
181 self
182 }
183
184 pub fn rgb_colors(mut self, color: RgbColors) -> Self {
194 self.rgb_colors = color;
195 self
196 }
197
198 pub fn unicode(mut self, unicode: bool) -> Self {
201 self.unicode = Some(unicode);
202 self
203 }
204
205 pub fn force_graphical(mut self, force: bool) -> Self {
208 self.force_graphical = Some(force);
209 self
210 }
211
212 pub fn force_narrated(mut self, force: bool) -> Self {
214 self.force_narrated = Some(force);
215 self
216 }
217
218 pub fn footer(mut self, footer: String) -> Self {
220 self.footer = Some(footer);
221 self
222 }
223
224 pub fn context_lines(mut self, context_lines: usize) -> Self {
226 self.context_lines = Some(context_lines);
227 self
228 }
229
230 pub fn tab_width(mut self, width: usize) -> Self {
232 self.tab_width = Some(width);
233 self
234 }
235
236 pub fn build(self) -> MietteHandler {
238 let graphical = self.is_graphical();
239 let width = self.get_width();
240 if !graphical {
241 let mut handler = NarratableReportHandler::new();
242 if let Some(footer) = self.footer {
243 handler = handler.with_footer(footer);
244 }
245 if let Some(context_lines) = self.context_lines {
246 handler = handler.with_context_lines(context_lines);
247 }
248 if let Some(with_cause_chain) = self.with_cause_chain {
249 if with_cause_chain {
250 handler = handler.with_cause_chain();
251 } else {
252 handler = handler.without_cause_chain();
253 }
254 }
255 MietteHandler {
256 inner: Box::new(handler),
257 }
258 } else {
259 let linkify = self.use_links();
260 let characters = match self.unicode {
261 Some(true) => ThemeCharacters::unicode(),
262 Some(false) => ThemeCharacters::ascii(),
263 None if syscall::supports_unicode() => ThemeCharacters::unicode(),
264 None => ThemeCharacters::ascii(),
265 };
266 let styles = if self.color == Some(false) {
267 ThemeStyles::none()
268 } else if let Some(color_has_16m) = syscall::supports_color_has_16m() {
269 match self.rgb_colors {
270 RgbColors::Always => ThemeStyles::rgb(),
271 RgbColors::Preferred if color_has_16m => ThemeStyles::rgb(),
272 _ => ThemeStyles::ansi(),
273 }
274 } else if self.color == Some(true) {
275 match self.rgb_colors {
276 RgbColors::Always => ThemeStyles::rgb(),
277 _ => ThemeStyles::ansi(),
278 }
279 } else {
280 ThemeStyles::none()
281 };
282 #[cfg(not(feature = "syntect-highlighter"))]
283 let highlighter = self.highlighter.unwrap_or_else(MietteHighlighter::nocolor);
284 #[cfg(feature = "syntect-highlighter")]
285 let highlighter = if self.color == Some(false) {
286 MietteHighlighter::nocolor()
287 } else if self.color == Some(true) || syscall::supports_color() {
288 match self.highlighter {
289 Some(highlighter) => highlighter,
290 None => match self.rgb_colors {
291 RgbColors::Never => MietteHighlighter::nocolor(),
296 _ => MietteHighlighter::syntect_truecolor(),
297 },
298 }
299 } else {
300 MietteHighlighter::nocolor()
301 };
302 let theme = self.theme.unwrap_or(GraphicalTheme { characters, styles });
303 let mut handler = GraphicalReportHandler::new_themed(theme)
304 .with_width(width)
305 .with_links(linkify);
306 handler.highlighter = highlighter;
307 if let Some(with_cause_chain) = self.with_cause_chain {
308 if with_cause_chain {
309 handler = handler.with_cause_chain();
310 } else {
311 handler = handler.without_cause_chain();
312 }
313 }
314 if let Some(footer) = self.footer {
315 handler = handler.with_footer(footer);
316 }
317 if let Some(context_lines) = self.context_lines {
318 handler = handler.with_context_lines(context_lines);
319 }
320 if let Some(w) = self.tab_width {
321 handler = handler.tab_width(w);
322 }
323 if let Some(b) = self.break_words {
324 handler = handler.with_break_words(b)
325 }
326 if let Some(b) = self.wrap_lines {
327 handler = handler.with_wrap_lines(b)
328 }
329 if let Some(s) = self.word_separator {
330 handler = handler.with_word_separator(s)
331 }
332 if let Some(s) = self.word_splitter {
333 handler = handler.with_word_splitter(s)
334 }
335
336 MietteHandler {
337 inner: Box::new(handler),
338 }
339 }
340 }
341
342 pub(crate) fn is_graphical(&self) -> bool {
343 if let Some(force_narrated) = self.force_narrated {
344 !force_narrated
345 } else if let Some(force_graphical) = self.force_graphical {
346 force_graphical
347 } else if let Ok(env) = std::env::var("NO_GRAPHICS") {
348 env == "0"
349 } else {
350 true
351 }
352 }
353
354 pub(crate) fn use_links(&self) -> bool {
357 if let Some(linkify) = self.linkify {
358 linkify
359 } else {
360 syscall::supports_hyperlinks()
361 }
362 }
363
364 pub(crate) fn get_width(&self) -> usize {
365 self.width
366 .unwrap_or_else(|| syscall::terminal_width().unwrap_or(80))
367 }
368}
369
370#[allow(missing_debug_implementations)]
385pub struct MietteHandler {
386 inner: Box<dyn ReportHandler + Send + Sync>,
387}
388
389impl MietteHandler {
390 pub fn new() -> Self {
392 Default::default()
393 }
394}
395
396impl Default for MietteHandler {
397 fn default() -> Self {
398 MietteHandlerOpts::new().build()
399 }
400}
401
402impl ReportHandler for MietteHandler {
403 fn debug(&self, diagnostic: &(dyn Diagnostic), f: &mut fmt::Formatter<'_>) -> fmt::Result {
404 if f.alternate() {
405 return fmt::Debug::fmt(diagnostic, f);
406 }
407
408 self.inner.debug(diagnostic, f)
409 }
410}
411
412mod syscall {
413 use cfg_if::cfg_if;
414
415 #[inline]
416 pub(super) fn terminal_width() -> Option<usize> {
417 cfg_if! {
418 if #[cfg(any(feature = "fancy-no-syscall", miri))] {
419 None
420 } else {
421 terminal_size::terminal_size().map(|size| size.0 .0 as usize)
422 }
423 }
424 }
425
426 #[inline]
427 pub(super) fn supports_hyperlinks() -> bool {
428 cfg_if! {
429 if #[cfg(feature = "fancy-no-syscall")] {
430 false
431 } else {
432 supports_hyperlinks::on(supports_hyperlinks::Stream::Stderr)
433 }
434 }
435 }
436
437 #[cfg(feature = "syntect-highlighter")]
438 #[inline]
439 pub(super) fn supports_color() -> bool {
440 cfg_if! {
441 if #[cfg(feature = "fancy-no-syscall")] {
442 false
443 } else {
444 supports_color::on(supports_color::Stream::Stderr).is_some()
445 }
446 }
447 }
448
449 #[inline]
450 pub(super) fn supports_color_has_16m() -> Option<bool> {
451 cfg_if! {
452 if #[cfg(feature = "fancy-no-syscall")] {
453 None
454 } else {
455 supports_color::on(supports_color::Stream::Stderr).map(|color| color.has_16m)
456 }
457 }
458 }
459
460 #[inline]
461 pub(super) fn supports_unicode() -> bool {
462 cfg_if! {
463 if #[cfg(feature = "fancy-no-syscall")] {
464 false
465 } else {
466 supports_unicode::on(supports_unicode::Stream::Stderr)
467 }
468 }
469 }
470}