mirror of https://github.com/zkat/miette.git
feat(narrated): updated narrated handler
This commit is contained in:
parent
b33084bdbf
commit
fbf6664ef5
|
|
@ -15,6 +15,7 @@ exclude = ["images/", "tests/", "miette-derive/"]
|
||||||
thiserror = "1.0.26"
|
thiserror = "1.0.26"
|
||||||
miette-derive = { path = "miette-derive", version = "=3.0.0-beta.0"}
|
miette-derive = { path = "miette-derive", version = "=3.0.0-beta.0"}
|
||||||
once_cell = "1.8.0"
|
once_cell = "1.8.0"
|
||||||
|
itertools = "0.10.1"
|
||||||
|
|
||||||
owo-colors = { version = "2.0.0", optional = true }
|
owo-colors = { version = "2.0.0", optional = true }
|
||||||
atty = { version = "0.2.14", optional = true }
|
atty = { version = "0.2.14", optional = true }
|
||||||
|
|
@ -23,7 +24,6 @@ term_size = { version = "0.3.2", optional = true }
|
||||||
supports-hyperlinks = { version = "1.1.0", optional = true }
|
supports-hyperlinks = { version = "1.1.0", optional = true }
|
||||||
supports-color = { version = "1.0.4", optional = true }
|
supports-color = { version = "1.0.4", optional = true }
|
||||||
supports-unicode = { version = "1.0.0", optional = true }
|
supports-unicode = { version = "1.0.0", optional = true }
|
||||||
itertools = { version = "0.10.1", optional = true }
|
|
||||||
backtrace = { version = "0.3.61", optional = true }
|
backtrace = { version = "0.3.61", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|
@ -46,7 +46,6 @@ fancy = [
|
||||||
"supports-hyperlinks",
|
"supports-hyperlinks",
|
||||||
"supports-color",
|
"supports-color",
|
||||||
"supports-unicode",
|
"supports-unicode",
|
||||||
"itertools",
|
|
||||||
"backtrace"
|
"backtrace"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
|
use itertools::Itertools;
|
||||||
|
|
||||||
use crate::chain::Chain;
|
use crate::chain::Chain;
|
||||||
use crate::protocol::{Diagnostic, Severity};
|
use crate::protocol::{Diagnostic, Severity};
|
||||||
use crate::{ReportHandler, SourceCode, SourceSpan, SpanContents};
|
use crate::{LabeledSpan, MietteError, ReportHandler, SourceCode, SourceSpan, SpanContents};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
[ReportHandler] that renders plain text and avoids extraneous graphics.
|
[ReportHandler] that renders plain text and avoids extraneous graphics.
|
||||||
|
|
@ -11,6 +13,7 @@ non-graphical environments, such as non-TTY output.
|
||||||
*/
|
*/
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct NarratableReportHandler {
|
pub struct NarratableReportHandler {
|
||||||
|
context_lines: usize,
|
||||||
footer: Option<String>,
|
footer: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -18,7 +21,10 @@ impl NarratableReportHandler {
|
||||||
/// Create a new [NarratableReportHandler]. There are no customization
|
/// Create a new [NarratableReportHandler]. There are no customization
|
||||||
/// options.
|
/// options.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self { footer: None }
|
Self {
|
||||||
|
footer: None,
|
||||||
|
context_lines: 1,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the footer to be displayed at the end of the report.
|
/// Set the footer to be displayed at the end of the report.
|
||||||
|
|
@ -26,6 +32,12 @@ impl NarratableReportHandler {
|
||||||
self.footer = Some(footer);
|
self.footer = Some(footer);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the number of lines of context to show around each error.
|
||||||
|
pub fn with_context_lines(mut self, lines: usize) -> Self {
|
||||||
|
self.context_lines = lines;
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for NarratableReportHandler {
|
impl Default for NarratableReportHandler {
|
||||||
|
|
@ -46,13 +58,7 @@ impl NarratableReportHandler {
|
||||||
) -> fmt::Result {
|
) -> fmt::Result {
|
||||||
self.render_header(f, diagnostic)?;
|
self.render_header(f, diagnostic)?;
|
||||||
self.render_causes(f, diagnostic)?;
|
self.render_causes(f, diagnostic)?;
|
||||||
|
self.render_snippets(f, diagnostic)?;
|
||||||
// if let Some(labels) = diagnostic.labels() {
|
|
||||||
// for label in labels {
|
|
||||||
// self.render_label(f, &label)?;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
self.render_footer(f, diagnostic)?;
|
self.render_footer(f, diagnostic)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -91,22 +97,131 @@ impl NarratableReportHandler {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
fn render_snippets(
|
||||||
|
&self,
|
||||||
|
f: &mut impl fmt::Write,
|
||||||
|
diagnostic: &(dyn Diagnostic),
|
||||||
|
) -> fmt::Result {
|
||||||
|
if let Some(source) = diagnostic.source_code() {
|
||||||
|
if let Some(labels) = diagnostic.labels() {
|
||||||
|
let mut labels = labels.collect::<Vec<_>>();
|
||||||
|
labels.sort_unstable_by_key(|l| l.inner().offset());
|
||||||
|
if !labels.is_empty() {
|
||||||
|
let contents = labels
|
||||||
|
.iter()
|
||||||
|
.map(|label| {
|
||||||
|
source.read_span(label.inner(), self.context_lines, self.context_lines)
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<Box<dyn SpanContents<'_>>>, MietteError>>()
|
||||||
|
.map_err(|_| fmt::Error)?;
|
||||||
|
let contexts = labels.iter().cloned().zip(contents.iter()).coalesce(
|
||||||
|
|(left, left_conts), (right, right_conts)| {
|
||||||
|
let left_end = left.offset() + left.len();
|
||||||
|
let right_end = right.offset() + right.len();
|
||||||
|
if left_conts.line() + left_conts.line_count() >= right_conts.line() {
|
||||||
|
// The snippets will overlap, so we create one Big Chunky Boi
|
||||||
|
let new_span = LabeledSpan::new(
|
||||||
|
left.label().map(String::from),
|
||||||
|
left.offset(),
|
||||||
|
if right_end >= left_end {
|
||||||
|
// Right end goes past left end
|
||||||
|
right_end - left.offset()
|
||||||
|
} else {
|
||||||
|
// right is contained inside left
|
||||||
|
left.len()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if source
|
||||||
|
.read_span(
|
||||||
|
new_span.inner(),
|
||||||
|
self.context_lines,
|
||||||
|
self.context_lines,
|
||||||
|
)
|
||||||
|
.is_ok()
|
||||||
|
{
|
||||||
|
Ok((
|
||||||
|
new_span, // We'll throw this away later
|
||||||
|
left_conts,
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Err(((left, left_conts), (right, right_conts)))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(((left, left_conts), (right, right_conts)))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
for (ctx, _) in contexts {
|
||||||
|
self.render_context(f, source, &ctx, &labels[..])?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_context<'a>(
|
||||||
|
&self,
|
||||||
|
f: &mut impl fmt::Write,
|
||||||
|
source: &'a dyn SourceCode,
|
||||||
|
context: &LabeledSpan,
|
||||||
|
labels: &[LabeledSpan],
|
||||||
|
) -> fmt::Result {
|
||||||
|
let (contents, lines) = self.get_lines(source, context.inner())?;
|
||||||
|
write!(f, "Begin snippet")?;
|
||||||
|
if let Some(filename) = contents.name() {
|
||||||
|
write!(f, " for {}", filename,)?;
|
||||||
|
}
|
||||||
|
writeln!(
|
||||||
|
f,
|
||||||
|
" starting at line {}, column {}",
|
||||||
|
contents.line() + 1,
|
||||||
|
contents.column() + 1
|
||||||
|
)?;
|
||||||
|
writeln!(f)?;
|
||||||
|
for line in &lines {
|
||||||
|
writeln!(f, "snippet line {}: {}", line.line_number, line.text)?;
|
||||||
|
let relevant = labels.iter().filter(|l| line.span_starts(l.inner()));
|
||||||
|
for label in relevant {
|
||||||
|
let contents = source
|
||||||
|
.read_span(label.inner(), self.context_lines, self.context_lines)
|
||||||
|
.map_err(|_| fmt::Error)?;
|
||||||
|
if contents.line() + 1 == line.line_number {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
" label starting at line {}, column {}",
|
||||||
|
contents.line() + 1,
|
||||||
|
contents.column() + 1
|
||||||
|
)?;
|
||||||
|
if let Some(label) = label.label() {
|
||||||
|
write!(f, ": {}", label)?;
|
||||||
|
}
|
||||||
|
writeln!(f)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn get_lines<'a>(
|
fn get_lines<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
source: &'a dyn Source,
|
source: &'a dyn SourceCode,
|
||||||
) -> Result<(Box<dyn SpanContents + 'a>, Vec<Line>), fmt::Error> {
|
context_span: &'a SourceSpan,
|
||||||
let context_data = source.read_span(&snippet.context).map_err(|_| fmt::Error)?;
|
) -> Result<(Box<dyn SpanContents<'a> + 'a>, Vec<Line>), fmt::Error> {
|
||||||
|
let context_data = source
|
||||||
|
.read_span(context_span, self.context_lines, self.context_lines)
|
||||||
|
.map_err(|_| fmt::Error)?;
|
||||||
let context = std::str::from_utf8(context_data.data()).expect("Bad utf8 detected");
|
let context = std::str::from_utf8(context_data.data()).expect("Bad utf8 detected");
|
||||||
let mut line = context_data.line();
|
let mut line = context_data.line();
|
||||||
let mut column = context_data.column();
|
let mut column = context_data.column();
|
||||||
let mut offset = snippet.context.offset();
|
let mut offset = context_data.span().offset();
|
||||||
let mut line_offset = offset;
|
let mut line_offset = offset;
|
||||||
let mut iter = context.chars().peekable();
|
let mut iter = context.chars().peekable();
|
||||||
let mut line_str = String::new();
|
let mut line_str = String::new();
|
||||||
let mut lines = Vec::new();
|
let mut lines = Vec::new();
|
||||||
while let Some(char) = iter.next() {
|
while let Some(char) = iter.next() {
|
||||||
offset += char.len_utf8();
|
offset += char.len_utf8();
|
||||||
|
let mut at_end_of_file = false;
|
||||||
match char {
|
match char {
|
||||||
'\r' => {
|
'\r' => {
|
||||||
if iter.next_if_eq(&'\n').is_some() {
|
if iter.next_if_eq(&'\n').is_some() {
|
||||||
|
|
@ -117,8 +232,10 @@ impl NarratableReportHandler {
|
||||||
line_str.push(char);
|
line_str.push(char);
|
||||||
column += 1;
|
column += 1;
|
||||||
}
|
}
|
||||||
|
at_end_of_file = iter.peek().is_none();
|
||||||
}
|
}
|
||||||
'\n' => {
|
'\n' => {
|
||||||
|
at_end_of_file = iter.peek().is_none();
|
||||||
line += 1;
|
line += 1;
|
||||||
column = 0;
|
column = 0;
|
||||||
}
|
}
|
||||||
|
|
@ -127,7 +244,8 @@ impl NarratableReportHandler {
|
||||||
column += 1;
|
column += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if iter.peek().is_none() {
|
|
||||||
|
if iter.peek().is_none() && !at_end_of_file {
|
||||||
line += 1;
|
line += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -143,7 +261,6 @@ impl NarratableReportHandler {
|
||||||
}
|
}
|
||||||
Ok((context_data, lines))
|
Ok((context_data, lines))
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ReportHandler for NarratableReportHandler {
|
impl ReportHandler for NarratableReportHandler {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue