mirror of https://github.com/zkat/miette.git
Merge branch 'main' into feature/syntect
This commit is contained in:
commit
8a7c0ddf99
|
|
@ -58,6 +58,7 @@ pub struct MietteHandlerOpts {
|
|||
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>,
|
||||
|
|
@ -129,6 +130,16 @@ impl MietteHandlerOpts {
|
|||
self
|
||||
}
|
||||
|
||||
/// If true, long lines can be wrapped.
|
||||
///
|
||||
/// If false, long lines will not be broken when they exceed the width.
|
||||
///
|
||||
/// Defaults to true.
|
||||
pub fn wrap_lines(mut self, wrap_lines: bool) -> Self {
|
||||
self.wrap_lines = Some(wrap_lines);
|
||||
self
|
||||
}
|
||||
|
||||
/// If true, long words can be broken when wrapping.
|
||||
///
|
||||
/// If false, long words will not be broken when they exceed the width.
|
||||
|
|
@ -138,7 +149,6 @@ impl MietteHandlerOpts {
|
|||
self.break_words = Some(break_words);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the `textwrap::WordSeparator` to use when determining wrap points.
|
||||
pub fn word_separator(mut self, word_separator: textwrap::WordSeparator) -> Self {
|
||||
self.word_separator = Some(word_separator);
|
||||
|
|
@ -299,7 +309,7 @@ impl MietteHandlerOpts {
|
|||
MietteHighlighter::nocolor()
|
||||
};
|
||||
let theme = self.theme.unwrap_or(GraphicalTheme { characters, styles });
|
||||
let mut handler = GraphicalReportHandler::new()
|
||||
let mut handler = GraphicalReportHandler::new_themed(theme)
|
||||
.with_width(width)
|
||||
.with_links(linkify)
|
||||
.with_theme(theme);
|
||||
|
|
@ -323,6 +333,9 @@ impl MietteHandlerOpts {
|
|||
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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use crate::diagnostic_chain::{DiagnosticChain, ErrorKind};
|
|||
use crate::handlers::theme::*;
|
||||
use crate::highlighters::{Highlighter, MietteHighlighter};
|
||||
use crate::protocol::{Diagnostic, Severity};
|
||||
use crate::{LabeledSpan, MietteError, ReportHandler, SourceCode, SourceSpan, SpanContents};
|
||||
use crate::{LabeledSpan, ReportHandler, SourceCode, SourceSpan, SpanContents};
|
||||
|
||||
/**
|
||||
A [`ReportHandler`] that displays a given [`Report`](crate::Report) in a
|
||||
|
|
@ -31,6 +31,7 @@ pub struct GraphicalReportHandler {
|
|||
pub(crate) context_lines: usize,
|
||||
pub(crate) tab_width: usize,
|
||||
pub(crate) with_cause_chain: bool,
|
||||
pub(crate) wrap_lines: bool,
|
||||
pub(crate) break_words: bool,
|
||||
pub(crate) word_separator: Option<textwrap::WordSeparator>,
|
||||
pub(crate) word_splitter: Option<textwrap::WordSplitter>,
|
||||
|
|
@ -56,6 +57,7 @@ impl GraphicalReportHandler {
|
|||
context_lines: 1,
|
||||
tab_width: 4,
|
||||
with_cause_chain: true,
|
||||
wrap_lines: true,
|
||||
break_words: true,
|
||||
word_separator: None,
|
||||
word_splitter: None,
|
||||
|
|
@ -72,6 +74,7 @@ impl GraphicalReportHandler {
|
|||
footer: None,
|
||||
context_lines: 1,
|
||||
tab_width: 4,
|
||||
wrap_lines: true,
|
||||
with_cause_chain: true,
|
||||
break_words: true,
|
||||
word_separator: None,
|
||||
|
|
@ -135,6 +138,12 @@ impl GraphicalReportHandler {
|
|||
self
|
||||
}
|
||||
|
||||
/// Enables or disables wrapping of lines to fit the width.
|
||||
pub fn with_wrap_lines(mut self, wrap_lines: bool) -> Self {
|
||||
self.wrap_lines = wrap_lines;
|
||||
self
|
||||
}
|
||||
|
||||
/// Enables or disables breaking of words during wrapping.
|
||||
pub fn with_break_words(mut self, break_words: bool) -> Self {
|
||||
self.break_words = break_words;
|
||||
|
|
@ -218,7 +227,7 @@ impl GraphicalReportHandler {
|
|||
opts = opts.word_splitter(word_splitter);
|
||||
}
|
||||
|
||||
writeln!(f, "{}", textwrap::fill(footer, opts))?;
|
||||
writeln!(f, "{}", self.wrap(footer, opts))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -279,7 +288,7 @@ impl GraphicalReportHandler {
|
|||
opts = opts.word_splitter(word_splitter);
|
||||
}
|
||||
|
||||
writeln!(f, "{}", textwrap::fill(&diagnostic.to_string(), opts))?;
|
||||
writeln!(f, "{}", self.wrap(&diagnostic.to_string(), opts))?;
|
||||
|
||||
if !self.with_cause_chain {
|
||||
return Ok(());
|
||||
|
|
@ -329,16 +338,17 @@ impl GraphicalReportHandler {
|
|||
ErrorKind::Diagnostic(diag) => {
|
||||
let mut inner = String::new();
|
||||
|
||||
// Don't print footer for inner errors
|
||||
let mut inner_renderer = self.clone();
|
||||
// Don't print footer for inner errors
|
||||
inner_renderer.footer = None;
|
||||
// Cause chains are already flattened, so don't double-print the nested error
|
||||
inner_renderer.with_cause_chain = false;
|
||||
inner_renderer.render_report(&mut inner, diag)?;
|
||||
|
||||
writeln!(f, "{}", textwrap::fill(&inner, opts))?;
|
||||
writeln!(f, "{}", self.wrap(&inner, opts))?;
|
||||
}
|
||||
ErrorKind::StdError(err) => {
|
||||
writeln!(f, "{}", textwrap::fill(&err.to_string(), opts))?;
|
||||
writeln!(f, "{}", self.wrap(&err.to_string(), opts))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -362,7 +372,7 @@ impl GraphicalReportHandler {
|
|||
opts = opts.word_splitter(word_splitter);
|
||||
}
|
||||
|
||||
writeln!(f, "{}", textwrap::fill(&help.to_string(), opts))?;
|
||||
writeln!(f, "{}", self.wrap(&help.to_string(), opts))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -374,6 +384,9 @@ impl GraphicalReportHandler {
|
|||
parent_src: Option<&dyn SourceCode>,
|
||||
) -> fmt::Result {
|
||||
if let Some(related) = diagnostic.related() {
|
||||
let mut inner_renderer = self.clone();
|
||||
// Re-enable the printing of nested cause chains for related errors
|
||||
inner_renderer.with_cause_chain = true;
|
||||
writeln!(f)?;
|
||||
for rel in related {
|
||||
match rel.severity() {
|
||||
|
|
@ -381,12 +394,12 @@ impl GraphicalReportHandler {
|
|||
Some(Severity::Warning) => write!(f, "Warning: ")?,
|
||||
Some(Severity::Advice) => write!(f, "Advice: ")?,
|
||||
};
|
||||
self.render_header(f, rel)?;
|
||||
self.render_causes(f, rel)?;
|
||||
inner_renderer.render_header(f, rel)?;
|
||||
inner_renderer.render_causes(f, rel)?;
|
||||
let src = rel.source_code().or(parent_src);
|
||||
self.render_snippets(f, rel, src)?;
|
||||
self.render_footer(f, rel)?;
|
||||
self.render_related(f, rel, src)?;
|
||||
inner_renderer.render_snippets(f, rel, src)?;
|
||||
inner_renderer.render_footer(f, rel)?;
|
||||
inner_renderer.render_related(f, rel, src)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
|
@ -398,66 +411,58 @@ impl GraphicalReportHandler {
|
|||
diagnostic: &(dyn Diagnostic),
|
||||
opt_source: Option<&dyn SourceCode>,
|
||||
) -> fmt::Result {
|
||||
if let Some(source) = opt_source {
|
||||
if let Some(labels) = diagnostic.labels() {
|
||||
let mut labels = labels.collect::<Vec<_>>();
|
||||
labels.sort_unstable_by_key(|l| l.inner().offset());
|
||||
if !labels.is_empty() {
|
||||
let contents = labels
|
||||
.iter()
|
||||
.map(|label| {
|
||||
source.read_span(label.inner(), self.context_lines, self.context_lines)
|
||||
})
|
||||
.collect::<Result<Vec<Box<dyn SpanContents<'_>>>, MietteError>>()
|
||||
.map_err(|_| fmt::Error)?;
|
||||
let mut contexts = Vec::with_capacity(contents.len());
|
||||
for (right, right_conts) in labels.iter().cloned().zip(contents.iter()) {
|
||||
if contexts.is_empty() {
|
||||
contexts.push((right, right_conts));
|
||||
} else {
|
||||
let (left, left_conts) = contexts.last().unwrap().clone();
|
||||
let left_end = left.offset() + left.len();
|
||||
let right_end = right.offset() + right.len();
|
||||
if left_conts.line() + left_conts.line_count() >= right_conts.line() {
|
||||
// The snippets will overlap, so we create one Big Chunky Boi
|
||||
let new_span = LabeledSpan::new(
|
||||
left.label().map(String::from),
|
||||
left.offset(),
|
||||
if right_end >= left_end {
|
||||
// Right end goes past left end
|
||||
right_end - left.offset()
|
||||
} else {
|
||||
// right is contained inside left
|
||||
left.len()
|
||||
},
|
||||
);
|
||||
if source
|
||||
.read_span(
|
||||
new_span.inner(),
|
||||
self.context_lines,
|
||||
self.context_lines,
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
contexts.pop();
|
||||
contexts.push((
|
||||
// We'll throw this away later
|
||||
new_span, left_conts,
|
||||
));
|
||||
} else {
|
||||
contexts.push((right, right_conts));
|
||||
}
|
||||
} else {
|
||||
contexts.push((right, right_conts));
|
||||
}
|
||||
}
|
||||
}
|
||||
for (ctx, _) in contexts {
|
||||
self.render_context(f, source, &ctx, &labels[..])?;
|
||||
}
|
||||
let source = match opt_source {
|
||||
Some(source) => source,
|
||||
None => return Ok(()),
|
||||
};
|
||||
let labels = match diagnostic.labels() {
|
||||
Some(labels) => labels,
|
||||
None => return Ok(()),
|
||||
};
|
||||
|
||||
let mut labels = labels.collect::<Vec<_>>();
|
||||
labels.sort_unstable_by_key(|l| l.inner().offset());
|
||||
|
||||
let mut contexts = Vec::with_capacity(labels.len());
|
||||
for right in labels.iter().cloned() {
|
||||
let right_conts = source
|
||||
.read_span(right.inner(), self.context_lines, self.context_lines)
|
||||
.map_err(|_| fmt::Error)?;
|
||||
|
||||
if contexts.is_empty() {
|
||||
contexts.push((right, right_conts));
|
||||
continue;
|
||||
}
|
||||
|
||||
let (left, left_conts) = contexts.last().unwrap();
|
||||
if left_conts.line() + left_conts.line_count() >= right_conts.line() {
|
||||
// The snippets will overlap, so we create one Big Chunky Boi
|
||||
let left_end = left.offset() + left.len();
|
||||
let right_end = right.offset() + right.len();
|
||||
let new_end = std::cmp::max(left_end, right_end);
|
||||
|
||||
let new_span = LabeledSpan::new(
|
||||
left.label().map(String::from),
|
||||
left.offset(),
|
||||
new_end - left.offset(),
|
||||
);
|
||||
// Check that the two contexts can be combined
|
||||
if let Ok(new_conts) =
|
||||
source.read_span(new_span.inner(), self.context_lines, self.context_lines)
|
||||
{
|
||||
contexts.pop();
|
||||
// We'll throw the contents away later
|
||||
contexts.push((new_span, new_conts));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
contexts.push((right, right_conts));
|
||||
}
|
||||
for (ctx, _) in contexts {
|
||||
self.render_context(f, source, &ctx, &labels[..])?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -470,10 +475,16 @@ impl GraphicalReportHandler {
|
|||
) -> fmt::Result {
|
||||
let (contents, lines) = self.get_lines(source, context.inner())?;
|
||||
|
||||
let primary_label = labels
|
||||
.iter()
|
||||
// only consider labels from the context as primary label
|
||||
let ctx_labels = labels.iter().filter(|l| {
|
||||
context.inner().offset() <= l.inner().offset()
|
||||
&& l.inner().offset() + l.inner().len()
|
||||
<= context.inner().offset() + context.inner().len()
|
||||
});
|
||||
let primary_label = ctx_labels
|
||||
.clone()
|
||||
.find(|label| label.primary())
|
||||
.or_else(|| labels.first());
|
||||
.or_else(|| ctx_labels.clone().next());
|
||||
|
||||
// sorting is your friend
|
||||
let labels = labels
|
||||
|
|
@ -835,6 +846,41 @@ impl GraphicalReportHandler {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn wrap(&self, text: &str, opts: textwrap::Options<'_>) -> String {
|
||||
if self.wrap_lines {
|
||||
textwrap::fill(text, opts)
|
||||
} else {
|
||||
// Format without wrapping, but retain the indentation options
|
||||
// Implementation based on `textwrap::indent`
|
||||
let mut result = String::with_capacity(2 * text.len());
|
||||
let trimmed_indent = opts.subsequent_indent.trim_end();
|
||||
for (idx, line) in text.split_terminator('\n').enumerate() {
|
||||
if idx > 0 {
|
||||
result.push('\n');
|
||||
}
|
||||
if idx == 0 {
|
||||
if line.trim().is_empty() {
|
||||
result.push_str(opts.initial_indent.trim_end());
|
||||
} else {
|
||||
result.push_str(opts.initial_indent);
|
||||
}
|
||||
} else {
|
||||
if line.trim().is_empty() {
|
||||
result.push_str(trimmed_indent);
|
||||
} else {
|
||||
result.push_str(opts.subsequent_indent);
|
||||
}
|
||||
}
|
||||
result.push_str(line);
|
||||
}
|
||||
if text.ends_with('\n') {
|
||||
// split_terminator will have eaten the final '\n'.
|
||||
result.push('\n');
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
fn write_linum(&self, f: &mut impl fmt::Write, width: usize, linum: usize) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
use lazy_static::lazy_static;
|
||||
use miette::{Diagnostic, MietteHandler, MietteHandlerOpts, ReportHandler, RgbColors};
|
||||
use regex::Regex;
|
||||
use std::ffi::OsString;
|
||||
use std::fmt::{self, Debug};
|
||||
use std::sync::Mutex;
|
||||
use thiserror::Error;
|
||||
|
|
@ -42,16 +43,29 @@ fn color_format(handler: MietteHandler) -> ColorFormat {
|
|||
}
|
||||
}
|
||||
|
||||
/// Runs a function with an environment variable set to a specific value, then
|
||||
/// sets it back to it's original value once completed.
|
||||
fn with_env_var<F: FnOnce()>(var: &str, value: &str, body: F) {
|
||||
let old_value = std::env::var_os(var);
|
||||
std::env::set_var(var, value);
|
||||
body();
|
||||
if let Some(old_value) = old_value {
|
||||
std::env::set_var(var, old_value);
|
||||
} else {
|
||||
std::env::remove_var(var);
|
||||
/// Store the current value of an environment variable on construction, and then
|
||||
/// restore that value when the guard is dropped.
|
||||
struct EnvVarGuard<'a> {
|
||||
var: &'a str,
|
||||
old_value: Option<OsString>,
|
||||
}
|
||||
|
||||
impl EnvVarGuard<'_> {
|
||||
fn new(var: &str) -> EnvVarGuard<'_> {
|
||||
EnvVarGuard {
|
||||
var,
|
||||
old_value: std::env::var_os(var),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for EnvVarGuard<'_> {
|
||||
fn drop(&mut self) {
|
||||
if let Some(old_value) = &self.old_value {
|
||||
std::env::set_var(self.var, old_value);
|
||||
} else {
|
||||
std::env::remove_var(self.var);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -72,22 +86,33 @@ fn check_colors<F: Fn(MietteHandlerOpts) -> MietteHandlerOpts>(
|
|||
//
|
||||
// Since environment variables are shared for the entire process, we need
|
||||
// to ensure that only one test that modifies these env vars runs at a time.
|
||||
let guard = COLOR_ENV_VARS.lock().unwrap();
|
||||
let lock = COLOR_ENV_VARS.lock().unwrap();
|
||||
|
||||
with_env_var("NO_COLOR", "1", || {
|
||||
let handler = make_handler(MietteHandlerOpts::new()).build();
|
||||
assert_eq!(color_format(handler), no_support);
|
||||
});
|
||||
with_env_var("FORCE_COLOR", "1", || {
|
||||
let handler = make_handler(MietteHandlerOpts::new()).build();
|
||||
assert_eq!(color_format(handler), ansi_support);
|
||||
});
|
||||
with_env_var("FORCE_COLOR", "3", || {
|
||||
let handler = make_handler(MietteHandlerOpts::new()).build();
|
||||
assert_eq!(color_format(handler), rgb_support);
|
||||
});
|
||||
let guards = (
|
||||
EnvVarGuard::new("NO_COLOR"),
|
||||
EnvVarGuard::new("FORCE_COLOR"),
|
||||
);
|
||||
// Clear color environment variables that may be set outside of 'cargo test'
|
||||
std::env::remove_var("NO_COLOR");
|
||||
std::env::remove_var("FORCE_COLOR");
|
||||
|
||||
drop(guard);
|
||||
std::env::set_var("NO_COLOR", "1");
|
||||
let handler = make_handler(MietteHandlerOpts::new()).build();
|
||||
assert_eq!(color_format(handler), no_support);
|
||||
std::env::remove_var("NO_COLOR");
|
||||
|
||||
std::env::set_var("FORCE_COLOR", "1");
|
||||
let handler = make_handler(MietteHandlerOpts::new()).build();
|
||||
assert_eq!(color_format(handler), ansi_support);
|
||||
std::env::remove_var("FORCE_COLOR");
|
||||
|
||||
std::env::set_var("FORCE_COLOR", "3");
|
||||
let handler = make_handler(MietteHandlerOpts::new()).build();
|
||||
assert_eq!(color_format(handler), rgb_support);
|
||||
std::env::remove_var("FORCE_COLOR");
|
||||
|
||||
drop(guards);
|
||||
drop(lock);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -220,6 +220,50 @@ fn word_wrap_options() -> Result<(), MietteError> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wrap_option() -> Result<(), MietteError> {
|
||||
// A line should break on the width
|
||||
let out = fmt_report_with_settings(
|
||||
Report::msg("abc def ghi jkl mno pqr stu vwx yz abc def ghi jkl mno pqr stu vwx yz"),
|
||||
|handler| handler.with_width(15),
|
||||
);
|
||||
let expected = r#" × abc def
|
||||
│ ghi jkl
|
||||
│ mno pqr
|
||||
│ stu vwx
|
||||
│ yz abc
|
||||
│ def ghi
|
||||
│ jkl mno
|
||||
│ pqr stu
|
||||
│ vwx yz
|
||||
"#
|
||||
.to_string();
|
||||
assert_eq!(expected, out);
|
||||
|
||||
// Unless, wrapping is disabled
|
||||
let out = fmt_report_with_settings(
|
||||
Report::msg("abc def ghi jkl mno pqr stu vwx yz abc def ghi jkl mno pqr stu vwx yz"),
|
||||
|handler| handler.with_width(15).with_wrap_lines(false),
|
||||
);
|
||||
let expected =
|
||||
" × abc def ghi jkl mno pqr stu vwx yz abc def ghi jkl mno pqr stu vwx yz\n".to_string();
|
||||
assert_eq!(expected, out);
|
||||
|
||||
// Then, user-defined new lines should be preserved wrapping is disabled
|
||||
let out = fmt_report_with_settings(
|
||||
Report::msg("abc def ghi jkl mno pqr stu vwx yz\nabc def ghi jkl mno pqr stu vwx yz\nabc def ghi jkl mno pqr stu vwx yz"),
|
||||
|handler| handler.with_width(15).with_wrap_lines(false),
|
||||
);
|
||||
let expected = r#" × abc def ghi jkl mno pqr stu vwx yz
|
||||
│ abc def ghi jkl mno pqr stu vwx yz
|
||||
│ abc def ghi jkl mno pqr stu vwx yz
|
||||
"#
|
||||
.to_string();
|
||||
assert_eq!(expected, out);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_source() -> Result<(), MietteError> {
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
|
|
@ -1811,4 +1855,95 @@ fn syntax_highlighter_on_real_file() {
|
|||
);
|
||||
assert!(out.contains("\u{1b}[38;2;180;142;173m"));
|
||||
assert_eq!(expected, strip_ansi_escapes::strip_str(out));
|
||||
|
||||
#[test]
|
||||
fn triple_adjacent_highlight() -> Result<(), MietteError> {
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
#[error("oops!")]
|
||||
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||
struct MyBad {
|
||||
#[source_code]
|
||||
src: NamedSource,
|
||||
#[label = "this bit here"]
|
||||
highlight1: SourceSpan,
|
||||
#[label = "also this bit"]
|
||||
highlight2: SourceSpan,
|
||||
#[label = "finally we got"]
|
||||
highlight3: SourceSpan,
|
||||
}
|
||||
|
||||
let src = "source\n\n\n text\n\n\n here".to_string();
|
||||
let err = MyBad {
|
||||
src: NamedSource::new("bad_file.rs", src),
|
||||
highlight1: (0, 6).into(),
|
||||
highlight2: (11, 4).into(),
|
||||
highlight3: (22, 4).into(),
|
||||
};
|
||||
let out = fmt_report(err.into());
|
||||
println!("Error: {}", out);
|
||||
let expected = "oops::my::bad
|
||||
|
||||
× oops!
|
||||
╭─[bad_file.rs:1:1]
|
||||
1 │ source
|
||||
· ───┬──
|
||||
· ╰── this bit here
|
||||
2 │
|
||||
3 │
|
||||
4 │ text
|
||||
· ──┬─
|
||||
· ╰── also this bit
|
||||
5 │
|
||||
6 │
|
||||
7 │ here
|
||||
· ──┬─
|
||||
· ╰── finally we got
|
||||
╰────
|
||||
help: try doing it better next time?
|
||||
";
|
||||
assert_eq!(expected, &out);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_adjacent_highlight() -> Result<(), MietteError> {
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
#[error("oops!")]
|
||||
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||
struct MyBad {
|
||||
#[source_code]
|
||||
src: NamedSource,
|
||||
#[label = "this bit here"]
|
||||
highlight1: SourceSpan,
|
||||
#[label = "also this bit"]
|
||||
highlight2: SourceSpan,
|
||||
}
|
||||
|
||||
let src = "source\n\n\n\n text here".to_string();
|
||||
let err = MyBad {
|
||||
src: NamedSource::new("bad_file.rs", src),
|
||||
highlight1: (0, 6).into(),
|
||||
highlight2: (12, 4).into(),
|
||||
};
|
||||
let out = fmt_report(err.into());
|
||||
println!("Error: {}", out);
|
||||
let expected = "oops::my::bad
|
||||
|
||||
× oops!
|
||||
╭─[bad_file.rs:1:1]
|
||||
1 │ source
|
||||
· ───┬──
|
||||
· ╰── this bit here
|
||||
2 │
|
||||
╰────
|
||||
╭─[bad_file.rs:5:3]
|
||||
4 │
|
||||
5 │ text here
|
||||
· ──┬─
|
||||
· ╰── also this bit
|
||||
╰────
|
||||
help: try doing it better next time?
|
||||
";
|
||||
assert_eq!(expected, &out);
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -194,3 +194,85 @@ fn test_nested_diagnostic_source_is_output() {
|
|||
|
||||
assert_eq!(expected, out);
|
||||
}
|
||||
|
||||
#[derive(Debug, miette::Diagnostic, thiserror::Error)]
|
||||
#[error("A multi-error happened")]
|
||||
struct MultiError {
|
||||
#[related]
|
||||
related_errs: Vec<Box<dyn Diagnostic>>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "fancy-no-backtrace")]
|
||||
#[test]
|
||||
fn test_nested_cause_chains_for_related_errors_are_output() {
|
||||
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 multi_error = MultiError {
|
||||
related_errs: vec![Box::new(first_error), Box::new(second_error)],
|
||||
};
|
||||
let diag = NestedError {
|
||||
code: String::from("the outside world"),
|
||||
label: (6, 4),
|
||||
the_other_err: Box::new(multi_error),
|
||||
};
|
||||
let mut out = String::new();
|
||||
miette::GraphicalReportHandler::new_themed(miette::GraphicalTheme::unicode_nocolor())
|
||||
.with_width(80)
|
||||
.with_footer("Yooo, a footer".to_string())
|
||||
.render_report(&mut out, &diag)
|
||||
.unwrap();
|
||||
println!("{}", out);
|
||||
|
||||
let expected = r#" × A nested error happened
|
||||
╰─▶ × A multi-error happened
|
||||
|
||||
Error: × A nested error happened
|
||||
├─▶ × TestError
|
||||
│
|
||||
╰─▶ × A complex error happened
|
||||
╭────
|
||||
1 │ This is another error
|
||||
· ──┬─
|
||||
· ╰── here
|
||||
╰────
|
||||
help: You should fix this
|
||||
|
||||
╭────
|
||||
1 │ right here
|
||||
· ──┬─
|
||||
· ╰── here
|
||||
╰────
|
||||
Error: × A complex error happened
|
||||
╭────
|
||||
1 │ You're actually a mess
|
||||
· ──┬─
|
||||
· ╰── here
|
||||
╰────
|
||||
help: Get a grip...
|
||||
|
||||
╭────
|
||||
1 │ the outside world
|
||||
· ──┬─
|
||||
· ╰── here
|
||||
╰────
|
||||
|
||||
Yooo, a footer
|
||||
"#;
|
||||
|
||||
assert_eq!(expected, out);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue