diff options
Diffstat (limited to 'vendor/miette/src/handler.rs')
-rw-r--r-- | vendor/miette/src/handler.rs | 323 |
1 files changed, 323 insertions, 0 deletions
diff --git a/vendor/miette/src/handler.rs b/vendor/miette/src/handler.rs new file mode 100644 index 0000000..e983a55 --- /dev/null +++ b/vendor/miette/src/handler.rs @@ -0,0 +1,323 @@ +use std::fmt; + +use crate::protocol::Diagnostic; +use crate::GraphicalReportHandler; +use crate::GraphicalTheme; +use crate::NarratableReportHandler; +use crate::ReportHandler; +use crate::ThemeCharacters; +use crate::ThemeStyles; + +/// Settings to control the color format used for graphical rendering. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum RgbColors { + /// Use RGB colors even if the terminal does not support them + Always, + /// Use RGB colors instead of ANSI if the terminal supports RGB + Preferred, + /// Always use ANSI, regardless of terminal support for RGB + Never, +} + +impl Default for RgbColors { + fn default() -> RgbColors { + RgbColors::Never + } +} + +/** +Create a custom [`MietteHandler`] from options. + +## Example + +```no_run +miette::set_hook(Box::new(|_| { + Box::new(miette::MietteHandlerOpts::new() + .terminal_links(true) + .unicode(false) + .context_lines(3) + .build()) +})) +# .unwrap(); +``` +*/ +#[derive(Default, Debug, Clone)] +pub struct MietteHandlerOpts { + pub(crate) linkify: Option<bool>, + pub(crate) width: Option<usize>, + pub(crate) theme: Option<GraphicalTheme>, + pub(crate) force_graphical: Option<bool>, + pub(crate) force_narrated: Option<bool>, + pub(crate) rgb_colors: RgbColors, + pub(crate) color: Option<bool>, + pub(crate) unicode: Option<bool>, + pub(crate) footer: Option<String>, + pub(crate) context_lines: Option<usize>, + pub(crate) tab_width: Option<usize>, + pub(crate) with_cause_chain: Option<bool>, +} + +impl MietteHandlerOpts { + /// Create a new `MietteHandlerOpts`. + pub fn new() -> Self { + Default::default() + } + + /// If true, specify whether the graphical handler will make codes be + /// clickable links in supported terminals. Defaults to auto-detection + /// based on known supported terminals. + pub fn terminal_links(mut self, linkify: bool) -> Self { + self.linkify = Some(linkify); + self + } + + /// Set a graphical theme for the handler when rendering in graphical mode. + /// Use [`force_graphical()`](`MietteHandlerOpts::force_graphical) to force + /// graphical mode. This option overrides + /// [`color()`](`MietteHandlerOpts::color). + pub fn graphical_theme(mut self, theme: GraphicalTheme) -> Self { + self.theme = Some(theme); + self + } + + /// Sets the width to wrap the report at. Defaults to 80. + pub fn width(mut self, width: usize) -> Self { + self.width = Some(width); + self + } + + /// Include the cause chain of the top-level error in the report. + pub fn with_cause_chain(mut self) -> Self { + self.with_cause_chain = Some(true); + self + } + + /// Do not include the cause chain of the top-level error in the report. + pub fn without_cause_chain(mut self) -> Self { + self.with_cause_chain = Some(false); + self + } + + /// If true, colors will be used during graphical rendering, regardless + /// of whether or not the terminal supports them. + /// + /// If false, colors will never be used. + /// + /// If unspecified, colors will be used only if the terminal supports them. + /// + /// The actual format depends on the value of + /// [`MietteHandlerOpts::rgb_colors`]. + pub fn color(mut self, color: bool) -> Self { + self.color = Some(color); + self + } + + /// Controls which color format to use if colors are used in graphical + /// rendering. + /// + /// The default is `Never`. + /// + /// This value does not control whether or not colors are being used in the + /// first place. That is handled by the [`MietteHandlerOpts::color`] + /// setting. If colors are not being used, the value of `rgb_colors` has + /// no effect. + pub fn rgb_colors(mut self, color: RgbColors) -> Self { + self.rgb_colors = color; + self + } + + /// If true, forces unicode display for graphical output. If set to false, + /// forces ASCII art display. + pub fn unicode(mut self, unicode: bool) -> Self { + self.unicode = Some(unicode); + self + } + + /// If true, graphical rendering will be used regardless of terminal + /// detection. + pub fn force_graphical(mut self, force: bool) -> Self { + self.force_graphical = Some(force); + self + } + + /// If true, forces use of the narrated renderer. + pub fn force_narrated(mut self, force: bool) -> Self { + self.force_narrated = Some(force); + self + } + + /// Set a footer to be displayed at the bottom of the report. + pub fn footer(mut self, footer: String) -> Self { + self.footer = Some(footer); + self + } + + /// Sets the number of context lines before and after a span to display. + pub fn context_lines(mut self, context_lines: usize) -> Self { + self.context_lines = Some(context_lines); + self + } + + /// Set the displayed tab width in spaces. + pub fn tab_width(mut self, width: usize) -> Self { + self.tab_width = Some(width); + self + } + + /// Builds a [`MietteHandler`] from this builder. + pub fn build(self) -> MietteHandler { + let graphical = self.is_graphical(); + let width = self.get_width(); + if !graphical { + let mut handler = NarratableReportHandler::new(); + if let Some(footer) = self.footer { + handler = handler.with_footer(footer); + } + if let Some(context_lines) = self.context_lines { + handler = handler.with_context_lines(context_lines); + } + if let Some(with_cause_chain) = self.with_cause_chain { + if with_cause_chain { + handler = handler.with_cause_chain(); + } else { + handler = handler.without_cause_chain(); + } + } + MietteHandler { + inner: Box::new(handler), + } + } else { + let linkify = self.use_links(); + let characters = match self.unicode { + Some(true) => ThemeCharacters::unicode(), + Some(false) => ThemeCharacters::ascii(), + None if supports_unicode::on(supports_unicode::Stream::Stderr) => { + ThemeCharacters::unicode() + } + None => ThemeCharacters::ascii(), + }; + let styles = if self.color == Some(false) { + ThemeStyles::none() + } else if let Some(color) = supports_color::on(supports_color::Stream::Stderr) { + match self.rgb_colors { + RgbColors::Always => ThemeStyles::rgb(), + RgbColors::Preferred if color.has_16m => ThemeStyles::rgb(), + _ => ThemeStyles::ansi(), + } + } else if self.color == Some(true) { + match self.rgb_colors { + RgbColors::Always => ThemeStyles::rgb(), + _ => ThemeStyles::ansi(), + } + } else { + ThemeStyles::none() + }; + let theme = self.theme.unwrap_or(GraphicalTheme { characters, styles }); + let mut handler = GraphicalReportHandler::new() + .with_width(width) + .with_links(linkify) + .with_theme(theme); + if let Some(with_cause_chain) = self.with_cause_chain { + if with_cause_chain { + handler = handler.with_cause_chain(); + } else { + handler = handler.without_cause_chain(); + } + } + if let Some(footer) = self.footer { + handler = handler.with_footer(footer); + } + if let Some(context_lines) = self.context_lines { + handler = handler.with_context_lines(context_lines); + } + if let Some(w) = self.tab_width { + handler = handler.tab_width(w); + } + MietteHandler { + inner: Box::new(handler), + } + } + } + + pub(crate) fn is_graphical(&self) -> bool { + if let Some(force_narrated) = self.force_narrated { + !force_narrated + } else if let Some(force_graphical) = self.force_graphical { + force_graphical + } else if let Ok(env) = std::env::var("NO_GRAPHICS") { + env == "0" + } else { + true + } + } + + // Detects known terminal apps based on env variables and returns true if + // they support rendering links. + pub(crate) fn use_links(&self) -> bool { + if let Some(linkify) = self.linkify { + linkify + } else { + supports_hyperlinks::on(supports_hyperlinks::Stream::Stderr) + } + } + + #[cfg(not(miri))] + pub(crate) fn get_width(&self) -> usize { + self.width.unwrap_or_else(|| { + terminal_size::terminal_size() + .unwrap_or((terminal_size::Width(80), terminal_size::Height(0))) + .0 + .0 as usize + }) + } + + #[cfg(miri)] + // miri doesn't support a syscall (specifically ioctl) + // performed by terminal_size, which causes test execution to fail + // so when miri is running we'll just fallback to a constant + pub(crate) fn get_width(&self) -> usize { + self.width.unwrap_or(80) + } +} + +/** +A [`ReportHandler`] that displays a given [`Report`](crate::Report) in a +quasi-graphical way, using terminal colors, unicode drawing characters, and +other such things. + +This is the default reporter bundled with `miette`. + +This printer can be customized by using +[`GraphicalReportHandler::new_themed()`] and handing it a [`GraphicalTheme`] of +your own creation (or using one of its own defaults). + +See [`set_hook`](crate::set_hook) for more details on customizing your global +printer. +*/ +#[allow(missing_debug_implementations)] +pub struct MietteHandler { + inner: Box<dyn ReportHandler + Send + Sync>, +} + +impl MietteHandler { + /// Creates a new [`MietteHandler`] with default settings. + pub fn new() -> Self { + Default::default() + } +} + +impl Default for MietteHandler { + fn default() -> Self { + MietteHandlerOpts::new().build() + } +} + +impl ReportHandler for MietteHandler { + fn debug(&self, diagnostic: &(dyn Diagnostic), f: &mut fmt::Formatter<'_>) -> fmt::Result { + if f.alternate() { + return fmt::Debug::fmt(diagnostic, f); + } + + self.inner.debug(diagnostic, f) + } +} |