Skip to content

Commit

Permalink
feat(handlers): Update graphical handler to use new label protocol (#66)
Browse files Browse the repository at this point in the history
  • Loading branch information
zkat authored Sep 20, 2021
1 parent 645990c commit 6cd44a8
Show file tree
Hide file tree
Showing 6 changed files with 172 additions and 71 deletions.
6 changes: 3 additions & 3 deletions src/eyreish/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@ pub use ReportHandler as EyreContext;
#[allow(unreachable_pub)]
pub use WrapErr as Context;

#[cfg(not(feature = "fancy"))]
use crate::DebugReportHandler;
use crate::Diagnostic;
#[cfg(feature = "fancy")]
use crate::MietteHandler;
#[cfg(not(feature = "fancy"))]
use crate::DebugReportHandler;

use error::ErrorImpl;

Expand Down Expand Up @@ -165,7 +165,7 @@ pub trait ReportHandler: core::any::Any + Send + Sync {
/// ```
fn debug(
&self,
error: &(dyn Diagnostic + 'static),
error: &(dyn Diagnostic),
f: &mut core::fmt::Formatter<'_>,
) -> core::fmt::Result;

Expand Down
6 changes: 1 addition & 5 deletions src/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,11 +205,7 @@ impl Default for MietteHandler {
}

impl ReportHandler for MietteHandler {
fn debug(
&self,
diagnostic: &(dyn Diagnostic + 'static),
f: &mut fmt::Formatter<'_>,
) -> fmt::Result {
fn debug(&self, diagnostic: &(dyn Diagnostic), f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
return fmt::Debug::fmt(diagnostic, f);
}
Expand Down
109 changes: 70 additions & 39 deletions src/handlers/graphical.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use unicode_width::UnicodeWidthStr;
use crate::chain::Chain;
use crate::handlers::theme::*;
use crate::protocol::{Diagnostic, Severity};
use crate::{LabeledSpan, ReportHandler, SourceCode, SourceSpan, SpanContents};
use crate::{LabeledSpan, MietteError, ReportHandler, SourceCode, SourceSpan, SpanContents};

/**
A [ReportHandler] that displays a given [crate::Report] in a quasi-graphical
Expand All @@ -26,6 +26,7 @@ pub struct GraphicalReportHandler {
pub(crate) termwidth: usize,
pub(crate) theme: GraphicalTheme,
pub(crate) footer: Option<String>,
pub(crate) context_lines: usize,
}

impl GraphicalReportHandler {
Expand All @@ -37,6 +38,7 @@ impl GraphicalReportHandler {
termwidth: 200,
theme: GraphicalTheme::default(),
footer: None,
context_lines: 1,
}
}

Expand All @@ -47,6 +49,7 @@ impl GraphicalReportHandler {
termwidth: 200,
theme,
footer: None,
context_lines: 1,
}
}

Expand All @@ -73,6 +76,12 @@ impl GraphicalReportHandler {
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 GraphicalReportHandler {
Expand Down Expand Up @@ -239,25 +248,51 @@ impl GraphicalReportHandler {
source: &dyn SourceCode,
labels: Vec<LabeledSpan>,
) -> fmt::Result {
// TODO: Actually do the rewrite against the new protocol.
let contexts: Vec<_> = labels
let contents = labels
.iter()
.cloned()
.coalesce(|left, right| {
if left.offset() + left.len() >= right.offset() {
let left_end = left.offset() + left.len();
let right_end = right.offset() + right.len();
Ok(LabeledSpan::new(
left.label().map(String::from),
left.offset(),
right_end - left_end,
.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
Ok((
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()
},
),
// We'll throw this away later
left_conts,
))
} else {
Err((left, right))
Err(((left, left_conts), (right, right_conts)))
}
})
.collect();
let (contents, lines) = self.get_lines(source, &labels)?;
},
);
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())?;

// sorting is your friend
let labels = labels
Expand Down Expand Up @@ -298,19 +333,21 @@ impl GraphicalReportHandler {
self.theme.characters.ltop,
self.theme.characters.hbar,
)?;
// TODO: filenames
// if let Some(source_name) = source.name() {
// let source_name = source_name.style(self.theme.styles.link);
// writeln!(
// f,
// "[{}:{}:{}]",
// source_name,
// contents.line() + 1,
// contents.column() + 1
// )?;
// } else {
// writeln!(f, "[{}:{}]", contents.line() + 1, contents.column() + 1)?;
// }

if let Some(source_name) = contents.name() {
let source_name = source_name.style(self.theme.styles.link);
writeln!(
f,
"[{}:{}:{}]",
source_name,
contents.line() + 1,
contents.column() + 1
)?;
} else if lines.len() == 1 {
writeln!(f, "{}", self.theme.characters.hbar.to_string().repeat(3))?;
} else {
writeln!(f, "[{}:{}]", contents.line() + 1, contents.column() + 1)?;
}

// Blank line to improve readability
writeln!(
Expand Down Expand Up @@ -574,22 +611,15 @@ impl GraphicalReportHandler {
fn get_lines<'a>(
&'a self,
source: &'a dyn SourceCode,
labels: &'a [LabeledSpan],
context_span: &'a SourceSpan,
) -> Result<(Box<dyn SpanContents<'a> + 'a>, Vec<Line>), fmt::Error> {
let first = labels.first().expect("MIETTE BUG: This should be safe.");
let last = labels.last().expect("MIETTE BUG: This should be safe.");
let context_span = (
first.inner().offset(),
last.inner().offset() + last.inner().len(),
)
.into();
let context_data = source
.read_span(&context_span, 1, 1)
.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 = context_span.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();
Expand Down Expand Up @@ -653,6 +683,7 @@ impl ReportHandler for GraphicalReportHandler {
Support types
*/

#[derive(Debug)]
struct Line {
line_number: usize,
offset: usize,
Expand Down
2 changes: 2 additions & 0 deletions src/named_source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,10 @@ impl SourceCode for NamedSource {
Ok(Box::new(MietteSpanContents::new_named(
self.name.clone(),
contents.data(),
contents.span().clone(),
contents.line(),
contents.column(),
contents.line_count(),
)))
}
}
39 changes: 34 additions & 5 deletions src/protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -243,39 +243,55 @@ Includes line and column information to optimize highlight calculations.
pub trait SpanContents<'a> {
/// Reference to the data inside the associated span, in bytes.
fn data(&self) -> &'a [u8];
/// [SourceSpan] representing the span covered by this SpanContents.
fn span(&self) -> &SourceSpan;
/// An optional (file?) name for the container of this SpanContents.
fn name(&self) -> Option<&'a str> {
fn name(&self) -> Option<&str> {
None
}
/// The 0-indexed line in the associated [SourceCode] where the data begins.
fn line(&self) -> usize;
/// The 0-indexed column in the associated [SourceCode] where the data begins,
/// relative to `line`.
fn column(&self) -> usize;
/// Total number of lines covered by this SpanContents.
fn line_count(&self) -> usize;
}

/**
Basic implementation of the [SpanContents] trait, for convenience.
*/
#[derive(Clone, Debug)]
pub struct MietteSpanContents<'a> {
/// Data from a [SourceCode], in bytes.
// Data from a [SourceCode], in bytes.
data: &'a [u8],
// span actually covered by this SpanContents.
span: SourceSpan,
// The 0-indexed line where the associated [SourceSpan] _starts_.
line: usize,
// The 0-indexed column where the associated [SourceSpan] _starts_.
column: usize,
// Number of line in this snippet.
line_count: usize,
// Optional filename
name: Option<String>,
}

impl<'a> MietteSpanContents<'a> {
/// Make a new [MietteSpanContents] object.
pub fn new(data: &'a [u8], line: usize, column: usize) -> MietteSpanContents<'a> {
pub fn new(
data: &'a [u8],
span: SourceSpan,
line: usize,
column: usize,
line_count: usize,
) -> MietteSpanContents<'a> {
MietteSpanContents {
data,
span,
line,
column,
line_count,
name: None,
}
}
Expand All @@ -284,13 +300,17 @@ impl<'a> MietteSpanContents<'a> {
pub fn new_named(
name: String,
data: &'a [u8],
span: SourceSpan,
line: usize,
column: usize,
line_count: usize,
) -> MietteSpanContents<'a> {
MietteSpanContents {
data,
span,
line,
column,
line_count,
name: Some(name),
}
}
Expand All @@ -300,18 +320,27 @@ impl<'a> SpanContents<'a> for MietteSpanContents<'a> {
fn data(&self) -> &'a [u8] {
self.data
}
fn span(&self) -> &SourceSpan {
&self.span
}
fn line(&self) -> usize {
self.line
}
fn column(&self) -> usize {
self.column
}
fn line_count(&self) -> usize {
self.line_count
}
fn name(&self) -> Option<&str> {
self.name.as_deref()
}
}

/**
Span within a [SourceCode] with an associated message.
*/
#[derive(Clone, Debug)]
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub struct SourceSpan {
/// The start of the span.
offset: SourceOffset,
Expand Down Expand Up @@ -371,7 +400,7 @@ pub type ByteOffset = usize;
/**
Newtype that represents the [ByteOffset] from the beginning of a [SourceCode]
*/
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub struct SourceOffset(ByteOffset);

impl SourceOffset {
Expand Down
Loading

0 comments on commit 6cd44a8

Please sign in to comment.