mirror of https://github.com/zkat/miette.git
feat(printer): lots of small improvements to printer
This commit is contained in:
parent
d675334e48
commit
5fbcd53026
|
|
@ -11,7 +11,6 @@ readme = "README.md"
|
|||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
indenter = "0.3.3"
|
||||
thiserror = "1.0.26"
|
||||
miette-derive = { version = "=0.11.0", path = "miette-derive" }
|
||||
once_cell = "1.8.0"
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
use std::fmt;
|
||||
|
||||
use indenter::indented;
|
||||
use owo_colors::{OwoColorize, Style};
|
||||
|
||||
use crate::chain::Chain;
|
||||
use crate::printer::theme::*;
|
||||
use crate::protocol::{Diagnostic, DiagnosticReportPrinter, DiagnosticSnippet, Severity};
|
||||
use crate::SourceSpan;
|
||||
use crate::{SourceSpan, SpanContents};
|
||||
|
||||
/**
|
||||
Reference implementation of the [DiagnosticReportPrinter] trait. This is generally
|
||||
|
|
@ -44,13 +43,10 @@ impl DefaultReportPrinter {
|
|||
) -> fmt::Result {
|
||||
self.render_header(f, diagnostic)?;
|
||||
self.render_causes(f, diagnostic)?;
|
||||
|
||||
if let Some(snippets) = diagnostic.snippets() {
|
||||
let mut pre = false;
|
||||
for snippet in snippets {
|
||||
if !pre {
|
||||
writeln!(f)?;
|
||||
pre = true;
|
||||
}
|
||||
writeln!(f)?;
|
||||
self.render_snippet(f, &snippet)?;
|
||||
}
|
||||
}
|
||||
|
|
@ -60,41 +56,46 @@ impl DefaultReportPrinter {
|
|||
}
|
||||
|
||||
fn render_header(&self, f: &mut impl fmt::Write, diagnostic: &(dyn Diagnostic)) -> fmt::Result {
|
||||
let sev = match diagnostic.severity() {
|
||||
Some(Severity::Error) | None => "Error".style(self.theme.styles.error),
|
||||
Some(Severity::Warning) => "Warning".style(self.theme.styles.warning),
|
||||
Some(Severity::Advice) => "Advice".style(self.theme.styles.advice),
|
||||
}
|
||||
.to_string();
|
||||
let (severity_style, severity_icon) = match diagnostic.severity() {
|
||||
Some(Severity::Error) | None => (self.theme.styles.error, self.theme.characters.x),
|
||||
Some(Severity::Warning) => (self.theme.styles.warning, self.theme.characters.warning),
|
||||
Some(Severity::Advice) => (self.theme.styles.advice, self.theme.characters.point_right),
|
||||
};
|
||||
let code = diagnostic.code();
|
||||
let msg = diagnostic.to_string();
|
||||
writeln!(
|
||||
f,
|
||||
"{} [{}]: {}",
|
||||
sev,
|
||||
"{}[{}]{}",
|
||||
self.theme.characters.hbar.to_string().repeat(4),
|
||||
code.style(self.theme.styles.code),
|
||||
msg
|
||||
self.theme.characters.hbar.to_string().repeat(20),
|
||||
)?;
|
||||
writeln!(f)?;
|
||||
writeln!(
|
||||
f,
|
||||
" {} {}",
|
||||
severity_icon.style(severity_style),
|
||||
diagnostic.style(severity_style)
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn render_causes(&self, f: &mut impl fmt::Write, diagnostic: &(dyn Diagnostic)) -> fmt::Result {
|
||||
use fmt::Write as _;
|
||||
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,
|
||||
};
|
||||
|
||||
if let Some(cause) = diagnostic.source() {
|
||||
writeln!(f)?;
|
||||
write!(f, "Caused by:")?;
|
||||
|
||||
let multiple = cause.source().is_some();
|
||||
|
||||
for (n, error) in Chain::new(cause).enumerate() {
|
||||
let msg = format!("{}", error);
|
||||
writeln!(f)?;
|
||||
if multiple {
|
||||
write!(indented(f).ind(n), "{}", msg)?;
|
||||
let mut cause_iter = Chain::new(cause).peekable();
|
||||
while let Some(error) = cause_iter.next() {
|
||||
let char = if cause_iter.peek().is_some() {
|
||||
self.theme.characters.lcross
|
||||
} else {
|
||||
write!(indented(f), "{}", msg)?;
|
||||
}
|
||||
self.theme.characters.lbot
|
||||
};
|
||||
let msg = format!(" {}{}{} {}", char, self.theme.characters.hbar, self.theme.characters.rarrow, error).style(severity_style).to_string();
|
||||
writeln!(f, "{}", msg)?;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -105,27 +106,13 @@ impl DefaultReportPrinter {
|
|||
if let Some(help) = diagnostic.help() {
|
||||
let help = help.style(self.theme.styles.help);
|
||||
writeln!(f)?;
|
||||
writeln!(f, "{} {}", self.theme.characters.eq, help)?;
|
||||
writeln!(f, " {} {}", self.theme.characters.fyi, help)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn render_snippet(&self, f: &mut impl fmt::Write, snippet: &DiagnosticSnippet) -> fmt::Result {
|
||||
// Boring: The Header
|
||||
if let Some(source_name) = snippet.context.label() {
|
||||
let source_name = source_name.style(self.theme.styles.filename);
|
||||
write!(f, "[{}]", source_name)?;
|
||||
}
|
||||
if let Some(msg) = &snippet.message {
|
||||
write!(f, " {}:", msg)?;
|
||||
}
|
||||
writeln!(f)?;
|
||||
writeln!(f)?;
|
||||
|
||||
// Fun time!
|
||||
|
||||
// Our actual code, line by line! Handy!
|
||||
let lines = self.get_lines(snippet)?;
|
||||
let (contents, lines) = self.get_lines(snippet)?;
|
||||
|
||||
// Highlights are the bits we're going to underline in our overall
|
||||
// snippet, and we need to do some analysis first to come up with
|
||||
|
|
@ -161,6 +148,25 @@ impl DefaultReportPrinter {
|
|||
.to_string()
|
||||
.len();
|
||||
|
||||
// Header
|
||||
if let Some(source_name) = snippet.context.label() {
|
||||
let source_name = source_name.style(self.theme.styles.filename);
|
||||
write!(
|
||||
f,
|
||||
"{}{}{}[{}:{}:{}]",
|
||||
" ".repeat(linum_width + 2),
|
||||
self.theme.characters.ltop,
|
||||
self.theme.characters.hbar.to_string().repeat(3),
|
||||
source_name,
|
||||
contents.line() + 1,
|
||||
contents.column() + 1
|
||||
)?;
|
||||
if let Some(msg) = &snippet.message {
|
||||
write!(f, " {}:", msg)?;
|
||||
}
|
||||
writeln!(f)?;
|
||||
}
|
||||
|
||||
// Now it's time for the fun part--actually rendering everything!
|
||||
for line in &lines {
|
||||
// Line number, appropriately padded.
|
||||
|
|
@ -222,7 +228,7 @@ impl DefaultReportPrinter {
|
|||
let mut arrow = false;
|
||||
for (i, hl) in applicable.enumerate() {
|
||||
if line.span_starts(hl) {
|
||||
gutter.push_str(&chars.ltop.to_string().style(hl.style).to_string());
|
||||
gutter.push_str(&chars.ltop.style(hl.style).to_string());
|
||||
gutter.push_str(
|
||||
&chars
|
||||
.hbar
|
||||
|
|
@ -231,14 +237,14 @@ impl DefaultReportPrinter {
|
|||
.style(hl.style)
|
||||
.to_string(),
|
||||
);
|
||||
gutter.push_str(&chars.rarrow.to_string().style(hl.style).to_string());
|
||||
gutter.push_str(&chars.rarrow.style(hl.style).to_string());
|
||||
arrow = true;
|
||||
break;
|
||||
} else if line.span_ends(hl) {
|
||||
if hl.label().is_some() {
|
||||
gutter.push_str(&chars.lcross.to_string().style(hl.style).to_string());
|
||||
gutter.push_str(&chars.lcross.style(hl.style).to_string());
|
||||
} else {
|
||||
gutter.push_str(&chars.lbot.to_string().style(hl.style).to_string());
|
||||
gutter.push_str(&chars.lbot.style(hl.style).to_string());
|
||||
}
|
||||
gutter.push_str(
|
||||
&chars
|
||||
|
|
@ -248,11 +254,11 @@ impl DefaultReportPrinter {
|
|||
.style(hl.style)
|
||||
.to_string(),
|
||||
);
|
||||
gutter.push_str(&chars.rarrow.to_string().style(hl.style).to_string());
|
||||
gutter.push_str(&chars.rarrow.style(hl.style).to_string());
|
||||
arrow = true;
|
||||
break;
|
||||
} else if line.span_flyby(hl) {
|
||||
gutter.push_str(&chars.vbar.to_string().style(hl.style).to_string());
|
||||
gutter.push_str(&chars.vbar.style(hl.style).to_string());
|
||||
} else {
|
||||
gutter.push(' ');
|
||||
}
|
||||
|
|
@ -283,7 +289,7 @@ impl DefaultReportPrinter {
|
|||
let applicable = highlights.iter().filter(|hl| line.span_applies(hl));
|
||||
for (i, hl) in applicable.enumerate() {
|
||||
if !line.span_line_only(hl) && line.span_ends(hl) {
|
||||
gutter.push_str(&chars.lbot.to_string().style(hl.style).to_string());
|
||||
gutter.push_str(&chars.lbot.style(hl.style).to_string());
|
||||
gutter.push_str(
|
||||
&chars
|
||||
.hbar
|
||||
|
|
@ -294,7 +300,7 @@ impl DefaultReportPrinter {
|
|||
);
|
||||
break;
|
||||
} else {
|
||||
gutter.push_str(&chars.vbar.to_string().style(hl.style).to_string());
|
||||
gutter.push_str(&chars.vbar.style(hl.style).to_string());
|
||||
}
|
||||
}
|
||||
write!(f, "{:width$}", gutter, width = max_gutter + 1)?;
|
||||
|
|
@ -389,13 +395,16 @@ impl DefaultReportPrinter {
|
|||
writeln!(
|
||||
f,
|
||||
"{} {}",
|
||||
self.theme.characters.hbar.to_string().style(hl.style),
|
||||
self.theme.characters.hbar.style(hl.style),
|
||||
hl.label().unwrap_or_else(|| "".into()),
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_lines(&self, snippet: &DiagnosticSnippet) -> Result<Vec<Line>, fmt::Error> {
|
||||
fn get_lines<'a>(
|
||||
&'a self,
|
||||
snippet: &'a DiagnosticSnippet,
|
||||
) -> Result<(Box<dyn SpanContents + 'a>, Vec<Line>), fmt::Error> {
|
||||
let context_data = snippet
|
||||
.source
|
||||
.read_span(&snippet.context)
|
||||
|
|
@ -445,7 +454,7 @@ impl DefaultReportPrinter {
|
|||
line_offset = offset;
|
||||
}
|
||||
}
|
||||
Ok(lines)
|
||||
Ok((context_data, lines))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -9,10 +9,10 @@ use once_cell::sync::OnceCell;
|
|||
use crate::protocol::{Diagnostic, DiagnosticReportPrinter, Severity};
|
||||
use crate::MietteError;
|
||||
|
||||
pub use default_reporter::*;
|
||||
pub use default_printer::*;
|
||||
pub use theme::*;
|
||||
|
||||
mod default_reporter;
|
||||
mod default_printer;
|
||||
mod theme;
|
||||
|
||||
static REPORTER: OnceCell<Box<dyn DiagnosticReportPrinter + Send + Sync + 'static>> =
|
||||
|
|
|
|||
|
|
@ -65,8 +65,8 @@ impl MietteStyles {
|
|||
advice: style().cyan(),
|
||||
code: style().yellow(),
|
||||
help: style().cyan(),
|
||||
filename: style().green(),
|
||||
highlights: vec![style().red(), style().magenta(), style().cyan()],
|
||||
filename: style().cyan().underline().bold(),
|
||||
highlights: vec![style().red().bold(), style().yellow().bold(), style().cyan().bold()],
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -111,7 +111,10 @@ pub struct MietteCharacters {
|
|||
pub underbar: char,
|
||||
pub underline: char,
|
||||
|
||||
pub eq: char,
|
||||
pub fyi: char,
|
||||
pub x: char,
|
||||
pub warning: char,
|
||||
pub point_right: char,
|
||||
}
|
||||
|
||||
impl MietteCharacters {
|
||||
|
|
@ -135,7 +138,10 @@ impl MietteCharacters {
|
|||
rcross: '┤',
|
||||
underbar: '┬',
|
||||
underline: '─',
|
||||
eq: '﹦',
|
||||
fyi: '‽',
|
||||
x: '×',
|
||||
warning: '⚠',
|
||||
point_right: '☞',
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -159,7 +165,10 @@ impl MietteCharacters {
|
|||
rcross: '|',
|
||||
underbar: '|',
|
||||
underline: '^',
|
||||
eq: '=',
|
||||
fyi: 'i',
|
||||
x: 'x',
|
||||
warning: '!',
|
||||
point_right: '>',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
115
tests/printer.rs
115
tests/printer.rs
|
|
@ -40,7 +40,21 @@ fn single_line_highlight() -> Result<(), MietteError> {
|
|||
};
|
||||
let out = fmt_report(err.into());
|
||||
println!("{}", out);
|
||||
assert_eq!("Error [oops::my::bad]: oops!\n\n[bad_file.rs] This is the part that broke:\n\n 1 │ source\n 2 │ text\n · ──┬─\n · ╰── this bit here\n 3 │ here\n\n﹦ try doing it better next time?\n".to_string(), out);
|
||||
let expected = r#"
|
||||
────[oops::my::bad]────────────────────
|
||||
|
||||
× oops!
|
||||
|
||||
╭───[bad_file.rs:1:1] This is the part that broke:
|
||||
1 │ source
|
||||
2 │ text
|
||||
· ──┬─
|
||||
· ╰── this bit here
|
||||
3 │ here
|
||||
|
||||
‽ try doing it better next time?
|
||||
"#.trim_start().to_string();
|
||||
assert_eq!(expected, out);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -66,7 +80,20 @@ fn single_line_highlight_no_label() -> Result<(), MietteError> {
|
|||
};
|
||||
let out = fmt_report(err.into());
|
||||
println!("{}", out);
|
||||
assert_eq!("Error [oops::my::bad]: oops!\n\n[bad_file.rs] This is the part that broke:\n\n 1 │ source\n 2 │ text\n · ────\n 3 │ here\n\n﹦ try doing it better next time?\n".to_string(), out);
|
||||
let expected = r#"
|
||||
────[oops::my::bad]────────────────────
|
||||
|
||||
× oops!
|
||||
|
||||
╭───[bad_file.rs:1:1] This is the part that broke:
|
||||
1 │ source
|
||||
2 │ text
|
||||
· ────
|
||||
3 │ here
|
||||
|
||||
‽ try doing it better next time?
|
||||
"#.trim_start().to_string();
|
||||
assert_eq!(expected, out);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -95,7 +122,22 @@ fn multiple_same_line_highlights() -> Result<(), MietteError> {
|
|||
};
|
||||
let out = fmt_report(err.into());
|
||||
println!("{}", out);
|
||||
assert_eq!("Error [oops::my::bad]: oops!\n\n[bad_file.rs] This is the part that broke:\n\n 1 │ source\n 2 │ text text text text text\n · ──┬─ ──┬─\n · ╰── this bit here\n · ╰── also this bit\n 3 │ here\n\n﹦ try doing it better next time?\n".to_string(), out);
|
||||
let expected = r#"
|
||||
────[oops::my::bad]────────────────────
|
||||
|
||||
× oops!
|
||||
|
||||
╭───[bad_file.rs:1:1] This is the part that broke:
|
||||
1 │ source
|
||||
2 │ text text text text text
|
||||
· ──┬─ ──┬─
|
||||
· ╰── this bit here
|
||||
· ╰── also this bit
|
||||
3 │ here
|
||||
|
||||
‽ try doing it better next time?
|
||||
"#.trim_start().to_string();
|
||||
assert_eq!(expected, out);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -121,7 +163,20 @@ fn multiline_highlight_adjacent() -> Result<(), MietteError> {
|
|||
};
|
||||
let out = fmt_report(err.into());
|
||||
println!("{}", out);
|
||||
assert_eq!("Error [oops::my::bad]: oops!\n\n[bad_file.rs] This is the part that broke:\n\n 1 │ source\n 2 │ ╭─▶ text\n 3 │ ├─▶ here\n · ╰──── these two lines\n\n﹦ try doing it better next time?\n".to_string(), out);
|
||||
let expected = r#"
|
||||
────[oops::my::bad]────────────────────
|
||||
|
||||
× oops!
|
||||
|
||||
╭───[bad_file.rs:1:1] This is the part that broke:
|
||||
1 │ source
|
||||
2 │ ╭─▶ text
|
||||
3 │ ├─▶ here
|
||||
· ╰──── these two lines
|
||||
|
||||
‽ try doing it better next time?
|
||||
"#.trim_start().to_string();
|
||||
assert_eq!(expected, out);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -156,7 +211,23 @@ line5
|
|||
};
|
||||
let out = fmt_report(err.into());
|
||||
println!("{}", out);
|
||||
assert_eq!("Error [oops::my::bad]: oops!\n\n[bad_file.rs] This is the part that broke:\n\n 1 │ ╭──▶ line1\n 2 │ │╭─▶ line2\n 3 │ ││ line3\n 4 │ │├─▶ line4\n · │╰──── block 2\n 6 │ ├──▶ line5\n · ╰───── block 1\n\n﹦ try doing it better next time?\n".to_string(), out);
|
||||
let expected =r#"
|
||||
────[oops::my::bad]────────────────────
|
||||
|
||||
× oops!
|
||||
|
||||
╭───[bad_file.rs:1:1] This is the part that broke:
|
||||
1 │ ╭──▶ line1
|
||||
2 │ │╭─▶ line2
|
||||
3 │ ││ line3
|
||||
4 │ │├─▶ line4
|
||||
· │╰──── block 2
|
||||
6 │ ├──▶ line5
|
||||
· ╰───── block 1
|
||||
|
||||
‽ try doing it better next time?
|
||||
"#.trim_start().to_string();
|
||||
assert_eq!(expected, out);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -191,7 +262,22 @@ line5
|
|||
};
|
||||
let out = fmt_report(err.into());
|
||||
println!("{}", out);
|
||||
assert_eq!("Error [oops::my::bad]: oops!\n\n[bad_file.rs] This is the part that broke:\n\n 1 │ ╭──▶ line1\n 2 │ │╭─▶ line2\n 3 │ ││ line3\n 4 │ │╰─▶ line4\n 6 │ ├──▶ line5\n · ╰───── block 1\n\n﹦ try doing it better next time?\n".to_string(), out);
|
||||
let expected = r#"
|
||||
────[oops::my::bad]────────────────────
|
||||
|
||||
× oops!
|
||||
|
||||
╭───[bad_file.rs:1:1] This is the part that broke:
|
||||
1 │ ╭──▶ line1
|
||||
2 │ │╭─▶ line2
|
||||
3 │ ││ line3
|
||||
4 │ │╰─▶ line4
|
||||
6 │ ├──▶ line5
|
||||
· ╰───── block 1
|
||||
|
||||
‽ try doing it better next time?
|
||||
"#.trim_start().to_string();
|
||||
assert_eq!(expected, out);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -220,7 +306,22 @@ fn multiple_multiline_highlights_adjacent() -> Result<(), MietteError> {
|
|||
};
|
||||
let out = fmt_report(err.into());
|
||||
println!("{}", out);
|
||||
assert_eq!("Error [oops::my::bad]: oops!\n\n[bad_file.rs] This is the part that broke:\n\n 1 │ ╭─▶ source\n 2 │ ├─▶ text\n · ╰──── this bit here\n 3 │ ╭─▶ here\n 4 │ ├─▶ more here\n · ╰──── also this bit\n\n﹦ try doing it better next time?\n".to_string(), out);
|
||||
let expected =r#"
|
||||
────[oops::my::bad]────────────────────
|
||||
|
||||
× oops!
|
||||
|
||||
╭───[bad_file.rs:1:1] This is the part that broke:
|
||||
1 │ ╭─▶ source
|
||||
2 │ ├─▶ text
|
||||
· ╰──── this bit here
|
||||
3 │ ╭─▶ here
|
||||
4 │ ├─▶ more here
|
||||
· ╰──── also this bit
|
||||
|
||||
‽ try doing it better next time?
|
||||
"#.trim_start().to_string();
|
||||
assert_eq!(expected, out);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue