feat(printer): lots of small improvements to printer

This commit is contained in:
Kat Marchán 2021-08-20 21:45:24 -07:00
parent d675334e48
commit 5fbcd53026
No known key found for this signature in database
GPG Key ID: AEB529C08A3C7E9E
5 changed files with 190 additions and 72 deletions

View File

@ -11,7 +11,6 @@ readme = "README.md"
edition = "2018" edition = "2018"
[dependencies] [dependencies]
indenter = "0.3.3"
thiserror = "1.0.26" thiserror = "1.0.26"
miette-derive = { version = "=0.11.0", path = "miette-derive" } miette-derive = { version = "=0.11.0", path = "miette-derive" }
once_cell = "1.8.0" once_cell = "1.8.0"

View File

@ -1,12 +1,11 @@
use std::fmt; use std::fmt;
use indenter::indented;
use owo_colors::{OwoColorize, Style}; use owo_colors::{OwoColorize, Style};
use crate::chain::Chain; use crate::chain::Chain;
use crate::printer::theme::*; use crate::printer::theme::*;
use crate::protocol::{Diagnostic, DiagnosticReportPrinter, DiagnosticSnippet, Severity}; use crate::protocol::{Diagnostic, DiagnosticReportPrinter, DiagnosticSnippet, Severity};
use crate::SourceSpan; use crate::{SourceSpan, SpanContents};
/** /**
Reference implementation of the [DiagnosticReportPrinter] trait. This is generally Reference implementation of the [DiagnosticReportPrinter] trait. This is generally
@ -44,13 +43,10 @@ impl DefaultReportPrinter {
) -> fmt::Result { ) -> fmt::Result {
self.render_header(f, diagnostic)?; self.render_header(f, diagnostic)?;
self.render_causes(f, diagnostic)?; self.render_causes(f, diagnostic)?;
if let Some(snippets) = diagnostic.snippets() { if let Some(snippets) = diagnostic.snippets() {
let mut pre = false;
for snippet in snippets { for snippet in snippets {
if !pre { writeln!(f)?;
writeln!(f)?;
pre = true;
}
self.render_snippet(f, &snippet)?; 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 { fn render_header(&self, f: &mut impl fmt::Write, diagnostic: &(dyn Diagnostic)) -> fmt::Result {
let sev = match diagnostic.severity() { let (severity_style, severity_icon) = match diagnostic.severity() {
Some(Severity::Error) | None => "Error".style(self.theme.styles.error), Some(Severity::Error) | None => (self.theme.styles.error, self.theme.characters.x),
Some(Severity::Warning) => "Warning".style(self.theme.styles.warning), Some(Severity::Warning) => (self.theme.styles.warning, self.theme.characters.warning),
Some(Severity::Advice) => "Advice".style(self.theme.styles.advice), Some(Severity::Advice) => (self.theme.styles.advice, self.theme.characters.point_right),
} };
.to_string();
let code = diagnostic.code(); let code = diagnostic.code();
let msg = diagnostic.to_string();
writeln!( writeln!(
f, f,
"{} [{}]: {}", "{}[{}]{}",
sev, self.theme.characters.hbar.to_string().repeat(4),
code.style(self.theme.styles.code), 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(()) Ok(())
} }
fn render_causes(&self, f: &mut impl fmt::Write, diagnostic: &(dyn Diagnostic)) -> fmt::Result { 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() { if let Some(cause) = diagnostic.source() {
writeln!(f)?; let mut cause_iter = Chain::new(cause).peekable();
write!(f, "Caused by:")?; while let Some(error) = cause_iter.next() {
let char = if cause_iter.peek().is_some() {
let multiple = cause.source().is_some(); self.theme.characters.lcross
for (n, error) in Chain::new(cause).enumerate() {
let msg = format!("{}", error);
writeln!(f)?;
if multiple {
write!(indented(f).ind(n), "{}", msg)?;
} else { } 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() { if let Some(help) = diagnostic.help() {
let help = help.style(self.theme.styles.help); let help = help.style(self.theme.styles.help);
writeln!(f)?; writeln!(f)?;
writeln!(f, "{} {}", self.theme.characters.eq, help)?; writeln!(f, " {} {}", self.theme.characters.fyi, help)?;
} }
Ok(()) Ok(())
} }
fn render_snippet(&self, f: &mut impl fmt::Write, snippet: &DiagnosticSnippet) -> fmt::Result { fn render_snippet(&self, f: &mut impl fmt::Write, snippet: &DiagnosticSnippet) -> fmt::Result {
// Boring: The Header let (contents, lines) = self.get_lines(snippet)?;
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)?;
// Highlights are the bits we're going to underline in our overall // 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 // snippet, and we need to do some analysis first to come up with
@ -161,6 +148,25 @@ impl DefaultReportPrinter {
.to_string() .to_string()
.len(); .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! // Now it's time for the fun part--actually rendering everything!
for line in &lines { for line in &lines {
// Line number, appropriately padded. // Line number, appropriately padded.
@ -222,7 +228,7 @@ impl DefaultReportPrinter {
let mut arrow = false; let mut arrow = false;
for (i, hl) in applicable.enumerate() { for (i, hl) in applicable.enumerate() {
if line.span_starts(hl) { 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( gutter.push_str(
&chars &chars
.hbar .hbar
@ -231,14 +237,14 @@ impl DefaultReportPrinter {
.style(hl.style) .style(hl.style)
.to_string(), .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; arrow = true;
break; break;
} else if line.span_ends(hl) { } else if line.span_ends(hl) {
if hl.label().is_some() { 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 { } 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( gutter.push_str(
&chars &chars
@ -248,11 +254,11 @@ impl DefaultReportPrinter {
.style(hl.style) .style(hl.style)
.to_string(), .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; arrow = true;
break; break;
} else if line.span_flyby(hl) { } 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 { } else {
gutter.push(' '); gutter.push(' ');
} }
@ -283,7 +289,7 @@ impl DefaultReportPrinter {
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.to_string().style(hl.style).to_string()); gutter.push_str(&chars.lbot.style(hl.style).to_string());
gutter.push_str( gutter.push_str(
&chars &chars
.hbar .hbar
@ -294,7 +300,7 @@ impl DefaultReportPrinter {
); );
break; break;
} else { } 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)?; write!(f, "{:width$}", gutter, width = max_gutter + 1)?;
@ -389,13 +395,16 @@ impl DefaultReportPrinter {
writeln!( writeln!(
f, f,
"{} {}", "{} {}",
self.theme.characters.hbar.to_string().style(hl.style), self.theme.characters.hbar.style(hl.style),
hl.label().unwrap_or_else(|| "".into()), hl.label().unwrap_or_else(|| "".into()),
)?; )?;
Ok(()) 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 let context_data = snippet
.source .source
.read_span(&snippet.context) .read_span(&snippet.context)
@ -445,7 +454,7 @@ impl DefaultReportPrinter {
line_offset = offset; line_offset = offset;
} }
} }
Ok(lines) Ok((context_data, lines))
} }
} }

View File

@ -9,10 +9,10 @@ use once_cell::sync::OnceCell;
use crate::protocol::{Diagnostic, DiagnosticReportPrinter, Severity}; use crate::protocol::{Diagnostic, DiagnosticReportPrinter, Severity};
use crate::MietteError; use crate::MietteError;
pub use default_reporter::*; pub use default_printer::*;
pub use theme::*; pub use theme::*;
mod default_reporter; mod default_printer;
mod theme; mod theme;
static REPORTER: OnceCell<Box<dyn DiagnosticReportPrinter + Send + Sync + 'static>> = static REPORTER: OnceCell<Box<dyn DiagnosticReportPrinter + Send + Sync + 'static>> =

View File

@ -65,8 +65,8 @@ impl MietteStyles {
advice: style().cyan(), advice: style().cyan(),
code: style().yellow(), code: style().yellow(),
help: style().cyan(), help: style().cyan(),
filename: style().green(), filename: style().cyan().underline().bold(),
highlights: vec![style().red(), style().magenta(), style().cyan()], highlights: vec![style().red().bold(), style().yellow().bold(), style().cyan().bold()],
} }
} }
@ -111,7 +111,10 @@ pub struct MietteCharacters {
pub underbar: char, pub underbar: char,
pub underline: char, pub underline: char,
pub eq: char, pub fyi: char,
pub x: char,
pub warning: char,
pub point_right: char,
} }
impl MietteCharacters { impl MietteCharacters {
@ -135,7 +138,10 @@ impl MietteCharacters {
rcross: '', rcross: '',
underbar: '', underbar: '',
underline: '', underline: '',
eq: '', fyi: '',
x: '×',
warning: '',
point_right: '',
} }
} }
@ -159,7 +165,10 @@ impl MietteCharacters {
rcross: '|', rcross: '|',
underbar: '|', underbar: '|',
underline: '^', underline: '^',
eq: '=', fyi: 'i',
x: 'x',
warning: '!',
point_right: '>',
} }
} }
} }

View File

@ -40,7 +40,21 @@ fn single_line_highlight() -> Result<(), MietteError> {
}; };
let out = fmt_report(err.into()); let out = fmt_report(err.into());
println!("{}", out); 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(()) Ok(())
} }
@ -66,7 +80,20 @@ fn single_line_highlight_no_label() -> Result<(), MietteError> {
}; };
let out = fmt_report(err.into()); let out = fmt_report(err.into());
println!("{}", out); 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(()) Ok(())
} }
@ -95,7 +122,22 @@ fn multiple_same_line_highlights() -> Result<(), MietteError> {
}; };
let out = fmt_report(err.into()); let out = fmt_report(err.into());
println!("{}", out); 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(()) Ok(())
} }
@ -121,7 +163,20 @@ fn multiline_highlight_adjacent() -> Result<(), MietteError> {
}; };
let out = fmt_report(err.into()); let out = fmt_report(err.into());
println!("{}", out); 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(()) Ok(())
} }
@ -156,7 +211,23 @@ line5
}; };
let out = fmt_report(err.into()); let out = fmt_report(err.into());
println!("{}", out); 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(()) Ok(())
} }
@ -191,7 +262,22 @@ line5
}; };
let out = fmt_report(err.into()); let out = fmt_report(err.into());
println!("{}", out); 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(()) Ok(())
} }
@ -220,7 +306,22 @@ fn multiple_multiline_highlights_adjacent() -> Result<(), MietteError> {
}; };
let out = fmt_report(err.into()); let out = fmt_report(err.into());
println!("{}", out); 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(()) Ok(())
} }