|
1 | 1 | use std::borrow::Cow;
|
2 | 2 | use std::convert::Infallible;
|
3 | 3 | use std::path::Path;
|
| 4 | +use std::sync::LazyLock; |
4 | 5 |
|
5 |
| -use ariadne::{ColorGenerator, Label, Report, ReportKind, Source}; |
6 | 6 | use lc3_ensemble::err::ErrSpan;
|
| 7 | +use miette::highlighters::{Highlighter, HighlighterState}; |
| 8 | +use miette::{Diagnostic, GraphicalReportHandler, GraphicalTheme, NamedSource, Severity, ThemeCharacters, ThemeStyles}; |
7 | 9 | use neon::context::Context;
|
8 | 10 | use neon::result::Throw;
|
| 11 | +use owo_colors::style; |
9 | 12 |
|
10 |
| -pub(crate) fn simple_reporter<E: std::fmt::Display + ?Sized>(err: &E) -> Reporter<'_, E> { |
11 |
| - Reporter { |
12 |
| - err, |
13 |
| - filename: None, |
14 |
| - source: None, |
15 |
| - span: None, |
16 |
| - help: None, |
17 |
| - msg_includes_fname: true |
| 13 | +struct FlatHighlighter(owo_colors::Style); |
| 14 | +impl Highlighter for FlatHighlighter { |
| 15 | + fn start_highlighter_state<'h>( |
| 16 | + &'h self, |
| 17 | + _: &dyn miette::SpanContents<'_>, |
| 18 | + ) -> Box<dyn HighlighterState + 'h> { |
| 19 | + struct Highlighted(owo_colors::Style); |
| 20 | + impl HighlighterState for Highlighted { |
| 21 | + fn highlight_line<'s>(&mut self, line: &'s str) -> Vec<owo_colors::Styled<&'s str>> { |
| 22 | + vec![self.0.style(line)] |
| 23 | + } |
| 24 | + } |
| 25 | + |
| 26 | + Box::new(Highlighted(self.0)) |
18 | 27 | }
|
19 | 28 | }
|
| 29 | +static REPORT_HANDLER: LazyLock<GraphicalReportHandler> = LazyLock::new(|| { |
| 30 | + let style_dimmed = style().fg_rgb::<0xA0, 0xA0, 0xA0>(); |
| 31 | + |
| 32 | + GraphicalReportHandler::new_themed(GraphicalTheme { |
| 33 | + characters: ThemeCharacters { |
| 34 | + error: String::from("Error:"), |
| 35 | + warning: String::from("Warning:"), |
| 36 | + advice: String::from("Info:"), |
| 37 | + ..ThemeCharacters::unicode() |
| 38 | + }, |
| 39 | + styles: ThemeStyles { |
| 40 | + link: style(), |
| 41 | + linum: style_dimmed, |
| 42 | + highlights: vec![ |
| 43 | + style().magenta(), |
| 44 | + style().yellow(), |
| 45 | + style().green(), |
| 46 | + ], |
| 47 | + ..ThemeStyles::ansi() |
| 48 | + } |
| 49 | + }) |
| 50 | + .with_context_lines(1) |
| 51 | + .with_syntax_highlighting(FlatHighlighter(style_dimmed)) // Make code gray |
| 52 | +}); |
20 | 53 |
|
21 |
| -pub(crate) fn io_reporter<'a, E: std::fmt::Display + ?Sized>(err: &'a E, fp: &'a Path) -> Reporter<'a, E> { |
22 |
| - Reporter { |
23 |
| - err, |
24 |
| - filename: fp.file_name().and_then(|s| s.to_str()), |
25 |
| - source: None, |
26 |
| - span: None, |
27 |
| - help: None, |
28 |
| - msg_includes_fname: true |
| 54 | +#[derive(Debug)] |
| 55 | +enum ReporterSource<'s> { |
| 56 | + Unlabeled(&'s str), |
| 57 | + Labeled(NamedSource<String>) |
| 58 | +} |
| 59 | +impl<'s> ReporterSource<'s> { |
| 60 | + fn new<'a>(filename: Option<&'a str>, source: &'s str) -> Self { |
| 61 | + match filename { |
| 62 | + Some(name) => ReporterSource::Labeled(NamedSource::new(name, source.to_string())), |
| 63 | + None => ReporterSource::Unlabeled(source), |
| 64 | + } |
29 | 65 | }
|
30 | 66 | }
|
31 |
| -pub(crate) fn error_reporter<'a, E: lc3_ensemble::err::Error + ?Sized>(err: &'a E, fp: &'a Path, src: &'a str) -> Reporter<'a, E> { |
32 |
| - let span = err.span(); |
33 |
| - let help = err.help(); |
34 |
| - |
35 |
| - Reporter { |
36 |
| - err, |
37 |
| - filename: fp.file_name().and_then(|s| s.to_str()), |
38 |
| - source: Some(Source::from(src.to_string())), |
39 |
| - span, |
40 |
| - help, |
41 |
| - msg_includes_fname: false, |
| 67 | +impl miette::SourceCode for ReporterSource<'_> { |
| 68 | + fn read_span<'a>( |
| 69 | + &'a self, |
| 70 | + span: &miette::SourceSpan, |
| 71 | + context_lines_before: usize, |
| 72 | + context_lines_after: usize, |
| 73 | + ) -> Result<Box<dyn miette::SpanContents<'a> + 'a>, miette::MietteError> { |
| 74 | + match self { |
| 75 | + ReporterSource::Unlabeled(s) => s.read_span(span, context_lines_before, context_lines_after), |
| 76 | + ReporterSource::Labeled(s) => s.read_span(span, context_lines_before, context_lines_after), |
| 77 | + } |
42 | 78 | }
|
43 | 79 | }
|
44 | 80 |
|
45 |
| -pub(crate) struct Reporter<'c, E: ?Sized> { |
46 |
| - err: &'c E, |
47 |
| - filename: Option<&'c str>, |
48 |
| - source: Option<Source<String>>, |
| 81 | +pub(crate) struct Reporter<'r, E: std::fmt::Display + ?Sized> { |
| 82 | + /// Error (and message to print) |
| 83 | + err: &'r E, |
| 84 | + /// The filename of file where this error occurred (if there is a file) |
| 85 | + filename: Option<&'r str>, |
| 86 | + /// Source code |
| 87 | + source: Option<ReporterSource<'r>>, |
| 88 | + /// Span where error occurred |
49 | 89 | span: Option<ErrSpan>,
|
50 |
| - help: Option<Cow<'c, str>>, |
51 |
| - msg_includes_fname: bool |
| 90 | + /// Relevant help messages |
| 91 | + help: Option<Cow<'r, str>>, |
| 92 | + /// Whether to include the filename in the error message |
| 93 | + /// (can be false if it'd already appear elsewhere) |
| 94 | + include_name_in_msg: bool |
52 | 95 | }
|
| 96 | +impl<E: std::fmt::Display + ?Sized> std::fmt::Debug for Reporter<'_, E> { |
| 97 | + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| 98 | + f.debug_struct("Reporter") |
| 99 | + .field("err", &self.err.to_string()) |
| 100 | + .field("filename", &self.filename) |
| 101 | + .field("source", &self.source) |
| 102 | + .field("span", &self.span) |
| 103 | + .field("help", &self.help) |
| 104 | + .field("include_name_in_msg", &self.include_name_in_msg) |
| 105 | + .finish() |
| 106 | + } |
| 107 | +} |
| 108 | +impl<E: std::fmt::Display + ?Sized> std::fmt::Display for Reporter<'_, E> { |
| 109 | + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| 110 | + match self.filename { |
| 111 | + Some(filename) if self.include_name_in_msg => write!(f, "{filename}: {}", self.err), |
| 112 | + _ => write!(f, "{}", self.err), |
| 113 | + } |
| 114 | + } |
| 115 | +} |
| 116 | +impl<E: std::fmt::Display + ?Sized> std::error::Error for Reporter<'_, E> {} |
| 117 | +impl<E: std::fmt::Display + ?Sized> Diagnostic for Reporter<'_, E> { |
| 118 | + fn severity(&self) -> Option<Severity> { |
| 119 | + Some(Severity::Error) |
| 120 | + } |
53 | 121 |
|
54 |
| -impl<E: std::fmt::Display + ?Sized> Reporter<'_, E> { |
55 |
| - pub(crate) fn report(&mut self, writer: &mut impl std::io::Write) { |
56 |
| - let mut colors = ColorGenerator::new(); |
57 |
| - |
58 |
| - let msg = if self.msg_includes_fname { |
59 |
| - if let Some(fname) = self.filename { |
60 |
| - format!("{}: {}", fname, self.err) |
61 |
| - } else { |
62 |
| - self.err.to_string() |
63 |
| - } |
64 |
| - } else { |
65 |
| - self.err.to_string() |
66 |
| - }; |
67 |
| - let fname = self.filename.unwrap_or("source"); |
68 |
| - let span = self.span.as_ref().map_or(0..0, |s| s.first()); |
69 |
| - |
70 |
| - let mut report = Report::build(ReportKind::Error, (fname, span)).with_message(msg); |
71 |
| - match self.span.clone() { |
72 |
| - Some(ErrSpan::One(r)) => { |
73 |
| - report.add_label({ |
74 |
| - let mut label = Label::new((fname, r)) |
75 |
| - .with_color(colors.next()); |
76 |
| - |
77 |
| - if let Some(help) = self.help.as_deref() { |
78 |
| - label = label.with_message(help); |
79 |
| - } |
| 122 | + fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> { |
| 123 | + self.help.as_ref().filter(|s| !s.is_empty()).map(|s| Box::new(s) as _) |
| 124 | + } |
80 | 125 |
|
81 |
| - label |
82 |
| - }) |
83 |
| - }, |
84 |
| - Some(ErrSpan::Two([r0, r1])) => { |
85 |
| - report.add_label({ |
86 |
| - Label::new((fname, r0)) |
87 |
| - .with_color(colors.next()) |
88 |
| - .with_message("") |
89 |
| - }); |
90 |
| - report.add_label({ |
91 |
| - Label::new((fname, r1)) |
92 |
| - .with_color(colors.next()) |
93 |
| - .with_message("") |
94 |
| - }); |
| 126 | + fn source_code(&self) -> Option<&dyn miette::SourceCode> { |
| 127 | + self.source.as_ref().map(|s| s as &dyn miette::SourceCode) |
| 128 | + } |
95 | 129 |
|
96 |
| - if let Some(help) = self.help.as_deref() { |
97 |
| - report.set_help(help); |
98 |
| - } |
99 |
| - }, |
100 |
| - Some(ErrSpan::Many(mr)) => { |
101 |
| - report.add_labels({ |
102 |
| - mr.into_iter() |
103 |
| - .map(|s| { |
104 |
| - Label::new((fname, s.clone())) |
105 |
| - .with_color(colors.next()) |
106 |
| - .with_message("") |
107 |
| - }) |
108 |
| - }); |
| 130 | + fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> { |
| 131 | + self.span.as_ref().map(|s| Box::new( |
| 132 | + s.iter().map(|s| miette::LabeledSpan::new_with_span(None, s.clone())) |
| 133 | + ) as _) |
| 134 | + } |
| 135 | +} |
109 | 136 |
|
110 |
| - if let Some(help) = self.help.as_deref() { |
111 |
| - report.set_help(help); |
112 |
| - } |
113 |
| - }, |
114 |
| - None => { |
115 |
| - if let Some(help) = self.help.as_deref() { |
116 |
| - report.add_label(Label::new((fname, 0..0))); |
117 |
| - report.set_help(help); |
118 |
| - }; |
119 |
| - } |
| 137 | +impl<'r, E: std::fmt::Display + ?Sized> Reporter<'r, E> { |
| 138 | + pub(crate) fn simple(err: &'r E) -> Self { |
| 139 | + Reporter { |
| 140 | + err, |
| 141 | + filename: None, |
| 142 | + source: None, |
| 143 | + span: None, |
| 144 | + help: None, |
| 145 | + include_name_in_msg: true |
| 146 | + } |
| 147 | + } |
| 148 | + |
| 149 | + pub(crate) fn io(err: &'r E, fp: &'r Path) -> Self { |
| 150 | + Reporter { |
| 151 | + err, |
| 152 | + filename: fp.file_name().and_then(|s| s.to_str()), |
| 153 | + source: None, |
| 154 | + span: None, |
| 155 | + help: None, |
| 156 | + include_name_in_msg: true |
| 157 | + } |
| 158 | + } |
| 159 | + pub(crate) fn ensemble(err: &'r E, fp: &'r Path, src: &'r str) -> Self |
| 160 | + where E: lc3_ensemble::err::Error |
| 161 | + { |
| 162 | + let span = err.span(); |
| 163 | + let help = err.help(); |
| 164 | + let filename = fp.file_name().and_then(|s| s.to_str()); |
| 165 | + Reporter { |
| 166 | + err, |
| 167 | + filename, |
| 168 | + source: Some(ReporterSource::new(filename, src)), |
| 169 | + span, |
| 170 | + help, |
| 171 | + include_name_in_msg: false, |
120 | 172 | }
|
121 |
| - |
122 |
| - let source = match &self.source { |
123 |
| - Some(s) => s.clone(), |
124 |
| - None => Source::from(String::new()), |
125 |
| - }; |
126 |
| - report.finish() |
127 |
| - .write((fname, source), writer) |
128 |
| - .unwrap(); |
| 173 | + } |
| 174 | +} |
| 175 | + |
| 176 | +impl<E: std::fmt::Display + ?Sized> Reporter<'_, E> { |
| 177 | + pub(crate) fn report(&mut self, writer: &mut Vec<u8>) { |
| 178 | + let mut report = String::new(); |
| 179 | + REPORT_HANDLER.render_report(&mut report, self).unwrap(); |
| 180 | + |
| 181 | + // Remove whitespace that miette adds at the beginning of reports |
| 182 | + writer.extend(report.trim_start().as_bytes()); |
129 | 183 | }
|
130 | 184 |
|
131 |
| - pub(crate) fn report_and_throw<'a>(mut self, writer: &mut impl std::io::Write, cx: &mut impl Context<'a>) -> Throw { |
| 185 | + pub(crate) fn report_and_throw<'a>(mut self, writer: &mut Vec<u8>, cx: &mut impl Context<'a>) -> Throw { |
132 | 186 | self.report(writer);
|
133 | 187 |
|
134 | 188 | cx.throw_error::<_, Infallible>(self.err.to_string())
|
|
0 commit comments