Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add show_related_errors_as_nested-option #417

Merged
merged 1 commit into from
Dec 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
58 changes: 58 additions & 0 deletions tests/test_diagnostic_source_macro.rs
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,64 @@ fn test_nested_cause_chains_for_related_errors_are_output() {
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...
"#;
assert_eq!(expected, out);
}

#[cfg(feature = "fancy-no-backtrace")]
#[derive(Debug, miette::Diagnostic, thiserror::Error)]
#[error("A case1 error happened")]
Expand Down
Loading