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"
|
||||
miette-derive = { path = "miette-derive", version = "=3.0.0-beta.0"}
|
||||
once_cell = "1.8.0"
|
||||
itertools = "0.10.1"
|
||||
|
||||
owo-colors = { version = "2.0.0", 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-color = { version = "1.0.4", optional = true }
|
||||
supports-unicode = { version = "1.0.0", optional = true }
|
||||
itertools = { version = "0.10.1", optional = true }
|
||||
backtrace = { version = "0.3.61", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
|
|
@ -46,7 +46,6 @@ fancy = [
|
|||
"supports-hyperlinks",
|
||||
"supports-color",
|
||||
"supports-unicode",
|
||||
"itertools",
|
||||
"backtrace"
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
use std::fmt;
|
||||
|
||||
use itertools::Itertools;
|
||||
|
||||
use crate::chain::Chain;
|
||||
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.
|
||||
|
|
@ -11,6 +13,7 @@ non-graphical environments, such as non-TTY output.
|
|||
*/
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NarratableReportHandler {
|
||||
context_lines: usize,
|
||||
footer: Option<String>,
|
||||
}
|
||||
|
||||
|
|
@ -18,7 +21,10 @@ impl NarratableReportHandler {
|
|||
/// Create a new [NarratableReportHandler]. There are no customization
|
||||
/// options.
|
||||
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.
|
||||
|
|
@ -26,6 +32,12 @@ impl NarratableReportHandler {
|
|||
self.footer = Some(footer);
|
||||
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 {
|
||||
|
|
@ -46,13 +58,7 @@ impl NarratableReportHandler {
|
|||
) -> fmt::Result {
|
||||
self.render_header(f, diagnostic)?;
|
||||
self.render_causes(f, diagnostic)?;
|
||||
|
||||
// if let Some(labels) = diagnostic.labels() {
|
||||
// for label in labels {
|
||||
// self.render_label(f, &label)?;
|
||||
// }
|
||||
// }
|
||||
|
||||
self.render_snippets(f, diagnostic)?;
|
||||
self.render_footer(f, diagnostic)?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -91,22 +97,131 @@ impl NarratableReportHandler {
|
|||
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>(
|
||||
&'a self,
|
||||
source: &'a dyn Source,
|
||||
) -> Result<(Box<dyn SpanContents + 'a>, Vec<Line>), fmt::Error> {
|
||||
let context_data = source.read_span(&snippet.context).map_err(|_| fmt::Error)?;
|
||||
source: &'a dyn SourceCode,
|
||||
context_span: &'a SourceSpan,
|
||||
) -> 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 mut line = context_data.line();
|
||||
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 iter = context.chars().peekable();
|
||||
let mut line_str = String::new();
|
||||
let mut lines = Vec::new();
|
||||
while let Some(char) = iter.next() {
|
||||
offset += char.len_utf8();
|
||||
let mut at_end_of_file = false;
|
||||
match char {
|
||||
'\r' => {
|
||||
if iter.next_if_eq(&'\n').is_some() {
|
||||
|
|
@ -117,8 +232,10 @@ impl NarratableReportHandler {
|
|||
line_str.push(char);
|
||||
column += 1;
|
||||
}
|
||||
at_end_of_file = iter.peek().is_none();
|
||||
}
|
||||
'\n' => {
|
||||
at_end_of_file = iter.peek().is_none();
|
||||
line += 1;
|
||||
column = 0;
|
||||
}
|
||||
|
|
@ -127,7 +244,8 @@ impl NarratableReportHandler {
|
|||
column += 1;
|
||||
}
|
||||
}
|
||||
if iter.peek().is_none() {
|
||||
|
||||
if iter.peek().is_none() && !at_end_of_file {
|
||||
line += 1;
|
||||
}
|
||||
|
||||
|
|
@ -143,7 +261,6 @@ impl NarratableReportHandler {
|
|||
}
|
||||
Ok((context_data, lines))
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
impl ReportHandler for NarratableReportHandler {
|
||||
|
|
|
|||
Loading…
Reference in New Issue