diff --git a/Cargo.toml b/Cargo.toml index 41afbf8..cf4a357 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/src/printer/default_reporter.rs b/src/printer/default_printer.rs similarity index 84% rename from src/printer/default_reporter.rs rename to src/printer/default_printer.rs index 3ce7331..c939823 100644 --- a/src/printer/default_reporter.rs +++ b/src/printer/default_printer.rs @@ -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, fmt::Error> { + fn get_lines<'a>( + &'a self, + snippet: &'a DiagnosticSnippet, + ) -> Result<(Box, Vec), 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)) } } diff --git a/src/printer/mod.rs b/src/printer/mod.rs index 407a8fc..f3b5fbd 100644 --- a/src/printer/mod.rs +++ b/src/printer/mod.rs @@ -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> = diff --git a/src/printer/theme.rs b/src/printer/theme.rs index 0eff3be..f4cb0c2 100644 --- a/src/printer/theme.rs +++ b/src/printer/theme.rs @@ -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: '>', } } } diff --git a/tests/printer.rs b/tests/printer.rs index 049f09c..ec9a0b3 100644 --- a/tests/printer.rs +++ b/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(()) }