mirror of https://github.com/zkat/miette.git
feat(graphical): support rendering labels that contain newlines (#318)
Fixes: https://github.com/zkat/miette/issues/85
This commit is contained in:
parent
251d6d5929
commit
865d67c8dd
|
|
@ -20,7 +20,7 @@ This printer can be customized by using [`new_themed()`](GraphicalReportHandler:
|
||||||
|
|
||||||
See [`set_hook()`](crate::set_hook) for more details on customizing your global
|
See [`set_hook()`](crate::set_hook) for more details on customizing your global
|
||||||
printer.
|
printer.
|
||||||
*/
|
*/
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct GraphicalReportHandler {
|
pub struct GraphicalReportHandler {
|
||||||
pub(crate) links: LinkStyle,
|
pub(crate) links: LinkStyle,
|
||||||
|
|
@ -545,7 +545,13 @@ impl GraphicalReportHandler {
|
||||||
// no line number!
|
// no line number!
|
||||||
self.write_no_linum(f, linum_width)?;
|
self.write_no_linum(f, linum_width)?;
|
||||||
// gutter _again_
|
// gutter _again_
|
||||||
self.render_highlight_gutter(f, max_gutter, line, &labels)?;
|
self.render_highlight_gutter(
|
||||||
|
f,
|
||||||
|
max_gutter,
|
||||||
|
line,
|
||||||
|
&labels,
|
||||||
|
LabelRenderMode::SingleLine,
|
||||||
|
)?;
|
||||||
self.render_single_line_highlights(
|
self.render_single_line_highlights(
|
||||||
f,
|
f,
|
||||||
line,
|
line,
|
||||||
|
|
@ -557,11 +563,7 @@ impl GraphicalReportHandler {
|
||||||
}
|
}
|
||||||
for hl in multi_line {
|
for hl in multi_line {
|
||||||
if hl.label().is_some() && line.span_ends(hl) && !line.span_starts(hl) {
|
if hl.label().is_some() && line.span_ends(hl) && !line.span_starts(hl) {
|
||||||
// no line number!
|
self.render_multi_line_end(f, &labels, max_gutter, linum_width, line, hl)?;
|
||||||
self.write_no_linum(f, linum_width)?;
|
|
||||||
// gutter _again_
|
|
||||||
self.render_highlight_gutter(f, max_gutter, line, &labels)?;
|
|
||||||
self.render_multi_line_end(f, hl)?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -575,6 +577,91 @@ impl GraphicalReportHandler {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn render_multi_line_end(
|
||||||
|
&self,
|
||||||
|
f: &mut impl fmt::Write,
|
||||||
|
labels: &[FancySpan],
|
||||||
|
max_gutter: usize,
|
||||||
|
linum_width: usize,
|
||||||
|
line: &Line,
|
||||||
|
label: &FancySpan,
|
||||||
|
) -> fmt::Result {
|
||||||
|
// no line number!
|
||||||
|
self.write_no_linum(f, linum_width)?;
|
||||||
|
|
||||||
|
if let Some(label_parts) = label.label_parts() {
|
||||||
|
// if it has a label, how long is it?
|
||||||
|
let (first, rest) = label_parts
|
||||||
|
.split_first()
|
||||||
|
.expect("cannot crash because rest would have been None, see docs on the `label` field of FancySpan");
|
||||||
|
|
||||||
|
if rest.is_empty() {
|
||||||
|
// gutter _again_
|
||||||
|
self.render_highlight_gutter(
|
||||||
|
f,
|
||||||
|
max_gutter,
|
||||||
|
line,
|
||||||
|
&labels,
|
||||||
|
LabelRenderMode::SingleLine,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
self.render_multi_line_end_single(
|
||||||
|
f,
|
||||||
|
first,
|
||||||
|
label.style,
|
||||||
|
LabelRenderMode::SingleLine,
|
||||||
|
)?;
|
||||||
|
} else {
|
||||||
|
// gutter _again_
|
||||||
|
self.render_highlight_gutter(
|
||||||
|
f,
|
||||||
|
max_gutter,
|
||||||
|
line,
|
||||||
|
&labels,
|
||||||
|
LabelRenderMode::MultiLineFirst,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
self.render_multi_line_end_single(
|
||||||
|
f,
|
||||||
|
first,
|
||||||
|
label.style,
|
||||||
|
LabelRenderMode::MultiLineFirst,
|
||||||
|
)?;
|
||||||
|
for label_line in rest {
|
||||||
|
// no line number!
|
||||||
|
self.write_no_linum(f, linum_width)?;
|
||||||
|
// gutter _again_
|
||||||
|
self.render_highlight_gutter(
|
||||||
|
f,
|
||||||
|
max_gutter,
|
||||||
|
line,
|
||||||
|
&labels,
|
||||||
|
LabelRenderMode::MultiLineRest,
|
||||||
|
)?;
|
||||||
|
self.render_multi_line_end_single(
|
||||||
|
f,
|
||||||
|
label_line,
|
||||||
|
label.style,
|
||||||
|
LabelRenderMode::MultiLineRest,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// gutter _again_
|
||||||
|
self.render_highlight_gutter(
|
||||||
|
f,
|
||||||
|
max_gutter,
|
||||||
|
line,
|
||||||
|
&labels,
|
||||||
|
LabelRenderMode::SingleLine,
|
||||||
|
)?;
|
||||||
|
// has no label
|
||||||
|
writeln!(f, "{}", self.theme.characters.hbar.style(label.style))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn render_line_gutter(
|
fn render_line_gutter(
|
||||||
&self,
|
&self,
|
||||||
f: &mut impl fmt::Write,
|
f: &mut impl fmt::Write,
|
||||||
|
|
@ -643,6 +730,7 @@ impl GraphicalReportHandler {
|
||||||
max_gutter: usize,
|
max_gutter: usize,
|
||||||
line: &Line,
|
line: &Line,
|
||||||
highlights: &[FancySpan],
|
highlights: &[FancySpan],
|
||||||
|
render_mode: LabelRenderMode,
|
||||||
) -> fmt::Result {
|
) -> fmt::Result {
|
||||||
if max_gutter == 0 {
|
if max_gutter == 0 {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
|
@ -652,15 +740,33 @@ impl GraphicalReportHandler {
|
||||||
let applicable = highlights.iter().filter(|hl| line.span_applies(hl));
|
let applicable = highlights.iter().filter(|hl| line.span_applies(hl));
|
||||||
for (i, hl) in applicable.enumerate() {
|
for (i, hl) in applicable.enumerate() {
|
||||||
if !line.span_line_only(hl) && line.span_ends(hl) {
|
if !line.span_line_only(hl) && line.span_ends(hl) {
|
||||||
gutter.push_str(&chars.lbot.style(hl.style).to_string());
|
if render_mode == LabelRenderMode::MultiLineRest {
|
||||||
gutter.push_str(
|
// this is to make multiline labels work. We want to make the right amount
|
||||||
&chars
|
// of horizontal space for them, but not actually draw the lines
|
||||||
.hbar
|
for _ in 0..max_gutter.saturating_sub(i) + 2 {
|
||||||
.to_string()
|
gutter.push(' ');
|
||||||
.repeat(max_gutter.saturating_sub(i) + 2)
|
}
|
||||||
.style(hl.style)
|
} else {
|
||||||
.to_string(),
|
gutter.push_str(&chars.lbot.style(hl.style).to_string());
|
||||||
);
|
|
||||||
|
gutter.push_str(
|
||||||
|
&chars
|
||||||
|
.hbar
|
||||||
|
.to_string()
|
||||||
|
.repeat(
|
||||||
|
max_gutter.saturating_sub(i)
|
||||||
|
// if we are rendering a multiline label, then leave a bit of space for the
|
||||||
|
// rcross character
|
||||||
|
+ if render_mode == LabelRenderMode::MultiLineFirst {
|
||||||
|
1
|
||||||
|
} else {
|
||||||
|
2
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.style(hl.style)
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
gutter.push_str(&chars.vbar.style(hl.style).to_string());
|
gutter.push_str(&chars.vbar.style(hl.style).to_string());
|
||||||
|
|
@ -811,27 +917,40 @@ impl GraphicalReportHandler {
|
||||||
writeln!(f, "{}", underlines)?;
|
writeln!(f, "{}", underlines)?;
|
||||||
|
|
||||||
for hl in single_liners.iter().rev() {
|
for hl in single_liners.iter().rev() {
|
||||||
if let Some(label) = hl.label() {
|
if let Some(label) = hl.label_parts() {
|
||||||
self.write_no_linum(f, linum_width)?;
|
if label.len() == 1 {
|
||||||
self.render_highlight_gutter(f, max_gutter, line, all_highlights)?;
|
self.write_label_text(
|
||||||
let mut curr_offset = 1usize;
|
f,
|
||||||
for (offset_hl, vbar_offset) in &vbar_offsets {
|
line,
|
||||||
while curr_offset < *vbar_offset + 1 {
|
linum_width,
|
||||||
write!(f, " ")?;
|
max_gutter,
|
||||||
curr_offset += 1;
|
all_highlights,
|
||||||
}
|
chars,
|
||||||
if *offset_hl != hl {
|
&vbar_offsets,
|
||||||
write!(f, "{}", chars.vbar.to_string().style(offset_hl.style))?;
|
hl,
|
||||||
curr_offset += 1;
|
&label[0],
|
||||||
} else {
|
LabelRenderMode::SingleLine,
|
||||||
let lines = format!(
|
)?;
|
||||||
"{}{} {}",
|
} else {
|
||||||
chars.lbot,
|
let mut first = true;
|
||||||
chars.hbar.to_string().repeat(2),
|
for label_line in &label {
|
||||||
label,
|
self.write_label_text(
|
||||||
);
|
f,
|
||||||
writeln!(f, "{}", lines.style(hl.style))?;
|
line,
|
||||||
break;
|
linum_width,
|
||||||
|
max_gutter,
|
||||||
|
all_highlights,
|
||||||
|
chars,
|
||||||
|
&vbar_offsets,
|
||||||
|
hl,
|
||||||
|
label_line,
|
||||||
|
if first {
|
||||||
|
LabelRenderMode::MultiLineFirst
|
||||||
|
} else {
|
||||||
|
LabelRenderMode::MultiLineRest
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
first = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -839,13 +958,80 @@ impl GraphicalReportHandler {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_multi_line_end(&self, f: &mut impl fmt::Write, hl: &FancySpan) -> fmt::Result {
|
// I know it's not good practice, but making this a function makes a lot of sense
|
||||||
writeln!(
|
// and making a struct for this does not...
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
fn write_label_text(
|
||||||
|
&self,
|
||||||
|
f: &mut impl fmt::Write,
|
||||||
|
line: &Line,
|
||||||
|
linum_width: usize,
|
||||||
|
max_gutter: usize,
|
||||||
|
all_highlights: &[FancySpan],
|
||||||
|
chars: &ThemeCharacters,
|
||||||
|
vbar_offsets: &[(&&FancySpan, usize)],
|
||||||
|
hl: &&FancySpan,
|
||||||
|
label: &str,
|
||||||
|
render_mode: LabelRenderMode,
|
||||||
|
) -> fmt::Result {
|
||||||
|
self.write_no_linum(f, linum_width)?;
|
||||||
|
self.render_highlight_gutter(
|
||||||
f,
|
f,
|
||||||
"{} {}",
|
max_gutter,
|
||||||
self.theme.characters.hbar.style(hl.style),
|
line,
|
||||||
hl.label().unwrap_or_else(|| "".into()),
|
all_highlights,
|
||||||
|
LabelRenderMode::SingleLine,
|
||||||
)?;
|
)?;
|
||||||
|
let mut curr_offset = 1usize;
|
||||||
|
for (offset_hl, vbar_offset) in vbar_offsets {
|
||||||
|
while curr_offset < *vbar_offset + 1 {
|
||||||
|
write!(f, " ")?;
|
||||||
|
curr_offset += 1;
|
||||||
|
}
|
||||||
|
if *offset_hl != hl {
|
||||||
|
write!(f, "{}", chars.vbar.to_string().style(offset_hl.style))?;
|
||||||
|
curr_offset += 1;
|
||||||
|
} else {
|
||||||
|
let lines = match render_mode {
|
||||||
|
LabelRenderMode::SingleLine => format!(
|
||||||
|
"{}{} {}",
|
||||||
|
chars.lbot,
|
||||||
|
chars.hbar.to_string().repeat(2),
|
||||||
|
label,
|
||||||
|
),
|
||||||
|
LabelRenderMode::MultiLineFirst => {
|
||||||
|
format!("{}{}{} {}", chars.lbot, chars.hbar, chars.rcross, label,)
|
||||||
|
}
|
||||||
|
LabelRenderMode::MultiLineRest => {
|
||||||
|
format!(" {} {}", chars.vbar, label,)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
writeln!(f, "{}", lines.style(hl.style))?;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_multi_line_end_single(
|
||||||
|
&self,
|
||||||
|
f: &mut impl fmt::Write,
|
||||||
|
label: &str,
|
||||||
|
style: Style,
|
||||||
|
render_mode: LabelRenderMode,
|
||||||
|
) -> fmt::Result {
|
||||||
|
match render_mode {
|
||||||
|
LabelRenderMode::SingleLine => {
|
||||||
|
writeln!(f, "{} {}", self.theme.characters.hbar.style(style), label)?;
|
||||||
|
}
|
||||||
|
LabelRenderMode::MultiLineFirst => {
|
||||||
|
writeln!(f, "{} {}", self.theme.characters.rcross.style(style), label)?;
|
||||||
|
}
|
||||||
|
LabelRenderMode::MultiLineRest => {
|
||||||
|
writeln!(f, "{} {}", self.theme.characters.vbar.style(style), label)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -924,6 +1110,16 @@ impl ReportHandler for GraphicalReportHandler {
|
||||||
Support types
|
Support types
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#[derive(PartialEq, Debug)]
|
||||||
|
enum LabelRenderMode {
|
||||||
|
/// we're rendering a single line label (or not rendering in any special way)
|
||||||
|
SingleLine,
|
||||||
|
/// we're rendering a multiline label
|
||||||
|
MultiLineFirst,
|
||||||
|
/// we're rendering the rest of a multiline label
|
||||||
|
MultiLineRest,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct Line {
|
struct Line {
|
||||||
line_number: usize,
|
line_number: usize,
|
||||||
|
|
@ -941,10 +1137,10 @@ impl Line {
|
||||||
let spanlen = if span.len() == 0 { 1 } else { span.len() };
|
let spanlen = if span.len() == 0 { 1 } else { span.len() };
|
||||||
// Span starts in this line
|
// Span starts in this line
|
||||||
(span.offset() >= self.offset && span.offset() < self.offset + self.length)
|
(span.offset() >= self.offset && span.offset() < self.offset + self.length)
|
||||||
// Span passes through this line
|
// Span passes through this line
|
||||||
|| (span.offset() < self.offset && span.offset() + spanlen > self.offset + self.length) //todo
|
|| (span.offset() < self.offset && span.offset() + spanlen > self.offset + self.length) //todo
|
||||||
// Span ends on this line
|
// Span ends on this line
|
||||||
|| (span.offset() + spanlen > self.offset && span.offset() + spanlen <= self.offset + self.length)
|
|| (span.offset() + spanlen > self.offset && span.offset() + spanlen <= self.offset + self.length)
|
||||||
}
|
}
|
||||||
|
|
||||||
// A 'flyby' is a multi-line span that technically covers this line, but
|
// A 'flyby' is a multi-line span that technically covers this line, but
|
||||||
|
|
@ -974,7 +1170,10 @@ impl Line {
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct FancySpan {
|
struct FancySpan {
|
||||||
label: Option<String>,
|
/// this is deliberately an option of a vec because I wanted to be very explicit
|
||||||
|
/// that there can also be *no* label. If there is a label, it can have multiple
|
||||||
|
/// lines which is what the vec is for.
|
||||||
|
label: Option<Vec<String>>,
|
||||||
span: SourceSpan,
|
span: SourceSpan,
|
||||||
style: Style,
|
style: Style,
|
||||||
}
|
}
|
||||||
|
|
@ -985,9 +1184,17 @@ impl PartialEq for FancySpan {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn split_label(v: String) -> Vec<String> {
|
||||||
|
v.split('\n').map(|i| i.to_string()).collect()
|
||||||
|
}
|
||||||
|
|
||||||
impl FancySpan {
|
impl FancySpan {
|
||||||
fn new(label: Option<String>, span: SourceSpan, style: Style) -> Self {
|
fn new(label: Option<String>, span: SourceSpan, style: Style) -> Self {
|
||||||
FancySpan { label, span, style }
|
FancySpan {
|
||||||
|
label: label.map(split_label),
|
||||||
|
span,
|
||||||
|
style,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn style(&self) -> Style {
|
fn style(&self) -> Style {
|
||||||
|
|
@ -997,7 +1204,15 @@ impl FancySpan {
|
||||||
fn label(&self) -> Option<String> {
|
fn label(&self) -> Option<String> {
|
||||||
self.label
|
self.label
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|l| l.style(self.style()).to_string())
|
.map(|l| l.join("\n").style(self.style()).to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn label_parts(&self) -> Option<Vec<String>> {
|
||||||
|
self.label.as_ref().map(|l| {
|
||||||
|
l.iter()
|
||||||
|
.map(|i| i.style(self.style()).to_string())
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn offset(&self) -> usize {
|
fn offset(&self) -> usize {
|
||||||
|
|
|
||||||
|
|
@ -251,6 +251,52 @@ fn empty_source() -> Result<(), MietteError> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn multiple_spans_multiline() {
|
||||||
|
#[derive(Error, Debug, Diagnostic)]
|
||||||
|
#[error("oops!")]
|
||||||
|
#[diagnostic(severity(Error))]
|
||||||
|
struct MyBad {
|
||||||
|
#[source_code]
|
||||||
|
src: NamedSource,
|
||||||
|
#[label("big")]
|
||||||
|
big: SourceSpan,
|
||||||
|
#[label("small")]
|
||||||
|
small: SourceSpan,
|
||||||
|
}
|
||||||
|
let err = MyBad {
|
||||||
|
src: NamedSource::new(
|
||||||
|
"issue",
|
||||||
|
"\
|
||||||
|
if true {
|
||||||
|
a
|
||||||
|
} else {
|
||||||
|
b
|
||||||
|
}",
|
||||||
|
),
|
||||||
|
big: (0, 32).into(),
|
||||||
|
small: (14, 1).into(),
|
||||||
|
};
|
||||||
|
let out = fmt_report(err.into());
|
||||||
|
println!("Error: {}", out);
|
||||||
|
|
||||||
|
let expected = r#" × oops!
|
||||||
|
╭─[issue:1:1]
|
||||||
|
1 │ ╭─▶ if true {
|
||||||
|
2 │ │╭▶ a
|
||||||
|
· ││ ┬
|
||||||
|
· ││ ╰── small
|
||||||
|
3 │ │ } else {
|
||||||
|
4 │ │ b
|
||||||
|
5 │ ├─▶ }
|
||||||
|
· ╰──── big
|
||||||
|
╰────
|
||||||
|
"#
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
assert_eq!(expected, out);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn single_line_highlight_span_full_line() {
|
fn single_line_highlight_span_full_line() {
|
||||||
#[derive(Error, Debug, Diagnostic)]
|
#[derive(Error, Debug, Diagnostic)]
|
||||||
|
|
@ -725,6 +771,94 @@ fn single_line_highlight_at_line_start() -> Result<(), MietteError> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn multiline_label() -> 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\nand\nthis\ntoo")]
|
||||||
|
highlight: SourceSpan,
|
||||||
|
}
|
||||||
|
|
||||||
|
let src = "source\ntext\n here".to_string();
|
||||||
|
let err = MyBad {
|
||||||
|
src: NamedSource::new("bad_file.rs", src),
|
||||||
|
highlight: (7, 4).into(),
|
||||||
|
};
|
||||||
|
let out = fmt_report(err.into());
|
||||||
|
println!("Error: {}", out);
|
||||||
|
let expected = r#"oops::my::bad
|
||||||
|
|
||||||
|
× oops!
|
||||||
|
╭─[bad_file.rs:2:1]
|
||||||
|
1 │ source
|
||||||
|
2 │ text
|
||||||
|
· ──┬─
|
||||||
|
· ╰─┤ this bit here
|
||||||
|
· │ and
|
||||||
|
· │ this
|
||||||
|
· │ too
|
||||||
|
3 │ here
|
||||||
|
╰────
|
||||||
|
help: try doing it better next time?
|
||||||
|
"#
|
||||||
|
.trim_start()
|
||||||
|
.to_string();
|
||||||
|
assert_eq!(expected, out);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn multiple_multi_line_labels() -> 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 = "x\ny"]
|
||||||
|
highlight1: SourceSpan,
|
||||||
|
#[label = "z\nw"]
|
||||||
|
highlight2: SourceSpan,
|
||||||
|
#[label = "a\nb"]
|
||||||
|
highlight3: SourceSpan,
|
||||||
|
}
|
||||||
|
|
||||||
|
let src = "source\n text text text text text\n here".to_string();
|
||||||
|
let err = MyBad {
|
||||||
|
src: NamedSource::new("bad_file.rs", src),
|
||||||
|
highlight1: (9, 4).into(),
|
||||||
|
highlight2: (14, 4).into(),
|
||||||
|
highlight3: (24, 4).into(),
|
||||||
|
};
|
||||||
|
let out = fmt_report(err.into());
|
||||||
|
println!("Error: {}", out);
|
||||||
|
let expected = r#"oops::my::bad
|
||||||
|
|
||||||
|
× oops!
|
||||||
|
╭─[bad_file.rs:2:3]
|
||||||
|
1 │ source
|
||||||
|
2 │ text text text text text
|
||||||
|
· ──┬─ ──┬─ ──┬─
|
||||||
|
· │ │ ╰─┤ a
|
||||||
|
· │ │ │ b
|
||||||
|
· │ ╰─┤ z
|
||||||
|
· │ │ w
|
||||||
|
· ╰─┤ x
|
||||||
|
· │ y
|
||||||
|
3 │ here
|
||||||
|
╰────
|
||||||
|
help: try doing it better next time?
|
||||||
|
"#
|
||||||
|
.trim_start()
|
||||||
|
.to_string();
|
||||||
|
assert_eq!(expected, out);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn multiple_same_line_highlights() -> Result<(), MietteError> {
|
fn multiple_same_line_highlights() -> Result<(), MietteError> {
|
||||||
#[derive(Debug, Diagnostic, Error)]
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
|
|
@ -853,6 +987,43 @@ fn multiline_highlight_adjacent() -> Result<(), MietteError> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn multiline_highlight_multiline_label() -> 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 = "these two lines\nare the problem"]
|
||||||
|
highlight: SourceSpan,
|
||||||
|
}
|
||||||
|
|
||||||
|
let src = "source\n text\n here".to_string();
|
||||||
|
let err = MyBad {
|
||||||
|
src: NamedSource::new("bad_file.rs", src),
|
||||||
|
highlight: (9, 11).into(),
|
||||||
|
};
|
||||||
|
let out = fmt_report(err.into());
|
||||||
|
println!("Error: {}", out);
|
||||||
|
let expected = r#"oops::my::bad
|
||||||
|
|
||||||
|
× oops!
|
||||||
|
╭─[bad_file.rs:2:3]
|
||||||
|
1 │ source
|
||||||
|
2 │ ╭─▶ text
|
||||||
|
3 │ ├─▶ here
|
||||||
|
· ╰──┤ these two lines
|
||||||
|
· │ are the problem
|
||||||
|
╰────
|
||||||
|
help: try doing it better next time?
|
||||||
|
"#
|
||||||
|
.trim_start()
|
||||||
|
.to_string();
|
||||||
|
assert_eq!(expected, out);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn multiline_highlight_flyby() -> Result<(), MietteError> {
|
fn multiline_highlight_flyby() -> Result<(), MietteError> {
|
||||||
#[derive(Debug, Diagnostic, Error)]
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue