use std::fmt;
use crate::highlighters::Highlighter;
use crate::highlighters::MietteHighlighter;
use crate::protocol::Diagnostic;
use crate::GraphicalReportHandler;
use crate::GraphicalTheme;
use crate::NarratableReportHandler;
use crate::ReportHandler;
use crate::ThemeCharacters;
use crate::ThemeStyles;
#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)]
pub enum RgbColors {
Always,
Preferred,
#[default]
Never,
}
#[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>,
pub(crate) break_words: Option<bool>,
pub(crate) wrap_lines: Option<bool>,
pub(crate) word_separator: Option<textwrap::WordSeparator>,
pub(crate) word_splitter: Option<textwrap::WordSplitter>,
pub(crate) highlighter: Option<MietteHighlighter>,
pub(crate) show_related_as_nested: Option<bool>,
}
impl MietteHandlerOpts {
pub fn new() -> Self {
Default::default()
}
pub fn terminal_links(mut self, linkify: bool) -> Self {
self.linkify = Some(linkify);
self
}
pub fn graphical_theme(mut self, theme: GraphicalTheme) -> Self {
self.theme = Some(theme);
self
}
pub fn with_syntax_highlighting(
mut self,
highlighter: impl Highlighter + Send + Sync + 'static,
) -> Self {
self.highlighter = Some(MietteHighlighter::from(highlighter));
self
}
pub fn without_syntax_highlighting(mut self) -> Self {
self.highlighter = Some(MietteHighlighter::nocolor());
self
}
pub fn width(mut self, width: usize) -> Self {
self.width = Some(width);
self
}
pub fn wrap_lines(mut self, wrap_lines: bool) -> Self {
self.wrap_lines = Some(wrap_lines);
self
}
pub fn break_words(mut self, break_words: bool) -> Self {
self.break_words = Some(break_words);
self
}
pub fn word_separator(mut self, word_separator: textwrap::WordSeparator) -> Self {
self.word_separator = Some(word_separator);
self
}
pub fn word_splitter(mut self, word_splitter: textwrap::WordSplitter) -> Self {
self.word_splitter = Some(word_splitter);
self
}
pub fn with_cause_chain(mut self) -> Self {
self.with_cause_chain = Some(true);
self
}
pub fn without_cause_chain(mut self) -> Self {
self.with_cause_chain = Some(false);
self
}
pub fn show_related_errors_as_siblings(mut self) -> Self {
self.show_related_as_nested = Some(false);
self
}
pub fn show_related_errors_as_nested(mut self) -> Self {
self.show_related_as_nested = Some(true);
self
}
pub fn color(mut self, color: bool) -> Self {
self.color = Some(color);
self
}
pub fn rgb_colors(mut self, color: RgbColors) -> Self {
self.rgb_colors = color;
self
}
pub fn unicode(mut self, unicode: bool) -> Self {
self.unicode = Some(unicode);
self
}
pub fn force_graphical(mut self, force: bool) -> Self {
self.force_graphical = Some(force);
self
}
pub fn force_narrated(mut self, force: bool) -> Self {
self.force_narrated = Some(force);
self
}
pub fn footer(mut self, footer: String) -> Self {
self.footer = Some(footer);
self
}
pub fn context_lines(mut self, context_lines: usize) -> Self {
self.context_lines = Some(context_lines);
self
}
pub fn tab_width(mut self, width: usize) -> Self {
self.tab_width = Some(width);
self
}
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 syscall::supports_unicode() => ThemeCharacters::unicode(),
None => ThemeCharacters::ascii(),
};
let styles = if self.color == Some(false) {
ThemeStyles::none()
} else if let Some(color_has_16m) = syscall::supports_color_has_16m() {
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()
};
#[cfg(not(feature = "syntect-highlighter"))]
let highlighter = self.highlighter.unwrap_or_else(MietteHighlighter::nocolor);
#[cfg(feature = "syntect-highlighter")]
let highlighter = if self.color == Some(false) {
MietteHighlighter::nocolor()
} else if self.color == Some(true) || syscall::supports_color() {
match self.highlighter {
Some(highlighter) => highlighter,
None => match self.rgb_colors {
RgbColors::Never => MietteHighlighter::nocolor(),
_ => MietteHighlighter::syntect_truecolor(),
},
}
} else {
MietteHighlighter::nocolor()
};
let theme = self.theme.unwrap_or(GraphicalTheme { characters, styles });
let mut handler = GraphicalReportHandler::new_themed(theme)
.with_width(width)
.with_links(linkify);
handler.highlighter = highlighter;
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);
}
if let Some(b) = self.break_words {
handler = handler.with_break_words(b)
}
if let Some(b) = self.wrap_lines {
handler = handler.with_wrap_lines(b)
}
if let Some(s) = self.word_separator {
handler = handler.with_word_separator(s)
}
if let Some(s) = self.word_splitter {
handler = handler.with_word_splitter(s)
}
if let Some(b) = self.show_related_as_nested {
handler = handler.with_show_related_as_nested(b)
}
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
}
}
pub(crate) fn use_links(&self) -> bool {
if let Some(linkify) = self.linkify {
linkify
} else {
syscall::supports_hyperlinks()
}
}
pub(crate) fn get_width(&self) -> usize {
self.width
.unwrap_or_else(|| syscall::terminal_width().unwrap_or(80))
}
}
#[allow(missing_debug_implementations)]
pub struct MietteHandler {
inner: Box<dyn ReportHandler + Send + Sync>,
}
impl MietteHandler {
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)
}
}
mod syscall {
use cfg_if::cfg_if;
#[inline]
pub(super) fn terminal_width() -> Option<usize> {
cfg_if! {
if #[cfg(any(feature = "fancy-no-syscall", miri))] {
None
} else {
terminal_size::terminal_size().map(|size| size.0 .0 as usize)
}
}
}
#[inline]
pub(super) fn supports_hyperlinks() -> bool {
cfg_if! {
if #[cfg(feature = "fancy-no-syscall")] {
false
} else {
supports_hyperlinks::on(supports_hyperlinks::Stream::Stderr)
}
}
}
#[cfg(feature = "syntect-highlighter")]
#[inline]
pub(super) fn supports_color() -> bool {
cfg_if! {
if #[cfg(feature = "fancy-no-syscall")] {
false
} else {
supports_color::on(supports_color::Stream::Stderr).is_some()
}
}
}
#[inline]
pub(super) fn supports_color_has_16m() -> Option<bool> {
cfg_if! {
if #[cfg(feature = "fancy-no-syscall")] {
None
} else {
supports_color::on(supports_color::Stream::Stderr).map(|color| color.has_16m)
}
}
}
#[inline]
pub(super) fn supports_unicode() -> bool {
cfg_if! {
if #[cfg(feature = "fancy-no-syscall")] {
false
} else {
supports_unicode::on(supports_unicode::Stream::Stderr)
}
}
}
}