Skip to content

Commit

Permalink
feat: Add show_related_errors_as_nested
Browse files Browse the repository at this point in the history
  • Loading branch information
elkowar committed Dec 22, 2024
1 parent c7eeada commit dd72986
Show file tree
Hide file tree
Showing 3 changed files with 157 additions and 14 deletions.
16 changes: 16 additions & 0 deletions src/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ pub struct MietteHandlerOpts {
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 {
Expand Down Expand Up @@ -167,6 +168,18 @@ impl MietteHandlerOpts {
self
}

/// Show related errors as siblings.
pub fn show_related_errors_as_siblings(mut self) -> Self {
self.show_related_as_nested = Some(false);
self
}

/// Show related errors as nested errors.
pub fn show_related_errors_as_nested(mut self) -> Self {
self.show_related_as_nested = Some(true);
self
}

/// If true, colors will be used during graphical rendering, regardless
/// of whether or not the terminal supports them.
///
Expand Down Expand Up @@ -332,6 +345,9 @@ impl MietteHandlerOpts {
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),
Expand Down
95 changes: 82 additions & 13 deletions src/handlers/graphical.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ pub struct GraphicalReportHandler {
pub(crate) word_splitter: Option<textwrap::WordSplitter>,
pub(crate) highlighter: MietteHighlighter,
pub(crate) link_display_text: Option<String>,
pub(crate) show_related_as_nested: bool,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
Expand Down Expand Up @@ -64,6 +65,7 @@ impl GraphicalReportHandler {
word_splitter: None,
highlighter: MietteHighlighter::default(),
link_display_text: None,
show_related_as_nested: false,
}
}

Expand All @@ -83,6 +85,7 @@ impl GraphicalReportHandler {
word_splitter: None,
highlighter: MietteHighlighter::default(),
link_display_text: None,
show_related_as_nested: false,
}
}

Expand Down Expand Up @@ -177,6 +180,12 @@ impl GraphicalReportHandler {
self
}

/// Sets whether to render related errors as nested errors.
pub fn with_show_related_as_nested(mut self, show_related_as_nested: bool) -> Self {
self.show_related_as_nested = show_related_as_nested;
self
}

/// Enable syntax highlighting for source code snippets, using the given
/// [`Highlighter`]. See the [highlighters](crate::highlighters) crate
/// for more details.
Expand Down Expand Up @@ -414,23 +423,83 @@ impl GraphicalReportHandler {
diagnostic: &(dyn Diagnostic),
parent_src: Option<&dyn SourceCode>,
) -> fmt::Result {
let src = diagnostic.source_code().or(parent_src);

if let Some(related) = diagnostic.related() {
let severity_style = match diagnostic.severity() {
Some(Severity::Error) | None => self.theme.styles.error,
Some(Severity::Warning) => self.theme.styles.warning,
Some(Severity::Advice) => self.theme.styles.advice,
};

let mut inner_renderer = self.clone();
// Re-enable the printing of nested cause chains for related errors
inner_renderer.with_cause_chain = true;
for rel in related {
writeln!(f)?;
match rel.severity() {
Some(Severity::Error) | None => write!(f, "Error: ")?,
Some(Severity::Warning) => write!(f, "Warning: ")?,
Some(Severity::Advice) => write!(f, "Advice: ")?,
};
inner_renderer.render_header(f, rel)?;
let src = rel.source_code().or(parent_src);
inner_renderer.render_causes(f, rel, src)?;
inner_renderer.render_snippets(f, rel, src)?;
inner_renderer.render_footer(f, rel)?;
inner_renderer.render_related(f, rel, src)?;
if self.show_related_as_nested {
let width = self.termwidth.saturating_sub(2);
let mut related = related.peekable();
while let Some(rel) = related.next() {
let is_last = related.peek().is_none();
let char = if !is_last {
self.theme.characters.lcross
} else {
self.theme.characters.lbot
};
let initial_indent = format!(
" {}{}{} ",
char, self.theme.characters.hbar, self.theme.characters.rarrow
)
.style(severity_style)
.to_string();
let rest_indent = format!(
" {} ",
if is_last {
' '
} else {
self.theme.characters.vbar
}
)
.style(severity_style)
.to_string();

let mut opts = textwrap::Options::new(width)
.initial_indent(&initial_indent)
.subsequent_indent(&rest_indent)
.break_words(self.break_words);
if let Some(word_separator) = self.word_separator {
opts = opts.word_separator(word_separator);
}
if let Some(word_splitter) = self.word_splitter.clone() {
opts = opts.word_splitter(word_splitter);
}

let mut inner = String::new();

let mut inner_renderer = self.clone();
inner_renderer.footer = None;
inner_renderer.with_cause_chain = false;
inner_renderer.termwidth -= rest_indent.width();
inner_renderer.render_report_inner(&mut inner, rel, src)?;

// If there was no header, remove the leading newline
let inner = inner.trim_matches('\n');
writeln!(f, "{}", self.wrap(inner, opts))?;
}
} else {
for rel in related {
writeln!(f)?;
match rel.severity() {
Some(Severity::Error) | None => write!(f, "Error: ")?,
Some(Severity::Warning) => write!(f, "Warning: ")?,
Some(Severity::Advice) => write!(f, "Advice: ")?,
};
inner_renderer.render_header(f, rel)?;
let src = rel.source_code().or(parent_src);
inner_renderer.render_causes(f, rel, src)?;
inner_renderer.render_snippets(f, rel, src)?;
inner_renderer.render_footer(f, rel)?;
inner_renderer.render_related(f, rel, src)?;
}
}
}
Ok(())
Expand Down
60 changes: 59 additions & 1 deletion tests/test_diagnostic_source_macro.rs
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,65 @@ fn test_nested_cause_chains_for_related_errors_are_output() {
Yooo, a footer
"#;

assert_eq!(expected, out);
pretty_assertions::assert_eq!(expected, out);
}

#[cfg(feature = "fancy-no-backtrace")]
#[test]
fn test_display_related_errors_as_nested() {
let inner_error = TestStructError {
asdf_inner_foo: SourceError {
code: String::from("This is another error"),
help: String::from("You should fix this"),
label: (3, 4),
},
};
let first_error = NestedError {
code: String::from("right here"),
label: (6, 4),
the_other_err: Box::new(inner_error),
};
let second_error = SourceError {
code: String::from("You're actually a mess"),
help: String::from("Get a grip..."),
label: (3, 4),
};
let diag = MultiError {
related_errs: vec![
Box::new(MultiError {
related_errs: vec![Box::new(first_error), Box::new(AnErr)],
}),
Box::new(second_error),
],
};

let mut out = String::new();
miette::GraphicalReportHandler::new_themed(miette::GraphicalTheme::unicode_nocolor())
.with_width(80)
.with_show_related_as_nested(true)
.render_report(&mut out, &diag)
.unwrap();
println!("{}", out);

let expected = r#"
× A multi-error happened
├─▶ × A multi-error happened
│ ├─▶ × A nested error happened
│ │ ╭────
│ │ 1 │ right here
│ │ · ──┬─
│ │ · ╰── here
│ │ ╰────
│ ╰─▶ × AnErr
╰─▶ × A complex error happened
╭────
1 │ You're actually a mess
· ──┬─
· ╰── here
╰────
help: Get a grip...
"#;
pretty_assertions::assert_eq!(expected, out);
}

#[cfg(feature = "fancy-no-backtrace")]
Expand Down

0 comments on commit dd72986

Please sign in to comment.